Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all 5930 articles
Browse latest View live

SQLite数据库使用详解

$
0
0

SQLite简介

Google为Andriod的较大的数据处理提供了SQLite,他在数据存储、管理、维护等各方面都相当出色,功能也非常的强大。SQLite具备下列特点:

1.轻量级

使用 SQLite 只需要带一个动态库,就可以享受它的全部功能,而且那个动态库的尺寸想当小。

2.独立性

SQLite 数据库的核心引擎不需要依赖第三方软件,也不需要所谓的“安装”。

3.隔离性

SQLite 数据库中所有的信息(比如表、视图、触发器等)都包含在一个文件夹内,方便管理和维护。

4.跨平台

SQLite 目前支持大部分操作系统,不至电脑操作系统更在众多的手机系统也是能够运行,比如:Android。

5.多语言接口

SQLite 数据库支持多语言编程接口。

6.安全性

SQLite 数据库通过数据库级上的独占性和共享锁来实现独立事务处理。这意味着多个进程可以在同一时间从同一数据库读取数据,但只能有一个可以写入数据。


SQLite具体应用过程

1、为了能够更好的管理和维护数据库,我们会封装一个继承自SQLiteOpenHelper类的数据库操作类,然后以这个类为基础,再封装我们的业务逻辑方法。


DBHelper.java

public class DBHelper extends SQLiteOpenHelper
{
    private static final String TAG = DBHelper.class.getSimpleName();
    /** 数据库名* */
    public final static String DATABASE_NAME = "launcher.db";
    /** 数据库版本 **/
    public final static int DATABASE_VERSION = 9;
    private static DBHelper instance;

    private DBHelper(Context context)
    {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    public static DBHelper getHelper(Context context)
    {
        if (instance == null)
            instance = new DBHelper(context);
        return instance;
    }

    public static void resetDBHelper(Context context)
    {
        instance = null;
        DBHelper.getHelper(context);
    }

    @Override
    public void onCreate(SQLiteDatabase db)
    {
        createTable(db, MyAppManager.TABLE_NAME, MyAppManager.mAttrs);
        createTable(db, Settings.TABLE_NAME, Settings.mAttrs);
        createTable(db, OrderDB.TABLE_NAME, OrderDB.mAttrs);
        createTable(db, RechargeDB.TABLE_NAME, RechargeDB.mAttrs);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
    {
        updateTable(db);
    }
    /**
     * must be override, if not ,it will throw exception when database degradation
     */
    @Override
    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion)
    {
        // TODO Auto-generated method stub
        updateTable(db);
    }

    private void updateTable(SQLiteDatabase db)
    {
        dropTable(db, MyAppManager.TABLE_NAME);
        dropTable(db, Settings.TABLE_NAME);
        dropTable(db, OrderDB.TABLE_NAME);
        dropTable(db, RechargeDB.TABLE_NAME);
        onCreate(db);
    }

    public static boolean createTable(SQLiteDatabase db, String tableName, TableAttrs attrs)
    {
        if (attrs == null)
        {
            return false;
        }
        Set<String> keys = attrs.keySet();
        if (keys.size() <= 0)
        {
            return false;
        }
        StringBuilder builder = new StringBuilder("create table if not exists ");
        builder.append(tableName);
        builder.append(" (");
        for (String key : keys)
        {
            String attr = attrs.getAtrr(key);
            builder.append(key);
            builder.append(" ");
            builder.append(attr);
            builder.append(",");
        }
        builder.deleteCharAt(builder.length() - 1);
        builder.append(")");
        try
        {
            db.execSQL(builder.toString());
        }
        catch (SQLiteException e)
        {
            Log.e(TAG, e.getMessage());
            return false;
        }
        return true;
    }

    public static boolean dropTable(SQLiteDatabase db, String table)
    {
        try
        {
            String sql = "drop table if exists " + table;
            db.execSQL(sql);
            return true;
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        return false;
    }

    public static boolean tableIsExist(SQLiteDatabase db, String tableName)
    {
        boolean result = false;
        Cursor cursor = null;
        try
        {
            String sql = "select count(*) as c from Sqlite_master  where type ='table' and name ='" + tableName.trim() + "' ";
            cursor = db.rawQuery(sql, null);
            if (cursor.moveToNext())
            {
                int count = cursor.getInt(0);
                if (count > 0)
                {
                    result = true;
                }
            }
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (cursor != null)
                cursor.close();
        }
        return result;
    }
}
正如上面所述,数据库第一次创建时onCreate方法会被调用,我们可以执行创建表的语句,当系统发现版本变化之后,会调用onUpgrade/onDowngrade方法,我们可以执行修改表结构等语句。


2、然后,我们需要一个DBManager基类,来封装我们所有的基本业务方法,代码如下:

/**
 * SQLiteOpenHelper 基本实现类
 *
 * @author meto
 *
 */
public class DBManager implements BaseColumns
{
       public static String TAG = "DBManager";
       /**
        * 查询表的记录数
        *
        * @param tableName
        * @param whereSql
        * @param orderBy
        * @param db
        * @return
        */
       public int getCount(String tableName, String whereSql, String orderBy)
       {
              Cursor cursor = null;
              try
              {
                     SQLiteDatabase db = getSQLiteDatabase();
                     cursor = db.query(tableName, null, null, null, null, whereSql, orderBy);
                     int count = cursor.getCount();
                     return count;
              }
              catch (Exception e)
              {
                     Log.e(TAG, "getcount()=>查询表的记录数异常:" + e.getMessage());
              }
              finally
              {
                     if (cursor != null)
                           cursor.close();
              }
              return 0;
       }
       /**
        * 查询表的记录集
        *
        * @param tableName
        * @param whereSql
        * @param orderBy
        * @param db
        * @return
        */
       public Cursor select(String tableName, String whereSql, String[] whereValue, String orderBy)
       {
              Cursor cursor = null;
              try
              {
                     SQLiteDatabase db = getSQLiteDatabase();
                     /**
                      * query(table, columns, selection, selectionArgs, groupBy, having,orderBy, limit)方法各参数的含义:
                      * table:表名。相当于select语句from关键字后面的部分。如果是多表联合查询,可以用逗号将两个表名分开。
                      * columns:要查询出来的列名。相当于select语句select关键字后面的部分。
                      * selection:查询条件子句,相当于select语句where关键字后面的部分,在条件子句允许使用占位符“?”
                      * selectionArgs:对应于selection语句中占位符的值,值在数组中的位置与占位符在语句中的位置必须一致,否则就会有异常。
                      * groupBy:相当于select语句group by关键字后面的部分
                      * having:相当于select语句having关键字后面的部分 orderBy:相当于select语句order by关键字后面的部分,如:personid desc, age asc;
                      * limit:指定偏移量和获取的记录数,相当于select语句limit关键字后面的部分。
                      */
                     cursor = db.query(tableName, null, whereSql, whereValue, null, null, orderBy);
                     return cursor;
              }
              catch (Exception e)
              {
                     e.printStackTrace();
                     Log.e(TAG, "select()=>查询表的记录集:" + e.getMessage());
              }
              return null;
       }
       /**
        * 模糊查找
        *
        * @param whereSql
        *            查找SQL语句
        * @param keywords
        *            对应查找值
        * @return
        */
       public Cursor searhByKeyword(String whereSql, String[] keywords)
       {
              try
              {
                     SQLiteDatabase db = getSQLiteDatabase();
                     Cursor cursor = db.rawQuery(whereSql, keywords);
                     return cursor;
              }
              catch (Exception e)
              {
                     Log.e(TAG, "select()=>模糊查找异常:" + e.getMessage());
              }
              return null;
       }
       /**
        * 播入记录到表
        *
        * @param tableName
        * @param mReqParams
        * @param db
        * @return
        */
       public boolean insert(String tableName, LinkedHashMap<String, Object> mReqParams)
       {
              try
              {
                     SQLiteDatabase db = getSQLiteDatabase();
                     ContentValues cv = new ContentValues();
                     for (String key : mReqParams.keySet())
                     {
                           Object param = mReqParams.get(key);
                           String value = "";
                           if (param == null)
                           {
                                  value = "";
                           }
                           else
                           {
                                  value = param.toString();
                           }
                           cv.put(key, value);
                     }
                     
                     Log.e(TAG, "cv= "+cv);
                     
                     long row = db.insert(tableName, null, cv);
                     if (row > 0)
                     {
                           return true;
                     }
              }
              catch (Exception e)
              {
                     Log.e(TAG, "insert()=>播入记录到表异常:" + e.getMessage());
              }
              return false;
       }
       public boolean insert(String tableName, ContentValues cv)
       {
              boolean bOk = false;
              try
              {
                     SQLiteDatabase db = getSQLiteDatabase();
                     long row = db.insert(tableName, null, cv);
                     if (row > 0)
                     {
                           bOk = true;
                     }
              }
              catch (Exception e)
              {
                     Log.e(TAG, "insert()=>播入记录到表异常:" + e.getMessage());
                     bOk = false;
              }
              return bOk;
       }
       
       public boolean update(String tableName, ContentValues cv, String whereClause, String[] whereArgs)
       {
              boolean bOk = false;
              try
              {
                     SQLiteDatabase db = getSQLiteDatabase();
                     long row = db.update(tableName, cv, whereClause, whereArgs);
                     if (row > 0)
                     {
                           bOk = true;
                     }
              }
              catch (Exception e)
              {
                     Log.e(TAG, "insert()=>播入记录到表异常:" + e.getMessage());
                     bOk = false;
              }
              return bOk;
       }
       /**
        * 按条件删除记录
        *
        * @param table
        * @param numer
        */
       public void deleteBySql(String table, String whereSQL, String[] whereValue)
       {
              try
              {
                     SQLiteDatabase db = getSQLiteDatabase();
                     db.delete(table, whereSQL, whereValue);
              }
              catch (Exception e)
              {
                     Log.e(TAG, "deleteBySql()=>按条件删除记录异常:" + e.getMessage());
              }
       }
       
       /**
        * CN:清空表记录
        * @param table
        */
       public void clearFeedTable(String table)
       {
              String sql = "DELETE FROM " + table + ";";
              SQLiteDatabase db = getSQLiteDatabase();
              db.execSQL(sql);
              revertSeq(table);
       }
       
       /**
        * CN:还原自增长
        * @param table
        */
       private void revertSeq(String table)
       {
              String sql = "update sqlite_sequence set seq=0 where name='" + table + "'";
              SQLiteDatabase db = getSQLiteDatabase();
              db.execSQL(sql);
       }
       /**
        * 执行SQL语句
        *
        * @param sql
        */
       public void execSQL(String sql)
       {
              try
              {
                     SQLiteDatabase db = getSQLiteDatabase();
                     db.execSQL(sql);
              }
              catch (Exception e)
              {
                     Log.e(TAG, "execSQL()=>异常:" + e.getMessage());
              }
       }
       /**
        * 删除表
        *
        * @param tableName
        * @return
        */
       public boolean dropTable(String tableName)
       {
              return DBHelper.dropTable(getSQLiteDatabase(), tableName);
       }
       public Boolean isTableExist(String tableName)
       {
              return DBHelper.tabbleIsExist(getSQLiteDatabase(), tableName);
       }
       protected boolean createTable(String tableName, TableAttrs attrs)
       {
              SQLiteDatabase db = getSQLiteDatabase();
              return DBHelper.createTable(db, tableName, attrs);
       }
       /**
        * 方法1:检查某表列是否存在
        *
        * @param db
        * @param tableName
        *            表名
        * @param columnName
        *            列名
        * @return
        */
       public boolean checkColumnExist(String tableName, String columnName)
       {
              SQLiteDatabase db = getSQLiteDatabase();
              boolean result = false;
              Cursor cursor = null;
              try
              {
                     // 查询一行
                     cursor = db.rawQuery("SELECT * FROM " + tableName + " LIMIT 0", null);
                     result = cursor != null && cursor.getColumnIndex(columnName) != -1;
              }
              catch (Exception e)
              {
                     Log.e(TAG, "checkColumnExists1..." + e.getMessage());
              }
              finally
              {
                     if (null != cursor && !cursor.isClosed())
                     {
                           cursor.close();
                     }
              }
              return result;
       }
       /**
        * 获取数据库操作句柄
        *
        * @return
        */
       public SQLiteDatabase getSQLiteDatabase()
       {
              try
              {
                     SQLiteDatabase db = DBHelper.getHelper(MyApp.mApplication).getWritableDatabase();
                     return db;
              }
              catch (Exception e)
              {
                     Log.e(TAG, "getSQLiteDatabase()=>获取数据库操作句柄异常:" + e.getMessage());
              }
              return null;
       }
}


3、当然在此之前,我们可以抽象出一个接口BaseColumns,用来定义自增ID:

public interface BaseColumns
{
    /**
     * The unique ID for a row.
     * <P>Type: INTEGER (long)</P>
     */
    public static final String ID = "id";
}


4、然后对于每一个具体的数据库业务模块,只需要继承上面的DBManager并实现自己的相关功能即可。

以节目订购功能OrderDB.java为例:

/**
 * @author john
 * @created 2016-8-3
 */
public class OrderDB extends DBManager
{
       private static String TAG = "OrderDB";
       public static final String TABLE_NAME = "order_table";
       public static final String ORDER_ID = "id";
       public static final String ORDER_PRODUCTNAME = "productname";
       public static final String ORDER_PRODUCTPRICE = "productprice";
       public static final String ORDER_BUYTIME = "buytime";
       private static OrderDB instance;
       public static final TableAttrs mAttrs = new TableAttrs()
       {
              {
                     addAttr(ORDER_ID, "text");
                     addAttr(ORDER_PRODUCTNAME, "text");
                     addAttr(ORDER_PRODUCTPRICE, "text");
                     addAttr(ORDER_BUYTIME, "text");
              }
       };
       /**
        * singleton
        *
        * @return
        */
       public static OrderDB getInstance()
       {
              if (instance == null)
              {
                     instance = new OrderDB();
              }
              return instance;
       }
       public List<OrderHistoryVO> getAllOrderHistory()
       {
              List<OrderHistoryVO> list = new ArrayList<OrderHistoryVO>();
              Cursor cursor = select(TABLE_NAME, null, null, null);
              if (cursor != null)
              {
                     while (cursor.moveToNext())
                     {
                           OrderHistoryVO vo = new OrderHistoryVO();
                           vo.setId(cursor.getInt(0));
                           vo.setProductName(cursor.getString(1));
                           vo.setProductPrice(cursor.getInt(2));
                           vo.setBuyTime(cursor.getString(3));
                           list.add(vo);
                     }
                     cursor.close();
              }
              return list;
       }
       /**
        * 更新所有数据到表中去
        *
        * @param list
        */
       public void updateAll(List<OrderHistoryVO> list)
       {
              if (list != null && list.size() > 0)
              {
                     clearFeedTable(TABLE_NAME);
                     int len = list.size();
                     for (int i = 0; i < len; i++)
                     {
                           OrderHistoryVO vo = list.get(i);
                           LinkedHashMap<String, Object> map = new LinkedHashMap<String, Object>();
                           map.put(ORDER_ID, vo.getId());
                           map.put(ORDER_PRODUCTNAME, vo.getProductName());
                           map.put(ORDER_PRODUCTPRICE, vo.getProductPrice());
                           map.put(ORDER_BUYTIME, vo.getBuyTime());
                           
                           insert(TABLE_NAME, map);
                     }
              }
       }
}


5、为了方便我们面向对象的使用数据,我们建一个OrderHistoryVO类,OrderHistoryVO是我们的order_table表对应的JavaBean,对应order_table表中的字段,如下:

/**
 * @author john
 * @created 2016-7-19
 */
public class OrderHistoryVO implements Parcelable
{
       // "id": 12,
       // "productname": "Asterix en los Juegos Olímpicos",
       // "productprice": 10,
       // "buytime": "2016-07-08 11:45:06.0"
       private int id;
       private String productName;
       private int productPrice;
       private String buyTime;
       public int getId()
       {
              return id;
       }
       public void setId(int id)
       {
              this.id = id;
       }
       public String getProductName()
       {
              return productName;
       }
       public void setProductName(String productName)
       {
              this.productName = productName;
       }
       public int getProductPrice()
       {
              return productPrice;
       }
       public void setProductPrice(int productPrice)
       {
              this.productPrice = productPrice;
       }
       public String getBuyTime()
       {
              return buyTime;
       }
       public void setBuyTime(String buyTime)
       {
              this.buyTime = buyTime;
       }
       @Override
       public int describeContents()
       {
              // TODO Auto-generated method stub
              return 0;
       }
       @Override
       public void writeToParcel(Parcel dest, int flags)
       {
              // TODO Auto-generated method stub
              dest.writeInt(id);
              dest.writeString(productName);
              dest.writeInt(productPrice);
              dest.writeString(buyTime);
       }
       public static final Parcelable.Creator<OrderHistoryVO> CREATOR = new Parcelable.Creator<OrderHistoryVO>()
       {
              @Override
              public OrderHistoryVO createFromParcel(Parcel source)
              {
                     // TODO Auto-generated method stub
                     OrderHistoryVO model = new OrderHistoryVO();
                     model.id = source.readInt();
                     model.productName = source.readString();
                     model.productPrice = source.readInt();
                     model.buyTime = source.readString();
                     return model;
              }
              @Override
              public OrderHistoryVO[] newArray(int size)
              {
                     // TODO Auto-generated method stub
                     return new OrderHistoryVO[size];
              }
       };
}


6、这里我们使用LinkedHashMap创建数据库表,LinkedHashMap是HashMap的一个子类,它保留插入的顺序,如果需要输出的顺序和输入时的相同,那么就选用LinkedHashMap。

LinkedHashMap是Map接口的哈希表和链接列表实现,具有可预知的迭代顺序。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。

LinkedHashMap实现与HashMap的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序可以是插入顺序或者是访问顺序。

注意,此实现不是同步的。如果多个线程同时访问链接的哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须保持外部同步。

根据链表中元素的顺序可以分为:按插入顺序的链表,和按访问顺序(调用get方法)的链表。 

默认是按插入顺序排序,如果指定按访问顺序排序,那么调用get方法后,会将这次访问的元素移至链表尾部,不断访问可以形成按访问顺序排序的链表。可以重写removeEldestEntry方法返回true值指定插入元素时移除最老的元素。


LinkedHashMap的实现:TableAttrs.java

public class TableAttrs
{
       private Map<String, String> mValues = new LinkedHashMap<String, String>();
       public void addAttr(String name, String attr)
       {
              mValues.put(name, attr);
       }
       public String getAtrr(String name)
       {
              return mValues.get(name);
       }
       public Set<String> keySet()
       {
              return mValues.keySet();
       }
}


7、最后就是如何使用这些数据操作方法来显示数据

/**
 * @author john
 * @created 2016-7-19
 */
public class AccountInfoFragment extends SpecialFragment implements OtherKeyDown, KeyCode
{
    private static final String TAG = AccountInfoFragment.class.getSimpleName();
    private View mMainView;
    private TextView mUserId;
    private TextView mWallet;
    private ListView mOrderHistoryListView;
    private ListView mRechargeHistoryListView;

    private ImageView orderSelector = null;
    private ImageView rechargeSelector = null;

    private LinearLayout mContentLayout=null;
    private LinearLayout noDataView=null;
    private TextView noDataTxt;

    private List<OrderHistoryVO> orderHistoryList;
    private List<RechargeVO> rechargeList;

    private List<Map<String, Object>> orderHistoryListData = null;
    private List<Map<String, Object>> rechargeListData = null;

    private AccountAdapter orderHistoryAdapter = null;
    private AccountAdapter rechargeHistoryAdapter = null;

    private static final int ORDER = 0;
    private static final int RECHARGE = 1;
    private static final int INIT_DATA=2;

    private Context mContext = null;

    private Handler mHandler=new Handler()
    {
        @Override
        public void handleMessage(Message msg)
        {
            // TODO Auto-generated method stub
            super.handleMessage(msg);
            switch (msg.what)
            {
                case INIT_DATA:
                    if(isAdded())
                    {
                        initData();   
                    }
                    break;

                default :
                    break;
            }
        }

    };

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        mContext = getActivity();
        new MyThread().start();
    }

    class MyThread extends Thread
    {
        @Override
        public void run()
        {
            Looper.prepare();
            startLoadDataService();
            Looper.loop();
        }
    }

    protected void startLoadDataService()
    {
        // TODO Auto-generated method stub
        try
        {
            OrderDB.getInstance().updateAll(getOrderHistoryList());
            RechargeDB.getInstance().updateAll(getRechargeList());

            orderHistoryList = OrderDB.getInstance().getAllOrderHistory();
            Log.e("john", "startLoadDataService orderHistoryList: "+orderHistoryList);
            rechargeList = RechargeDB.getInstance().getAllRechargeHistory();
            Log.e("john", "startLoadDataService rechargeList: "+rechargeList);
            if(orderHistoryList==null||orderHistoryList.size()==0)
            {
                orderHistoryList=getOrderHistoryList();   
            }
            if(rechargeList==null||rechargeList.size()==0)
            {
                rechargeList=getRechargeList();   
            }
            Log.e("john", "============startLoadDataService================");
        }
        catch (Exception e)
        {
            // TODO: handle exception
            orderHistoryList = null;
            rechargeList = null;
        }
        mHandler.sendEmptyMessage(INIT_DATA);   
    }

    @SuppressWarnings("unchecked")
    public List<OrderHistoryVO> getOrderHistoryList()
    {
        // TODO Auto-generated method stub
        List<OrderHistoryVO> orderHistoryList = null;
        ParseResultVO reVO = ServerManager.obtain().requestOrderHistory(Settings.Obtain().getString(Settings.ORDER_HISTORY_URL_NEW));

        if (reVO.getResult() == Constant.ParseData_Success)
        {
            orderHistoryList = (List<OrderHistoryVO>) reVO.getObj();
        }
        Log.e("john", "mOrderHistoryList: " + orderHistoryList);
        return orderHistoryList;
    }

    @SuppressWarnings("unchecked")
    public List<RechargeVO> getRechargeList()
    {
        // TODO Auto-generated method stub
        List<RechargeVO> rechargeList = null;
        ParseResultVO reVO = ServerManager.obtain().requestChargeHistory(Settings.Obtain().getString(Settings.RECHARGE_URL));

        if (reVO.getResult() == Constant.ParseData_Success)
        {
            rechargeList = (List<RechargeVO>) reVO.getObj();
        }
        Log.e("john", "mRechargeList: " + rechargeList);
        return rechargeList;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        mMainView = inflater.inflate(R.layout.fragment_account_info, null);
        mUserId = (TextView) mMainView.findViewById(R.id.user_id);
        mWallet = (TextView) mMainView.findViewById(R.id.wallet);
        mOrderHistoryListView = (ListView) mMainView.findViewById(R.id.order_history_list);
        mRechargeHistoryListView = (ListView) mMainView.findViewById(R.id.recharge_history_list);
        orderSelector = (ImageView) mMainView.findViewById(R.id.selector1);
        rechargeSelector = (ImageView) mMainView.findViewById(R.id.selector2);
        mContentLayout=(LinearLayout) mMainView.findViewById(R.id.account_content);
        noDataView=(LinearLayout) mMainView.findViewById(R.id.view_no_data);
        noDataTxt=(TextView) mMainView.findViewById(R.id.no_data_txt);
        noDataTxt.setText(R.string.tips_loading);
        mContentLayout.setVisibility(View.GONE);
        noDataView.setVisibility(View.VISIBLE);
        return mMainView;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState)
    {
        super.onActivityCreated(savedInstanceState);
        Log.e("john", "============onActivityCreated================");

    }

    private void initData()
    {
        orderHistoryListData = Collections.synchronizedList(new ArrayList<Map<String, Object>>());
        rechargeListData = Collections.synchronizedList(new ArrayList<Map<String, Object>>());

        String sn = SystemProperty.systemPropertyGet("persist.sys.sn");
        sn = String.format(getResources().getString(R.string.STR_USER_ID), sn);
        mUserId.setText(sn);
        if (rechargeList != null&&rechargeList.size()>0)
        {
            String data = getResources().getString(R.string.STR_WALLET);
            data = String.format(data, rechargeList.get(0).getWallet());
            mWallet.setText(data);

            if(orderHistoryList==null||(orderHistoryList!=null&&orderHistoryList.size()==0))
            {
                noDataTxt.setText(R.string.STR_NO_ORDER_HISTORY);
                noDataView.setVisibility(View.VISIBLE);
                mContentLayout.setVisibility(View.GONE);
            }
            else
            {
                noDataView.setVisibility(View.GONE);
                mContentLayout.setVisibility(View.VISIBLE);
            }
        }
        else
        {
            noDataTxt.setText(R.string.STR_NO_ACCOUNT);
            noDataView.setVisibility(View.VISIBLE);
            mContentLayout.setVisibility(View.GONE);
        }

        setOrderData();
        setRechargeData();

        orderHistoryAdapter = new AccountAdapter(getActivity(), orderHistoryListData, false);
        mOrderHistoryListView.setAdapter(orderHistoryAdapter);

        rechargeHistoryAdapter = new AccountAdapter(getActivity(), rechargeListData, true);
        mRechargeHistoryListView.setAdapter(rechargeHistoryAdapter);

    }

    private void setOrderData()
    {
        // TODO Auto-generated method stub

        if (orderHistoryList != null)
        {
            mapData(ORDER);   
        }

        mOrderHistoryListView.setOnKeyListener(orderKeyListener);
        mOrderHistoryListView.setOnItemSelectedListener(new OnItemSelectedListener()
        {

            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
            {
                orderSelector.animate().translationY(view.getTop()).setDuration(animFlag ? normalDur : noDur).start();
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent)
            {
                // TODO Auto-generated method stub

            }
        });
    }

    private void setRechargeData()
    {
        // TODO Auto-generated method stub
        if (rechargeList != null)
        {
            mapData(RECHARGE);       
        }

        mRechargeHistoryListView.setOnKeyListener(rechargeKeyListener);
        mRechargeHistoryListView.setOnItemSelectedListener(new OnItemSelectedListener()
        {

            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id)
            {
                rechargeSelector.animate().translationY(view.getTop()).setDuration(animFlag ? normalDur : noDur).start();
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent)
            {
                // TODO Auto-generated method stub

            }
        });

    }

    private void mapData(int iType)
    {
        switch (iType)
        {

        case ORDER:
            orderHistoryListData.clear();
            for(int i=0;i< orderHistoryList.size();i++)
            {

                    Map<String, Object> map = new HashMap<String, Object>();

                    map.put("id", orderHistoryList.get(i).getId());
                    map.put("productName", orderHistoryList.get(i).getProductName());
                    map.put("productPrice", orderHistoryList.get(i).getProductPrice());
                    map.put("buyTime", orderHistoryList.get(i).getBuyTime());

                    orderHistoryListData.add(map);
            }       
            break;

        case RECHARGE:
            rechargeListData.clear();
            for (int i =0; i < rechargeList.size(); i++)
            {
                Map<String, Object> map = new HashMap<String, Object>();

                map.put("id", rechargeList.get(i).getId());
                map.put("businessOffice", rechargeList.get(i).getBusinessOffice());
                map.put("businessWorker", rechargeList.get(i).getBusinessWorker());
                map.put("payment", rechargeList.get(i).getPayment());
                map.put("chargeTime", rechargeList.get(i).getChargeTime());

                rechargeListData.add(map);
            }
            break;

        default:
            break;

        }
        notifyDataSetChanged();
    }

    private void notifyDataSetChanged()
    {
        // TODO Auto-generated method stub
        if(orderHistoryAdapter!=null)
        {
        orderHistoryAdapter.notifyData(orderHistoryListData);
        }
        if(rechargeHistoryAdapter!=null)
        {
        rechargeHistoryAdapter.notifyData(rechargeListData);
        }
    }

    @Override
    public void onDestroyView()
    {
        super.onDestroyView();

    }

    @Override
    public void onResume()
    {
        // TODO Auto-generated method stub
        // FpanelManager.getInstance(getActivity()).setFpanelMsgDispName(getResources().getString(R.string.STR_ACCOUNT_INFO));
        super.onResume();

    }

    private OnKeyListener orderKeyListener = new OnKeyListener()
    {
        @Override
        public boolean onKey(View v, int keyCode, KeyEvent event)
        {
            int iSelect=mOrderHistoryListView.getSelectedItemPosition();
            if (event.getAction() == KeyEvent.ACTION_DOWN)
            {
                if (isNotAvaliableKey())
                {
                    return true;
                }
                switch (keyCode)
                {

                case KEY_UP:

                    if (orderHistoryListData.size() > 0)
                    {
                        if (iSelect==0)
                        {
                            mOrderHistoryListView.setSelection(mOrderHistoryListView.getCount()-1);
                        }
                        else
                        {
                            mOrderHistoryListView.onKeyDown(keyCode, event);
                        }
                    }
                    return true;

                case KEY_DOWN:
                    if (orderHistoryListData.size() > 0)
                    {

                        if (iSelect ==mOrderHistoryListView.getCount()-1 )
                        {
                            mOrderHistoryListView.setSelection(0);
                        }
                        else
                        {
                            mOrderHistoryListView.onKeyDown(keyCode, event);
                        }

                    }
                    return true;

                case KEY_LEFT:
                    return true;

                case KEY_RIGHT:
                    if (!mRechargeHistoryListView.hasFocus())
                    {

                        mOrderHistoryListView.setFocusable(false);
                        mOrderHistoryListView.clearFocus();
                        mRechargeHistoryListView.setFocusable(true);
                        mRechargeHistoryListView.requestFocus();

                        rechargeSelector.setVisibility(View.VISIBLE);
                        PropertyValuesHolder pivotX = PropertyValuesHolder.ofFloat("pivotX", 0);
                        PropertyValuesHolder scaleX1 = PropertyValuesHolder.ofFloat("scaleX", 1f, 0f);
                        ObjectAnimator.ofPropertyValuesHolder(orderSelector, pivotX, scaleX1).setDuration(animFlag ? normalDur : noDur).start();
                        PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 0f, 1f);
                        ObjectAnimator.ofPropertyValuesHolder(rechargeSelector, pivotX, scaleX).setDuration(animFlag ? normalDur : noDur).start();

                    }
                    return true;

                case KEY_EXIT:
                case KEY_BACK:
                    onKeyDown(keyCode, event);
                    return true;
                }
            }
            return false;
        }
    };

    private OnKeyListener rechargeKeyListener = new OnKeyListener()
    {
        @Override
        public boolean onKey(View v, int keyCode, KeyEvent event)
        {
            int iSelect=mRechargeHistoryListView.getSelectedItemPosition();
            if (event.getAction() == KeyEvent.ACTION_DOWN)
            {
                if (isNotAvaliableKey())
                {
                    return true;
                }
                switch (keyCode)
                {

                case KEY_UP:

                    if (rechargeListData.size() > 0)
                    {

                        if (iSelect==0)
                        {
                            mRechargeHistoryListView.setSelection(mRechargeHistoryListView.getCount()-1);
                        }
                        else
                        {
                            mRechargeHistoryListView.onKeyDown(keyCode, event);
                        }

                    }
                    return true;

                case KEY_DOWN:
                    if (rechargeListData.size() > 0)
                    {

                        if (iSelect ==mRechargeHistoryListView.getCount()-1)
                        {
                            mRechargeHistoryListView.setSelection(0);
                        }
                        else
                        {
                            mRechargeHistoryListView.onKeyDown(keyCode, event);
                        }

                    }
                    return true;

                case KEY_LEFT:
                    if (!mOrderHistoryListView.hasFocus())
                    {

                        mRechargeHistoryListView.setFocusable(false);
                        mRechargeHistoryListView.clearFocus();
                        mOrderHistoryListView.setFocusable(true);
                        mOrderHistoryListView.requestFocus();

                        orderSelector.setVisibility(View.VISIBLE);
                        PropertyValuesHolder pivotX = PropertyValuesHolder.ofFloat("pivotX", 0);
                        PropertyValuesHolder scaleX1 = PropertyValuesHolder.ofFloat("scaleX", 1f, 0f);
                        ObjectAnimator.ofPropertyValuesHolder(rechargeSelector, pivotX, scaleX1).setDuration(animFlag ? normalDur : noDur).start();
                        PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 0f, 1f);
                        ObjectAnimator.ofPropertyValuesHolder(orderSelector, pivotX, scaleX).setDuration(animFlag ? normalDur : noDur).start();

                    }
                    return true;

                case KEY_RIGHT:
                    return true;

                case KEY_EXIT:
                case KEY_BACK:
                    onKeyDown(keyCode, event);
                    return true;
                }
            }
            return false;

        }
    };

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event)
    {
        // TODO Auto-generated method stub
        setAnimFlag();
        switch (keyCode)
        {

        case KEY_VOLDOWN:
        case KEY_VOLUP:
        case KEY_MUTE:
        case KeyEvent.KEYCODE_VOLUME_MUTE:
            return true;

        case KEY_OK:
        case KEY_CENTER:
        case KEY_RIGHT:
            if (!mOrderHistoryListView.hasFocus())
            {
                getActivity().findViewById(R.id.listview_menu).setFocusable(false);
                getActivity().findViewById(R.id.listview_menu).clearFocus();
                mOrderHistoryListView.setFocusable(true);
                mOrderHistoryListView.requestFocus();
                orderSelector.setVisibility(View.VISIBLE);
                PropertyValuesHolder pivotX = PropertyValuesHolder.ofFloat("pivotX", 0);
                PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 0f, 1f);
                ObjectAnimator.ofPropertyValuesHolder(orderSelector, pivotX, scaleX).setDuration(animFlag ? normalDur : noDur).start();

            }
            return true;

        case KEY_BACK:
        case KEY_EXIT:
            PropertyValuesHolder pivotX1 = PropertyValuesHolder.ofFloat("pivotX", 0);
            PropertyValuesHolder scaleX1 = PropertyValuesHolder.ofFloat("scaleX", 1f, 0f);
            if (mOrderHistoryListView.hasFocus())
            {
                mOrderHistoryListView.setFocusable(false);
                mOrderHistoryListView.clearFocus();
                getActivity().findViewById(R.id.listview_menu).setFocusable(true);
                getActivity().findViewById(R.id.listview_menu).requestFocus();
                ((ListView) getActivity().findViewById(R.id.listview_menu)).setSelection(0);

                ObjectAnimator.ofPropertyValuesHolder(orderSelector, pivotX1, scaleX1).setDuration(animFlag ? normalDur : noDur).start();

            }
            else if(mRechargeHistoryListView.hasFocus())
             {
                 mRechargeHistoryListView.setFocusable(false);
                 mRechargeHistoryListView.clearFocus();
                 getActivity().findViewById(R.id.listview_menu).setFocusable(true);
                 getActivity().findViewById(R.id.listview_menu).requestFocus();
                 ((ListView) getActivity().findViewById(R.id.listview_menu)).setSelection(0);

                 ObjectAnimator.ofPropertyValuesHolder(rechargeSelector, pivotX1, scaleX1).setDuration(animFlag ? normalDur : noDur).start();

             }
            else
            {
                getActivity().finish();
                // OSDSettingManager.getInstance(getActivity()).setActivityExitAni(getActivity());
            }
            break;

        default:
            break;
        }
        return false;
    }
}
注意,数据库操作不能放在UI线程,否则会导致ANR。

作者:johnWcheung 发表于2016/10/21 14:03:18 原文链接
阅读:8 评论:0 查看评论

Android 学习笔记:View的事件分发机制 解析及实例讲解

$
0
0

想必大家对编写自定义控件的流程不陌生,独自编写过许多继承ViewViewGroup之类的自定义控件。在编写的过程中肯定要考虑到View的事件分发机制,不可避免的重要部分,各位有考虑过以下问题:

1. 事件的传递机制?
2. 事件的分发过程涉及到的方法?
3. 接收事件的方法的优先级?

接下来的内容依次来解析:
(以下融合个人理解和任玉刚老师的《Android开发艺术探索》书中内容)




点击事件的传递规则

1.方法介绍

以下分析的主要对象就是 点击事件(MotionEvent) 。而点击事件的事件分发,其实就是对 MotionEvent 事件的分发过程。当屏幕上的点击事件产生后,Android系统会将此事件传递一个具体的View去响应,而这个传递的过程就是分发过程

接下来介绍的三种方法就是来完成点击事件的分发过程:

1). dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent ev)

用来进行事件的分发。如果点击事件能够传递到当前View,那此方法一定会被调用。

返回结果受当前 ViewonTouchEvent 和下级ViewdispatchTouchEvent方法的影响(若下级View指定消费点击事件,则它的返回结果是false),表示是否消耗当前事件。


2). onIterceptTouchEvent

public boolean onInterceptTouchEvent(MotionEvent event)

在上述方法内部调用,用于判断是否拦截点击事件。若当前View拦截了点击事件,在同一个事件序列中,此方法不会再次被调用。返回结果表示是否拦截当前点击事件。


3). onTouchEvent

public boolean onTouchEvent(MotionEvent event)

也是在 dispatchTouchEvent 方法中调用,用于处理具体点击事件(最常用的三个:DOWNUPMOVE)。返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前无法再次接收到事件!

【举个栗子:】
比如说在onTouchEvent 方法中分别监视了DOWNUPMOVE三种状态,并有对应的逻辑处理,若不将返回结果改为true,则手指在屏幕上操作时,只会响应 DOWN 的逻辑,后面的则忽略掉!

【未修改返回值:】
这里写图片描述

【修改返回值true:】
这里写图片描述

以上GIF动图明显对比出不同了,这是一个自定义粘性控件,我在onTouchEvent 方法中分别监视了DOWNUPMOVE三种状态,并有对应的逻辑处理。而第一个GIF动图中方法返回值未修改:
return super.onTouchEvent(event);
所以如上所说,由于返回值为修改,它检测到DOWN事件后,做出响应,而后的MOVEUP事件都无法检测,显示出来的效果是卡顿的感觉。

而第二个GIF动图中设置了返回值:
return true;
表示消耗当前事件,所以在同一个事件序列中,可以再次接收到事件!而这才是我们需要的结果。

由此可见返回值的重要性,接下来继续深入了解这三个方法:




2. 三个方法之间的联系

其实上述三个方法的区别与联系可用以下伪代码表示:

public boolean dispatchTouchEvent(MotionEvent ev){
    boolean consume = false;
    if(onInterceptTouchEvent(ev)){
        consume = onTouchEvent(ev);
    }else {
        consume = child.dispatchTouchEvent(ev);
    }

    return consume;
}

通过以上伪代码,可得知点击事件的传递规则:当一个点击事件产生后,一个根ViewGroup会最先接收!这时它的 dispatchTouchEvent 方法会被调用:

1). 若此方法返回值为true,代表它要拦截此事件,所以此点击事件会交给此 ViewGroup 处理,即它的onTouchevent 方法随之被调用;

2). 若此方法返回值为false,代表它不拦截此事件,这时当前事件被继续传递给它的子元素,接着子ViewdispatchTouchEvent 方法会被调用,再根据返回值做出选择,如此反复直到此点击事件被最终处理!

这里写图片描述




3. OnTouchListener 和 onTouchEvent 和 OnClickListener 优先级

//OnTouchListener 

        view.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                return false;
            }
        });
//onTouchEvent

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return super.onTouchEvent(event);
    }
//OnClickListener
        view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

            }
        });

当一个View 需要处理事件时,如果它设置了 OnTouchListener ,则 OnTouchListener 中的onTouch 方法会被回调。这时事件如何处理取决于onTouch 方法的返回值:

1)若返回false,则当前ViewonTouchEvent 方法会被调用。
2)若返true,此点击事件已被OnTouchListener 中的onTouch 方法消费掉, onTouchEvent 方法不会被调用!

可得出部分结论:View设置的OnTouchListener ,其优先级比onTouchEvent 高!

而在onTouchEvent 方法中,如果View 还设置了 OnClickListener,这时它的onClick 方法才会被调用!

最终结论:
优先级排序:OnTouchListener >onTouchEvent >OnClickListener




4. 点击事件的传递顺序(Activity 、Window 、View )

当一个点击事件产生后,传递顺序为: Activity —> Window –> View即事件总是先传递给Activity ,再传递给Window,最后Window传递给顶级View。而顶级View 接收到此点击事件,就会按照事件分发机制(即上面的逻辑图)。

考虑一个特殊情况:如果一个子ViewonTouchEvent 返回false,那么它的父容器的 onTouchEvent 会被调用,依次类推。若所有的元素不处理此事件,那么此点击事件最终传递给Activity处理,因此ActivityonTouchEvent 方法会被调用!

举一个通俗易懂的栗子:这个点击事件就是一个苹果,而Activity就是爷爷,依次传递的是爸爸和儿子。现在爷爷收到一个苹果,心疼上班辛苦的爸爸,给他吃,而爸爸心疼自己的小儿子,给儿子吃。儿子拿到了苹果,嫌弃苹果不新鲜,又还给了爸爸(onTouchEvent返回false),爸爸一看是有些不新鲜,还给了爷爷,爷爷舍不得丢,自己处理掉了(上级的onTouchEvent被调用)。这就是一个传递过程(领导与员工的例子也很贴切)






事件传递机制的一些结论及注意

1. 事件序列

(1). 定义:同一事件序列是从手指接触屏幕的那一刻起,到手指离开屏幕的那一刻结束。Touch时最明显的三个状态,事件序列从DOWN 事件开始,手指在屏幕上滑动即MOVE事件,最后以手指抬起 UP事件结束掉这个事件序列。

(2)注意:某个View一旦拦截此点击事件,那么这个事件序列都会由它来处理,并且它的onInterceptTouchEvent不会被调用!也证实了一开始所讲dispatchTouchEvent方法中返回true后,无需再调用此ViewonInterceptTouchEvent来确定是否需要拦截。

正常情况下,一个事件序列只能被一个View拦截且消耗。


2. ViewGroup

ViewGroup默认不拦截任何事件。Android源码中ViewGrouponInterceptTouchEvent 方法默认返回值为false.


3.View

(1) View 没有 onInterceptTouchEvent 方法,一旦有点击事件传递给它,它的onTouchEvent方法会被调用。

(2) ViewonTouchEvent默认都会消耗事件(返回true),除非它是不可点击的(clickablelongClickable 同时为false)。ViewlongClickable 属性默认值为falseclickable属性要分情况:比如Buttonclickable 属性默认为true,而TextViewclickable属性默认为false

(3) Viewenable 属性不影响 onTouchevent 的默认返回值。哪怕一个Viewdisable状态的,只要它的clickable或者 longClickable属性默认为true,而TextViewclickable属性默认为false.

(4) onClick 会发生的前提是当前View是可点击的,并且它收到了DOWNUP的事件。


4 .总结

事件传递过程是由外向内的,即事件总是先传递给父元素,然后再由父元素分发给子View,通过 requestDisallowInterceptTouchEvent 方法可以在子元素中敢于父元素的事件分发过程,ACTION_DOWN事件除外。





实例证明

1. 自定义控件(ViewGroup)

这里写图片描述

如上图所示,这里是一个彷QQ侧滑栏的自定义控件(ViewGroup)。很明显,操作过程中产生了点击事件:右滑显示侧边栏,点击可直接关闭侧边栏。

这里需要注意的是两个不同的子View(主界面和侧滑栏)里还有下级ViewlistView),所以当点击事件传递到自定义控件SlideMenu这个View的时候,必须拦截下来,消费掉此点击事件!

所以我们需要重写onInterceptTouchEvent 方法和onTouchEvent 方法,并且修改两个方法的返回值!

    //3.触摸事件传递及拦截事件
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return  viewDragHelper.shouldInterceptTouchEvent(ev);

    }
   //返回true,代表消费此事件,自己来处理
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        viewDragHelper.processTouchEvent(event);
        return true;
    }

由以上代码可知,这里重写并修改了返回值,onTouchEvent 方法中返回值为true,代表消耗此点击事件!也许你有个疑问:这里重写了两个方法,那第一个方法dispatchTouchEvent

正因如此,证实了在介绍dispatchTouchEvent 方法时说的:返回结果受当前 ViewonTouchEvent 和下级ViewdispatchTouchEvent方法的影响!所以重写的两个方法中已经决定此方法的返回值。



2. 自定义控件(View)

这里写图片描述

如上图所示,这是个粘性自定义控件(View),对屏幕的操作产生了一系列的事件序列,所以此控件需要消费掉此点击事件!

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            ......
            break;
        case MotionEvent.ACTION_MOVE:
            ......
            break;
        case MotionEvent.ACTION_UP:
            ......
            break;
        }
        invalidate();
        return true;

    }

以上代码所示,这里的事件序列是从 DOWN事件开始产生的,持续MOVE事件,最后以手指抬起UP事件结束!所以重写onTouchEvent 方法,并且修改返回值为true

同时也证实了上述结论中的一点:
View 没有 onInterceptTouchEvent 方法,一旦有点击事件传递给它,它的onTouchEvent方法会被调用。






以上大部分都是摘于任玉刚老师的《Android开发艺术探索》,非常好的一本书!详细介绍了View部分,而这边博客也是基于“View的事件分发机制”这一点进行讲解,考虑到纯理论部分有些枯燥,加了一些例子讲解,后面再写一篇源码分析的,也是书中的内容,推荐大家~

希望对你们有帮助:)

作者:ITermeng 发表于2016/10/21 14:09:59 原文链接
阅读:3 评论:0 查看评论

Xamarin android如何调用百度地图入门示例(一)

$
0
0
在Xamarin android如何调用百度地图呢?

首先我们要区分清楚,百度地图这是一个广泛的概念,很多刚刚接触这个名词”百度地图api”,的确是泛泛而谈,我们来看一下百度地图的官网:


android上使用百度地图的有Android地图SDK,定位SDK,导航SDK,全景SDK......等等虽然平时项目中可能会用到,但是不一定每一个SDK都能熟练掌握,xamarin android中如何使用百度地图的这些SDK呢,好吧,说这么多废话其实我想写的就是

在Xamarin android如何使用百度地图的定位sdk

首先我来看一下效果:

下面主要流程分为以下几步:
  1. 新建项目 Binging Labrary(android)
  2. 下载百度地图 --Android定位SDK    
  3. Binging Labrary项目添加相关文件
  4. 注册百度开发平台,创建应用,获取AK(要注意这一点)
  5. 创建Android项目引用Binging Labrary项目
  6. 写入百度提供示例代码,成功。

第一步:创建Binding Labrary(Android)


第二步:下载百度地图  Android定位SDK http://lbsyun.baidu.com/index.php?title=android-locsdk/geosdk-android-download

第三步:在Jars文件夹下添加jar文件,并将jar文件的生成操作改为Embedded.jar如图:


重新生成马上就会报这个错误:


只需要打开项目DWService - Transforms文件夹 修改Transforms-metadata.xml文件为以下,就不会生成错误。

<metadata>
  <attr path="/api/package[@name='com.baidu.location']/class[@name='Address']/field[@name='address']" name="name">Addresses</attr>
</metadata>
新建文件夹Assets:把下载的以下文件粘贴到Assets文件夹下,并将so文件的生成操作修改为EmbeddedNativeLibrary
第四步:到了最关键的时候,如果不是很明白这一步的操作,可以百度一下。这一步的目的是获取访问应用(AK)如图:

创建应用后,填写SHA1,那么如何获取SHA1的方法有很多,看了很多博客,但是Xamarin android 获取sha1这种方式 在Dos中输入命令的方式有时候没有作用,之前在我笔记本上的操作居然没有作用,有点郁闷,我这个台式又可以获取 。 输入路径 :C:\Users\zhanglin\AppData\Local\Xamarin\Mono for Android 命令:keytool -list -v -keystore debug.keystore 输入密钥口令:直接回车键,如果不行的输入Android。创建应用的时候包名就是AndroidManifest.xml文件中package属性的值


当然你可以用其他的方式获取sha1,下载MCodeCheck,也可以查看sha1值

第五步:创建一个Android项目并添加引用,引用就是项目-解决方案新建的这个BaiduServer

第六步:在Android项目中的AndroidManifest.xml文件中进行一下配置:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="BaiduDW.BaiduDW" android:versionCode="1" android:versionName="1.0">
  <uses-sdk android:minSdkVersion="16" />
  <application android:label="BaiduDW">
    <service android:name="com.baidu.location.f" android:enabled="true" android:process=":remote">
    </service>
    <meta-data
            android:name="com.baidu.lbsapi.API_KEY"
            android:value="CUm5LGTd4Y38YLXXzs3xjfNa0dvV8BOT" /> //key:开发者申请的Key
  </application>
  <!-- 这个权限用于进行网络定位-->
  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"></uses-permission>
  <!-- 这个权限用于访问GPS定位-->
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"></uses-permission>
  <!-- 用于访问wifi网络信息,wifi信息会用于进行网络定位-->
  <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"></uses-permission>
  <!-- 获取运营商信息,用于支持提供运营商信息相关的接口-->
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission>
  <!-- 这个权限用于获取wifi的获取权限,wifi信息会用来进行网络定位-->
  <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"></uses-permission>
  <!-- 用于读取手机当前的状态-->
  <uses-permission android:name="android.permission.READ_PHONE_STATE"></uses-permission>
  <!-- 写入扩展存储,向扩展卡写入数据,用于写入离线定位数据-->
  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
  <!-- 访问网络,网络定位需要上网-->
  <uses-permission android:name="android.permission.INTERNET" />
  <!-- SD卡读取权限,用户写入离线定位数据-->
  <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"></uses-permission>
</manifest>
在MainActivity.cs中引入Com.Baidu.Location , System.Text,并实现接口IBDLocationListener,例子代码是百度官方的。所实现的效果调试中的输出可以看到。

using System;
using Android.App;
using Android.OS;
using Com.Baidu.Location;
using System.Text;
namespace BaiduMapDemo
{
    [Activity(Label = "BaiduMapDemo", MainLauncher = true, Icon = "@drawable/icon")]
    public class MainActivity : Activity,IBDLocationListener
    {
        int count = 1;
        public LocationClient mLocationClient = null;
        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);
            SetContentView(Resource.Layout.Main);
            mLocationClient = new LocationClient(ApplicationContext);     //声明LocationClient类
            mLocationClient.RegisterLocationListener(this);    //注册监听函数
            StartLocationService();
        }
        private void StartLocationService()
        {
            LocationClientOption option = new LocationClientOption();
            option.SetLocationMode(LocationClientOption.LocationMode.HightAccuracy);//可选,默认高精度,设置定位模式,高精度,低功耗,仅设备
            option.CoorType = "bd09ll";//可选,默认gcj02,设置返回的定位结果坐标系
            int span = 1000;
            option.ScanSpan = span;//可选,默认0,即仅定位一次,设置发起定位请求的间隔需要大于等于1000ms才是有效的
            option.SetIsNeedAddress(true);//可选,设置是否需要地址信息,默认不需要
            option.OpenGps = true;//可选,默认false,设置是否使用gps
            option.LocationNotify = true;//可选,默认false,设置是否当GPS有效时按照1S/1次频率输出GPS结果
            option.SetIsNeedLocationDescribe(true);//可选,默认false,设置是否需要位置语义化结果,可以在BDLocation.getLocationDescribe里得到,结果类似于“在北京天安门附近”
            option.SetIsNeedLocationPoiList(true);//可选,默认false,设置是否需要POI结果,可以在BDLocation.getPoiList里得到
            option.SetIgnoreKillProcess(false);//可选,默认true,定位SDK内部是一个SERVICE,并放到了独立进程,设置是否在stop的时候杀死这个进程,默认不杀死  
            option.SetIgnoreCacheException(false);//可选,默认false,设置是否收集CRASH信息,默认收集
            option.EnableSimulateGps = false;//可选,默认false,设置是否需要过滤GPS仿真结果,默认需要
            mLocationClient.LocOption = option;
            mLocationClient.Start();
        }
        public void OnReceiveLocation(BDLocation location)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("time : ");
            sb.Append(location.Time);
            sb.Append("\nerror code : ");
            sb.Append(location.LocType);
            sb.Append("\nlatitude : ");
            sb.Append(location.Latitude);
            sb.Append("\nlontitude : ");
            sb.Append(location.Longitude);
            sb.Append("\nradius : ");
            sb.Append(location.Radius);
            if (location.LocType == BDLocation.TypeGpsLocation)
            {// GPS定位结果
                sb.Append("\nspeed : ");
                sb.Append(location.Speed);// 单位:公里每小时
                sb.Append("\nsatellite : ");
                sb.Append(location.SatelliteNumber);
                sb.Append("\nheight : ");
                sb.Append(location.Altitude);// 单位:米
                sb.Append("\ndirection : ");
                sb.Append(location.Direction);// 单位度
                sb.Append("\naddr : ");
                sb.Append(location.AddrStr);
                sb.Append("\ndescribe : ");
                sb.Append("gps定位成功");
            }
            else if (location.LocType == BDLocation.TypeNetWorkLocation)
            {// 网络定位结果
                sb.Append("\naddr : ");
                sb.Append(location.AddrStr);
                //运营商信息
                sb.Append("\noperationers : ");
                sb.Append(location.Operators);
                sb.Append("\ndescribe : ");
                sb.Append("网络定位成功");
            }
            else if (location.LocType == BDLocation.TypeOffLineLocation)
            {// 离线定位结果
                sb.Append("\ndescribe : ");
                sb.Append("离线定位成功,离线定位结果也是有效的");
            }
            else if (location.LocType == BDLocation.TypeServerError)
            {
                sb.Append("\ndescribe : ");
                sb.Append("服务端网络定位失败,可以反馈IMEI号和大体定位时间到loc-bugs@baidu.com,会有人追查原因");
            }
            else if (location.LocType == BDLocation.TypeNetWorkException)
            {
                sb.Append("\ndescribe : ");
                sb.Append("网络不同导致定位失败,请检查网络是否通畅");
            }
            else if (location.LocType == BDLocation.TypeCriteriaException)
            {
                sb.Append("\ndescribe : ");
                sb.Append("无法获取有效定位依据导致定位失败,一般是由于手机的原因,处于飞行模式下一般会造成这种结果,可以试着重启手机");
            }
            sb.Append("\nlocationdescribe : ");
            sb.Append(location.LocationDescribe);// 位置语义化信息
            System.Collections.Generic.IList<Poi> list = location.PoiList;// POI数据
            if (list != null)
            {
                sb.Append("\npoilist size = : ");
                sb.Append(list.Count.ToString());
                foreach (Poi p in list)
                {
                    sb.Append("\npoi= : ");
                    sb.Append(p.Id + " " + p.Name + " " + p.Rank);
                }
            }
            System.Diagnostics.Debug.Write(sb.ToString());
        }
    }
}
作者:张林

标题:xamarin android如何调用百度地图入门示例(一)原文地址:http://blog.csdn.net/kebi007/article/details/52887570

转载随意注明出处



作者:kebi007 发表于2016/10/22 0:04:39 原文链接
阅读:386 评论:0 查看评论

Android中的动态加载机制--薛彦顺

$
0
0

  在目前的软硬件环境下,Native App与Web App在用户体验上有着明显的优势,但在实际项目中有些会因为业务的频繁变更而频繁的升级客户端,造成较差的用户体验,而这也恰恰是Web App的优势。本文对网上Android动态加载jar的资料进行梳理和实践在这里与大家一起分享,试图改善频繁升级这一弊病。

Android应用开发在一般情况下,常规的开发方式和代码架构就能满足我们的普通需求。但是有些特殊问题,常常引发我们进一步的沉思。我们从沉思中产生顿悟,从而产生新的技术形式。
如何开发一个可以自定义控件的Android应用?就像eclipse一样,可以动态加载插件;如何让Android应用执行服务器上的不可预知的代码?如何对Android应用加密,而只在执行时自解密,从而防止被破解?……
熟悉Java技术的朋友,可能意识到,我们需要使用类加载器灵活的加载执行的类。这在Java里已经算是一项比较成熟的技术了,但是在Android中,我们大多数人都还非常陌生。
类加载机制
       Dalvik虚拟机如同其他Java虚拟机一样,在运行程序时首先需要将对应的类加载到内存中。而在Java标准的虚拟机中,类加载可以从class文件中读取,也可以是其他形式的二进制流,因此,我们常常利用这一点,在程序运行时手动加载Class,从而达到代码动态加载执行的目的
       然而Dalvik虚拟机毕竟不算是标准的Java虚拟机,因此在类加载机制上,它们有相同的地方,也有不同之处。我们必须区别对待

       例如,在使用标准Java虚拟机时,我们经常自定义继承自ClassLoader的类加载器。然后通过defineClass方法来从一个二进制流中加载Class。然而,这在Android里是行不通的,大家就没必要走弯路了。参看源码我们知道,Android中ClassLoader的defineClass方法具体是调用VMClassLoader的defineClass本地静态方法。而这个本地方法除了抛出一个“UnsupportedOperationException”之外,什么都没做,甚至连返回值都为空

[cpp] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. static void Dalvik_java_lang_VMClassLoader_defineClass(const u4* args,JValue* pResult){  
  2.     Object* loader = (Object*) args[0];  
  3.     StringObject* nameObj = (StringObject*) args[1];  
  4.     const u1* data = (const u1*) args[2];  
  5.     int offset = args[3];  
  6.     int len = args[4];  
  7.     Object* pd = (Object*) args[5];  
  8.     char* name = NULL;  
  9.     name = dvmCreateCstrFromString(nameObj);  
  10.     LOGE("ERROR: defineClass(%p, %s, %p, %d, %d, %p)\n",loader, name, data, offset, len, pd);  
  11.     dvmThrowException("Ljava/lang/UnsupportedOperationException;","can't load this type of class file");  
  12.     free(name);  
  13.     RETURN_VOID();  
  14. }  

Dalvik虚拟机类加载机制
       那如果在Dalvik虚拟机里,ClassLoader不好使,我们如何实现动态加载类呢?Android为我们从ClassLoader派生出了两个类:DexClassLoader和PathClassLoader。其中需要特别说明的是PathClassLoader中一段被注释掉的代码:

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. /* --this doesn't work in current version of Dalvik-- 
  2.     if (data != null) { 
  3.         System.out.println("--- Found class " + name 
  4.             + " in zip[" + i + "] '" + mZips[i].getName() + "'"); 
  5.         int dotIndex = name.lastIndexOf('.'); 
  6.         if (dotIndex != -1) { 
  7.             String packageName = name.substring(0, dotIndex); 
  8.             synchronized (this) { 
  9.                 Package packageObj = getPackage(packageName); 
  10.                 if (packageObj == null) { 
  11.                     definePackage(packageName, null, null, 
  12.                             null, null, null, null, null); 
  13.                 } 
  14.             } 
  15.         } 
  16.         return defineClass(name, data, 0, data.length); 
  17.     } 
  18. */  

这从另一方面佐证了defineClass函数在Dalvik虚拟机里确实是被阉割了。而在这两个继承自ClassLoader的类加载器,本质上是重载了ClassLoader的findClass方法。在执行loadClass时,我们可以参看ClassLoader部分源码:

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {  
  2. Class<?> clazz = findLoadedClass(className);  
  3.     if (clazz == null) {  
  4.         try {  
  5.             clazz = parent.loadClass(className, false);  
  6.         } catch (ClassNotFoundException e) {  
  7.             // Don't want to see this.  
  8.         }  
  9.         if (clazz == null) {  
  10.             clazz = findClass(className);  
  11.         }  
  12.     }  
  13.     return clazz;  
  14. }  

因此DexClassLoader和PathClassLoader都属于符合双亲委派模型的类加载器(因为它们没有重载loadClass方法)。也就是说,它们在加载一个类之前,回去检查自己以及自己以上的类加载器是否已经加载了这个类。如果已经加载过了,就会直接将之返回,而不会重复加载。
DexClassLoader和PathClassLoader其实都是通过DexFile这个类来实现类加载的。这里需要顺便提一下的是,Dalvik虚拟机识别的是dex文件,而不是class文件。因此,我们供类加载的文件也只能是dex文件,或者包含有dex文件的.apk或.jar文件。
也许有人想到,既然DexFile可以直接加载类,那么我们为什么还要使用ClassLoader的子类呢?DexFile在加载类时,具体是调用成员方法loadClass或者loadClassBinaryName。其中loadClassBinaryName需要将包含包名的类名中的”.”转换为”/”我们看一下loadClass代码就清楚了:

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. public Class loadClass(String name, ClassLoader loader) {  
  2.         String slashName = name.replace('.''/');  
  3.         return loadClassBinaryName(slashName, loader);  
  4. }  

       在这段代码前有一段注释,截取关键一部分就是说:If you are not calling this from a class loader, this is most likely not going to do what you want. Use {@link Class#forName(String)} instead. 这就是我们需要使用ClassLoader子类的原因。至于它是如何验证是否是在ClassLoader中调用此方法的,我没有研究,大家如果有兴趣可以继续深入下去。
       有一个细节,可能大家不容易注意到。PathClassLoader是通过构造函数new DexFile(path)来产生DexFile对象的;而DexClassLoader则是通过其静态方法loadDex(path, outpath, 0)得到DexFile对象。这两者的区别在于DexClassLoader需要提供一个可写的outpath路径,用来释放.apk包或者.jar包中的dex文件。换个说法来说,就是PathClassLoader不能主动从zip包中释放出dex,因此只支持直接操作dex格式文件,或者已经安装的apk(因为已经安装的apk在cache中存在缓存的dex文件)。而DexClassLoader可以支持.apk、.jar和.dex文件,并且会在指定的outpath路径释放出dex文件。

       另外,PathClassLoader在加载类时调用的是DexFile的loadClassBinaryName,而DexClassLoader调用的是loadClass。因此,在使用PathClassLoader时类全名需要用”/”替换”.”


实际操作

使用到的工具都比较常规:javac、dx、eclipse等其中dx工具最好是指明--no-strict,因为class文件的路径可能不匹配
加载好类后,通常我们可以通过Java反射机制来使用这个类但是这样效率相对不高,而且老用反射代码也比较复杂凌乱。更好的做法是定义一个interface,并将这个interface写进容器端。待加载的类,继承自这个interface,并且有一个参数为空的构造函数,以使我们能够通过Class的newInstance方法产生对象然后将对象强制转换为interface对象,于是就可以直接调用成员方法了,下面是具体的实现步骤了:
第一步:

编写好动态代码类:


[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. package com.dynamic.interfaces;  
  2. import android.app.Activity;  
  3. /** 
  4.  * 动态加载类的接口 
  5.  */  
  6. public interface IDynamic {  
  7.     /**初始化方法*/  
  8.     public void init(Activity activity);  
  9.     /**自定义方法*/  
  10.     public void showBanner();  
  11.     public void showDialog();  
  12.     public void showFullScreen();  
  13.     public void showAppWall();  
  14.     /**销毁方法*/  
  15.     public void destory();  
  16. }  
实现类代码如下:

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. package com.dynamic.impl;  
  2.   
  3. import android.app.Activity;  
  4. import android.widget.Toast;  
  5.   
  6. import com.dynamic.interfaces.IDynamic;  
  7.   
  8. /** 
  9.  * 动态类的实现 
  10.  * 
  11.  */  
  12. public class Dynamic implements IDynamic{  
  13.   
  14.     private Activity mActivity;  
  15.       
  16.     @Override  
  17.     public void init(Activity activity) {  
  18.         mActivity = activity;  
  19.     }  
  20.       
  21.     @Override  
  22.     public void showBanner() {  
  23.         Toast.makeText(mActivity, "我是ShowBannber方法"1500).show();  
  24.     }  
  25.   
  26.     @Override  
  27.     public void showDialog() {  
  28.         Toast.makeText(mActivity, "我是ShowDialog方法"1500).show();  
  29.     }  
  30.   
  31.     @Override  
  32.     public void showFullScreen() {  
  33.         Toast.makeText(mActivity, "我是ShowFullScreen方法"1500).show();  
  34.     }  
  35.   
  36.     @Override  
  37.     public void showAppWall() {  
  38.         Toast.makeText(mActivity, "我是ShowAppWall方法"1500).show();  
  39.     }  
  40.   
  41.     @Override  
  42.     public void destory() {  
  43.     }  
  44.   
  45. }  
这样动态类就开发好了


第二步:

将上面开发好的动态类打包成.jar,这里要注意的是只打包实现类Dynamic.Java,不打包接口类IDynamic.java,


然后将打包好的jar文件拷贝到android的安装目录中的platform-tools目录下,使用dx命令:(我的jar文件是dynamic.jar)

dx --dex --output=dynamic_temp.jar dynamic.jar

这样就生成了dynamic_temp.jar,这个jar和dynamic.jar有什么区别呢?

其实这条命令主要做的工作是:首先将dynamic.jar编译成dynamic.dex文件(Android虚拟机认识的字节码文件),然后再将dynamic.dex文件压缩成dynamic_temp.jar,当然你也可以压缩成.zip格式的,或者直接编译成.apk文件都可以的,这个后面会说到。

到这里还不算完事,因为你想想用什么来连接动态类和目标类呢?那就是动态类的接口了,所以这时候还要打个.jar包,这时候只需要打接口类IDynamic.java了


然后将这个.jar文件引用到目标类中,下面来看一下目标类的实现:

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. package com.jiangwei.demo;  
  2.   
  3. import java.io.File;  
  4. import java.util.List;  
  5.   
  6. import android.app.Activity;  
  7. import android.content.Intent;  
  8. import android.content.pm.ActivityInfo;  
  9. import android.content.pm.PackageManager;  
  10. import android.content.pm.ResolveInfo;  
  11. import android.os.Bundle;  
  12. import android.os.Environment;  
  13. import android.view.View;  
  14. import android.widget.Button;  
  15. import android.widget.Toast;  
  16.   
  17. import com.dynamic.interfaces.IDynamic;  
  18.   
  19. import dalvik.system.DexClassLoader;  
  20. import dalvik.system.PathClassLoader;  
  21.   
  22. public class AndroidDynamicLoadClassActivity extends Activity {  
  23.       
  24.     //动态类加载接口  
  25.     private IDynamic lib;  
  26.       
  27.     @Override  
  28.     public void onCreate(Bundle savedInstanceState) {  
  29.         super.onCreate(savedInstanceState);  
  30.         setContentView(R.layout.main);  
  31.         //初始化组件  
  32.         Button showBannerBtn = (Button) findViewById(R.id.show_banner_btn);  
  33.         Button showDialogBtn = (Button) findViewById(R.id.show_dialog_btn);  
  34.         Button showFullScreenBtn = (Button) findViewById(R.id.show_fullscreen_btn);  
  35.         Button showAppWallBtn = (Button) findViewById(R.id.show_appwall_btn);  
  36.         /**使用DexClassLoader方式加载类*/  
  37.         //dex压缩文件的路径(可以是apk,jar,zip格式)  
  38.         String dexPath = Environment.getExternalStorageDirectory().toString() + File.separator + "Dynamic.apk";  
  39.         //dex解压释放后的目录  
  40.         //String dexOutputDir = getApplicationInfo().dataDir;  
  41.         String dexOutputDirs = Environment.getExternalStorageDirectory().toString();  
  42.         //定义DexClassLoader  
  43.         //第一个参数:是dex压缩文件的路径  
  44.         //第二个参数:是dex解压缩后存放的目录  
  45.         //第三个参数:是C/C++依赖的本地库文件目录,可以为null  
  46.         //第四个参数:是上一级的类加载器  
  47.         DexClassLoader cl = new DexClassLoader(dexPath,dexOutputDirs,null,getClassLoader());  
  48.           
  49.         /**使用PathClassLoader方法加载类*/  
  50.         //创建一个意图,用来找到指定的apk:这里的"com.dynamic.impl是指定apk中在AndroidMainfest.xml文件中定义的<action name="com.dynamic.impl"/>    
  51.         Intent intent = new Intent("com.dynamic.impl"null);    
  52.         //获得包管理器    
  53.         PackageManager pm = getPackageManager();    
  54.         List<ResolveInfo> resolveinfoes =  pm.queryIntentActivities(intent, 0);    
  55.         //获得指定的activity的信息    
  56.         ActivityInfo actInfo = resolveinfoes.get(0).activityInfo;    
  57.         //获得apk的目录或者jar的目录    
  58.         String apkPath = actInfo.applicationInfo.sourceDir;    
  59.         //native代码的目录    
  60.         String libPath = actInfo.applicationInfo.nativeLibraryDir;    
  61.         //创建类加载器,把dex加载到虚拟机中    
  62.         //第一个参数:是指定apk安装的路径,这个路径要注意只能是通过actInfo.applicationInfo.sourceDir来获取  
  63.         //第二个参数:是C/C++依赖的本地库文件目录,可以为null  
  64.         //第三个参数:是上一级的类加载器  
  65.         PathClassLoader pcl = new PathClassLoader(apkPath,libPath,this.getClassLoader());  
  66.         //加载类  
  67.         try {  
  68.             //com.dynamic.impl.Dynamic是动态类名  
  69.             //使用DexClassLoader加载类  
  70.             //Class libProviderClazz = cl.loadClass("com.dynamic.impl.Dynamic");  
  71.             //使用PathClassLoader加载类  
  72.             Class libProviderClazz = pcl.loadClass("com.dynamic.impl.Dynamic");  
  73.             lib = (IDynamic)libProviderClazz.newInstance();  
  74.             if(lib != null){  
  75.                 lib.init(AndroidDynamicLoadClassActivity.this);  
  76.             }  
  77.         } catch (Exception exception) {  
  78.             exception.printStackTrace();  
  79.         }  
  80.         /**下面分别调用动态类中的方法*/  
  81.         showBannerBtn.setOnClickListener(new View.OnClickListener() {  
  82.             public void onClick(View view) {  
  83.                if(lib != null){  
  84.                    lib.showBanner();  
  85.                }else{  
  86.                    Toast.makeText(getApplicationContext(), "类加载失败"1500).show();  
  87.                }  
  88.             }  
  89.         });  
  90.         showDialogBtn.setOnClickListener(new View.OnClickListener() {  
  91.             public void onClick(View view) {  
  92.                if(lib != null){  
  93.                    lib.showDialog();  
  94.                }else{  
  95.                    Toast.makeText(getApplicationContext(), "类加载失败"1500).show();  
  96.                }  
  97.             }  
  98.         });  
  99.         showFullScreenBtn.setOnClickListener(new View.OnClickListener() {  
  100.             public void onClick(View view) {  
  101.                if(lib != null){  
  102.                    lib.showFullScreen();  
  103.                }else{  
  104.                    Toast.makeText(getApplicationContext(), "类加载失败"1500).show();  
  105.                }  
  106.             }  
  107.         });  
  108.         showAppWallBtn.setOnClickListener(new View.OnClickListener() {  
  109.             public void onClick(View view) {  
  110.                if(lib != null){  
  111.                    lib.showAppWall();  
  112.                }else{  
  113.                    Toast.makeText(getApplicationContext(), "类加载失败"1500).show();  
  114.                }  
  115.             }  
  116.         });  
  117.     }  
  118. }  
这里面定义了一个IDynamic接口变量,同时使用了DexClassLoader和PathClassLoader来加载类,这里面先来说一说DexClassLoader方式加载:

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. //定义DexClassLoader  
  2. //第一个参数:是dex压缩文件的路径  
  3. //第二个参数:是dex解压缩后存放的目录  
  4. //第三个参数:是C/C++依赖的本地库文件目录,可以为null  
  5. //第四个参数:是上一级的类加载器  
  6. DexClassLoader cl = new DexClassLoader(dexPath,dexOutputDirs,null,getClassLoader());  
上面已经说了,DexClassLoader是继承ClassLoader类的,这里面的参数说明:

第一个参数是:dex压缩文件的路径:这个就是我们将上面编译后的dynamic_temp.jar存放的目录,当然也可以是.zip和.apk格式的

第二个参数是:dex解压后存放的目录:这个就是将.jar,.zip,.apk文件解压出的dex文件存放的目录,这个就和PathClassLoader方法有区别了,同时你也可以看到PathClassLoader方法中没有这个参数,这个也真是这两个类的区别:

PathClassLoader不能主动从zip包中释放出dex,因此只支持直接操作dex格式文件,或者已经安装的apk(因为已经安装的apk在手机的data/dalvik目录中存在缓存的dex文件)。而DexClassLoader可以支持.apk、.jar和.dex文件,并且会在指定的outpath路径释放出dex文件。


然而我们可以通过DexClassLoader方法指定解压后的dex文件的存放目录,但是我们一般不这么做,因为这样做无疑的暴露了dex文件,所以我们一般不会将.jar/.zip/.apk压缩文件存放到用户可以察觉到的位置,同时解压dex的目录也是不能让用户看到的。

第三个参数和第四个参数用到的不是很多,所以这里就不做太多的解释了。

这里还要注意一点就是PathClassLoader方法的时候,第一个参数是dex存放的路径,这里传递的是:

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. //获得apk的目录或者jar的目录    
  2. String apkPath = actInfo.applicationInfo.sourceDir;    
指定的apk安装路径,这个值只能这样获取,不然会加载类失败的


第三步:

运行目标类:

要做的工作是:

如果用的是DexClassLoader方式加载类:这时候需要将.jar或者.zip或者.apk文件放到指定的目录中,我这里为了方便就放到sd卡的根目录中

如果用的是PathClassLoader方法加载类:这时候需要先将Dynamic.apk安装到手机中,不然找不到这个activity,同时要注意的是:

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. //创建一个意图,用来找到指定的apk:这里的"com.dynamic.impl是指定apk中在AndroidMainfest.xml文件中定义的<action name="com.dynamic.impl"/>    
  2. Intent intent = new Intent("com.dynamic.impl"null);    
这里的com.dynamic.impl是一个action需要在指定的apk中定义,这个名称是动态apk和目标apk之间约定好的

运行结果


点击showBanner显示一个Toast,成功的运行了动态类中的代码!

其实更好的办法就是将动态的.jar.zip.apk文件从网络上获取,安全可靠,同时本地的目标项目不需要改动代码就可以执行不同的逻辑了


关于代码加密的一些设想
    最初设想将dex文件加密,然后通过JNI将解密代码写在Native层。解密之后直接传上二进制流,再通过defineClass将类加载到内存中。
    现在也可以这样做,但是由于不能直接使用defineClass,而必须传文件路径给dalvik虚拟机内核,因此解密后的文件需要写到磁盘上,增加了被破解的风险。
    Dalvik虚拟机内核仅支持从dex文件加载类的方式是不灵活的,由于没有非常深入的研究内核,我不能确定是Dalvik虚拟机本身不支持还是Android在移植时将其阉割了。不过相信Dalvik或者是Android开源项目都正在向能够支持raw数据定义类方向努力。
    我们可以在文档中看到Google说:Jar or APK file with "classes.dex". (May expand this to include "raw DEX" in the future.);在Android的Dalvik源码中我们也能看到RawDexFile的身影(不过没有具体实现)
    在RawDexFile出来之前,我们都只能使用这种存在一定风险的加密方式。需要注意释放的dex文件路径及权限管理,另外,在加载完毕类之后,除非出于其他目的否则应该马上删除临时的解密文件
作者:tarena_3G 发表于2016/10/22 9:16:29 原文链接
阅读:256 评论:0 查看评论

AndroidUI组件---SlidingTabLayout实现ViewPager页卡滑动效果

$
0
0

使用SlidingTabLayout需要准备2个类,分别是 SlidingTabLayout,与SlidingTabStrip,,放进项目中时只用修改下包名即可。
这里写图片描述

效果制作的不是很好。
这篇文章,也是在网上搜了很多资源参考,对 SlidingTabLayout.java和SlidingTabStrip.java进行了修改。大家可以更改他的格式字体大小、选中状态,分割线调整等等。先上传这两个文件,改动支出都做了注释。
SlidingTabLayout.java

/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.my.slidingtablayout;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Typeface;
import android.os.Build;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
import android.widget.TextView;

/**
 * To be used with ViewPager to provide a tab indicator component which give constant feedback as to
 * the user's scroll progress.
 * <p>
 * To use the component, simply add it to your view hierarchy. Then in your
 * {@link android.app.Activity} or {@link android.support.v4.app.Fragment} call
 * {@link #setViewPager(ViewPager)} providing it the ViewPager this layout is being used for.
 * <p>
 * The colors can be customized in two ways. The first and simplest is to provide an array of colors
 * via {@link #setSelectedIndicatorColors(int...)} and {@link #setDividerColors(int...)}. The
 * alternative is via the {@link TabColorizer} interface which provides you complete control over
 * which color is used for any individual position.
 * <p>
 * The views used as tabs can be customized by calling {@link #setCustomTabView(int, int)},
 * providing the layout ID of your custom layout.
 */
public class SlidingTabLayout extends HorizontalScrollView {

    /**
     * Allows complete control over the colors drawn in the tab layout. Set with
     * {@link #setCustomTabColorizer(TabColorizer)}.
     */
    public interface TabColorizer {

        /**
         * @return return the color of the indicator used when {@code position} is selected.
         */
        int getIndicatorColor(int position);

        /**
         * @return return the color of the divider drawn to the right of {@code position}.
         */
        int getDividerColor(int position);

    }

    private static final int TITLE_OFFSET_DIPS = 24;
    private static final int TAB_VIEW_PADDING_DIPS = 16;   //内边距
    private static int TAB_VIEW_TEXT_SIZE_SP = 16;   //字体大小

    private int mTitleOffset;

    private int mTabViewLayoutId;
    private int mTabViewTextViewId;

    // 定义两种需要添加的选项卡颜色
    private int mDefaultTextColor;
    private int mSelectedTextColor;

    private ViewPager mViewPager;
    private ViewPager.OnPageChangeListener mViewPagerPageChangeListener;

    private final SlidingTabStrip mTabStrip;

    public SlidingTabLayout(Context context) {
        this(context, null);
    }

    public SlidingTabLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SlidingTabLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        // 获取选项卡颜色,如果未定义的话,则使用主题默认的颜色
        TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.SlidingTabLayout);
        int defaultTextColor = a.getColor(
                R.styleable.SlidingTabLayout_android_textColorPrimary, 0);
        mDefaultTextColor = a.getColor(
                R.styleable.SlidingTabLayout_textColorTabDefault, defaultTextColor);
        mSelectedTextColor = a.getColor(
                R.styleable.SlidingTabLayout_textColorTabSelected ,defaultTextColor);
        a.recycle();


        // Disable the Scroll Bar
        setHorizontalScrollBarEnabled(false);
        // Make sure that the Tab Strips fills this View
        setFillViewport(true);

        mTitleOffset = (int) (TITLE_OFFSET_DIPS * getResources().getDisplayMetrics().density);

        mTabStrip = new SlidingTabStrip(context);
        addView(mTabStrip, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
    }

    // 在每次选项改变时更新选项卡文本颜色的新方法
    private void updateSelectedTitle(int position) {
        final PagerAdapter adapter = mViewPager.getAdapter();
        for (int i = 0; i < adapter.getCount(); i++) {
            final View tabView = mTabStrip.getChildAt(i);
            if (TextView.class.isInstance(tabView)) {
                TextView titleView = (TextView)tabView;
                boolean isSelected = i == position;
                titleView.setTextColor(isSelected ? mSelectedTextColor : mDefaultTextColor);
            }
        }
    }
    /**
     * Set the custom {@link TabColorizer} to be used.
     * <p>
     * If you only require simple custmisation then you can use
     * {@link #setSelectedIndicatorColors(int...)} and {@link #setDividerColors(int...)} to achieve
     * similar effects.
     */
    public void setCustomTabColorizer(TabColorizer tabColorizer) {
        mTabStrip.setCustomTabColorizer(tabColorizer);
    }



    /**
     * Sets the colors to be used for indicating the selected tab. These colors are treated as a
     * circular array. Providing one color will mean that all tabs are indicated with the same color.
     */
    public void setSelectedIndicatorColors(int... colors) {
        mTabStrip.setSelectedIndicatorColors(colors);
    }

    /**
     * Sets the colors to be used for tab dividers. These colors are treated as a circular array.
     * Providing one color will mean that all tabs are indicated with the same color.
     */
    public void setDividerColors(int... colors) {
        mTabStrip.setDividerColors(colors);
    }

    //...设置字体大小
    public void setTitleSize(int size) {
        this.TAB_VIEW_TEXT_SIZE_SP = size;
    }

    /**
     * Set the {@link ViewPager.OnPageChangeListener}. When using {@link SlidingTabLayout} you are
     * required to set any {@link ViewPager.OnPageChangeListener} through this method. This is so
     * that the layout can update it's scroll position correctly.
     *
     * @see ViewPager#setOnPageChangeListener(ViewPager.OnPageChangeListener)
     */
    public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) {
        mViewPagerPageChangeListener = listener;
    }

    /**
     * Set the custom layout to be inflated for the tab views.
     *
     * @param layoutResId Layout id to be inflated
     * @param textViewId  id of the {@link TextView} in the inflated view
     */
    public void setCustomTabView(int layoutResId, int textViewId) {
        mTabViewLayoutId = layoutResId;
        mTabViewTextViewId = textViewId;
    }

    /**
     * Sets the associated view pager. Note that the assumption here is that the pager content
     * (number of tabs and tab titles) does not change after this call has been made.
     */
    public void setViewPager(ViewPager viewPager) {
        mTabStrip.removeAllViews();

        mViewPager = viewPager;
        if (viewPager != null) {
            viewPager.setOnPageChangeListener(new InternalViewPagerListener());
            populateTabStrip();
        }
    }

    /**
     * Create a default view to be used for tabs. This is called if a custom tab view is not set via
     * {@link #setCustomTabView(int, int)}.
     */
    protected TextView createDefaultTabView(Context context) {
        TextView textView = new TextView(context);
        textView.setGravity(Gravity.CENTER);
        textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, TAB_VIEW_TEXT_SIZE_SP);
        textView.setTypeface(Typeface.DEFAULT_BOLD);

        //...这会移除 Holo 的默认背景强调以及选项卡的粗体文本
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            // If we're running on Honeycomb or newer, then we can use the Theme's
            // selectableItemBackground to ensure that the View has a pressed state
            TypedValue outValue = new TypedValue();
            getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackground,
                    outValue, true);
            textView.setBackgroundResource(outValue.resourceId);
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            // If we're running on ICS or newer, enable all-caps to match the Action Bar tab style
            textView.setAllCaps(true);
        }

        int padding = (int) (TAB_VIEW_PADDING_DIPS * getResources().getDisplayMetrics().density);
        textView.setPadding(padding, padding, padding, padding);

        return textView;
    }

    private void populateTabStrip() {
        final PagerAdapter adapter = mViewPager.getAdapter();
        final OnClickListener tabClickListener = new TabClickListener();

        for (int i = 0; i < adapter.getCount(); i++) {
            View tabView = null;
            TextView tabTitleView = null;

            if (mTabViewLayoutId != 0) {
                // If there is a custom tab view layout id set, try and inflate it
                tabView = LayoutInflater.from(getContext()).inflate(mTabViewLayoutId, mTabStrip,
                        false);
                tabTitleView = (TextView) tabView.findViewById(mTabViewTextViewId);
            }

            if (tabView == null) {
                tabView = createDefaultTabView(getContext());
            }

            if (tabTitleView == null && TextView.class.isInstance(tabView)) {
                tabTitleView = (TextView) tabView;
            }

            tabTitleView.setText(adapter.getPageTitle(i));
            //... 设置头部均分
            LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(0, LayoutParams.WRAP_CONTENT, 1.0f);
            tabView.setLayoutParams(layoutParams);
            tabView.setOnClickListener(tabClickListener);

            mTabStrip.addView(tabView);
        }
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();

        if (mViewPager != null) {
            scrollToTab(mViewPager.getCurrentItem(), 0);
        }
    }

    private void scrollToTab(int tabIndex, int positionOffset) {
        final int tabStripChildCount = mTabStrip.getChildCount();
        if (tabStripChildCount == 0 || tabIndex < 0 || tabIndex >= tabStripChildCount) {
            return;
        }

        View selectedChild = mTabStrip.getChildAt(tabIndex);
        if (selectedChild != null) {

            // 调用在每次选项改变时更新文本颜色的新方案
            updateSelectedTitle(tabIndex);

            int targetScrollX = selectedChild.getLeft() + positionOffset;

            if (tabIndex > 0 || positionOffset > 0) {
                // If we're not at the first child and are mid-scroll, make sure we obey the offset
                targetScrollX -= mTitleOffset;
            }

            scrollTo(targetScrollX, 0);
        }
    }

    private class InternalViewPagerListener implements ViewPager.OnPageChangeListener {
        private int mScrollState;

        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            int tabStripChildCount = mTabStrip.getChildCount();
            if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) {
                return;
            }

            mTabStrip.onViewPagerPageChanged(position, positionOffset);

            View selectedTitle = mTabStrip.getChildAt(position);
            int extraOffset = (selectedTitle != null)
                    ? (int) (positionOffset * selectedTitle.getWidth())
                    : 0;
            scrollToTab(position, extraOffset);

            if (mViewPagerPageChangeListener != null) {
                mViewPagerPageChangeListener.onPageScrolled(position, positionOffset,
                        positionOffsetPixels);
            }
        }

        @Override
        public void onPageScrollStateChanged(int state) {
            mScrollState = state;

            if (mViewPagerPageChangeListener != null) {
                mViewPagerPageChangeListener.onPageScrollStateChanged(state);
            }
        }

        @Override
        public void onPageSelected(int position) {
            if (mScrollState == ViewPager.SCROLL_STATE_IDLE) {
                mTabStrip.onViewPagerPageChanged(position, 0f);
                scrollToTab(position, 0);
            }

            if (mViewPagerPageChangeListener != null) {
                mViewPagerPageChangeListener.onPageSelected(position);
            }
        }

    }

    private class TabClickListener implements OnClickListener {
        @Override
        public void onClick(View v) {
            for (int i = 0; i < mTabStrip.getChildCount(); i++) {
                if (v == mTabStrip.getChildAt(i)) {
                    mViewPager.setCurrentItem(i);
                    return;
                }
            }
        }
    }

}

SlidingTabStrip.java

/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.my.slidingtablayout;

import android.R;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.widget.LinearLayout;

class SlidingTabStrip extends LinearLayout {

    private static final int DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS = 0;  //去除阴影
    private static final byte DEFAULT_BOTTOM_BORDER_COLOR_ALPHA = 0x26;
    private static final int SELECTED_INDICATOR_THICKNESS_DIPS = 4;     //设置滚动条的高度
    private static final int DEFAULT_SELECTED_INDICATOR_COLOR = 0xFF33B5E5;


    private static final int DEFAULT_DIVIDER_THICKNESS_DIPS = 1;
    private static final byte DEFAULT_DIVIDER_COLOR_ALPHA = 0x20;
    private static final float DEFAULT_DIVIDER_HEIGHT = 0.5f;

    private final int mBottomBorderThickness;
    private final Paint mBottomBorderPaint;

    private final int mSelectedIndicatorThickness;
    private final Paint mSelectedIndicatorPaint;

    private final int mDefaultBottomBorderColor;

    private final Paint mDividerPaint;
    private final float mDividerHeight;

    private int mSelectedPosition;
    private float mSelectionOffset;

    private SlidingTabLayout.TabColorizer mCustomTabColorizer;
    private final SimpleTabColorizer mDefaultTabColorizer;

    SlidingTabStrip(Context context) {
        this(context, null);
    }

    SlidingTabStrip(Context context, AttributeSet attrs) {
        super(context, attrs);
        setWillNotDraw(false);

        final float density = getResources().getDisplayMetrics().density;

        TypedValue outValue = new TypedValue();
        context.getTheme().resolveAttribute(R.attr.colorForeground, outValue, true);
        final int themeForegroundColor = outValue.data;

        mDefaultBottomBorderColor = setColorAlpha(themeForegroundColor,
                DEFAULT_BOTTOM_BORDER_COLOR_ALPHA);

        mDefaultTabColorizer = new SimpleTabColorizer();
        mDefaultTabColorizer.setIndicatorColors(DEFAULT_SELECTED_INDICATOR_COLOR);
        mDefaultTabColorizer.setDividerColors(setColorAlpha(themeForegroundColor,
                DEFAULT_DIVIDER_COLOR_ALPHA));

        mBottomBorderThickness = (int) (DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS * density);
        mBottomBorderPaint = new Paint();
        mBottomBorderPaint.setColor(mDefaultBottomBorderColor);

        mSelectedIndicatorThickness = (int) (SELECTED_INDICATOR_THICKNESS_DIPS * density);
        mSelectedIndicatorPaint = new Paint();

        mDividerHeight = DEFAULT_DIVIDER_HEIGHT;
        mDividerPaint = new Paint();
        mDividerPaint.setStrokeWidth((int) (DEFAULT_DIVIDER_THICKNESS_DIPS * density));
    }

    void setCustomTabColorizer(SlidingTabLayout.TabColorizer customTabColorizer) {
        mCustomTabColorizer = customTabColorizer;
        invalidate();
    }

    void setSelectedIndicatorColors(int... colors) {
        // Make sure that the custom colorizer is removed
        mCustomTabColorizer = null;
        mDefaultTabColorizer.setIndicatorColors(colors);
        invalidate();
    }

    void setDividerColors(int... colors) {
        // Make sure that the custom colorizer is removed
        mCustomTabColorizer = null;
        mDefaultTabColorizer.setDividerColors(colors);
        invalidate();
    }

    void onViewPagerPageChanged(int position, float positionOffset) {
        mSelectedPosition = position;
        mSelectionOffset = positionOffset;
        invalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        final int height = getHeight();
        final int childCount = getChildCount();
        final int dividerHeightPx = (int) (Math.min(Math.max(0f, mDividerHeight), 1f) * height);
        final SlidingTabLayout.TabColorizer tabColorizer = mCustomTabColorizer != null
                ? mCustomTabColorizer
                : mDefaultTabColorizer;

        // Thick colored underline below the current selection
        if (childCount > 0) {
            View selectedTitle = getChildAt(mSelectedPosition);
            int left = selectedTitle.getLeft();
            int right = selectedTitle.getRight();
            int color = tabColorizer.getIndicatorColor(mSelectedPosition);

            if (mSelectionOffset > 0f && mSelectedPosition < (getChildCount() - 1)) {
                int nextColor = tabColorizer.getIndicatorColor(mSelectedPosition + 1);
                if (color != nextColor) {
                    color = blendColors(nextColor, color, mSelectionOffset);
                }

                // Draw the selection partway between the tabs
                View nextTitle = getChildAt(mSelectedPosition + 1);
                left = (int) (mSelectionOffset * nextTitle.getLeft() +
                        (1.0f - mSelectionOffset) * left);
                right = (int) (mSelectionOffset * nextTitle.getRight() +
                        (1.0f - mSelectionOffset) * right);
            }

            mSelectedIndicatorPaint.setColor(color);

            canvas.drawRect(left, height - mSelectedIndicatorThickness, right,
                    height, mSelectedIndicatorPaint);
        }

        // Thin underline along the entire bottom edge
        canvas.drawRect(0, height - mBottomBorderThickness, getWidth(), height, mBottomBorderPaint);

        // Vertical separators between the titles
        int separatorTop = (height - dividerHeightPx) / 2;
        for (int i = 0; i < childCount - 1; i++) {
            View child = getChildAt(i);
            mDividerPaint.setColor(tabColorizer.getDividerColor(i));
            canvas.drawLine(child.getRight(), separatorTop, child.getRight(),
                    separatorTop + dividerHeightPx, mDividerPaint);
        }
    }

    /**
     * Set the alpha value of the {@code color} to be the given {@code alpha} value.
     */
    private static int setColorAlpha(int color, byte alpha) {
        return Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color));
    }

    /**
     * Blend {@code color1} and {@code color2} using the given ratio.
     *
     * @param ratio of which to blend. 1.0 will return {@code color1}, 0.5 will give an even blend,
     *              0.0 will return {@code color2}.
     */
    private static int blendColors(int color1, int color2, float ratio) {
        final float inverseRation = 1f - ratio;
        float r = (Color.red(color1) * ratio) + (Color.red(color2) * inverseRation);
        float g = (Color.green(color1) * ratio) + (Color.green(color2) * inverseRation);
        float b = (Color.blue(color1) * ratio) + (Color.blue(color2) * inverseRation);
        return Color.rgb((int) r, (int) g, (int) b);
    }

    private static class SimpleTabColorizer implements SlidingTabLayout.TabColorizer {
        private int[] mIndicatorColors;    //
        private int[] mDividerColors;      //


        @Override
        public final int getIndicatorColor(int position) {
            return mIndicatorColors[position % mIndicatorColors.length];
        }

        @Override
        public final int getDividerColor(int position) {
            return mDividerColors[position % mDividerColors.length];
        }

        void setIndicatorColors(int... colors) {
            mIndicatorColors = colors;
        }

        void setDividerColors(int... colors) {
            mDividerColors = colors;
        }
    }
}

上边因为使用了自定义的颜色,所以这里要在attrs.xml声明一下,不然找不到:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="SlidingTabLayout">
        <attr name="android:textColorPrimary" />
        <attr name="textColorTabDefault" format="color" />
        <attr name="textColorTabSelected" format="color" />
    </declare-styleable>
</resources>

布局文件也要用到自定义:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.my.slidingtablayout.MainActivity">
    <!-- 在上方就在上面,在下方就在下面(tab栏) -->
    <com.example.my.slidingtablayout.SlidingTabLayout
        android:id="@+id/sliding"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:textColorTabDefault="#000000"
        app:textColorTabSelected="@color/colorAccent"
        />

    <android.support.v4.view.ViewPager
        android:id="@+id/view_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
       />
</LinearLayout>

最后一道就是在你的Activity运用这种开源:可以调整之处也做了说明

package com.example.my.slidingtablayout;

import android.graphics.Color;
import android.os.Bundle;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {
    //创建 颜色数组 用来做viewpager的背景
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ViewPager pager = (ViewPager) findViewById(R.id.view_pager);
        SlidingTabLayout tab = (SlidingTabLayout) findViewById(R.id.sliding);
        tab.setDividerColors(Color.TRANSPARENT); //设置标题的分割线
        tab.setSelectedIndicatorColors(Color.rgb(51, 181, 229)); //设置滚动条的颜色
        tab.setTitleSize(18);  //...设置字体的颜色,默认16

        MyAdapte adapter = new MyAdapte();
        pager.setAdapter(adapter);
        tab.setViewPager(pager);
    }

    int[] colors = {0xFF123456, 0xFF654321, 0xFF336699};

    class MyAdapte extends PagerAdapter {
        //可以考虑把这个数组添加到集合里面
        String[] titles = {"AA", "BB", "CC"};


        ArrayList<LinearLayout> layouts = new ArrayList<LinearLayout>();

        MyAdapte() {

            for (int i = 0; i < 3; i++) {
                LinearLayout l = new LinearLayout(MainActivity.this);
                l.setBackgroundColor(colors[i]);
                l.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
                layouts.add(l);
            }

        }

        @Override
        public int getCount() {
            return layouts.size();
        }

        @Override
        public boolean isViewFromObject(View view, Object o) {
            return view == o;
        }

        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            LinearLayout l = layouts.get(position);
            container.addView(l);
            return l;
        }

        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            container.removeView(layouts.get(position));
        }

        @Override
        public CharSequence getPageTitle(int position) {
            //...可以返回集合list.get(position);
            return titles[position];
        }
    }
}
作者:kuaizilanqiu 发表于2016/10/22 9:56:11 原文链接
阅读:76 评论:0 查看评论

Android 动画

$
0
0

android中动画分为3种:

1.Tween Animation:

补间动画(tweened animation)通过对场景里的对象不断做图像变换(平移、缩放、旋转)产生动画效果,即是一种渐变动画;

缺点 :补间动画是只能够作用在View上;它只能够实现移动缩放旋转淡入淡出这四种动画操作;它只是改变了View的显示效果而已,而不会真正去改变View的属性。它并不能改变事件响应的位置,它只是单纯地修改了显示。比如说实现一个按钮的移动,那么按钮的实际点击有效区依然在原来的地方,点击移动后的地方是不会有点击事件发生的。

Tween Animation有四种形式:

  alpha            渐变透明度动画效果

  scale             渐变尺寸伸缩动画效果

  translate             画面位置移动动画效果

  rotate              画面旋转动画效果

 这四种动画实现方式都是通过Animation类和AnimationUtils配合实现。

参数详细解释Android Animations动画使用详解

2.Frame Animation:

顺序播放事先做好的图像,是一种画面转换动画。帧动画(frame-by-frame animation)的工作原理很简单,其实就是将一个完整的动画拆分成一张张单独的图片,然后再将它们连贯起来进行播放,类似于动画片的工作原理。

3.Property Animation:

属性动画,通过动态地改变对象的属性从而达到动画效果,属性动画为API 11新特

新引入的属性动画机制已经不再是针对于View来设计的了,也不限定于只能实现移动、缩放、旋转和淡入淡出这几种动画操作,同时也不再只是一种视觉上的动画效果了。它实际上是一种不断地对值进行操作的机制,并将值赋值到指定对象的指定属性上,可以是任意对象的任意属性

Android 属性动画:

Android属性动画完全解析(上),初识属性动画的基本用法

Android属性动画完全解析(中),ValueAnimator和ObjectAnimator的高级用法

Android属性动画完全解析(下),Interpolator和ViewPropertyAnimator的用法

Android 属性动画(Property Animation) 完全解析 (上)

ValueAnimator

ValueAnimator是整个属性动画机制当中最核心的一个类,ObjectAnimator也是继承自ValueAnimator。前面我们已经提到了,属性动画的运行机制是通过不断地对值进行操作来实现的,而初始值和结束值之间的动画过渡就是由ValueAnimator这个类来负责计算的。它的内部使用一种时间循环的机制来计算值与值之间的动画过渡,我们只需要将初始值和结束值提供给ValueAnimator,并且告诉它动画所需运行的时长,那么ValueAnimator就会自动帮我们完成从初始值平滑地过渡到结束值这样的效果。除此之外,ValueAnimator还负责管理动画的播放次数、播放模式、以及对动画设置监听器等,确实是一个非常重要的类。

ValueAnimator当中最常用的应该就是ofFloat()和ofInt()这两个方法了,另外还有一个ofObject()方法。

那么除此之外,我们还可以调用setStartDelay()方法来设置动画延迟播放的时间,调用setRepeatCount()和setRepeatMode()方法来设置动画循环播放的次数以及循环播放的模式,循环模式包括RESTART和REVERSE两种,分别表示重新播放和倒序播放的意思。

ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);  
anim.setDuration(300);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {  
    @Override  
    public void onAnimationUpdate(ValueAnimator animation) {  
        float currentValue = (float) animation.getAnimatedValue();  
        Log.d("TAG", "cuurent value is " + currentValue);  
    }  
});  
anim.start();

这里我们通过addUpdateListener()方法来添加一个动画的监听器,在动画执行的过程中会不断地进行回调,这里用来监听数值的变化。

前面我们使用过了ValueAnimator的ofFloat()和ofInt()方法,分别用于对浮点型和整型的数据进行动画操作的,但实际上ValueAnimator中还有一个ofObject()方法,是用于对任意对象进行动画操作的。但是相比于浮点型或整型数据,对象的动画操作明显要更复杂一些,因为系统将完全无法知道如何从初始对象过度到结束对象,因此这个时候我们就需要实现一个自己的TypeEvaluator来告知系统如何进行过度。

下面来先定义一个Point类,如下所示:

public class Point {  
  
    private float x;  
  
    private float y;  
  
    public Point(float x, float y) {  
        this.x = x;  
        this.y = y;  
    }  
  
    public float getX() {  
        return x;  
    }  
  
    public float getY() {  
        return y;  
    }  
  
}

Point类非常简单,只有x和y两个变量用于记录坐标的位置,并提供了构造方法来设置坐标,以及get方法来获取坐标。接下来定义PointEvaluator,如下所示:

public class PointEvaluator implements TypeEvaluator{  
  
    @Override  
    public Object evaluate(float fraction, Object startValue, Object endValue) {  
        Point startPoint = (Point) startValue;  
        Point endPoint = (Point) endValue;  
        float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX());  
        float y = startPoint.getY() + fraction * (endPoint.getY() - startPoint.getY());  
        Point point = new Point(x, y);  
        return point;  
    }    
} 

可以看到,PointEvaluator同样实现了TypeEvaluator接口并重写了evaluate()方法。其实evaluate()方法中的逻辑还是非常简单的,先是将startValue和endValue强转成Point对象,然后同样根据fraction来计算当前动画的x和y的值,最后组装到一个新的Point对象当中并返回。

这样我们就将PointEvaluator编写完成了,接下来我们就可以非常轻松地对Point对象进行动画操作了,比如说我们有两个Point对象,现在需要将Point1通过动画平滑过度到Point2,就可以这样写:

Point point1 = new Point(0, 0);  
Point point2 = new Point(300, 300);  
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), point1, point2);  
anim.setDuration(5000);  
anim.start();

代码很简单,这里我们先是new出了两个Point对象,并在构造函数中分别设置了它们的坐标点。然后调用ValueAnimator的ofObject()方法来构建ValueAnimator的实例,这里需要注意的是,ofObject()方法要求多传入一个TypeEvaluator参数,这里我们只需要传入刚才定义好的PointEvaluator的实例就可以了。

Object Animator

相比于ValueAnimator,ObjectAnimator可能才是我们最常接触到的类,因为ValueAnimator只不过是对进行了一个平滑的动画过渡,但我们实际使用到这种功能的场景好像并不多。而ObjectAnimator则就不同了,它是可以直接对任意对象的任意属性进行动画操作的,比如说View的alpha属性。

由于ObjectAnimator继承自ValueAnimator,说明ValueAnimator中可以使用的方法在ObjectAnimator中也是可以正常使用的,它们的用法也非常类似。

ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f, 1f);  
animator.setDuration(5000);  
animator.start();

第一个参数要求传入一个object对象,我们想要对哪个对象进行动画操作就传入什么,这里我传入了一个textview。第二个参数是想要对该对象的哪个属性进行动画操作,由于我们想要改变TextView的不透明度,因此这里传入"alpha"。后面的参数就是不固定长度了,想要完成什么样的动画就传入什么值,这里传入的值就表示将TextView从常规变换成全透明,再从全透明变换成常规。之后调用setDuration()方法来设置动画的时长,然后调用start()方法启动动画。

ofFloat()方法的第二个参数到底可以传哪些值呢?其实这里ObjectAnimator内部的工作机制并不是直接对我们传入的属性名进行操作的,而是会去寻找这个属性名对应的get和set方法,因此这里传入的名字其实无所谓的,只是一个指示而已。

不过,在使用ObjectAnimator的时候,有一点非常的重要,就是要操作的属性必须具有get、set方法,不然ObjectAnimator就无法起效。

常用的可以直接使用属性动画的属性包括:
(1)translationXtranslationY:控制view从它布局容器左上角坐标偏移的位置;
(2)rotationrotationXrotationY:控制view围绕支点进行2D和3D旋转;
(3)scaleXscaleY:控制view围绕着它的支点进行2D缩放;
(4)pivotXpivotY:控制支点位置,围绕这个支点进行旋转和缩放处理。默认情况下,支点是view的中心点;
(5)xy:控制view在它的容器中的最终位置,它是最初的左上角坐标和translationX、translationY的累计和;
(6)alpha:控制透明度,默认是1(不透明)。

如果一个属性不具有get和set方法,有两种解决的方法:一种是通过自定义一个属性类或者包装类,来间接地给这个属性增加get和set方法;第二种就是通过ValueAnimator来实现。

使用包装类的方法给一个属性增加set和get的方法,代码如下:

private static class WrapperView{
    private View mTarget;
    public WrapperView(View target){
        mTarget = target;
    }
    public int getWidth(){
        return mTarget.getLayoutParams().width;
    }

    public void setWidth(int width){
        mTarget.getLayoutParams().width = width;
        mTarget.requestFocus();
    }

}

使用的时候只需要操作包装类就可以间接地调用get,set方法了

WrapperView wrapper = new WrapperView (mButton);
ObjectAnimator.ofInt(Wrapper,"width",500).setDuration(5000).start;

ObjectAnimator中的ofObject()方法同样是对任意对象进行动画操作。

ObjectAnimator内部的工作机制是通过寻找特定属性的get和set方法,然后通过方法不断地对值进行改变,从而实现动画效果的。因此我们就需要在MyAnimView中定义一个color属性,并提供它的get和set方法。那么接下来的问题就是怎样让setColor()方法得到调用了,毫无疑问,当然是要借助ObjectAnimator类,但是在使用ObjectAnimator之前我们还要完成一个非常重要的工作,就是编写一个用于告知系统如何进行颜色过度的TypeEvaluator。(具体使用见Android属性动画完全解析(中),ValueAnimator和ObjectAnimator的高级用法

组合动画AnimatorSet

独立的动画能够实现的视觉效果毕竟是相当有限的,因此将多个动画组合到一起播放就显得尤为重要。幸运的是,Android团队在设计属性动画的时候也充分考虑到了组合动画的功能,因此提供了一套非常丰富的API来让我们将多个动画组合到一起。

实现组合动画功能主要需要借助AnimatorSet这个类,这个类提供了一个play()方法,如果我们向这个方法中传入一个Animator对象(ValueAnimator或ObjectAnimator)将会返回一个AnimatorSet.Builder的实例,AnimatorSet.Builder中包括以下四个方法:

· after(Animator anim)   将现有动画插入到传入的动画之后执行

· after(long delay)   将现有动画延迟指定毫秒后执行

· before(Animator anim)   将现有动画插入到传入的动画之前执行

· with(Animator anim)   将现有动画和传入的动画同时执行

ObjectAnimator moveIn = ObjectAnimator.ofFloat(textview, "translationX", -500f, 0f);  
ObjectAnimator rotate = ObjectAnimator.ofFloat(textview, "rotation", 0f, 360f);  
ObjectAnimator fadeInOut = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f, 1f);  
AnimatorSet animSet = new AnimatorSet();  
animSet.play(rotate).with(fadeInOut).after(moveIn);  
animSet.setDuration(5000);  
animSet.start();

在属性动画中,AnimatorSet正是通过playTogether()、playSequentially()、animSet.play()、with()、before()、after()这些方法来控制多个动画的协同工作方式,从而做到对动画播放方式的精确控制。

Animator监听器

在很多时候,我们希望可以监听到动画的各种事件,比如动画何时开始,何时结束,然后在开始或者结束的时候去执行一些逻辑处理。这个功能是完全可以实现的,Animator类当中提供了一个addListener()方法,这个方法接收一个AnimatorListener,我们只需要去实现这个AnimatorListener就可以监听动画的各种事件了。

大家已经知道,ObjectAnimator是继承自ValueAnimator的,而ValueAnimator又是继承自Animator的,因此不管是ValueAnimator还是ObjectAnimator都是可以使用addListener()这个方法的。另外AnimatorSet也是继承自Animator的,因此addListener()这个方法算是个通用的方法。

添加一个监听器的代码如下所示:

anim.addListener(new AnimatorListener() {  
    @Override  
    public void onAnimationStart(Animator animation) {  
    }  
  
    @Override  
    public void onAnimationRepeat(Animator animation) {  
    }  
  
    @Override  
    public void onAnimationEnd(Animator animation) {  
    }  
  
    @Override  
    public void onAnimationCancel(Animator animation) {  
    }  
});

可以看到,我们需要实现接口中的四个方法,onAnimationStart()方法会在动画开始的时候调用,onAnimationRepeat()方法会在动画重复执行的时候调用,onAnimationEnd()方法会在动画结束的时候调用,onAnimationCancel()方法会在动画被取消的时候调用。

但是也许很多时候我们并不想要监听那么多个事件,可能我只想要监听动画结束这一个事件,那么每次都要将四个接口全部实现一遍就显得非常繁琐。没关系,为此Android提供了一个适配器类,叫作AnimatorListenerAdapter,使用这个类就可以解决掉实现接口繁琐的问题了,如下所示:

<span style="font-family:SimSun;font-size:12px;">1.anim.addListener(new AnimatorListenerAdapter() {  
});</span>

这里我们向addListener()方法中传入这个适配器对象,由于AnimatorListenerAdapter中已经将每个接口都实现好了,所以这里不用实现任何一个方法也不会报错。那么如果我想监听动画结束这个事件,就只需要单独重写这一个方法就可以了,如下所示:

anim.addListener(new AnimatorListenerAdapter() {  
    @Override  
    public void onAnimationEnd(Animator animation) {  
    }  
}); 

TypeEvaluator

可能在大多数情况下我们使用属性动画的时候都不会用到TypeEvaluator,但是大家还是应该了解一下它的用法,以防止当我们遇到一些解决不掉的问题时能够想起来还有这样的一种解决方案。

那么TypeEvaluator的作用到底是什么呢?简单来说,就是告诉动画系统如何从初始值过度到结束值。我们在上一篇文章中学到的ValueAnimator.ofFloat()方法就是实现了初始值与结束值之间的平滑过度,那么这个平滑过度是怎么做到的呢?其实就是系统内置了一个FloatEvaluator,它通过计算告知动画系统如何从初始值过度到结束值,我们来看一下FloatEvaluator的代码实现:


可以看到,FloatEvaluator实现了TypeEvaluator接口,然后重写evaluate()方法。evaluate()方法当中传入了三个参数,第一个参数fraction非常重要,这个参数用于表示动画的完成度的,我们应该根据它来计算当前动画的值应该是多少,第二第三个参数分别表示动画的初始值和结束值。那么上述代码的逻辑就比较清晰了,用结束值减去初始值,算出它们之间的差值,然后乘以fraction这个系数,再加上初始值,那么就得到当前动画的值了。

Interpolator

Interpolator这个东西很难进行翻译,直译过来的话是补间器的意思,它的主要作用是可以控制动画的变化速率,比如去实现一种非线性运动的动画效果。那么什么叫做非线性运动的动画效果呢?就是说动画改变的速率不是一成不变的,像加速运动以及减速运动都属于非线性运动。

TimeInterpolator接口已经有非常多的实现类了,这些都是Android系统内置好的并且我们可以直接使用的Interpolator。每个Interpolator都有它各自的实现效果,比如说AccelerateInterpolator就是一个加速运动的Interpolator,而DecelerateInterpolator就是一个减速运动的Interpolator。

<span style="font-family:SimSun;font-size:12px;">anim.setInterpolator(new AccelerateInterpolator(2f));  </span>

这里调用了setInterpolator()方法,然后传入了一个AccelerateInterpolator的实例,注意AccelerateInterpolator的构建函数可以接收一个float类型的参数,这个参数是用于控制加速度的。

编写自定义Interpolator:

最主要的难度都是在于数学计算方面的,因此这里也就写一个简单点的Interpolator来给大家演示一下。既然属性动画默认的Interpolator是先加速后减速的一种方式,这里我们就对它进行一个简单的修改,让它变成先减速后加速的方式。

新建DecelerateAccelerateInterpolator类,让它实现TimeInterpolator接口,代码如下所示:

public class DecelerateAccelerateInterpolator implements TimeInterpolator{  
  
    @Override  
    public float getInterpolation(float input) {  
        float result;  
        if (input <= 0.5) {  
            result = (float) (Math.sin(Math.PI * input)) / 2;  
        } else {  
            result = (float) (2 - Math.sin(Math.PI * input)) / 2;  
        }  
        return result;  
    }  
  
}

ViewProperty Animator

ViewPropertyAnimator其实算不上什么高级技巧,它的用法格外的简单,只不过和前面所学的所有属性动画的知识不同,它并不是在3.0系统当中引入的,而是在3.1系统当中附增的一个新的功能,因此这里我们把它作为整个属性动画系列的收尾部分。

我们都知道,属性动画的机制已经不是再针对于View而进行设计的了,而是一种不断地对值进行操作的机制,它可以将值赋值到指定对象的指定属性上。但是,在绝大多数情况下,我相信大家主要都还是对View进行动画操作的。Android开发团队也是意识到了这一点,没有为View的动画操作提供一种更加便捷的用法确实是有点太不人性化了,于是在Android 3.1系统当中补充了ViewPropertyAnimator这个机制。

那我们先来回顾一下之前的用法吧,比如我们想要让一个TextView从常规状态变成透明状态,就可以这样写:

ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "alpha", 0f);  
animator.start();  

我们要将操作的view、属性、变化的值都一起传入到ObjectAnimator.ofFloat()方法当中,虽然看上去也没写几行代码,但这不太像是我们平时使用的面向对象的思维。

那么下面我们就来看一下如何使用ViewPropertyAnimator来实现同样的效果,ViewPropertyAnimator提供了更加易懂、更加面向对象的API,如下所示:

textview.animate().alpha(0f); 

果然非常简单!不过textview.animate()这个方法是怎么回事呢?animate()方法就是在Android 3.1系统上新增的一个方法,这个方法的返回值是一个ViewPropertyAnimator对象,也就是说拿到这个对象之后我们就可以调用它的各种方法来实现动画效果了,这里我们调用了alpha()方法并转入0,表示将当前的textview变成透明状态。

怎么样?比起使用ObjectAnimator,ViewPropertyAnimator的用法明显更加简单易懂吧。除此之外,ViewPropertyAnimator还可以很轻松地将多个动画组合到一起,比如我们想要让textview运动到500,500这个坐标点上,就可以这样写:

textview.animate().x(500).y(500); 

可以看出,ViewPropertyAnimator是支持连缀用法的,我们想让textview移动到横坐标500这个位置上时调用了x(500)这个方法,然后让textview移动到纵坐标500这个位置上时调用了y(500)这个方法,将所有想要组合的动画通过这种连缀的方式拼接起来,这样全部动画就都会一起被执行。

那么怎样去设定动画的运行时长呢?很简单,也是通过连缀的方式设定即可,比如我们想要让动画运行5秒钟,就可以这样写:

textview.animate().x(500).y(500).setDuration(5000); 

除此之外,本篇文章第一部分所学的Interpolator技术我们也可以应用在ViewPropertyAnimator上面,如下所示:

textview.animate().x(500).y(500).setDuration(5000)  
        .setInterpolator(new BounceInterpolator());  

用法很简单,同样也是使用连缀的方式。相信大家现在都已经体验出来了,ViewPropertyAnimator其实并没有什么太多的技巧可言,用法基本都是大同小异的,需要用到什么功能就连缀一下,因此更多的用法大家只需要去查阅一下文档,看看还支持哪些功能,有哪些接口可以调用就可以了。

那么除了用法之外,关于ViewPropertyAnimator有几个细节还是值得大家注意一下的:

· 整个ViewPropertyAnimator的功能都是建立在View类新增的animate()方法之上的,这个方法会创建并返回一个ViewPropertyAnimator的实例,之后的调用的所有方法,设置的所有属性都是通过这个实例完成的。

· 大家注意到,在使用ViewPropertyAnimator时,我们自始至终没有调用过start()方法,这是因为新的接口中使用了隐式启动动画的功能,只要我们将动画定义完成之后,动画就会自动启动。并且这个机制对于组合动画也同样有效,只要我们不断地连缀新的方法,那么动画就不会立刻执行,等到所有在ViewPropertyAnimator上设置的方法都执行完毕后,动画就会自动启动。当然如果不想使用这一默认机制的话,我们也可以显式地调用start()方法来启动动画。

· ViewPropertyAnimator的所有接口都是使用连缀的语法来设计的,每个方法的返回值都是它自身的实例,因此调用完一个方法之后可以直接连缀调用它的另一个方法,这样把所有的功能都串接起来,我们甚至可以仅通过一行代码就完成任意复杂度的动画功能。

作者:Evan123mg 发表于2016/10/22 10:18:04 原文链接
阅读:234 评论:0 查看评论

Java Swing 水晶界面 Nimbus风格

$
0
0

Sun曾经开发了两套独立与平台的观感,一个称为Metal另外一个是在JAVA SE 6中新加入的水晶风格Nimubs

Meta风格


Nimbus风格


如果为一个Swing应用程序指令观感,其观感默认设置为Metal。

若要换成Nimbus风格只需在初始化界面时加上这句代码

	UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");


作者:su20145104009 发表于2016/10/22 10:45:32 原文链接
阅读:83 评论:0 查看评论

Unity中内嵌网页插件 UniWebView 2.8使用

$
0
0

2016.10.22   孙广东  

http://blog.csdn.net/u010019717

               

                  UniWebView2:适用于Andriod、ios和Mac os,在移动端效果最好。支持WP8,不支持windows桌面系统,包括编辑器状态。  Unity4.x版本是 UniWebView

Unity AssetStore 上的插件: https://www.assetstore.unity3d.com/en/#!/content/32461        

             在Unity中主要用于活动,就想《阴阳师》  手游!



 


开发有些不方便,因为不支持window下的 Editor的调试!,  只能在Mac下!

 

使用的主要思路

     插件的 官网 ;   http://uniwebview.onevcat.com/

下载插件然后安装导入 Unity
 

使用 Prefab 和设置 URL .

        从 UniWebView/Prefab 文件夹下 拖拽 UniWebViewObject  预制体,在Inspector 上设置 URL(在Windows上也设置不了), Mac 上Play就可以看到效果, 但是在Window上要打包成移动包才行。

         

 

UniWebView的工作方式

 

          在Mac 系统下可以设置 UniWebView组件上的:  Insets 能控制 web 视图的大小。Start函数中加载(Load On Start )和加载完成时自动显示(Auto Show WhenLoad Complete )。

 

 

           除了显示 web 页面的基本用法,UniWebView 有一些其他重要的功能、 与Unity场景通信, 侦听 web 页event事件和计算一些 javascript。

         请参阅详细的在线手册 http://uniwebview.onevcat.com/manual

         和脚本引用 http://uniwebview.onevcat.com/reference/class_uni_web_view.html

 

 

通过脚本设置  UniWebView  加载Web 内容并显示:

       假设, 你在 UniWebView.cs  脚本的所在对象上有一个脚本,  其中定义了字段:

private UniWebView _webView;


    你可以通过这样的方式 得到 引用:

var _webView = gameObject.GetComponent<UniWebView>();


       // 监听事件

_webView.OnLoadComplete += OnLoadComplete;
_webView.OnReceivedMessage += OnReceivedMessage;
_webView.OnEvalJavaScriptFinished +=OnEvalJavaScriptFinished;


            然后  设置 Web 页面的大小  和你 想要加载页面的Url :

 

_webView.insets = newUniWebViewEdgeInsets(5,5,5,5);    // 距离全屏差5个像素
_webView.url = "http://uniwebview.onevcat.com/demo/index.html";


 

             设置完成后,收到调用加载 页面 :

_webView.Load();


 

           页面加载成功或者失败的  完成回调函数大致如下:

voidOnLoadComplete(UniWebView webView, bool success, string errorMessage) {
 if (success) {
    //  显示 加载完成的界面
   webView.Show();
  } else {
    //   输出 错误码
   Debug.LogError("Something wrong in webview loading: " + errorMessage);
  }
}


 

如果 不想监听(其实是不处理失败检测)和自己处理这个 OnLoadComplete  事件,可以 设置属性:        你好像也可以 先调用 Show()  函数,然后在调用 Load(), 函数。

         autoShowWhenLoadComplete  =  true;


Web页面和  Unity游戏逻辑的通信

一、UniWebView发送消息给 Unity

          这个建议使用 url  方案。UniWebView 将监听 以  uniwebview:// 开始 的 url

如果玩家在 Web 页面点击了 Url 链接, UniWebView 将会解析为一个 UniWebViewMessage  对象  ,然后引发 OnReceivedMessage 事件。   一个 UniWebViewMessage 对象包含 一个路径字符串(其中有url 和args 参数字典)。

           例如当点击了链接 :  uniwebview://move?direction=up&distance=1

  将被解析为:

path = "move"
args = {
    direction ="up",
    distance ="1"
}


 

你在 监听了 OnReceivedMessage 事件 的回调中, 会得到 UniWebViewMessage对象,然后实现自己的逻辑:          (自己注:  在游戏中我们通常点击了活动的每个标签或者 Button可能是页面切换,可能是Button的网络请求,  都可以以这种方式来弄!  如果是C#写逻辑,那不就没有了一些热更新的优势了???)

voidOnReceivedMessage(UniWebView webView, UniWebViewMessage message) {
  Debug.Log(message.rawMessage);
   if (string.Equals(message.path,"move")) {
      // It is time to move!
      // In this example:
      // message.args["direction"] ="up"
      // message.args["distance"] ="1"
   }
}


 

关于更多的  url 方法可以看:  AddUrlScheme 这个API 函数

http://uniwebview.onevcat.com/reference/class_uni_web_view.html#a785e560917f32efe65d91874c632f7d5

 

 

二、Unity发送消息到 UniWebView

       你可以运行任何的 javescript 页面,  通过使用 EvaluatingJavaScript 你能调用并运行Javascript,  这个JavaScript代码可以是你游戏脚本中的字符串形式。  你能监听 OnEvalJavaScriptFinished  事件, 来处理这个结果。

 

 

其他功能:

http://uniwebview.onevcat.com/manual

 

X      背景透明-在 iOS 中,默认情况下web 视图还有一个灰色的背景 。你可以使用 SetTransparentBackground 来设置为 背景透明。

 

X      加载进度框 -  就是表示加载进度,以改善体验。您可以禁用 和 通过SetShowSpinnerWhenLoading 和SetSpinnerLabelText 自定义标签文本。

 

X      Back 按键 和导航工具栏按钮支持-用户可以使用后退按钮在 web 页面之间导航,Android 设备上像在本机浏览器中一样。Ios 是一个带有导航按钮的默认工具栏。

 

X      向前 和 向后 ,浏览器的两个行为,您可以在你的游戏中控制网页导航,通过 GoBack 和GoForward 方法。

 

X         清理缓存-web 视图将保持 url 请求,默认情况下,这可能会使旧的页面显示。

即使您更新您的 web 页。使用 CleanCache 来解决这一问题。

 

X       在web 视图  播放 youtube 视频 -只需加载url,它就如此简单的播放。

 

X       加载本地文件 和 html 字符串-  给定设置 本地文件的 url,或 调用 LoadHTMLString 具有 html字符串的值,您可以加载本地的内容。

 

X        更多使用- 你不能只用它来加载 html 和web 页,而且还显示一些图像。

 

X 不止一个 web 视图-  如果您希望在你的游戏中显示 多个 web 视图,那就是 有多个 挂有UniWebView 组件的GameObject 而已。。

 

 

 

设置  AndroidManifest.xml   

                因为 Unity垮平台, 肯定会使用第三方或者自己扩展的android 插件, 就需要维护同一个 AndroidManifest.xml  文件,  需要合并每个插件的需求 (解决冲突)。

        

http://uniwebview.onevcat.com/manual.html#merge_instruction

 

 

              在导入这个插件之前, 如果在您的项目中已经有一个AndroidManifest.xml 文件,你不应该从 UniWebView 包导入 AndroidManifest.xml 文件了(如果没有就直接全部导入就行了)。  而是您需要手动更新该文件。   其实是相当简单,只需不到2 分钟 。

 

             你可以遵循这些步骤,使其正常工作︰

1.    在项目中的 Assets/Plugins/Android   路径下 ,使用文本编辑器打开  AndroidManifest.xml  文件。

 

2.       文件中 搜索  android.intent.action.MAIN(肯定有且只有一个)       然后按照下面的步骤操作:

 

Merge(合并)

(1)   上面步骤二的搜索结果位于<activity>与</activity> 标记对之间。  然后插入下面两行数据:

 
<meta-dataandroid:name="android.app.lib_name" android:value="unity"/>
<meta-dataandroid:name="unityplayer.ForwardNativeEventsToDalvik"android:value="true" />

 

(2)  同样的在<activity...> 标记中,将 android: name 的值更改为"com.onevcat.uniwebview.AndroidPlugin"。这将使用 UniWebView 的activity 来启动你的游戏。这样做,这样我们可以避免 web 视图在 安卓系统的一些问题(类似的问题网上也是有人 提到过:

              “在安卓平台使用最大的问题是Unity失去焦点的问题。经过使用OnApplicationFocus方法测试,调用平台WebView打开界面时默认打开另一个Activity,Unity本身为一个Activity,当打开另外一个Activity时Unity会失去焦点,此时焦点不在Unity程序身上,如果锁屏或者按Home键再打开后只会调出最后一个显示的Activity,也就是UniWebView打开的网页,而不是Unity程序。也就是在打开网页的界面按下Home键或者锁屏后程序将无法正常返回。主要的问题在于Unity程序和打开的网页属于不同的Activity,解决的方法为更改Unity工程中的安卓配置文件,使一个Activity继承于另一个,显示在同一个Activity上。)。                    如果你的mainactivity  使用的不是 com.unity3d.player.UnityPlayerNativeActivitycom.unity3d.player.UnityPlayerActivitycom.unity3d.player.UnityPlayerProxyActivity 其中之一的话,您必须修改UniWebView源代码 修改到你的activity名字上。  请参阅重新编译的指南 http://uniwebview.onevcat.com/manual#manual-recompile ,更多地了解它。


(3) 同样的在 <activity...> 标记中,添加 android: hardwareAccelerated ="true"android:windowSoftInputMode="adjustResize" 。这将使安卓系统提供 html5功能。


最总   会变成这样的格式:

<activityandroid:name="com.onevcat.uniwebview.AndroidPlugin"
  android:label="@string/app_name"
   android:hardwareAccelerated="true"
   android:windowSoftInputMode="adjustResize"
   android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen">
   <intent-filter>
       <action android:name="android.intent.action.MAIN" />
       <category android:name="android.intent.category.LAUNCHER"/>
   </intent-filter>
   <meta-data android:name="android.app.lib_name"android:value="unity" />
   <meta-dataandroid:name="unityplayer.ForwardNativeEventsToDalvik"android:value="true" />
</activity>


 

(4) 最后,添加访问互联网的权限, 如果有了就不用添加了。 在</manifest> 标记  之前 添加:

 

<uses-permissionandroid:name="android.permission.INTERNET" />


 http://blog.csdn.net/u010019717

 

插件Demo的一些 在安卓手机上的截图:



 



作者:u010019717 发表于2016/10/22 11:00:13 原文链接
阅读:75 评论:0 查看评论

Android数据存储

$
0
0

参考:《第一行代码》

Android系统中主要提供了三种方式用于简单地实现数据持久化功能, 即文件存储SharedPreference存储以及数据库存储

当然,除了这三种方式之外,你还可以将数据保存在手机的 SD卡中,不过使用文件、SharedPreference或数据库来保存数据会相对更简单一些,而且比起将数据保存在 SD卡中会更加的安全。

文件存储

openFileOutput()方法,可以用于将数据 存储 到指定的文件中。第一个参数是文件名,注意这里指定的文件名不可以包含路径,因为所有的文件都是默认存储到/data/data/<package name>/files/目 录下 的 。第二个参数是文件的操作模式,主要有两种模式可选,MODE_PRIVATE MODE_APPEND。其中 MODE_PRIVATE是默认的操作模式,表示当指定同样文件名的时候,所写入的内容将会覆盖原文件中的内容,而 MODE_APPEND则表示 如果该文件已存在就往文件里面追加内容,不存在就创建新文件。



openFileInput()方法,用于从文件中 读取 数据。它只接收一个参数,即要读取的文件名,然后系统会自动到/data/data/<packagename>/files/目录下去加载这个文件,并返回一个 FileInputStream对象,得到了这个对象之后再通过 Java流的方式就可以将数据读取出来了。



SharedPreferences存储

Android 中主要提供了三种方法用于得到 SharedPreferences对象。

 1. Context类中的 getSharedPreferences()方法

此方法接收两个参数,第一个参数用于指定 SharedPreferences文件的名称,如果指定的文件不存在则会创建一个,SharedPreferences 文件都是存放在/data/data/<package name>/shared_prefs/目录下的。第二个参数用于指定操作模式,主要有两种模式可以选择,MODE_PRIVATE MODE_MULTI_PROCESSMODE_PRIVATE仍然是默认的操作模式,和直接传入0效果是相同的,表示只有当前的应用程序才可以对这个 SharedPreferences文件进行读写。MODE_MULTI_PROCESS则一般是用于会有多个进程中对同一个SharedPreferences文件进行读写的情况。

 2. Activity类中的 getPreferences()方法

这个方法和 Context中的 getSharedPreferences()方法很相似,不过它只接收一个操作模式参数,因为使用这个方法时会自动将当前活动的类名作为SharedPreferences的文件名

 3. PreferenceManager类中的 getDefaultSharedPreferences()方法

这是一个静态方法,它接收一个 Context参数,并自动使用当前应用程序的包名作为前缀来命名 SharedPreferences文件。

得到了SharedPreferences对象之后,就可以开始向SharedPreferences文件中存储数据了, 主要可以分为三步实现。

     1. 调用 SharedPreferences对象的 edit() 方法来获取一个 SharedPreferences.Editor对象。

     2. SharedPreferences.Editor 对象中添加数据,比如添加一个布尔型数据就使用 putBoolean方法,添加一个字符串则使用 putString()方法,以此类推。

     3. 调用 commit()方法将添加的数据提交,从而完成数据存储操作。


SharedPreferences中读取数据:


SharedPreference在使用过程中有什么注意点?commit()和apply()的区别

返回值

apply()没有返回值,而commit()返回boolean表明修改是否提交成功。

操作效率

apply()是将修改数据提交到内存, 而后异步真正提交到硬件磁盘。

commit()其过程是先把数据更新到内存,然后在当前线程中写文件操作同步的提交到硬件磁盘,提交完成返回提交状态 

建议

如果对提交的结果不关心的话,建议使用apply(),如果需要确保提交成功且有后续操作的话,还是需要用commit()

多进程表现

一句话:在多进程中,如果要交换数据,不要使用SharedPreference,因为在不同版本表现不稳定,推荐使用ContentProvider替代。

使用细节

· ContextImpl中有一个静态的ArrayMap变量sSharedPrefs,无论有多少个ContextImpl对象实例,系统都共享这一个sSharedPrefsMap,应用启动以后首次使用SharePreference时创建,系统结束时才可能会被垃圾回收器回收,所以如果我们一个App中频繁的使用不同文件名的SharedPreferences很多时这个Map就会很大,也即会占用移动设备宝贵的内存空间。所以我们应用中应该尽可能少的使用不同文件名的SharedPreferences,取而代之的是合并他们,减小内存使用

· SharedPreferences在实例化时首先会从sdcard异步读文件,然后缓存在内存中;接下来的读操作都是内存缓存操作而不是文件操作。

· SharedPreferencesEditor中如果用commit()方法提交数据,其过程是先把数据更新到内存,然后在当前线程中写文件操作,提交完成返回提交状态;如果用的是apply()方法提交数据,首先也是写到内存,接着在一个新线程中异步写文件,然后没有返回值。

· 在写操作commit时有三级锁操作,效率很低,所以当我们一次有多个修改写操作时等都批量put完了再一次提交确认,这样可以提高效率。

SharedPreferences内部工作原理:

1、调用getSharedPreferences();创建一个SharedPreferences对象,其中会先判断是否存在对应xml文件,如果发现存在则会有一个预加载操作,这个操作是把xml文件的内容通过I/O操作和XmlUitl解析后存入一个map对象中,所以我们调用SharedPreferences::getString();等get操作实际上是不会对文件做I/O操作,而是直接访问刚刚的map集合的内容,这提高了效率,如果对应的xml不存在则重新创建一个对应的xml文件。 
部分实现如下:


2、put写操作:写操作也有两步,一是把数据先写入内存中,即map集合,二是把数据写入硬盘文件中。这样才能保证数据的完整性,写操作有两个提交的方式:

commit():线程安全,性能慢,一般来说在当前线程完成写文件操作 
apply():线程不安全,性能高,异步处理IO操作,一定会把这个写文件操作放入一个SingleThreadExecutor线程池中处理

3、SharedPreferences在第一次创建后会一直维持一个Singleton,每次调用getSharedPreferences()都返回唯一的一个实例


SQLite数据库存储

Android为了让我们能够更加方便地管理数据库,专门提供了一个 SQLiteOpenHelper帮助类,借助这个类就可以非常简单地对数据库进行创建  升级SQLiteOpenHelper中还有两个非常重要的实例方法,getReadableDatabase()getWritableDatabase()。这两个方法都可以创建或打开一个现有的数据库(如果数据库已存在则直接打开,否则创建一个新的数据库),并返回一个可对数据库进行读写操作的对象。不同的是,当数据库不可写入的时候(如磁盘空间已满)getReadableDatabase()方法返回的对象将以只读的方式去打开数据库,而getWritableDatabase()方法则将出现异常。

SQLiteOpenHelper中有两个构造方法可供重写,一般使用参数少一点的那个构造方法即可。这个构造方法中接收四个参数,第一个参数  Context,这个没什么好说的,必须要有它才能对数据库进行操作。第二个参数 数据库名,创建数据库时使用的就是这里指定的名称。第三个参数 允许我们在查询数据的时候返回一个自定义的 Cursor,一般都是传入null第四个参数 表示当前数据库的版本号,可用于对数据库进行升级操作。构建出 SQLiteOpenHelper的实例之后,再调用它的 getReadableDatabase()getWritableDatabase()方法就能够创建数据库了,数据库文件会存放在/data/data/<package name>/databases/目录下。

此时,重写的onCreate() 方法也会得到执行,所以通常会在这里去处理一些创建表 的逻辑。



onUpgrade() 方法是用于对数据库进行升级的,它在整个数据库的管理工作当中起着非常重要的作用。(这一部分直接参考第一行代码)。

数据库的增删查改:

其中 C 代表添加 (Create),R代表查询(Retrieve),U代表更新(Update),D代表删除(Delete)。

增:SQLiteDatabase中提供了一个 insert() 方法,这个方法就是专门用于添加数据的。它接收三个参数,第一个参数表名,我们希望向哪张表里添加数据,这里就传入该表的名字。第二个参数用于在未指定添加数据的情况下给某些可为空的列自动赋值 NULL,一般我们用不到这个功能,直接传入 null即可。第三个参数是一个 ContentValues对象,它提供了一系列的put()方法重载,用于向ContentValues中添加数据,只需要将表中的每个列名以及相应的待添加数据传入即可。


改:update()方法用于对数据进行更新,这个方法接收四个参数,第一个参数表名第二个参数 ContentValues对象,要把更新数据在这里组装进去。第三、第四个参数用于去约束更新某一行或某几行中的数据,不指定的话默认就是更新所有行。这里在更新数据按钮的点击事件里面构建了一个 ContentValues对象,并且只给它指定了一组数据,说明我们只是想把价格这一列的数据更新成10.99。然后调用了SQLiteDatabase update()方法去执行具体的更新操作,可以看到,这里使用了第三、第四个参数来指定具体更新哪几行。第三个参数对应的是SQL语句的 where部分,表示去更新所有 name等于 ? 的行,而 ? 是一个占位符,可以通过第四个参数提供的一个字符串数组为第三个参数中的每个占位符指定相应的内容。因此上述代码想表达的意图就是,将名字是 The DaVinci Code 的这本书的价格改成 10.99

<span style="font-size:12px;">SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues(); 
values.put("price", 10.99); 
db.update("Book", values, "name = ?", new String[] { "The DaVinci Code" });</span>

删:delete() 方法专门用于删除数据,这个方法接收三个参数,第一个参数仍然是表名,这个已经没什么好说的了,第二、第三个参数又是用于去约束删除某一行或某几行的数据,不指定的话默认就是删除所有行。可以看到,我们在删除按钮的点击事件里指明去删除 Book表中的数据,并且通过第二、 第三个参数来指定仅删除那些页数超过 500页的书籍。

<span style="font-size:12px;">SQLiteDatabase db = dbHelper.getWritableDatabase(); 
db.delete("Book", "pages > ?", new String[] { "500" });</span>

查:query() 方法用于对数据进行查询。这个方法的参数非常复杂,最短的一个方法重载也需要传入七个参数。第一个参数表名,表示我们希望从哪张表中查询数据。第二个参数用于指定去查询哪几列,如果不指定则默认查询所有列。第三、第四个参数用于去约束查询某一行或某几行的数据,不指定则默认是查询所有行的数据。第五个参数用于指定需要去 group by的列,不指定则表示不对查询结果进行 groupby操作。第六个参数用于对 group by之后的数据进行进一步的过滤,不指定则表示不进行过滤。第七个参数用于指定查询结果的排序方式,不指定则表示使用默认的排序方式。



数据库的升级:

每一个数据库版本都会对应一个版本号,当指定的数据库版本号大于当前数据库版本号的时候,就会进入到 onUpgrade() 方法中去执行更新操作。这里需要为每一个版本号赋予它各自改变的内容,然后在 onUpgrade()方法中对当前数据库的版本号进行判断,再执行相应的改变就可以了。

(具体查看第一行代码P264



作者:Evan123mg 发表于2016/10/22 11:14:33 原文链接
阅读:73 评论:0 查看评论

RecyclerView的使用

$
0
0
RecyclerView在android5.0之后被引进,可以通过导入support-v7对其进行使用。 该控件用于在有限的窗口中展示大量数据集。提供了一种插拔式的体验,高度的解耦,异常的灵活,通过LayoutManager,ItemDecoration , ItemAnimator可以实现多样的效果。

实例一:

public class RecyclerActivity01 extends AppCompatActivity {
    private RecyclerView mRecyclerView;
    private List<String> mDatas;
    private BasicAdapter baseAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_recycler01);
        mRecyclerView = (RecyclerView) findViewById(R.id.rv);
        initData();

        baseAdapter = new BasicAdapter(mDatas, RecyclerActivity01.this);
        mRecyclerView.setAdapter(baseAdapter);
        //声明布局管理器,设置最终的展示效果
        LinearLayoutManager manager = new LinearLayoutManager(this, LinearLayout.VERTICAL, false);
        mRecyclerView.setLayoutManager(manager);

        //设置增加条目的动画
        mRecyclerView.setItemAnimator(new DefaultItemAnimator());

        baseAdapter.setOnItemClickListener(new BasicAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(View itemView, int position) {
                Toast.makeText(RecyclerActivity01.this,"你点击了:"+mDatas.get(position),Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onLongItemClick(View itemView, int position) {

            }
        });
    }

    private void initData() {
        mDatas = new ArrayList<>();
        for (int i = 0; i < 48; i++) {
            mDatas.add("第" + i + "条数据");
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.add:
                baseAdapter.addData(1);
                break;
            case R.id.delete:
                baseAdapter.removeData(1);
                break;
            case R.id.diplay_lv:
                LinearLayoutManager manger = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
                mRecyclerView.setLayoutManager(manger);
                break;
            case R.id.diplay_gv:
                //第二个参数:定义列数
                GridLayoutManager manager = new GridLayoutManager(this, 3);
                mRecyclerView.setLayoutManager(manager);
                break;
            case R.id.diplay_hlv:
                StaggeredGridLayoutManager sManager = new StaggeredGridLayoutManager(1, StaggeredGridLayoutManager.HORIZONTAL);
                mRecyclerView.setLayoutManager(sManager);
                break;
            case R.id.diplay_hgv:
                sManager = new StaggeredGridLayoutManager(5, StaggeredGridLayoutManager.HORIZONTAL);
                mRecyclerView.setLayoutManager(sManager);
                break;
            case R.id.diplay_staggered:
                Intent intent = new Intent(this, StaggeredActivity.class);
                startActivity(intent);
                break;
        }
        return super.onOptionsItemSelected(item);
    }
}

<android.support.v7.widget.RecyclerView
        android:id="@+id/rv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
public class BasicAdapter extends RecyclerView.Adapter<BasicAdapter.BasicViewHolder> {
    private List<String> mdatas;
    private Context context;
    private OnItemClickListener listener;

    public BasicAdapter(List<String> mdatas, Context context) {
        this.mdatas = mdatas;
        this.context = context;
    }

    /**
     * des:添加监听
     */
    public void setOnItemClickListener(OnItemClickListener listener) {
        this.listener = listener;
    }
    public interface OnItemClickListener {
         void onItemClick(View itemView, int position);
         void onLongItemClick(View itemView, int position);
    }

    @Override
    public BasicViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        View view = LayoutInflater.from(context).inflate(R.layout.item_recycler01, parent, false);
        BasicViewHolder holder = new BasicViewHolder(view);
        return holder;
    }

    public void addData(int pos) {
        mdatas.add(pos, "add_data");
        notifyItemInserted(pos);
    }

    public void removeData(int pos) {
        mdatas.remove(pos);
        notifyItemRemoved(pos);
    }

    @Override
    public void onBindViewHolder(BasicViewHolder holder, int position) {
        holder.tv.setText(mdatas.get(position));
        setUpEvent(holder);
    }

    protected void setUpEvent(final BasicViewHolder holder) {
        if (listener != null) {
            holder.itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    int position = holder.getLayoutPosition();
                    listener.onItemClick(holder.itemView, position);
                }
            });

            holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    int position = holder.getLayoutPosition();
                    listener.onLongItemClick(holder.itemView, position);
                    return false;
                }
            });
        }
    }

    @Override
    public int getItemCount() {
        return mdatas.size();
    }

    class BasicViewHolder extends RecyclerView.ViewHolder {
        TextView tv;
        public BasicViewHolder(View itemView) {
            super(itemView);
            tv = (TextView) itemView.findViewById(R.id.id_tv1);
        }
    }
}

效果图:



实例二:实现照片墙,宽度一定,高度不确定

public class PhoneWallActivity extends AppCompatActivity {
    private RecyclerView phrv;
    private PhoneWalladapter walladapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_phone_wall);
        phrv = (RecyclerView) findViewById(R.id.pwrv);
        walladapter = new PhoneWalladapter(this);

        phrv.setAdapter(walladapter);
        StaggeredGridLayoutManager manager = new StaggeredGridLayoutManager(2
                ,StaggeredGridLayoutManager.VERTICAL);
        phrv.setLayoutManager(manager);

    }
}
<android.support.v7.widget.RecyclerView
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:id="@+id/pwrv"/>
public class PhoneWalladapter extends RecyclerView.Adapter<PhoneWalladapter.PhotoViewHolder> {
private Context context;
    private String imgUrls[];
    private List<Integer>mHeight;

    public PhoneWalladapter(Context context) {
        this.context = context;
        imgUrls = ImageUrls.imageurls;
        mHeight = new ArrayList<>();
        for (int i = 0; i < imgUrls.length; i++) {
            mHeight.add((int)(300+Math.random()*400));
        }
    }


    @Override
    public PhotoViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(context).inflate(R.layout.item_phone,parent,false);
        PhotoViewHolder holder = new PhotoViewHolder(itemView);
        return holder;
    }

    @Override
    public void onBindViewHolder(PhotoViewHolder holder, int position) {

        ViewGroup.LayoutParams lp = holder.iv.getLayoutParams();
        lp.height = mHeight.get(position);
        holder.iv.setLayoutParams(lp);

        ImageOptions options = new ImageOptions.Builder().setFailureDrawableId(R.mipmap.ic_launcher)
                .setLoadingDrawableId(R.mipmap.ic_launcher).build();
        x.image().bind(holder.iv,imgUrls[position],options);
    }

    @Override
    public int getItemCount() {
        return imgUrls.length;
    }

    class PhotoViewHolder extends RecyclerView.ViewHolder{
        ImageView iv;
        public PhotoViewHolder(View itemView) {
            super(itemView);
            iv = (ImageView)itemView.findViewById(R.id.item_iv);
        }
    }
}
效果图:

实例三:实现下载刷新上拉加载效果:

public class RecyclerActivity04 extends AppCompatActivity {

    private RecyclerView mRecyclerView;
    private SwipeRefreshLayout swipeRefreshLayout;
    
    private List<Map<String,String>>mDatas;
    private LinearLayoutManager manager;
    private RecyclerAdapter4 adapter;
    private Handler handler = new Handler();

    private boolean isLoading = false;    //设置是否处于上啦加载状态
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_recycler04);
        initView();
        setEvent();
    }
    private void initView(){
        mRecyclerView = (RecyclerView) findViewById(R.id.id_rv);
        swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.srl);
        swipeRefreshLayout.setColorSchemeColors(Color.RED,Color.GREEN);
        manager = new LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false);
        mRecyclerView.setLayoutManager(manager);
        mDatas = new ArrayList<>();
        getData();
        adapter = new RecyclerAdapter4(this,mDatas);
        mRecyclerView.setAdapter(adapter);
    }

    private void setEvent(){
        //设置下拉刷新的操作
        swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                handler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        mDatas.clear();
                        getData();
                        adapter.notifyDataSetChanged();
                        swipeRefreshLayout.setRefreshing(false);
                    }
                },3000);
            }
        });
        //设置上啦加载的监听器
        mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                //获得可见条目最后一条的位置
                int lastVisibleItemPosition = manager.findLastVisibleItemPosition();
                if (lastVisibleItemPosition+1==adapter.getItemCount()){
                    //获取是否在下拉刷新
                    boolean isRefresh = swipeRefreshLayout.isRefreshing();
                    if (isRefresh){
                        return;
                    }

                    if (!isLoading){
                        isLoading = true;
                         //没有处于加载状态,然后需要加载数据,就可以分页加载了
                        handler.postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                getData();
                                adapter.notifyDataSetChanged();
                                adapter.notifyItemRemoved(adapter.getItemCount());
                                isLoading = false;
                            }
                        },4000);
                    }
                }
            }
        });
    }
    /**
     * 加载数据
     * */
    private void getData(){
        int size = mDatas.size();
        for (int i = 0; i <20 ; i++) {
            Map<String,String>map = new HashMap<>();
            map.put("title","title======"+(i+size));
            map.put("summary","summary--------"+(i+size));
            mDatas.add(map);
        }
    }
}
<android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/srl"
        android:layout_height="match_parent"
        android:layout_width="match_parent">
        <android.support.v7.widget.RecyclerView
            android:id="@+id/id_rv"
            android:layout_height="match_parent"
            android:layout_width="match_parent">

        </android.support.v7.widget.RecyclerView>
    </android.support.v4.widget.SwipeRefreshLayout>
public class RecyclerAdapter4 extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
    private Context context;
    private List<Map<String,String>>mDatas;
    private final int TYPE_COMMON = 0;
    private final int TYPE_FOOTER = 1;

    public RecyclerAdapter4(Context context, List<Map<String, String>> mDatas) {
        this.context = context;
        this.mDatas = mDatas;
    }
    /**
     * 获得指定的位置上的显示类型
     * */
    @Override
    public int getItemViewType(int position) {
        if (position==getItemCount()-1){
            return  TYPE_FOOTER;
        }
        return  TYPE_COMMON;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == TYPE_COMMON) {
            View itemView = LayoutInflater.from(context).inflate(R.layout.item_recycler04,parent,false);
            ItemViewHolder itemViewHolder = new ItemViewHolder(itemView);
            return  itemViewHolder;
        }else {
            View footerView = LayoutInflater.from(context).inflate(R.layout.item_footer,parent,false);
            FooterViewHolder holder = new FooterViewHolder(footerView);
            return  holder;
        }
    }

    @Override
    public int getItemCount() {
        return mDatas.size()==0?0:mDatas.size()+1;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            if (holder instanceof  ItemViewHolder) {
                ((ItemViewHolder)holder).tv_name.setText(mDatas.get(position).get("title"));
                ((ItemViewHolder)holder).tv_summary.setText(mDatas.get(position).get("summary"));
            }
    }

    class ItemViewHolder extends RecyclerView.ViewHolder{
        TextView tv_name,tv_summary;
        public ItemViewHolder(View itemView) {
            super(itemView);
            tv_name = (TextView)itemView.findViewById(R.id.tv_name);
            tv_summary = (TextView)itemView.findViewById(R.id.tv_summary);
        }
    }

    class FooterViewHolder extends RecyclerView.ViewHolder{

        public FooterViewHolder(View itemView) {
            super(itemView);
        }
    }
}
效果:

实例四;CardView的效果

 <android.support.v7.widget.CardView
        android:id="@+id/id_card"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:cardBackgroundColor="#669900"
        app:cardCornerRadius="10dp"
        app:cardElevation="20dp">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <ImageView
                android:layout_width="80dp"
                android:layout_height="80dp"
                android:layout_centerVertical="true"
                android:src="@mipmap/ic_launcher"/>
            <TextView
                android:layout_width="match_parent"
                android:layout_height="70dp"
                android:layout_margin="10dp"
                android:gravity="center"
                android:padding="10dp"
                android:text="显示效果"
                android:textColor="#000000"
                android:textSize="20sp" />
        </RelativeLayout>
    </android.support.v7.widget.CardView>










作者:qq_29882585 发表于2016/10/22 11:47:18 原文链接
阅读:62 评论:0 查看评论

蓝牙【GATT】协议介绍

$
0
0

在这之前我们得先了解一下一些专业词汇:
1、profile
profile可以理解为一种规范,一个标准的通信协议,它存在于从机中。蓝牙组织规定了一些标准的profile,例如 HID OVER GATT ,防丢器 ,心率计等。每个profile中会包含多个service,每个service代表从机的一种能力。
2、service
service可以理解为一个服务,在ble从机中,通过有多个服务,例如电量信息服务、系统信息服务等,每个service中又包含多个characteristic特征值。每个具体的characteristic特征值才是ble通信的主题。比如当前的电量是80%,所以会通过电量的characteristic特征值存在从机的profile里,这样主机就可以通过这个characteristic来读取80%这个数据
3、characteristic
characteristic特征值,ble主从机的通信均是通过characteristic来实现,可以理解为一个标签,通过这个标签可以获取或者写入想要的内容。
4、UUID
UUID,统一识别码,我们刚才提到的service和characteristic,都需要一个唯一的uuid来标识

整理一下,每个从机都会有一个叫做profile的东西存在,不管是上面的自定义的simpleprofile,还是标准的防丢器profile,他们都是由一些列service组成,然后每个service又包含了多个characteristic,主机和从机之间的通信,均是通过characteristic来实现。


--------------------------------------------------------------------------------------------------------------------------------------------------------------------------

一、 引言

现在低功耗蓝牙(BLE)连接都是建立在 GATT (Generic Attribute Profile) 协议之上。GATT 是一个在蓝牙连接之上的发送和接收很短的数据段的通用规范,这些很短的数据段被称为属性(Attribute)。

二、 GAP

详细介绍 GATT 之前,需要了解 GAP(Generic Access Profile),它在用来控制设备连接和广播。GAP 使你的设备被其他设备可见,并决定了你的设备是否可以或者怎样与合同设备进行交互。例如 Beacon 设备就只是向外广播,不支持连接,小米手环就等设备就可以与中心设备连接。

1. 设备角色

GAP 给设备定义了若干角色,其中主要的两个是:外围设备(Peripheral)和中心设备(Central)。

  • 外围设备:这一般就是非常小或者简单的低功耗设备,用来提供数据,并连接到一个更加相对强大的中心设备。例如小米手环。
  • 中心设备:中心设备相对比较强大,用来连接其他外围设备。例如手机等。

2. 广播数据

在 GAP 中外围设备通过两种方式向外广播数据: Advertising Data Payload(广播数据)和 Scan Response Data Payload(扫描回复),每种数据最长可以包含 31 byte。这里广播数据是必需的,因为外设必需不停的向外广播,让中心设备知道它的存在。扫描回复是可选的,中心设备可以向外设请求扫描回复,这里包含一些设备额外的信息,例如设备的名字。(广播的数据格式我将另外专门写一个篇博客来讲。)

3. 广播流程

GAP 的广播工作流程如下图所示。
adv_proc从图中我们可以清晰看出广播数据和扫描回复数据是怎么工作的。外围设备会设定一个广播间隔,每个广播间隔中,它会重新发送自己的广播数据。广播间隔越长,越省电,同时也不太容易扫描到。

4. 广播的网络拓扑结构

大部分情况下,外设通过广播自己来让中心设备发现自己,并建立 GATT 连接,从而进行更多的数据交换。也有些情况是不需要连接的,只要外设广播自己的数据即可。用这种方式主要目的是让外围设备,把自己的信息发送给多个中心设备。因为基于 GATT 连接的方式的,只能是一个外设连接一个中心设备。使用广播这种方式最典型的应用就是苹果的 iBeacon。广播工作模式下的网络拓扑图如下:BroadcastTopology

三、GATT

GATT 的全名是 Generic Attribute Profile(姑且翻译成:普通属性协议),它定义两个 BLE 设备通过叫做 ServiceCharacteristic 的东西进行通信。GATT 就是使用了 ATT(Attribute Protocol)协议,ATT 协议把 Service, Characteristic遗迹对应的数据保存在一个查找表中,次查找表使用 16 bit ID 作为每一项的索引。

一旦两个设备建立起了连接,GATT 就开始起作用了,这也意味着,你必需完成前面的 GAP 协议。这里需要说明的是,GATT 连接,必需先经过 GAP 协议。实际上,我们在 Android 开发中,可以直接使用设备的 MAC 地址,发起连接,可以不经过扫描的步骤。这并不意味不需要经过 GAP,实际上在芯片级别已经给你做好了,蓝牙芯片发起连接,总是先扫描设备,扫描到了才会发起连接。

GATT 连接需要特别注意的是:GATT 连接是独占的。也就是一个 BLE 外设同时只能被一个中心设备连接。一旦外设被连接,它就会马上停止广播,这样它就对其他设备不可见了。当设备断开,它又开始广播。

中心设备和外设需要双向通信的话,唯一的方式就是建立 GATT 连接。

1. GATT 连接的网络拓扑

下图展示了 GTT 连接网络拓扑结构。这里很清楚的显示,一个外设只能连接一个中心设备,而一个中心设备可以连接多个外设。ConnectedTopology一旦建立起了连接,通信就是双向的了,对比前面的 GAP 广播的网络拓扑,GAP 通信是单向的。如果你要让两个设备外设能通信,就只能通过中心设备中转。

2. GATT 通信事务

GATT 通信的双方是 C/S 关系。外设作为 GATT 服务端(Server),它维持了 ATT 的查找表以及 service 和 characteristic 的定义。中心设备是 GATT 客户端(Client),它向 Server 发起请求。需要注意的是,所有的通信事件,都是由客户端(也叫主设备,Master)发起,并且接收服务端(也叫从设备,Slave)的响应。

一旦连接建立,外设将会给中心设备建议一个连接间隔(Connection Interval),这样,中心设备就会在每个连接间隔尝试去重新连接,检查是否有新的数据。但是,这个连接间隔只是一个建议,你的中心设备可能并不会严格按照这个间隔来执行,例如你的中心设备正在忙于连接其他的外设,或者中心设备资源太忙。

下图展示一个外设(GATT 服务端)和中心设备(GATT 客户端)之间的数据交换流程,可以看到的是,每次都是主设备发起请求:GattMasterSlaveTransactions

3. GATT 结构

GATT 事务是建立在嵌套的Profiles, Services 和 Characteristics之上的的,如下图所示:
GattStructure

  • ProfileProfile 并不是实际存在于 BLE 外设上的,它只是一个被 Bluetooth SIG 或者外设设计者预先定义的 Service 的集合。例如心率Profile(Heart Rate Profile)就是结合了 Heart Rate Service 和 Device Information Service。所有官方通过 GATT Profile 的列表可以从这里找到。

  • ServiceService 是把数据分成一个个的独立逻辑项,它包含一个或者多个 Characteristic。每个 Service 有一个 UUID 唯一标识。 UUID 有 16 bit 的,或者 128 bit 的。16 bit 的 UUID 是官方通过认证的,需要花钱购买,128 bit 是自定义的,这个就可以自己随便设置。

官方通过了一些标准 Service,完整列表在这里。以 Heart Rate Service为例,可以看到它的官方通过 16 bit UUID 是 0x180D,包含 3 个 Characteristic:Heart Rate Measurement, Body Sensor LocationHeart Rate Control Point,并且定义了只有第一个是必须的,它是可选实现的。

  • Characteristic在 GATT 事务中的最低界别的是 Characteristic,Characteristic 是最小的逻辑数据单元,当然它可能包含一个组关联的数据,例如加速度计的 X/Y/Z 三轴值。

与 Service 类似,每个 Characteristic 用 16 bit 或者 128 bit 的 UUID 唯一标识。你可以免费使用 Bluetooth SIG 官方定义的标准 Characteristic,使用官方定义的,可以确保 BLE 的软件和硬件能相互理解。当然,你可以自定义 Characteristic,这样的话,就只有你自己的软件和外设能够相互理解。

举个例子, Heart Rate Measurement Characteristic,这是上面提到的 Heart Rate Service 必需实现的 Characteristic,它的 UUID 是 0x2A37。它的数据结构是,开始 8 bit 定义心率数据格式(是UINT8 还是 UINT16?),接下来就是对应格式的实际心率数据。

实际上,和 BLE 外设打交道,主要是通过 Characteristic。你可以从 Characteristic 读取数据,也可以往 Characteristic 写数据。这样就实现了双向的通信。所以你可以自己实现一个类似串口(UART)的 Sevice,这个 Service 中包含两个 Characteristic,一个被配置只读的通道(RX),另一个配置为只写的通道(TX)。

更多内容


作者:u013378580 发表于2016/10/22 12:14:48 原文链接
阅读:62 评论:0 查看评论

【周记-Android移动端开发】手机蓝牙与下位机HC-05蓝牙模块通信系统

$
0
0

【周记-Android移动端开发】手机蓝牙与下位机HC-05蓝牙模块通信系统

很久没有写博客了,计划一直都有,但总是被这样或者那样的事情给耽搁了,在此写下文字监督自己:不论长短,每周至少一篇!本文根据自己的实践总结而来,参考前人博客之余,也自己总结和开发了一些功能,在这里给自己备份也分享给大家。不同之处在于:自动打开并搜索蓝牙、修改蓝牙名字、完整接收蓝牙传输数据、修改蓝牙密码、解除蓝牙绑定。

  • 系统框架简介
  • 软件界面设计
  • 蓝牙开发

一、系统框架简介

系统由上、下位机两部分构成,旨在实现移动端app通过蓝牙通信,将app发送过来的数据存储在下位机的存储单元,与此同时,app也可以通过指令查询下位机的参数设置。系统框架图如下:

这里写图片描述

二、软件界面设计

话不多说,先上软件界面:

这里写图片描述

界面主要涉及到按键的监听和Activity的跳转:
①按键监听有四种方法,详情可以参考博客
②Activity的跳转包括:直接跳startActivity转和带返回跳startActivityForResult转,详情可以参考博客

二、蓝牙开发

蓝牙功能 函数名称
(1)开启&关闭蓝牙 enable()/disable()
(2)修改蓝牙名字 changeName()
(3)搜索&显示蓝牙设备 DeviceListActivity
(4)配对&连接蓝牙 connectToServerSocket(address)
(5)数据收发 write/ConnectedThread
(6)修改蓝牙密码 changeMima()
(7)取消蓝牙配对 removeBond

本系统中实现的功能如表所示,这里不打算赘述已经开发成熟的功能,可以参考这篇博客,讲得比较清晰,这里只针对自己在开发中添加的功能做一下记录,开发之前需要提醒的有两点:
①不要忘记蓝牙权限的设置:

<uses-permission android:name="android.permission.BLUETOOTH"/>  
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>  

②让主程序弹出搜索对话框时,不要忘了intent设置;

<activity android:name=".DeviceListActivity" 
android:theme="@android:style/Theme.Dialog" 
android:label="选取连接设备" 
android:configChanges="orientation|keyboardHidden"/>

(1)自动打开并搜索蓝牙

一般的做法都是监听按键(打开蓝牙按键和搜索设备按键),先打开蓝牙,接着搜索蓝牙。但是,这样做无疑使操作更加复杂,在实际生产和用户使用时并不方便,我要做的效果是程序启动的时候自动打开蓝牙并开始搜索设备,于是乎,出问题了,蓝牙虽然打开了,但是不接着搜索设备了,汗。。。解决办法是,打开蓝牙后延时1秒后开始搜索,这样就行了

//得到BluetoothAdapter对象
BluetoothAdapter mBtAdapter = BluetoothAdapter.getDefaultAdapter();
mBtAdapter.enable();//打开蓝牙
Timer timer=new Timer();
TimerTask task=new TimerTask(){//定时1k=new TimerTask(){//定时1秒后搜索设备
    @Override
     public void run() {
        Intent serverIntent = new Intent(MainActivity.this, DeviceListActivity.class); //跳转程序设置
        startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE);  //设置返回宏定义
    }          
};
timer.schedule(task,1000);

(3)修改蓝牙名字

这一功能有两种实现方式,一种是通过蓝牙传输将名字传输给下位机,用单片机操作蓝牙AT指令直接修改蓝牙名字,这样,以后任何手机搜索该蓝牙设备时,显示的名字都是你所修改的名字;另一种方式是在软件层面实现蓝牙名字的修改,其实是搜索出蓝牙设备的ID,然后将其对应到你想修改的名字,并将这一映射存在本地,那么,以后这部手机下次搜索到该设备时,就会显示你所修改的名字,而不影响其他手机对该设备的显示。这种方法各有特色,就看你的需求了。这里就讲讲第二种方式的实现:

步骤一:搜索蓝牙设备,获取并修改名字

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    // TODO Auto-generated method stub
    super.onActivityResult(requestCode, resultCode, data);
    switch(requestCode) {
    case REQUEST_CONNECT_DEVICE:     //连接结果,由DeviceListActivity设置返回
        if (resultCode == Activity.RESULT_OK) { //选择蓝牙
            String address = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
            device = mBluetoothAdapter.getRemoteDevice(address);// 得到蓝牙设备句柄
            connectToServerSocket(address);
            setting_thread=true;
            ConnectedThread thread=new ConnectedThread();
            thread.start();
        }
        break;
        default:break;
    }
}

在从DeviceListActivity返回的数据中,其实已经包含蓝牙设备的句柄,我们可以通过device.getName()获取当前连接的蓝牙模块的名字,然后将设备的名字或者其唯一标识符(例如,98:D3:34:90:8A:BC)与新修改的名字做映射保存在本地。
步骤二:存储设置到本地,下次搜索蓝牙时做映射显示
使用SharedPreferences存储数据,SharedPreferences是Android平台上一个轻量级的存储类,主要是保存一些常用的配置比如窗口状态,一般在Activity中 重载窗口状态onSaveInstanceState保存一般使用SharedPreferences完成,它提供了Android平台常规的Long长 整形、Int整形、String字符串型的保存。

//获取SharedPreferences对象
Context ctx = MainActivity.this;       
SharedPreferences sp = ctx.getSharedPreferences("SP", MODE_PRIVATE);
//存入数据
Editor editor = sp.edit();
editor.putString("STRING_KEY", "string");
editor.commit();

(4)完整接收蓝牙传输数据

在开发蓝牙数据接收时,发现一个问题:数据有时候会接收不完整。由于项目中的每一位数据返回都十分重要,所以必须保证数据的完整性,要解决这一问题需要清楚的了解如何从InputStream中读取数据,从输入流中读取数据最常用的方法基本上就是如下 3 个 read() 方法:

① read () 方法,这个方法从输入流中读取数据的下一个字节。返回 0 到 255 范围内的 int 字节值。如果因为已经到达流末尾而没有可用的字节,则返回值 -1 。
② read (byte[] b,int off,int len) 方法,将输入流中最多 len 个数据字节读入 byte 数组。尝试读取 len 个字节,但读取的字节也可能小于该值。以整数形式返回实际读取的字节数。
③ read (byte[] b) 方法, 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。以整数形式返回实际读取的字节数。

对于三种方法的选择,需要看具体项目的需求,如果你能够通过某种方法知道数据的长度,那么建议用第一种方法,每次读取一个字节,保证数据的完整性;如果数据长度不知道,那么就用②或者③,一般③用的比较多,这一方法效率高但是读取数据的长度不确定—-不超过数组b的长度,所以就需要多次读取直到把数据全部读完。

(5)修改蓝牙密码

本项目中用的是HC-05蓝牙模块,该模块有接收蓝牙指令模式(即通信模式),和AT指令集模式,一般默认状态为蓝牙通信模式,要想修改蓝牙连接密码,需要将蓝牙模块切换到AT指令集模式,具体过程见下面的流程图:
这里写图片描述

(6)解除蓝牙绑定(配对)

在真实场景中,要想在程序修改完密码后,之前连接的蓝牙设备都无法与该蓝牙模块建立连接,需要解除该模块与手机的绑定,即取消配对。要想解除所有的配对,当然是需要下位机控制蓝牙模块进入AT指令集模式,解除所有绑定,而与此同时,当前修改密码的手机也需要在软件层面解除绑定(配对),这样交互性更强。

用过Android系统设置(Setting)的人都知道蓝牙搜索之后可以建立配对和解除配对,但是这两项功能的函数没有在SDK中给出,本项目利用Java的反射机制去调用removeBond解除绑定,从而取消本机与该蓝牙模块的配对。具体实现程序如下:当程序接收到下位机传来的修改密码成功的指令后,程序会弹出一个强提示,当用户点击确认按钮之后,程序解除绑定并退出程序。

if(modify_flag){//修改密码成功
      AlertDialog.Builder builder = new Builder(MainActivity.this);
      builder.setTitle("密码修改成功");
      builder.setMessage("点击确定按钮重新启动!");
      builder.setCancelable(false);
      builder.setPositiveButton("确认", new OnClickListener() {
           @Override
           public void onClick(DialogInterface dialog, int which) {
           try {//接触绑定
               Method m = device.getClass().getMethod("removeBond", (Class[]) null);
               m.invoke(device, (Object[]) null);
           } catch (Exception e) {
                Log.e("erro", e.getMessage());
           }
           setting_thread=false;
           dialog.dismiss();
           mBluetoothAdapter.disable();//关闭蓝牙
           finish();
           System.exit(0);//销毁程序
           }
      });
      builder.setCancelable(false);
      builder.create().show();
}else {
     Toast.makeText(MainActivity.this, "set failed!", Toast.LENGTH_SHORT).show();
}
作者:dengpeng0419 发表于2016/10/22 13:06:55 原文链接
阅读:76 评论:0 查看评论

monkey测试环境搭建

$
0
0
注意:要将360关闭(占用端口)
一、JAVA环境的搭建
1.安装jdk-7u60-windows-x64(JAVA1.7.0,也可安装最新版的JAVA1.8.0),默认安装路径C盘;
2.JAVA环境变量的搭建:
我的电脑→右键属性→ →环境变量
 
点击新建

 


 

例如:


 

在系统变量里需找Path,点击编辑


 

添加%JAVA_HOME%\bin;后点击确定


 

添加完成后,按win+r键打开运行,输入cmd


 

在cmd输入java –version或者java


 
如果出现了JAVA的版本信息,说明环境变量设置成功
 


 



二、Android SDK工具安装

1.下载GoogleAndroidSDK_r24(最新版),安装路径可改(选择空间大的盘,之后下载的安卓工具会比较大),安装之后如下图:


 
2.安装完之后,先设置Android的环境变量,与JAVA一样

先新建ANDROID_HOME环境变量


 

在Path编辑加入%ANDROID_HOME%\tools;%ANDROID_HOME%\platform-tools;


 

3.运行


 

选择需要的安卓系统模拟器和安卓工具,点击Install下载安装


 
由于谷歌属于外国网站,运行安卓工具下载网络会特别慢,因此,找到了2种方法:
第1种:运行翻墙工具(或者使用影梭,我个人用的是影梭)
 


 
IP地址为127.0.0.1 端口为8580
在Android Sdk选择tools→options设置IP地址和端口
 


设置完就可以下载;

 第2种:下载离线安装包,下载完放在android-sdk\temp目录下,电脑断开网络,选择对应的安卓系统和安卓工具,离线安装:


 




 
三、Eclipse添加安卓项目(可不安装)
1.下载解压eclipse-jee-luna-SR2-win32-x86_64,打开eclipse
 
下载ADT离线包,help→install new software
 
点击ADD,Archive选择ADT离线包的路径
 



 
添加完ADT离线包之后,在window→preferences
 
2.新建安卓项目
File→new→other→android application project
 


 
 





 


 
点击完成创建,等待片刻
 




 
四、Monkey测试命令[注意:保证手机内存充足,否则无法测试]
1.win+r输入cmd
【路径为SDK的platform-tools的安装路径输入cmd:

F:\Program Files (x86)\Android\android-sdk\platform-tools】


 

输入adb空格shell:


 
出现error:device not found,说明安卓设备没有被找到,此时可以使用手机连接电脑,手机的USB模式必须打开,电脑上必须安装有手机的驱动,连上设备之后,我们在输入adb shell命令
 
上图就是能执行操作的命令,此时我们可以执行monkey命令:
Monkey –p com.qq –v 1000
此命令意思为执行1000次随机用户模拟操作,com.jianke.doctor为安装包的名字,例如
Monkey –p com.jianke.doctor –v 1000
 


 


导出日志:monkey -p com.junte -v 100 > /mnt/sdcard/monkey_test.txt


 

作者:Richard_Jason 发表于2016/10/22 13:28:18 原文链接
阅读:76 评论:0 查看评论

装饰器模式(从放弃到入门)

$
0
0

装饰器模式(从放弃到入门)

@(设计模式)

前面介绍了两篇设计模式,策略模式和观察者模式,其实自己也是在学习阶段,感觉收益很大。所以想继续分享,把java中的23中设计模式都总结一遍,在以后才能在实践中灵活运用。感兴趣的童鞋可以看看前面分享的两篇:
策略模式
观察者模式

前面两篇都是上来就是例子,需求,我想改变一下套路,今天先介绍装饰器的理论结构,再说例子。还是要再声明:例子来自于《HeadFirst 设计模式》,推荐大家看看原书,写得很浅显易懂。

理论知识

这里写图片描述

实际问题

今天的例子是一个 coffe 的例子,相信大家都去星巴克喝过咖啡,或者奶茶,我们在买coffe的时候,首先选择一个基本的coffe类型,比如 卡布奇洛,然后添加各种佐料:摩卡,豆浆,蒸奶等。基本类型是基础价,佐料又要宁外算钱。(真是聪明)

现在当前的系统设计是:
这里写图片描述

本来想自己画UML图,发现自己画也一样,还损失了书上一些重要信息,所以直接盗图好了,大家不要介意。
Beverage: 饮料,抽象类,getDescription() 返回描述(类型,佐料…),cost() 抽象方法,每种饮料话费不一样,所以子类自己去实现。
然后派生了4中饮料的子类: HoseBlend, DarkRoast, Decaf, Espresso(尼玛,我都没喝过),分别实现 cost方法,返回价格。

代码很简单,这里就不贴了,然后这个时候,如果饮料有100种,呵呵,这种设计类图就是这样:
这里写图片描述

类爆炸!这种设计,明显重用率太低,好吧,换种思路,我们把所有的佐料的放到公共父类中,让所有子类都拥有所有的佐料,只是在类中判断到底加没加,例如这样:

这里写图片描述

代码:

Beverage .java

public abstract class Beverage {
    protected String description;

    protected boolean milk;
    protected boolean soy;
    protected boolean mocha;
    protected boolean whip;

    public Beverage(){
        this.description = "unknown beverage";
    }

    public String getDescription() {
        return description;
    }

    public abstract double cost();
}

HoseBlend.java

public class HoseBlend extends Beverage {

    public HoseBlend() {
        description = "HoseBlend";

        milk = true;
        soy = false;
        mocha = false;
        whip = false;
    }

    public double cost() {
        double money = 10.0;
        if (milk)
            money += 2.0;
        if (soy)
            money += 3.0;
        if (mocha)
            money += 3.0;
        if (whip)
            money += 2.0;
        return money;
    }
}

使用:

Beverage hoseBlend = new HoseBlend();
System.out.println(hoseBlend.getDescription());
System.out.println(hoseBlend.cost());

其他类省略了,当然这里写得不规范,每种佐料的价格应该写成常量,这里直接用了数字,这里不是重点。这样的类设计出来,我们可以用一段话来描述:一个抽象的Beverage类,里面包含了很多佐料,子类决定是否添加这些佐料,并且计算初始价格和佐料价格。

与前一种不同的是,这种更加规范,父类决定了所有的佐料和价格,只是你填不填加,自己决定。突然想到了一种更好的方法,为何不让Beverage去计算价格呢?

Beverage.java

public abstract class Beverage {
    protected String description;
    protected double money;

    public Beverage(){
        this.description = "unknown beverage";
        this.money = 10.0;
    }

    public String getDescription() {
        return description;
    }

    public double cost(){
        return money;
    }

    public void addMilk() {
        this.money += 2.0;
    }

    public void addSoy(){
        this.money += 3.0;
    }

    public void addMocha(){
        this.money += 3.0;
    }

    public void addWhip(){
        this.money += 2.0;
    }
}

添加4中 addXXX() 方法,然后计算money,将计算价格留给父类,子类只需要添加佐料:

HoseBlend .java

public class HoseBlend extends Beverage {
    public HoseBlend() {
        description = "HoseBlend";
        addMilk();
    }
}

感觉这样写重用可以更好,并且子类工作也更少了。呵呵,书上没写,自己瞎想的。但是上面的这种设计,当遇到添加一种佐料时,都必须修改Beverate类,在设计模式原则中有一个非常重要的原则,就是开闭原则:对扩展开放,对修改关闭。上面的几种设计都存在一定的问题。我们来看看今天的主角,装饰器模式,怎么来完成。

装饰器模式

先来看一张形象的图:

这里写图片描述

最里层的 DarkRoast 是饮料类型外面添加一层 Mocha, 再外层添加 Whip。最终cost() 就从最外层,一直调用到最里层,累加得到价格,呵呵,是不是有点像递归,对多态的递归,看看类结构:

这里写图片描述

当然这是在最初,我们类爆炸那个例子中的结构扩展的:
Beverage依然是那个抽象类,依然有4种饮料继承与它。不同的是,多了一个 CondimentDecorator,继承于 Beverage,然后4种佐料都继承与 CondimentDecorator,并都包含一个 beverage 的引用,表示自己装饰的对象。

好了,看看代码:

Beverage .java 还是长这样

public abstract class Beverage {
    protected String description;

    public Beverage(){
        this.description = "unknown beverage";
    }

    public String getDescription() {
        return description;
    }

    public abstract double cost();
}

HouseBlend .java 第一种饮料,最低10.0元

public class HouseBlend extends Beverage {

    public HouseBlend(){
        description = "HoseBlend";
    }

    public double cost() {
        return 10.0;
    }
}

DarkRoast.java 第二种饮料,最低12.0元

public class DarkRoast extends Beverage {

    public DarkRoast(){
        description = "DarkRoast";
    }

    public double cost() {
        return 12.0;
    }
}

CondimentDecorator .java 让子类都重写getDescription() 方法

public abstract class CondimentDecorator extends Beverage {
    public abstract String getDescription();
}

Milk.java : 牛奶,每加一份2.0 元

public class Milk extends CondimentDecorator {

    Beverage beverage;

    public Milk(Beverage beverage) {
        this.beverage = beverage;
    }

    public String getDescription() {
        return this.beverage.getDescription() + "," + "Milk";
    }

    public double cost() {
        return this.beverage.cost() + 2.0;
    }
}

Mocha.java 摩卡,每份3.0元

public class Mocha extends CondimentDecorator {

    Beverage beverage;

    public Mocha(Beverage beverage) {
        this.beverage = beverage;
    }

    public String getDescription() {
        return this.beverage.getDescription() + "," + "Mocha";
    }

    public double cost() {
        return this.beverage.cost() + 3.0;
    }
}

Soy.java 酱油,每份3.0元

public class Soy extends CondimentDecorator {

    Beverage beverage;

    public Soy(Beverage beverage) {
        this.beverage = beverage;
    }

    public String getDescription() {
        return this.beverage.getDescription() + "," + "Soy";
    }

    public double cost() {
        return this.beverage.cost() + 3.0;
    }
}

好了,看看我们怎么调用:

public class Main {
    public static void main(String[] args) {
        Beverage b1 = new HouseBlend();
        System.out.println(b1.getDescription() + " $" + b1.cost());

        Beverage b2 = new DarkRoast();
        b2 = new Mocha(b2);
        b2 = new Soy(b2);
        b2 = new Milk(b2);
        System.out.println(b2.getDescription() + " $" + b2.cost());
    }
}

输出:

HoseBlend $10.0
DarkRoast,Mocha,Soy,Milk $20.0

总结

  1. 解耦合了吧,饮料和佐料分开,想怎么加怎么加,如果要添加新饮料或者佐料,只需继续添加,而无需修改以前的结构。这就是对扩展开放,对修改关闭
  2. 我前面提到了一句话:递归多态,哈哈哈,自己瞎编的!为什么会出现这种结果,如果你对多态熟悉的话,就很好理解了,看上面代码的 b2:
    • 为什么 Milk 可以赋值给 Beverage , 因为 Milk 的父类继承于 Beverage
    • b2调用 cost() 是调用 Milk 的 cost() = 2.0+ this.beverage.cost(), this.beverage指向的是 上一个b2,及 Soy, Soy调用cost(), 及调用 Mocha.cost() + 3.0 … , 知道最后调用 beverage 的 cost() , 是不是很像递归。

再一句话概括装饰者模式吧:

装饰者模式,就是装饰者(Docorator)需要继承与被装饰者(Component),并且持有被装饰者的引用(Component),从而可以通过复写,在原来 Component 的基础上对方法做一定的修饰。

作者:u013647382 发表于2016/10/22 13:45:04 原文链接
阅读:28 评论:0 查看评论

RxJava系列一

$
0
0
  • 1.简介
    • 1)RxJava它就是一个实现异步操作的库:a library for composing asynchronous and event-based programs using observable sequences for the Java VM
    • 2)Android 创造的 AsyncTask 和Handler ,其实都是为了让异步代码更加简洁,与众不同之处在于,随着程序逻辑变得越来越复杂,它依然能够保持简洁,这里的简洁是逻辑上的简洁而不是代码量的减少。
    • 3)优点:流式接口,典型的构建者模式,是一条从上到下的链式调用,没有任何嵌套,需求越是复杂越是明显

Demo:界面上有一个自定义的视图 imageCollectorView ,它的作用是显示多张图片,并能使用 addImage(Bitmap) 方法来任意增加显示的图片。现在需要程序将一个给出的目录数组 File[] folders 中每个目录下的 png 图片都加载出来并显示在 imageCollectorView 中。需要注意的是,由于读取图片的这一过程较为耗时,需要放在后台执行,而图片的显示则必须在 UI 线程执行。常用的实现方式有多种,我这里贴出其中一种:


自动 Lambda 化的预览会让你更加清晰地看到程序主逻辑,但不建议学习 Retrolambda

  • 2.设计原理——扩展的观察者模式

    • 1)控件需要先setOnClickListener注册register/订阅Subscribe,订阅之后用户点击按钮的瞬间,Android Framework 就会将点击事件发送给已经注册的 OnClickListener 。采取这样被动的观察方式,既省去了反复检索状态的资源消耗,也能够得到最高的反馈速度。观察者观察数据源的变化,当然是先订阅报纸,当报纸中心有新报纸出现时,会把这个变化的消息通知给注册订阅付款者,和现实有点不同的是,可观察者数据源是主动推送事件给观察者,这里的观察者还不是现实生活中主动观察,它其实只需要仅仅先登个记,登个记后就不需要担心数据变化来不了通知,后续也无需它操心,总之就是万事大吉了,把耦合已经降低到了最低。
    • 2)其扩展性在于其数据源可观察者下发给观察者/订报者/注册用户 事件流的类型更细化了。

  • 3.具体实现

    • 1)需要客户端/观察者实现具体的事件被派下来的业务逻辑。Observer 接口的实现的两种方式。
      方式一:

      方式二:内置了一个实现了 Observer 的抽象类:Subscriber。 Subscriber 对 Observer 接口进行了一些扩展

      两种方式的区别:
    • 1.1)onStart(): 这是 Subscriber 增加的方法。它会在 subscribe 刚开始,而事件还未发送之前被调用,可以用于做一些准备工作,例如数据的清零或重置。这是一个可选方法,默认情况下它的实现为空。需要注意的是,如果对准备工作的线程有要求(例如弹出一个显示进度的对话框,这必须在主线程执行), onStart() 就不适用了,因为它总是在 subscribe 所发生的线程被调用,而不能指定线程。要在指定的线程来做准备工作,可以使用 doOnSubscribe() 方法,具体可以在后面的文中看到。

    • 2.1)unsubscribe(): 这是 Subscriber 所实现的另一个接口 Subscription 的方法,用于取消订阅。在这个方法被调用后,Subscriber 将不再接收事件。一般在这个方法调用前,可以使用 isUnsubscribed() 先判断一下状态。 unsubscribe() 这个方法很重要,因为在 subscribe() 之后, Observable 会持有 Subscriber 的引用,这个引用如果不能及时被释放,将有内存泄露的风险。所以最好保持一个原则:要在不再使用的时候尽快在合适的地方(例如 onPause() onStop() 等方法中)调用 unsubscribe() 来解除引用关系,以避免内存泄露的发生。

  • 2)创建数据源被观察者Observable,它决定什么时候触发事件以及触发怎样的事件。 RxJava 使用 create() 方法来创建一个 Observable ,并为它定义事件触发规则,OnSubscribe 对象作为参数,会被存储在返回的 Observable 对象中,它的作用相当于一个计划表,并接管了framworks的行为,它会主动调用注册者的回调方法,当 Observable 被订阅的时候,OnSubscribe 的 call() 方法会自动被调用,事件序列就会依照设定的顺序依次触发:
    方式一:

    注意:网络请求的结果在请求返回之前是未知的,内容待定,所有事件在一瞬间被全部发送出去,而不是夹杂一些确定或不确定的时间间隔或者经过某种触发器来触发的。

方式二:创造事件序列的方法just(T…): 将传入的参数依次发送出来。


方式三:建立关联,即杂志订阅了读者,虽然API设计有点不符合思维逻辑,但是更符合流式API的设计规范。


1) 调用 Subscriber.onStart() 。这个方法在前面已经介绍过,是一个可选的准备方法。
2) 调用 Observable 中的 OnSubscribe.call(Subscriber) 。在这里,事件发送的逻辑开始运行。从这也可以看出,在 RxJava 中, Observable 并不是在创建的时候就立即开始发送事件,而是在它被订阅的时候,即当 subscribe() 方法执行的时候。
3) 将传入的 Subscriber 作为 Subscription 返回。这是为了方便 unsubscribe().其实还是由onSubscribe来call的

方式四:个性化定制,支持不完整定义的回调

注意: Action0 是 RxJava 的一个接口,它只有一个方法 call(),这个方法是无参无返回值的;由于 onCompleted() 方法也是无参无返回值的,因此 Action0 可以被当成一个包装对象,将 onCompleted() 的内容打包起来将自己作为一个参数传入 subscribe() 以实现不完整定义的回调。这样其实也可以看做将 onCompleted() 方法作为参数传进了 subscribe(),相当于其他某些语言中的『闭包』。 Action1 也是一个接口,它同样只有一个方法 call(T param),这个方法也无返回值,但有一个参数;与 Action0 同理,由于 onNext(T obj) 和 onError(Throwable error) 也是单参数无返回值的,因此 Action1 可以将 onNext(obj) 和 onError(error) 打包起来传入 subscribe() 以实现不完整定义的回调。事实上,虽然 Action0 和 Action1 在 API 中使用最广泛,但 RxJava 是提供了多个 ActionX 形式的接口 (例如 Action2, Action3) 的,它们可以被用以包装不同的无返回值的方法。
http://gank.io/post/560e15be2dca930e00da1083
https://www.zhihu.com/question/35511144
http://blog.csdn.net/lzyzsd
https://github.com/lzyzsd/Awesome-RxJava
http://coderrobin.com/2015/07/17/RxAndroid初探/
https://www.zhihu.com/question/32179258
http://www.cnblogs.com/tiantianbyconan/p/4578699.html
http://blog.csdn.net/caroline_wendy/article/details/50444461
https://www.zhihu.com/question/32209660
https://www.zhihu.com/question/32177130
https://github.com/ReactiveX/RxJava
https://github.com/ReactiveX/RxAndroid

作者:fumin466566941 发表于2016/10/22 14:27:34 原文链接
阅读:27 评论:0 查看评论

xUtils的使用(一)

$
0
0

说明:

目前xUtils主要有四大模块:

* DbUtils模块:
  > * android中的orm框架,一行代码就可以进行增删改查;
  > * 支持事务,默认关闭;
  > * 可通过注解自定义表名,列名,外键,唯一性约束,NOT NULL约束,CHECK约束等(需要混淆的时候请注解表名和列名);
  > * 支持绑定外键,保存实体时外键关联实体自动保存或更新;
  > * 自动加载外键关联实体,支持延时加载;
  > * 支持链式表达查询,更直观的查询语义,参考下面的介绍或sample中的例子。

* ViewUtils模块:
  > * android中的ioc框架,完全注解方式就可以进行UI,资源和事件绑定;
  > * 新的事件绑定方式,使用混淆工具混淆后仍可正常工作;
  > * 目前支持常用的20种事件绑定,参见ViewCommonEventListener类和包com.lidroid.xutils.view.annotation.event。

* HttpUtils模块:
  > * 支持同步,异步方式的请求;
  > * 支持大文件上传,上传大文件不会oom;
  > * 支持GET,POST,PUT,MOVE,COPY,DELETE,HEAD,OPTIONS,TRACE,CONNECT请求;
  > * 下载支持301/302重定向,支持设置是否根据Content-Disposition重命名下载的文件;
  > * 返回文本内容的请求(默认只启用了GET请求)支持缓存,可设置默认过期时间和针对当前请求的过期时间。

* BitmapUtils模块:
  > * 加载bitmap的时候无需考虑bitmap加载过程中出现的oom和android容器快速滑动时候出现的图片错位等现象;
  > * 支持加载网络图片和本地图片;
  > * 内存管理使用lru算法,更好的管理bitmap内存;

  > * 可配置线程加载线程数量,缓存大小,缓存路径,加载显示动画等...

## 使用xUtils快速开发框架需要有以下权限:
```xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
## 混淆时注意事项:
 * 添加Android默认混淆配置${sdk.dir}/tools/proguard/proguard-android.txt
 * 不要混淆xUtils中的注解类型,添加混淆配置:-keep class * extends java.lang.annotation.Annotation { *; }
 * 对使用DbUtils模块持久化的实体类不要混淆,或者注解所有表和列名称@Table(name="xxx"),@Id(column="xxx"),@Column(column="xxx"),@Foreign(column="xxx",foreign="xxx");

## ViewUtils使用方法
* 完全注解方式就可以进行UI绑定和事件绑定。
* 无需findViewById和setClickListener等。
// xUtils的view注解要求必须提供id,以使代码混淆不受影响。
@ViewInject(R.id.textView)
TextView textView;
//@ViewInject(vale=R.id.textView, parentId=R.id.parentView)
//TextView textView;
@ResInject(id = R.string.label, type = ResType.String)
private String label;
// 取消了之前使用方法名绑定事件的方式,使用id绑定不受混淆影响
// 支持绑定多个id @OnClick({R.id.id1, R.id.id2, R.id.id3})
// or @OnClick(value={R.id.id1, R.id.id2, R.id.id3}, parentId={R.id.pid1, R.id.pid2, R.id.pid3})
// 更多事件支持参见ViewCommonEventListener类和包com.lidroid.xutils.view.annotation.event。
@OnClick(R.id.test_button)
public void testButtonClick(View v) { // 方法签名必须和接口中的要求一致
    ...
}
...
//在Activity中注入:
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    ViewUtils.inject(this); //注入view和事件
    ...
    textView.setText("some text...");
    ...
}
//在Fragment中注入:
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.bitmap_fragment, container, false); // 加载fragment布局
    ViewUtils.inject(this, view); //注入view和事件
    ...
}
//在PreferenceFragment中注入:
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    ViewUtils.inject(this, getPreferenceScreen()); //注入view和事件
    ...
}
// 其他重载
// inject(View view);
// inject(Activity activity)
// inject(PreferenceActivity preferenceActivity)
// inject(Object handler, View view)
// inject(Object handler, Activity activity)
// inject(Object handler, PreferenceGroup preferenceGroup)
// inject(Object handler, PreferenceActivity preferenceActivity)
## HttpUtils使用方法:
### 普通get方法
HttpUtils http = new HttpUtils();
http.send(HttpRequest.HttpMethod.GET,
    "http://www.lidroid.com",
    new RequestCallBack<String>(){
        @Override
        public void onLoading(long total, long current, boolean isUploading) {
            testTextView.setText(current + "/" + total);
        }
        @Override
        public void onSuccess(ResponseInfo<String> responseInfo) {
            textView.setText(responseInfo.result);
        }
        @Override
        public void onStart() {
        }
        @Override
        public void onFailure(HttpException error, String msg) {
        }
});
### 使用HttpUtils上传文件 或者 提交数据 到服务器(post方法)
RequestParams params = new RequestParams();
params.addHeader("name", "value");
params.addQueryStringParameter("name", "value");
// 只包含字符串参数时默认使用BodyParamsEntity,
// 类似于UrlEncodedFormEntity("application/x-www-form-urlencoded")。
params.addBodyParameter("name", "value");
// 加入文件参数后默认使用MultipartEntity("multipart/form-data"),
// 如需"multipart/related",xUtils中提供的MultipartEntity支持设置subType为"related"。
// 使用params.setBodyEntity(httpEntity)可设置更多类型的HttpEntity(如:
// MultipartEntity,BodyParamsEntity,FileUploadEntity,InputStreamUploadEntity,StringEntity)。
// 例如发送json参数:params.setBodyEntity(new StringEntity(jsonStr,charset));
params.addBodyParameter("file", new File("path"));
HttpUtils http = new HttpUtils();
http.send(HttpRequest.HttpMethod.POST,
    "uploadUrl....",
    params,
    new RequestCallBack<String>() {
        @Override
        public void onStart() {
            testTextView.setText("conn...");
        }
        @Override
        public void onLoading(long total, long current, boolean isUploading) {
            if (isUploading) {
                testTextView.setText("upload: " + current + "/" + total);
            } else {
                testTextView.setText("reply: " + current + "/" + total);
            }
        }
        @Override
        public void onSuccess(ResponseInfo<String> responseInfo) {
            testTextView.setText("reply: " + responseInfo.result);
        }
        @Override
        public void onFailure(HttpException error, String msg) {
            testTextView.setText(error.getExceptionCode() + ":" + msg);
        }
});
----
### 使用HttpUtils下载文件:
* 支持断点续传,随时停止下载任务,开始任务
```java
HttpUtils http = new HttpUtils();
HttpHandler handler = http.download("http://apache.dataguru.cn/httpcomponents/httpclient/source/httpcomponents-client-4.2.5-src.zip",
    "/sdcard/httpcomponents-client-4.2.5-src.zip",
    true, // 如果目标文件存在,接着未完成的部分继续下载。服务器不支持RANGE时将从新下载。
    true, // 如果从请求返回信息中获取到文件名,下载完成后自动重命名。
    new RequestCallBack<File>() {
        @Override
        public void onStart() {
            testTextView.setText("conn...");
        }
        @Override
        public void onLoading(long total, long current, boolean isUploading) {
            testTextView.setText(current + "/" + total);
        }
        @Override
        public void onSuccess(ResponseInfo<File> responseInfo) {
            testTextView.setText("downloaded:" + responseInfo.result.getPath());
        }
        @Override
        public void onFailure(HttpException error, String msg) {
            testTextView.setText(msg);
        }
});
.
//调用cancel()方法停止下载
handler.cancel();

使用注解实例:

**注解功能
 * @ContentView(value = R.layout.xxxx)  完成了activity和xml文件的关联
 * @ViewInject(value = R.id.btn_get)   作用是找到控件的id,但是要求必须在变量声明的上一行写
 * @Event(value = {R.id.btn_get,R.id.btn_post,R.id.btn_download},type = View.OnClickListener.class)
    可以写任何事件的监听
 *  */
@ContentView(value = R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
    @ViewInject(value = R.id.btn_get)
    private Button getBtn;
    @ViewInject(value = R.id.btn_post)
    private Button postBtn;
    @ViewInject(value = R.id.btn_download)
    private Button downloadBtn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //声明使用了注解反射
        x.view().inject(this);
    }
    /**
     *1. 方法必须私有限定,
     * 2. 方法参数形式必须和type对应的Listener接口一致.
     * 3. 注解参数value支持数组: value={id1, id2, id3}
     * 4. 其它参数说明见Event类的说明.
     * */
    @Event(value = {R.id.btn_reflection,R.id.btn_get,R.id.btn_post,R.id.btn_download,R.id.btn_img,R.id.btn_database},
            type = View.OnClickListener.class)
    private void onClick(View view){
        Intent intent = new Intent();
        switch (view.getId()) {
            case R.id.btn_reflection:
                intent.setClass(this,AnnotationActivity.class);
                break;
            case R.id.btn_get:
                intent.setClass(this,GetDataActivity.class);
                break;
            case R.id.btn_post:
                intent.setClass(this,PostDataActivity.class);
                break;
            case R.id.btn_download:
                intent.setClass(this,DownloadFileActivity.class);
                break;

            case R.id.btn_img:
                intent.setClass(this,ImageActivity.class);
                break;

            case R.id.btn_database:
                intent.setClass(this,DatabaseActivity.class);
                break;
        }
        startActivity(intent);
    }

}

使用get方式获取数据:

@ContentView(value = R.layout.activity_get_data)
public class GetDataActivity extends AppCompatActivity {

    @ViewInject(value = R.id.id_get_tv)
    private TextView tv;
    private String url = "http://218.244.149.129:9010/api/industry.php";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        x.view().inject(this);

    }
    @Event(value = R.id.id_get_btn)
    private void initData(View view){

        //创建请求参数
        RequestParams params = new RequestParams(url);
//        params.addQueryStringParameter("","");

        //执行网络请求操作
        Callback.Cancelable cancelable = x.http().get(params, new Callback.CommonCallback<String>() {
            //成功时回调的方法
            @Override
            public void onSuccess(String result) {
                tv.setText(result);
            }
            //失败时回调的方法
            @Override
            public void onError(Throwable ex, boolean isOnCallback) {

            }
            //取消时回调的方法
            @Override
            public void onCancelled(CancelledException cex) {

            }
            //完成时回调的方法
            @Override
            public void onFinished() {

            }
        });
        //取消网络请求
//        cancelable.cancel();
    }
}
使用post方式获取数据:

@ContentView(value = R.layout.activity_get_data)
public class PostDataActivity extends AppCompatActivity {

    @ViewInject(value = R.id.id_get_tv)
    private TextView tv;
    private String url = "http://218.244.149.129:9010/api/companylist.php";
    //?industryid=行业ID
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        x.view().inject(this);
    }
    @Event(value = R.id.id_get_btn,type = View.OnClickListener.class)
    private void onClick(View v){

        Map<String ,String>map = new HashMap<>();
        map.put("industryid","98");
        initData(url,map);
    }
    public void initData(String url, Map<String,String>map){
        RequestParams params = new RequestParams(url);
      Set<Map.Entry<String,String>> entry = map.entrySet();
        Iterator<Map.Entry<String,String>>it = entry.iterator();
        while (it.hasNext()){
            Map.Entry<String,String>en = it.next();
            params.addParameter(en.getKey(),en.getValue());
        }
        params.setMultipart(true);
        x.http().post(params, new Callback.CommonCallback<String>() {
            @Override
            public void onSuccess(String result) {
                tv.setText(result);
            }

            @Override
            public void onError(Throwable ex, boolean isOnCallback) {

            }

            @Override
            public void onCancelled(CancelledException cex) {

            }

            @Override
            public void onFinished() {

            }
        });
    }
}

下载文件(安装包)的实例:

public class DownloadFileActivity extends AppCompatActivity {

    private String apkUrl = "http://218.244.149.129:9010/download.php?apkid=12";
    private String fileName = "hello.apk";
    @ViewInject(value = R.id.id_download_bar)
    private ProgressBar bar;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_download_file);
        x.view().inject(this);
    }
    @Event(value = R.id.id_download_btn)
    private void download(View view){
        String filePath = getExternalCacheDir().getAbsolutePath()+ File.separator+fileName;
        RequestParams params = new RequestParams(apkUrl);
        params.setSaveFilePath(filePath);

        x.http().get(params, new Callback.ProgressCallback<File>() {
            @Override
            public void onWaiting() {
            }
            @Override
            public void onStarted() {
            }
            @Override
            public void onLoading(long total, long current, boolean isDownloading) {
                bar.setProgress((int)(current*100/total));
            }
            @Override
            public void onSuccess(File result) {
                Intent intent = new Intent();
                intent.setAction(Intent.ACTION_VIEW);
                intent.setDataAndType(Uri.fromFile(result), "application/vnd.android.package-archive");
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                DownloadFileActivity.this.startActivity(intent);

            }
            @Override
            public void onError(Throwable ex, boolean isOnCallback) {

            }
            @Override
            public void onCancelled(CancelledException cex) {

            }
            @Override
            public void onFinished() {

            }
        });

    }
}




作者:qq_29882585 发表于2016/10/22 17:42:45 原文链接
阅读:112 评论:0 查看评论

第5课 Android四大组件之一——Activity知识汇总

$
0
0


一. Activity的定义及作用


官方定义:Activity是Android应用程序提供交互界面的一个重要组件。 也是Android最重要的组件之一


Activity是业务类 , 是承载应用程序的界面以及业务行为的基础 。 

包括UI , Service ...... 类似于我们的JavaBean

说Activity就不得不说View和Window

 

二. 创建一个 Activity

在 android 中创建一个 Activity 是很简单的事情,编写一个继承自 android.app.Activity的 Java 类并在 AndroidManifest.xml声明即可。下面是一个为了研究 Activity 生命周期的一个 Activity 实例(工程源码见下载):

Activity 文件:

 public class EX01 extends Activity { 
    private static final String LOG_TAG = EX01.class.getSimpleName(); 
    @Override 
    public void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState);      
        setContentView(R.layout.main); 
        Log.e(LOG_TAG, "onCreate"); 
    } 
   @Override 
    protected void onStart() { 
        Log.e(LOG_TAG, "onStart"); 
        super.onStart(); 
    } 
    @Override 
    protected void onResume() { 
        Log.e(LOG_TAG, "onResume"); 
        super.onResume(); 
    } 
    @Override 
    protected void onPause() { 
        Log.e(LOG_TAG, "onPause"); 
        super.onPause(); 
    } 
    @Override 
    protected void onStop() { 
        Log.e(LOG_TAG, "onStop"); 
        super.onStop(); 
    } 
    @Override 
    protected void onDestroy() { 
        Log.e(LOG_TAG, "onDestroy "); 
        super.onDestroy(); 
    } 
 }

AndroidManifest.xml 中通过 <activity> 节点说明 Activity,将 apk 文件安装后,系统根据这里的说明来查找读取 Activity,本例中的说明如下:

 <activity android:name=".EX01" android:label="@string/app_name"> 
	 <intent-filter> 
		 <action android:name="android.intent.action.MAIN" /> 
		 <category android:name="android.intent.category.LAUNCHER" /> 
	 </intent-filter> 
 </activity>

启动另外一个 Activity

Activity.startActivity()方法可以根据传入的参数启动另外一个 Activity:

 Intent intent =new Intent(CurrentActivity.this,OtherActivity.class); 
 startActivity(intent);

当然,OtherActivity同样需要在 AndroidManifest.xml 中定义。

结束一个Activity      

 finish()

 finishActivity()


创建Activity步骤

1.创建Activity及相关视图文件Layout(View)

2.配置AndroidManifest.xml

    后面我们会详细扩展<activity>属性含义

3.重载onCreate(), 绑定Activity和Layout(View)

    什么是View?

    研究setContentView()

    Activity , Window , View的关系

    如何用inflater来实现界面加载

4.为View(Layout)添加必要组件

        如何动态编码来控制界面

            建立界面控件树的概念

            findViewById()

            addView()

5.在onCreate()中实现初始业务逻辑

        加入事件比如按钮事件

            扩展:Java匿名内部类

            后面会详细讲事件机制


三. Activity 之间通信

使用 Intent 通信

那么既然叫做意图,就类似于一个男孩儿为了追一个女孩儿,传递纸条给她 ,向她要电话 ,女孩儿把电话写在纸条中传递给男孩儿。(当然,现代社会都用微信了,最起码都是短信了)

如此,我们认为Intent就是在不同组件之间传递值而设计的一个数据结构 

intent :

extras  - 加入附加信息

category - IntentFilter 

Action - 动作 : Data - 动作相关的值 

ComponentName –Context


在 Android 中,不同的 Activity 实例可能运行在一个进程中,也可能运行在不同的进程中。因此我们需要一种特别的机制帮助我们在 Activity 之间传递消息。Android 中通过 Intent 对象来表示一条消息,一个 Intent 对象不仅包含有这个消息的目的地,还可以包含消息的内容,这好比一封 Email,其中不仅应该包含收件地址,还可以包含具体的内容。对于一个 Intent 对象,消息“目的地”是必须的,而内容则是可选项。

在上面的实例中通过 Activity. startActivity(intent)启动另外一个 Activity 的时候,我们在 Intent 类的构造器中指定了“收件人地址”。

如果我们想要给“收件人”Activity 说点什么的话,那么可以通过下面这封“e-mail”来将我们消息传递出去:

 Intent intent =new Intent(CurrentActivity.this,OtherActivity.class);
  // 创建一个带“收件人地址”的 email 
 Bundle bundle =new Bundle();// 创建 email 内容
 bundle.putBoolean("boolean_key", true);// 编写内容
 bundle.putString("string_key", "string_value"); 
 intent.putExtra("key", bundle);// 封装 email 
 startActivity(intent);// 启动新的 Activity

那么“收件人”该如何收信呢?在 OtherActivity类的 onCreate()或者其它任何地方使用下面的代码就可以打开这封“e-mail”阅读其中的信息:

 Intent intent =getIntent();// 收取 email 
 Bundle bundle =intent.getBundleExtra("key");// 打开 email 
 bundle.getBoolean("boolean_key");// 读取内容
 bundle.getString("string_key");

上面我们通过 bundle对象来传递信息,bundle维护了一个 HashMap<String, Object>对象,将我们的数据存贮在这个 HashMap 中来进行传递。但是像上面这样的代码稍显复杂,因为 Intent 内部为我们准备好了一个 bundle,所以我们也可以使用这种更为简便的方法:

 Intent intent =new Intent(EX06.this,OtherActivity.class); 
 intent.putExtra("boolean_key", true); 
 intent.putExtra("string_key", "string_value"); 
 startActivity(intent);

接收:

 Intent intent=getIntent(); 
 intent.getBooleanExtra("boolean_key",false); 
 intent.getStringExtra("string_key");

使用 SharedPreferences

SharedPreferences 使用 xml 格式为 Android 应用提供一种永久的数据存贮方式。对于一个 Android 应用,它存贮在文件系统的 /data/ data/your_app_package_name/shared_prefs/目录下,可以被处在同一个应用中的所有 Activity 访问。Android 提供了相关的 API 来处理这些数据而不需要程序员直接操作这些文件或者考虑数据同步问题。

 // 写入 SharedPreferences 
 SharedPreferences preferences = getSharedPreferences("name", MODE_PRIVATE); 
 Editor editor = preferences.edit(); 
 editor.putBoolean("boolean_key", true); 
 editor.putString("string_key", "string_value"); 
 editor.commit(); 
        
 // 读取 SharedPreferences 
 SharedPreferences preferences = getSharedPreferences("name", MODE_PRIVATE); 
 preferences.getBoolean("boolean_key", false); 
 preferences.getString("string_key", "default_value");

其它方式

Android 提供了包括 SharedPreferences 在内的很多种数据存贮方式,比如 SQLite,文件等,程序员可以通过这些 API 实现 Activity 之间的数据交换。如果必要,我们还可以使用 IPC 方式。

Activity 的 Intent Filter

Intent Filter 描述了一个组件愿意接收什么样的 Intent 对象,Android 将其抽象为 android.content.IntentFilter 类。在 Android 的 AndroidManifest.xml 配置文件中可以通过 <intent-filter >节点为一个 Activity 指定其 Intent Filter,以便告诉系统该 Activity 可以响应什么类型的 Intent。

当程序员使用 startActivity(intent) 来启动另外一个 Activity 时,如果直接指定 intent 了对象的 Component 属性,那么 Activity Manager 将试图启动其 Component 属性指定的 Activity。否则 Android 将通过 Intent 的其它属性从安装在系统中的所有 Activity 中查找与之最匹配的一个启动,如果没有找到合适的 Activity,应用程序会得到一个系统抛出的异常。这个匹配的过程如下:

图 4. Activity 种 Intent Filter 的匹配过程
图 4. Activity 种 Intent Filter 的匹配过程

Action 匹配

Action 是一个用户定义的字符串,用于描述一个 Android 应用程序组件,一个 Intent Filter 可以包含多个 Action。在 AndroidManifest.xml 的 Activity 定义时可以在其 <intent-filter >节点指定一个 Action 列表用于标示 Activity 所能接受的“动作”,例如:

 <intent-filter > 
 <action android:name="android.intent.action.MAIN" /> 
 <action android:name="com.zy.myaction" /> 
……
 </intent-filter>

如果我们在启动一个 Activity 时使用这样的 Intent 对象:

 Intent intent =new Intent(); 
 intent.setAction("com.zy.myaction");

那么所有的 Action 列表中包含了“com.zy.myaction”的 Activity 都将会匹配成功。

Android 预定义了一系列的 Action 分别表示特定的系统动作。这些 Action 通过常量的方式定义在 android.content. Intent中,以“ACTION_”开头。我们可以在 Android 提供的文档中找到它们的详细说明。

URI 数据匹配

一个 Intent 可以通过 URI 携带外部数据给目标组件。在 <intent-filter >节点中,通过 <data/>节点匹配外部数据。

mimeType 属性指定携带外部数据的数据类型,scheme 指定协议,host、port、path 指定数据的位置、端口、和路径。如下:

 <data android:mimeType="mimeType" android:scheme="scheme" 
 android:host="host" android:port="port" android:path="path"/>

如果在 Intent Filter 中指定了这些属性,那么只有所有的属性都匹配成功时 URI 数据匹配才会成功。

Category 类别匹配

<intent-filter >节点中可以为组件定义一个 Category 类别列表,当 Intent 中包含这个列表的所有项目时 Category 类别匹配才会成功。



 

 


 

 

四. Activity生命周期

正常情况下的Activity生命周期

所谓正常情况下的生命周期,是指有用户参与的情况下,Activity所经过的生命周期的改变。正常情况下,Activity会经历如下过程。 
如图所示: 
Activity生命周期的切换过程

(1)onCreate:表示Activity正在被创建,适合做一些初始化工作。实际应用中一般会初始化成员变量和加载布局资源。 
(2)onRestrat:表示Activity正在被重新启动。一般是从不可见重新变为可见状态是调用。 
(3)onStart:表示Activity正在被启动,即将开始,此时已经可见,但仍旧在后台,无法与用户交互,虽可见,但是我们还看不到。 
(4)onResume:表示Activity已经可见了。此时Activity显示到前台。 
(5)onPause:表示Activity正在停止,此时可以做一些存储数据、停止动画等操作,但不宜太耗时。因为此方法执行完,新的Activity的onResume才会执行。 
(6)onStop:表示Activity即将停止,此时可以做一些回收工作,同样不能太耗时。 
(7)onDestroy:表示Activity即将被销毁,此时可以做一些资源释放。

需要注意的是,如果新的Activity采用了透明主题,当前Activity便不会回调onStop。一般情况下是按照图中的顺序来的。onStart和onStop是从Activity可见与否这个角度来配对的,onResume和onPause是从Activity是否位于前台这个角度来配对的。


2. 异常情况下的Activity生命周期

所谓异常情况下的生命周期,是指Activity被系统回收或者当前设备Configuration改变导致的Activity被销毁重建。

一种典型的触发条件是横竖屏时,手机会拿到两张不同的图片,此时Activity就会被销毁并且重建。异常销毁时,onPause、onStop、onDestroy均会被调用,在onStop之前,系统会调用onSaveInstanceState来保存当前Activity的状态(Activity会委托Window,Window再委托给顶层容器DecorView去保存数据,最后顶层容器再一一通知其子View来保存数据)。当重建时,系统会在onStart之后调用onRestoreInstanceState,销毁时onSaveInstanceState所保存的Bundle对象作为参数传给onRestoreInstanceState和onCreate,用于取出数据并恢复(Google建议我们采用前者去恢复数据)。

当然是有方法去阻止系统去重建Activity的,我们可以为Activity指定configChanges属性: 
(1)比如我们不想在屏幕旋转时重建Activity,那么就可以指定Android:configChanges=”orientation”。 
(2)其中用的比较多的另两个属性为locale、keyboardHidden。前者为设备的本地位置发生了改变,一般指切换了系统语言。后者一般指用户调出了键盘。 
(3)screenSize属性和smallestScreenSize属性比较特殊,他们是API13时添加的。分别表示的情况为屏幕尺寸发生变化和切换到外部显示设备时。若android:configChanges=”orientation|screenSize”,那么在min以及target均低于13时,不会导致重启,否则导致Activity重启。在不重建时,系统没有调用onSaveInstanceState以及onRestoreInstanceState方法,而是调用了onConfigurationChanged方法。 
(4)Android4.2增加了一个layoutDirection属性,当改变语言设置后,该属性也会成newConfig中的一个mask位。所以ActivityManagerService(实际在ActivityStack)在决定是否重启Activity的时候总是判断为重启。需要在android:configChanges 中同时添加locale和layoutDirection。在不退出应用的情况下切换到Settings里切换语言,发现该Activity还是重启了。

3. Android为什么要设计一个生命周期呢

Google官方文档解释说,确保提供一个流畅的用户体验,在Activity切换时,以及在导致你的Activity停止甚至是销毁的意外中断的情况下,保存好Activity状态。

1.在最前台的Activity处于Resumed状态,可见,且获得焦点。你也可能正在编辑信息,这个时候跳出来一个透明提示框,这个时候你当前的Activity就进入了Paused状态,你想再次回到这个Activity时看到你编辑到一半的信息,这个时候,你就需要在onPause这个回调方法中,来执行这些操作。

2.当你按HOME键退出一个应用,或者从一个应用进入了另一个应用,这个时候之前那个Activity就变得完全不可见了,进入了Stopped状态,那么它就应该把它大多数的资源都释放出来了,因为用户看不见它,它也就不需要维持了。所以这个时候,你就需要在onStop回调方法里面来执行这些操作。

3.当你接完一个电话,再次回到之前那个Activity,它就从Stopped状态变成了Resumed状态,这个时候你肯定希望它记录住了你离开时的状态,比如说编辑了一半的信息,正停留在新闻1/3的位置。那么这个时候,你就需要在onRestart和Start回调方法里面来执行这些操作,以使它恢复这些状态。

所以综上所述,之所以会设计出不同的生命周期状态,以及各状态间转换时的回调方法,就是为了适应用户使用过程中的不同场景,进而在特定的场景让Activity完成特定的事情,以此来确保提供一个流畅的用户体验

4. Activity的生命周期是由谁控制的

ActivityManagerService是负责管理Activity的生命周期的。ActivityManagerService是一个非常重要的接口,它不但负责启动Activity和Service,还负责管理Activity和Service。 

 

5. Activity 的状态及状态间的转换

在 android 中,Activity 拥有四种基本状态:

  1. Active/Runing一个新 Activity 启动入栈后,它在屏幕最前端,处于栈的最顶端,此时它处于可见并可和用户交互的激活状态。
  2. Paused 当 Activity 被另一个透明或者 Dialog 样式的 Activity 覆盖时的状态。此时它依然与窗口管理器保持连接,系统继续维护其内部状态,所以它仍然可见,但它已经失去了焦点故不可与用户交互。
  3. Stoped 当 Activity 被另外一个 Activity 覆盖、失去焦点并不可见时处于 Stoped状态。
  4. Killed Activity 被系统杀死回收或者没有被启动时处于 Killed状态。

当一个 Activity 实例被创建、销毁或者启动另外一个 Activity 时,它在这四种状态之间进行转换,这种转换的发生依赖于用户程序的动作。下图说明了 Activity 在不同状态间转换的时机和条件:

图 1. Activity 的状态转换
图 1. Activity 的状态转换

如上所示,Android 程序员可以决定一个 Activity 的“生”,但不能决定它的“死”,也就时说程序员可以启动一个 Activity,但是却不能手动的“结束”一个 Activity。当你调用 Activity.finish()方法时,结果和用户按下 BACK 键一样:告诉 Activity Manager 该 Activity 实例完成了相应的工作,可以被“回收”。随后 Activity Manager 激活处于栈第二层的 Activity 并重新入栈,同时原 Activity 被压入到栈的第二层,从 Active 状态转到 Paused 状态。例如:从 Activity1 中启动了 Activity2,则当前处于栈顶端的是 Activity2,第二层是 Activity1,当我们调用 Activity2.finish()方法时,Activity Manager 重新激活 Activity1 并入栈,Activity2 从 Active 状态转换 Stoped 状态,Activity1. onActivityResult(int requestCode, int resultCode, Intent data)方法被执行,Activity2 返回的数据通过 data参数返回给 Activity1。

 

6.存储Activity状态

       onSaveInstanceState()

       onRestoreInstanceState(), onCreate()

              如何选择用哪个函数?


7.处理设置改变Configuration changes

       设置改变的时候恢复一个大对象

       自己处理配置改变

 

 

五.Activity的任务栈

Activity 栈

Android 是通过一种 Activity 栈的方式来管理 Activity 的,一个 Activity 的实例的状态决定它在栈中的位置。处于前台的 Activity 总是在栈的顶端,当前台的 Activity 因为异常或其它原因被销毁时,处于栈第二层的 Activity 将被激活,上浮到栈顶。当新的 Activity 启动入栈时,原 Activity 会被压入到栈的第二层。一个 Activity 在栈中的位置变化反映了它在不同状态间的转换。Activity 的状态与它在栈中的位置关系如下图所示:

图 2. Activity 的状态与它在栈中的位置关系
图 2. Activity 的状态与它在栈中的位置关系

如上所示,除了最顶层即处在 Active 状态的 Activity 外,其它的 Activity 都有可能在系统内存不足时被回收,一个 Activity 的实例越是处在栈的底层,它被系统回收的可能性越大。系统负责管理栈中 Activity 的实例,它根据 Activity 所处的状态来改变其在栈中的位置。


1. Activity的启动模式

1.1 Standard标准模式

系统默认的启动模式,即便实例存在,每次启动都会创建一个新的实例,每个实例可以属于不同的任务栈。

若ActivityA以此模式启动了Activity B,那么B会进入A所在的栈。注意,若是非Activity类型的Context,如ApplicationContext,并没有任务栈,因此以ApplicationContext启动Standard模式的Activity会报错。我们可以在启动时创建一个新的任务栈,指定FLAG_ACTIVITY_NEW_TASK标记位。(此时实际上是以SingleTask模式启动)。标记为下文会讲述。


1.2 SingleTop栈顶复用模式

这种模式下,若新的Activity已位于栈顶,就不会重复创建。不同于Standard模式,此时这个Activity因为没有发生变化,它的onCreateonStart不会被调用

但是它的onNewIntent方法会被调用,通过此方法的参数可以得到请求信息。


1.3 SingleTask栈内复用模式

这种模式下,Activity想要的任务栈如果存在,并且此Activity在此栈中存在实例,多次启动此Activity都不会重新创建实例。同时该模式具有clearTop的效果,已存在的实例上面的Activity全部出栈onNewIntent方法会被调用。若不存在该实例就新建并压入该栈。

如果想要的任务栈不存在,就新建一个任务栈,并创建Activity实例放入该栈。


1.4 SingleInstance单实例模式

具有SingleTask模式的所有特性,此模式启动的Activity只能单独位于一个任务栈(新建的)。后续请求除非这个特殊的任务栈被销毁,否则不会创建新的Activity实例。

如SingleTask模式一样,如果按照相同的模式再次某Activity,不重新创建,只是暂停onStop了下,并且调用onNewIntent方法。接着调用onResume就又继续了

 

2. TaskAffinity属性

TaskAffinity属性标识了一个Activity所需要的任务栈的名字。默认为应用包名。

当TaskAffinity属性和SingleTask启动模式结合使用时,待启动的Activity会运行在名字TaskAffinity相同的任务栈中。

当TaskAffinity属性和allowTaskReparenting结合使用时,若应用A启动应用B中的Activity C(C运行在A的任务栈中),并且此Activity的allowTaskReparenting = true,当应用B被启动后,B的主Activity不会显示,因为Activity C会直接从应用A的任务栈转移到应用B的任务栈中(因为C想要的任务栈被创建了),所以会显示Activity C。

 

3. 指定启动模式

3.1 通过AndroidMenifest.xmlActivity指定

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. android:launchMode = "singleTask"  

3.2  通过Intent标识为Activity指定

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. Intent intent = new Intent();  
  2. intent.setClass(MainActivity.class,SecondActivity.class);  
  3. intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);  
  4. startActivity(intent);  

两种设置方式,第一种优先级较低,并且无法直接给Activity设置FLAG_ACTIVITY_CLEAR_TOP标识。

第二种无法为Activity指定singleInstance模式。


4. ActivityFlags

大部分情况我们不需要为Activity设置标记位,下面介绍几种比较常用的标记位。

 

4. 1 FLAG_ACTIVITY_NEW_TASK

这种Activity标记位,作用是为Activity指定SingleTask的启动模式。和在清单文件里设置效果相同。

 

4. 2 FLAG_ACTIVITY_SINGLE_TOP

这种Activity标记位,作用是为Activity指定SingleTop的启动模式。和在清单文件里设置效果相同。

 

4. 3 FLAG_ACTIVITY_CLEAR_TOP

SingleTask模式默认具有此标记效果,即被启动Activity的实例已经存在,onNewIntent方法会被调用,已存在的实例上面的Activity全部出栈

如果是Standard启动模式,那么它连同它之上的Activity都要出栈,并创建新的Activity实例入栈

 

4. 4 FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS

效果和在清单文件里指定Android:excludeFromRecents = “true”相同。即不会出现在历史Activity列表中。

 

六. Activity Manifest配置详解

       android:allowTaskReparenting

       android:alwaysRetainTaskState

       android:clearTaskOnLaunch

       android:configChanges

       android:finishOnTaskLaunch

       android:hardwareAccelerated

       android:launchMode

              Standard

              SingleTop

              SingleTask

              SingleInstance

       android:multiprocess

       android:noHistory

       android:process

       android:stateNotNeeded

       android:screenOrientation

       android:excludeFromRecents

 



七.Android提供的专有Activity

       MapActivity

       ListActivity

       ExpandableListActivity

       TabActivity

 

八.一些关于 Activity 的技巧

锁定 Activity 运行时的屏幕方向

Android 内置了方向感应器的支持。在 G1 中,Android 会根据 G1 所处的方向自动在竖屏和横屏间切换。但是有时我们的应用程序仅能在横屏 / 竖屏时运行,比如某些游戏,此时我们需要锁定该 Activity 运行时的屏幕方向,<activity >节点的 android:screenOrientation属性可以完成该项任务,示例代码如下:

 <activity android:name=".EX01"
 android:label="@string/app_name" 
 android:screenOrientation="portrait">// 竖屏 , 值为 landscape 时为横屏
…………
 </activity>

全屏的 Activity

要使一个 Activity 全屏运行,可以在其 onCreate()方法中添加如下代码实现:

 // 设置全屏模式
 getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 
    WindowManager.LayoutParams.FLAG_FULLSCREEN); 
 // 去除标题栏
 requestWindowFeature(Window.FEATURE_NO_TITLE);

在 Activity 的 Title 中加入进度条

为了更友好的用户体验,在处理一些需要花费较长时间的任务时可以使用一个进度条来提示用户“不要着急,我们正在努力的完成你交给的任务”。如下图:

在 Activity 的标题栏中显示进度条不失为一个好办法,下面是实现代码:

 // 不明确进度条
 requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); 
 setContentView(R.layout.main); 
 setProgressBarIndeterminateVisibility(true); 

 // 明确进度条
 requestWindowFeature(Window.FEATURE_PROGRESS); 
 setContentView(R.layout.main); 
 setProgress(5000);

       

作者:jinxinliu1 发表于2016/10/22 18:26:18 原文链接
阅读:115 评论:0 查看评论

SpannableString

$
0
0

效果图



SpanUtils.java

package com.ican.subjects.spannable_string.utils;

import android.content.Context;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextPaint;
import android.text.style.ClickableSpan;
import android.text.style.ForegroundColorSpan;
import android.view.View;

import com.ican.subjects.spannable_string.bean.Topic;
import com.ican.subjects.spannable_string.bean.User;

import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Created by tuwenyuan on 2016/8/31.<br/>
 * 描述:
 * </br>
 */
public class SpanUtils{

    public static class PatternString{
        /**
         * #号括起来的话题#
         */
        public static final String TOPIC_PATTERN = "#[^#]+#";

        /**
         * 表情[大笑]
         */
        public static final String EXPRESSION_PATTERN = "\\[[^\\]]+\\]";

        /**
         * 网址
         */
        public static final String URL_PATTERN = "(([hH]ttp[s]{0,1}|ftp)://[a-zA-Z0-9\\.\\-]+\\.([a-zA-Z]{2,4})(:\\d+)?(/[a-zA-Z0-9\\.\\-~!@#$%^&*+?:_/=<>]*)?)|(www.[a-zA-Z0-9\\.\\-]+\\.([a-zA-Z]{2,4})(:\\d+)?(/[a-zA-Z0-9\\.\\-~!@#$%^&*+?:_/=<>]*)?)";

    }

    /**
     *
     * @param <T>
     */
    public interface SpanClickListener<T>{
        void onSpanClick(T t);
    }


    /**
     * 关键词变色处理
     * @param str
     * @param patterStr 需要变色的关键词 或者 正则表达式
     * @return
     */
    public static SpannableString getKeyWordSpan(int color, String str, String patterStr) throws Exception {
        SpannableString spannableString = new SpannableString(str);
        Pattern patten = Pattern.compile(patterStr, Pattern.CASE_INSENSITIVE);
        dealPattern(color, spannableString, patten, 0);
        return spannableString;
    }

    /**
     * 自动识别话题并做颜色处理,可点击
     * @param color
     * @param str
     */
    public static SpannableString getTopicSpan(int color, String str, boolean clickable, SpanClickListener spanClickListener, Topic topic) throws Exception {
        SpannableString spannableString = new SpannableString(str);
        Pattern patten = Pattern.compile(PatternString.TOPIC_PATTERN, Pattern.CASE_INSENSITIVE);
        if(clickable){
            dealClick(spannableString, patten, 0, spanClickListener, topic);
        }
        dealPattern(color, spannableString, patten, 0);
        return spannableString;
    }

    /**
     * @用户 颜色处理、点击处理
     * @param color 前景色
     * @param str
     * @param clickable 是否可点击
     * @param spanClickListener
     * @param atUsers
     * @return
     * @throws Exception
     */
    public static SpannableString getAtUserSpan(int color, String str, boolean clickable, SpanClickListener spanClickListener, List<User> atUsers) throws Exception {
        SpannableString spannableString = new SpannableString(str);
        Pattern patten;
        for (User u : atUsers) {
            patten = Pattern.compile("@" + u.getName(), Pattern.CASE_INSENSITIVE);
            if(clickable){
                dealClick(spannableString, patten, 0, spanClickListener, u);
            }
            dealPattern(color, spannableString, patten, 0);
        }
        return spannableString;
    }

    /**
     * 表情处理
     * @param context
     * @param str
     * @return
     */
    public static SpannableString getExpressionSpan(Context context, String str) throws Exception {
        return ExpressionConvertUtil.getInstace().getExpressionString(context, str);
    }


    /**
     * 对spanableString进行正则判断,如果符合要求,则将内容变色
     * @param color
     * @param spannableString
     * @param patten
     * @param start
     * @throws Exception
     */
    private static void dealPattern(int color, SpannableString spannableString, Pattern patten, int start) throws Exception {
        Matcher matcher = patten.matcher(spannableString);
        while (matcher.find()) {
            String key = matcher.group();
            // 返回第一个字符的索引的文本匹配整个正则表达式,ture 则继续递归
            if (matcher.start() < start) {
                continue;
            }
            // 计算该内容的长度,也就是要替换的字符串的长度
            int end = matcher.start() + key.length();
            //设置前景色span
            spannableString.setSpan(new ForegroundColorSpan(color), matcher.start(), end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            if (end < spannableString.length()) {
                // 如果整个字符串还未验证完,则继续。。
                dealPattern(color, spannableString, patten, end);
            }
            break;
        }
    }

    /**
     * 对spanableString进行正则判断,如果符合要求,将内容设置可点击
     * @param spannableString
     * @param patten
     * @param start
     * @param spanClickListener
     * @param bean
     */
    private static void dealClick(SpannableString spannableString, Pattern patten, int start, final SpanClickListener spanClickListener, final Object bean){
        Matcher matcher = patten.matcher(spannableString);
        while (matcher.find()) {
            String key = matcher.group();
            // 返回第一个字符的索引的文本匹配整个正则表达式,ture 则继续递归
            if (matcher.start() < start) {
                continue;
            }
            // 计算该内容的长度,也就是要替换的字符串的长度
            int end = matcher.start() + key.length();
            spannableString.setSpan(new ClickableSpan() {
                @Override
                public void onClick(View widget) {
                    spanClickListener.onSpanClick(bean);
                }
                @Override
                public void updateDrawState(TextPaint ds) {
                    super.updateDrawState(ds);
                    //设置画笔属性
                    ds.setUnderlineText(false);//默认有下划线
                }
            }, matcher.start(), end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            if (end < spannableString.length()) {
                // 如果整个字符串还未验证完,则继续。。
                dealClick(spannableString, patten, end, spanClickListener, bean);
            }
            break;
        }
    }
}

SpannableStringActivity.java

package com.ican.subjects.spannable_string;

import android.graphics.Color;
import android.text.SpannableString;
import android.text.method.LinkMovementMethod;
import android.widget.TextView;
import android.widget.Toast;

import com.ican.subjects.R;
import com.ican.subjects.base.BaseActivity;
import com.ican.subjects.spannable_string.bean.Topic;
import com.ican.subjects.spannable_string.bean.User;
import com.ican.subjects.spannable_string.utils.SpanUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * 创建者     涂文远
 * 创建时间   2016/10/7 10:34
 * 描述	      ${TODO}
 * <p/>
 * 更新者     $Author$
 * 更新时间   $Date$
 * 更新描述   ${TODO}
 */
public class SpannableStringActivity extends BaseActivity {
    private TextView tvColoredKeywd;
    private TextView tvTopic;
    private TextView tvTestAt;
    private TextView tvExpression;

    @Override
    public int getLayout() {
        return R.layout.activity_spannable_string;
    }

    @Override
    protected void initView() {
        tvColoredKeywd = (TextView) findViewById(R.id.tv_keyword_colored);
        tvTopic = (TextView) findViewById(R.id.tv_topic);
        tvTestAt = (TextView) findViewById(R.id.tv_test_at);
        tvExpression = (TextView) findViewById(R.id.tv_text_expression);
    }

    @Override
    protected void initData() {
        testColoredKeywd();

        testTopic();

        textAtUsers();

        textExpression();
    }

    /**
     * 关键字变色
     */
    private void testColoredKeywd() {
        String string = "Android一词的本义指“机器人”,同时也是Google于2007年11月5日,Android logo相关图片,Android logo相关图片(36张)\n";
        SpannableString cardText = null;
        try {
            cardText = SpanUtils.getKeyWordSpan(getResources().getColor(R.color.md_green_600), string, "Android");
        } catch (Exception e) {
            e.printStackTrace();
        }
        tvColoredKeywd.setText(cardText);
    }

    /**
     * 测试话题
     */
    private void testTopic() {
        String topic = "#舌尖上的大连#四种金牌烤芝士吃法爱吃芝士的盆友不要错过了~L秒拍视频\n";
        SpannableString topicText = null;
        try {
            topicText = SpanUtils.getTopicSpan(Color.BLUE, topic, true, new SpanUtils.SpanClickListener<Topic>() {
                @Override
                public void onSpanClick(Topic t) {
                    Toast.makeText(SpannableStringActivity.this, "点击话题:" + t.toString() , Toast.LENGTH_SHORT).show();
                }
            }, new Topic(1, "舌尖上的大连"));
        } catch (Exception e) {
            e.printStackTrace();
        }
        tvTopic.setText(topicText);
        //如果想实现点击,必须要设置这个
        tvTopic.setMovementMethod(LinkMovementMethod.getInstance());
    }

    /**
     * 测试@好友
     */
    private void textAtUsers(){
        List<User> users = new ArrayList<>();
        users.add(new User(1, "好友1"));
        users.add(new User(2, "好友2"));
        StringBuilder sb = new StringBuilder("快来看看啊");
        for (User u : users) {
            sb.append("@").append(u.getName());
        }
        sb.append("\n");
        try {
            SpannableString atSpan = SpanUtils.getAtUserSpan(Color.BLUE, sb.toString(), true, new SpanUtils.SpanClickListener<User>() {
                @Override
                public void onSpanClick(User user) {
                    Toast.makeText(SpannableStringActivity.this, "@好友:" + user.toString(), Toast.LENGTH_SHORT).show();
                }
            }, users);

            tvTestAt.setText(atSpan);
            tvTestAt.setMovementMethod(LinkMovementMethod.getInstance());
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 测试表情显示
     */
    private void textExpression(){
        String exStr = "今天天气很好啊[呲牙],是不是应该做点什么[色]";
        SpannableString span = null;
        try {
            span = SpanUtils.getExpressionSpan(this, exStr);
        } catch (Exception e) {
            e.printStackTrace();
        }
        tvExpression.setText(span);
    }

    private void initViews() {
        tvColoredKeywd = (TextView) findViewById(R.id.tv_keyword_colored);
        tvTopic = (TextView) findViewById(R.id.tv_topic);
        tvTestAt = (TextView) findViewById(R.id.tv_test_at);
        tvExpression = (TextView) findViewById(R.id.tv_text_expression);
    }
}

activity_spannable_string.xml

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:tools="http://schemas.android.com/tools"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:paddingBottom="@dimen/activity_vertical_margin"
            android:paddingLeft="@dimen/activity_horizontal_margin"
            android:paddingRight="@dimen/activity_horizontal_margin"
            android:paddingTop="@dimen/activity_vertical_margin"
            android:background="#fff">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tv_keyword_colored"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />


        <TextView
            android:id="@+id/tv_topic"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
        />

        <TextView
            android:id="@+id/tv_test_at"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
        />

        <TextView
            android:id="@+id/tv_text_expression"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />


    </LinearLayout>
</ScrollView>





作者:daividtu 发表于2016/10/22 18:52:31 原文链接
阅读:89 评论:0 查看评论

Android 动画总结-属性动画

$
0
0

特点:

  • 改变的是对象的实际属性
  • 不仅可以应用于View, 有getter和setter方法的都可以

在xml中定义

放在res\animator
如:
animator_alpha.xml
这里写图片描述

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="2000"
    android:interpolator="@android:interpolator/accelerate_quad"
    android:propertyName="alpha"
    android:repeatCount="1"
    android:repeatMode="reverse"
    android:valueFrom="1"
    android:valueTo="0" />

animator_backgroud.xml

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:propertyName="BackgroundColor"
    android:repeatCount="infinite"
    android:repeatMode="restart"
    android:valueFrom="@color/colorAccent"
    android:valueTo="@color/colorPrimary"
    android:valueType="colorType" />

animator_set.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="sequentially">// 动画顺序
    <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="2000"
        android:interpolator="@android:interpolator/accelerate_quad"
        android:propertyName="alpha"
        android:repeatCount="1"
        android:repeatMode="reverse"
        android:valueFrom="1"
        android:valueTo="0" />

    <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="1000"
        android:propertyName="BackgroundColor"
        android:repeatCount="infinite"
        android:repeatMode="restart"
        android:valueFrom="@color/colorAccent"
        android:valueTo="@color/colorPrimary"
        android:valueType="colorType" />
</set>

作用于控件

Animator animator = AnimatorInflater.loadAnimator(this, R.animator.animator_alpha);
animator.setTarget(mImage);
animator.start();

PropertyAnimXMLActivity

public class PropertyAnimXMLActivity extends AppCompatActivity {
    private ImageView mImage;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_property_anim_xml);
        mImage = (ImageView) findViewById(R.id.image);
    }

    public void onClick(View view) {
        Animator animator;
        switch (view.getId()) {
            case R.id.btn_alpha:
                animator = AnimatorInflater.loadAnimator(this, R.animator.animator_alpha);
                break;
            case R.id.btn_background:
                animator = AnimatorInflater.loadAnimator(this, R.animator.animator_backgroud);
                break;
            case R.id.btn_set:
                animator = AnimatorInflater.loadAnimator(this, R.animator.animator_set);
                break;
            default:
                return;
        }
        animator.setTarget(mImage);
        animator.start();
    }
}

在Java代码中定义

这里写图片描述
PropertyAnimCodeActivity

public class PropertyAnimCodeActivity extends AppCompatActivity {
    private ImageView mImage;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_property_anim_code);
        mImage = (ImageView) findViewById(R.id.image);
    }

    public void onClick(View view) {
        ObjectAnimator objectAnimator;
        switch (view.getId()) {
            case R.id.btn_alpha:
                objectAnimator = ObjectAnimator.ofFloat(mImage, "alpha", 1, 0, 0.5f);
                break;
            case R.id.btn_translation:
                objectAnimator = ObjectAnimator.ofFloat(mImage, "translationX", 0, 50, 20, 60,
                        30);
                break;
            case R.id.btn_rotate:
                objectAnimator = ObjectAnimator.ofFloat(mImage, "rotation", 0, 360, 180, -180);
                break;
            case R.id.btn_scale:
                objectAnimator = ObjectAnimator.ofFloat(mImage, "scaleX", 0, 1, 0, 2);
                break;
            case R.id.btn_background:
                objectAnimator = ObjectAnimator.ofObject(mImage, "BackgroundColor",
                        new ArgbEvaluator(), Color.RED, Color.GRAY, Color.BLUE);
                break;
            case R.id.btn_set:
                AnimatorSet animatorSet = new AnimatorSet();
                ObjectAnimator oaAlpha = ObjectAnimator.ofFloat(mImage, "scaleX", 0, 2);
                ObjectAnimator oaScale = ObjectAnimator.ofFloat(mImage, "ScaleY", 0, 4);
                //这里propertyName 首字母大小写都可以
                animatorSet.setTarget(mImage);
                animatorSet.setDuration(2000);

                //同时执行:set.playTogether(animator1,animator2,animator3)
                //顺序执行:set.playSequentially(animator1,animator2,animator3)
                //分布执行:play().with(); play().after();
                animatorSet.playTogether(oaAlpha, oaScale);

//                after(Animator anim)   将现有动画插入到传入的动画之后执行
//                after(long delay)   将现有动画延迟指定毫秒后执行
//                before(Animator anim)   将现有动画插入到传入的动画之前执行
//                with(Animator anim)   将现有动画和传入的动画同时执行
//                animatorSet.play(oaAlpha).after(oaScale);
                animatorSet.start();
                //监听动画变化时四个状态
                animatorSet.addListener(new Animator.AnimatorListener() {
                    @Override
                    public void onAnimationStart(Animator animation) {

                    }

                    @Override
                    public void onAnimationEnd(Animator animation) {

                    }

                    @Override
                    public void onAnimationCancel(Animator animation) {

                    }

                    @Override
                    public void onAnimationRepeat(Animator animation) {

                    }
                });
                return;
            default:
                return;
        }
        objectAnimator.setRepeatCount(1);
        objectAnimator.setRepeatMode(ValueAnimator.REVERSE);
        objectAnimator.setDuration(2000);
        objectAnimator.start();
        //监听动画变化时某个状态
        //可以只复写部分方法, 这里传Animator.AnimatorListener的实现类AnimatorListenerAdapter
        objectAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                Toast.makeText(PropertyAnimCodeActivity.this, "动画结束", Toast.LENGTH_SHORT).show();
            }
        });
    }
}

特别的地方

这里写图片描述

  • 属性动画可以作用于任何有getter和setter方法的对象
  • propertyName就是set开头的方法的方法名不包含”set” 第一个字母大小写可以随意,但后面的部分必须与set方法后的大小写保持一致。
  • 可以使用Keyframe 时间/值 对定义属性动画, 可以理解为关键帧

PropertyAnimSpecialActivity

public class PropertyAnimSpecialActivity extends AppCompatActivity {
    private TextView mTvTitle;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_property_anim_special);
        mTvTitle = (TextView) findViewById(R.id.tv_title);

    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btn_colorValue:
                ObjectAnimator animator = ObjectAnimator.ofArgb(mTvTitle, "TextColor", Color.RED,
                        Color.YELLOW, Color.GREEN, Color.BLUE, Color.BLACK);
                animator.setDuration(5000).start();
                //监听动画变化时的实时值
                animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        int currentTextColor = mTvTitle.getCurrentTextColor();
                        mTvTitle.setText("#" + Integer.toHexString(currentTextColor));
                    }
                });
                break;
            case R.id.btn_value:
                final Test target = new Test();
                //propertyName就是set开头的方法的方法名不包含"set" 第一个字母大小写可以随意,但后面的部分必须与set方法后的大小写保持一致。
                ObjectAnimator animator1 = ObjectAnimator.ofInt(target, "value", 0, 100000);
                animator1.setDuration(5000).start();
                animator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        //参数animation可以得到值
                        mTvTitle.setText(animation.getAnimatedValue() + "------" + target.getValue());
                    }
                });
                break;
            case R.id.btn_keyFrames:
                //The time, expressed as a value between 0 and 1
                Keyframe keyframe1 = Keyframe.ofInt(0, R.color.colorAccent);
                Keyframe keyframe2 = Keyframe.ofInt(0.25f, R.color.colorPrimary);
                Keyframe keyframe3 = Keyframe.ofInt(0.5f, R.color.colorAccent);
                Keyframe keyframe4 = Keyframe.ofInt(0.75f, R.color.colorPrimary);
                Keyframe keyframe5 = Keyframe.ofInt(1.0f, R.color.colorAccent);
                PropertyValuesHolder valuesHolder = PropertyValuesHolder.ofKeyframe("BackgroundResource", keyframe1, keyframe2, keyframe3,
                        keyframe4, keyframe5);
                ObjectAnimator animator2 = ObjectAnimator.ofPropertyValuesHolder(mTvTitle, valuesHolder);
                animator2.setDuration(3000).start();
                break;
            default:
                break;
        }
    }

    public class Test {
        private int value;

        public int getValue() {
            return value;
        }

        public void setValue(int value) {
            //可以得知, ObjectAnimator调用了set方法
//            this.value = 666;
            this.value = value;
        }
    }
}

源码参见: http://download.csdn.net/detail/maimiho/9660930
Android 动画总结-Activity切换动画 http://write.blog.csdn.net/mdeditor
Android 动画总结-Layout动画 http://blog.csdn.net/maimiho/article/details/52888887
Android 动画总结-帧动画 http://blog.csdn.net/maimiho/article/details/52893291
Android 动画总结-补间动画 http://blog.csdn.net/maimiho/article/details/52893403
Android 动画总结-属性动画 http://blog.csdn.net/maimiho/article/details/52894023
Android 动画总结-ViewPropertyAnimator http://blog.csdn.net/maimiho/article/details/52894151
Android 动画总结-矢量动画 http://blog.csdn.net/maimiho/article/details/52894266

作者:MAIMIHO 发表于2016/10/22 19:00:47 原文链接
阅读:105 评论:0 查看评论

Android艺术开发探索——第二章:IPC机制(下)

$
0
0

Android艺术开发探索——第二章:IPC机制(下)


我们继续来讲IPC机制,在本篇中你将会学习到

  • ContentProvider
  • Socket
  • Binder连接池

一.使用ContentProvider

ContentProvider是Android中提供的专门用来不同应用之间数据共享的方式,从这一点来看,他天生就是适合进程间通信,和Messenger一样,ContentProvider的底层实现同样也是Binder,由此可见,Binder在Android系统中是何等的重要,虽然ContentProvider的底层实现是Binder,但是他的使用过程比AIDL简单多了,这是因为系统为我们封装了,使得我们无须关心底层实现即可轻松实现IPC,ContentProvider虽然使用起来很简单,包括自己创建一个ContentProvider也不是什么难事,尽管如此,它的细节还是相当多,比如CRUD操作,防止SQL注入和权限控制等。由于章节主题限制,在本节中,笔者暂时不对ContentProvider的使用细节以及工作机制进行详细分析,而是为读者介绍采用ContentProvider进行跨进程通信的主要流程,至于使用细节和内部工作机制会在后续章节进行详细分析。

系统预置了许多ContentProvider,比如通讯录信息、日程表信息等,要跨进程访问这些信息,只需要通过ContentResolver的query,update、insert 和 delete方法即可,在本节中,我们来实现一个自定义的ContentProvider,并演示如何在其他应用中获取ContentProvider中的数据从而实现进程间通信这一目的。首先,我们创建一个ContentProvider名字就叫BookProvider。创建一个自定义的ContentProvider很简单,只需要继承ContentProvider并且实现它的六个方法:onCreate、query、update、 insert和getType,这六个抽象方法都很好理解,onCreate代表ContentProvider的创建,一般我们要做一些初始化工作;getIype用来返回一个Uri请求的MIME类型(媒体类型,比如图片),这个媒体类型还是比较复杂的,如果我们的应用不关注这些选项,可以直接在这个方法中返回null或者/,剩下的四个方法对应于CRUD操作,即实现对数据表的增删查改功能,除了Binder的工作原理,我们知道这六个方法均运行在ContentProvider的进程中,除了onCreate由系统回调并并运行在主线程中,其他五个方法均由外界回调并且运行在Binder线程池中,这一点我们再接下来的例子中可以看到。

ContentProvider主要以表格的形式来组织数据,并且可以包含多个表,对于每个表格来说,它们都具有行和列的层次性,行往往对应一条记录,而列对应一条记录中的一个字段,这点和数据库很类似。除了表格的形式,ContentProvider还支持文件数据,比如图片、视频等。文件数据和表格数据的结构不同,因此处理这类数据时可以在ContentProvider中返回文件的句柄给外界从而让文件来访问Contentprovider中的文件信息。Android系统所提供的MediaStore功能就是文件类型的ContentProvider,详细实现可以参考MediaStore。另外,虽然ContentProvide的底层数据看起来很像一个SQLite数据库,但是ContentProvider对底层的数据存储方式没有任何要求,我们既可以使用SQLite数据库,也可以使用普通的文件,甚至可以采用内存中的一个对象来进行数据的存储,这一点在后续的章节中会再次介绍,所以这里不再深入了。

下面看一个最简单的示例,它演示了ContentProvider的工作工程。首先创建一个BookProvider类,它继承自ContentProvider并实现了ContentProvider的六个必须需要实现的抽象方法。在下面的代码中,我们什么都没干,尽管如此,这个BookProvider也是可以
工作的,只是它无法向外界提供有效的数据而已。

package com.liuguilin.contentprovidersampler;

/*
 *  项目名:  ContentProviderSampler 
 *  包名:    com.liuguilin.contentprovidersampler
 *  文件名:   BookProvider
 *  创建者:   LGL
 *  创建时间:  2016/10/20 13:49
 *  描述:    ContentProvider
 */

import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.support.annotation.Nullable;
import android.util.Log;

public class BookProvider extends ContentProvider{

    public static final String TAG = "BookProvider";

    @Override
    public boolean onCreate() {
        Log.i(TAG,"onCreate,current thread:" + Thread.currentThread().getName());
        return false;
    }

    @Nullable
    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        Log.i(TAG,"query,current thread:" + Thread.currentThread().getName());
        return null;
    }

    @Nullable
    @Override
    public String getType(Uri uri) {
        Log.i(TAG,"getType");
        return null;
    }

    @Nullable
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        Log.i(TAG,"insert");
        return null;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        Log.i(TAG,"delete");
        return 0;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        Log.i(TAG,"update");
        return 0;
    }
}

接着我们需要注册这个BookProvider,如下所示。其中android:authorities是ContenttProvider的唯一标识,通过这个属性外部应用就可以访问我们的BookProvider,因此android:authorities必须是唯一的,这里建议读者在命名的时候加上包名前缀。,为了演示进程间通讯,我们让BookProvider运行在独立的进程中并给它添加了权限,这样外界应用如果想访问BookProvider,就必须声明com.lgl.PROVIDER这个权限。ContentProvider的的权限还可以细分为读权限和写权限,分别对应androidreadPermission和
androidswritePermission 属性,如果分别声明了读权限和写权限,那么外界应用也必须依次声明相应的权限才可以进行读/写操作,否则外界应用会异常终止。关于权限这一块,请读者自行查阅相关资料,本章不进行详细介绍。

        <provider
            android:name=".BookProvider"
            android:authorities="com.liuguilin.contentprovidersampler.BookProvider"
            android:permission="com.lgl.PROVIDER"
            android:process=":provider"/>

注册了ContentProvider之后,我们就可以在外部应用中访问他了,为了方便演示,这里我们再统一个应用中其他进程去访问这个BookProvider,和其他应用中的访问效果一样,读者可以自行试下(要声明权限)

package com.liuguilin.contentprovidersampler;

/*
 *  项目名:  ContentProviderSampler 
 *  包名:    com.liuguilin.contentprovidersampler
 *  文件名:   ProviderActivity
 *  创建者:   LGL
 *  创建时间:  2016/10/20 13:55
 *  描述:    ContentProvider类
 */

import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;

public class ProviderActivity extends AppCompatActivity{

    @Override
    protected void onCreate( Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_provider);

        Uri uri = Uri.parse("content//com.liuguilin.contentprovidersampler.BookProvider");
        getContentResolver().query(uri,null,null,null,null);
        getContentResolver().query(uri,null,null,null,null);
        getContentResolver().query(uri,null,null,null,null);
    }
}

在上面的代码中,我们通过ContentResolver对象的query方法去查询BookProvider中的数据,其中“content//com.liuguilin.contentprovidersampler.BookProvider”唯一标识了BookProvider,而这
个标识正是我们前面为BookProvider的android:authorities属性所指定的值。我们运行后看一下 log。从下面log可以看出,BookProvider中的query方法被调用了三次,并且这三次调用不在同一个线程中。可以看出,它们运行在一个Binder线程中,前面提到update、insert和delete方法同样也运行在Binder线程中。另外,onCreate运行在main线程中,也就是
UI线程,所以我们不能在onCreate中做耗时操作。

到这里,整个ContentProvider的流程我们已经跑通了,虽然ContentProvider中没有返回任何数据。接下来,在上面的基础上,我们继续完善BookProvider,从而使其能够对外应用提供数据,继续本章提出的那个例子,现在我们要提供一个BookProvider,外部应用可以通过BookProvider来访问图书信息,为了更好地演示ContentProvider,还可以通过BookProvider访问到用户信息。为了完成上述功能,我们需要一个数据库来来管理图书和用户信息,这个数据库不难实现,代码如下,

package com.liuguilin.contentprovidersampler;

/*
 *  项目名:  ContentProviderSampler 
 *  包名:    com.liuguilin.contentprovidersampler
 *  文件名:   DbOPenHelper
 *  创建者:   LGL
 *  创建时间:  2016/10/20 13:58
 *  描述:    数据库
 */

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class DbOPenHelper extends SQLiteOpenHelper {

    public static final String DB_NAME = "book_provider.db";
    public static final String BOOK_TABLE_NAME = "book";
    public static final String USER_TABLE_NAME = "user";

    public static final int DB_VERSION = 1;

    //图书和用户信息表
    private String CREATE_BOOK_TABLE = "CREATE TABLE ID NOT EXISTS" + BOOK_TABLE_NAME + "(_id INTEGER PRIMARY KEY," + "name TEXT)";

    private String CREATE_USER_TABLE = "CREATE TABLE IF NOT EXISTS" + USER_TABLE_NAME + "(_id INTEGER PRIMARY KEY,"+"name TEXT,";

    public DbOPenHelper(Context context) {
        super(context, DB_NAME, null, DB_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK_TABLE);
        db.execSQL(CREATE_USER_TABLE);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}

上述代码是一个最简单的数据库的实现,我们借助SQLiteOpenHelper来管理数据库的创建、升级和降级。下面我们就要通过BookProvider向外界提供上述数据库中的信息了,我们知道,ContentProvider通过Uri来区分外界要访问的的数据集合,在本例中支持
对BookProvider中的book表和user表进行访问,为了知道外界要访问的是哪个表,我需要为它们定义单独的Uri和Uri_Code,并将Uri和对应的Uru_Code相关联,我们可以用UriMatcher的addURI方法将Uri和Ur_Code关联到一起。这样,当外界请求访问BookProvider时,我们就可以根据请求的Uri来得到Ur_Code,有了Uri_Code我们就知道外界想要访问哪个表,然后就可以进行相应的数据操作了,具体代码如下

public class BookProvider extends ContentProvider {

    public static final String TAG = "BookProvider";

    public static final String AUTHORITY = "com.liuguilin.contentprovidersampler.BookProvider";

    public static final Uri BOOK_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/book");

    public static final Uri USER_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/user");

    public static final int BOOK_URI_CODE = 0;

    public static final int USER_URI_CODE = 1;

    public static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    static {
        sUriMatcher.addURI(AUTHORITY,"book",BOOK_URI_CODE);
        sUriMatcher.addURI(AUTHORITY,"user",USER_URI_CODE);
    }
    ....
  }

从上面的代码来看,我们可以通过如下的方式来获取外界所要访问的数据源,根据Uri先取出Uri_code,关联的都是0和1,这个关联过程就是一句话

 sUriMatcher.addURI(AUTHORITY,"book",BOOK_URI_CODE);
 sUriMatcher.addURI(AUTHORITY,"user",USER_URI_CODE);

将Uri和uri_code管理好之后,我们可以通过如下方式来获取外界需要访问的数据,根据Uri先取出uri_code,根据Uri_code再来得到表的名称,接下来我么可以响应外界的增删查改请求了

private String getTableName(Uri uri) {
        String tableName = null;
        switch (sUriMatcher.match(uri)) {
            case BOOK_URI_CODE:
                tableName = DbOPenHelper.BOOK_TABLE_NAME;
                break;
            case USER_URI_CODE:
                tableName = DbOPenHelper.USER_TABLE_NAME;
                break;
        }
        return tableName;
    }

接着我们就可以实现增删查改的方法了,如果是qurey,首先我们要从拿到外界要访问的表名称,然后根据外界传递的信息进行数据库的查询操作了,这个过程比较简单:


    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        Log.i(TAG, "query,current thread:" + Thread.currentThread().getName());
        String table = getTableName(uri);
        if(table == null){
            throw new IllegalArgumentException("Unsupported URI:" + uri);
        }
        return mDb.query(table,projection,selection,selectionArgs,null,null,sortOrder,null);
    }

另外三个方法的实现思路和查询有点类似,只有一点不同,那就是这三个方法都会引起数据源的改变,这个时候我们需要通过ContentResolver的notifyChange中的数据改变情况,可以通过注册的方法来注册观察者,对于这三个方法,这里不再详细说,看代码:

package com.liuguilin.contentprovidersampler;

/*
 *  项目名:  ContentProviderSampler 
 *  包名:    com.liuguilin.contentprovidersampler
 *  文件名:   BookProvider
 *  创建者:   LGL
 *  创建时间:  2016/10/20 13:49
 *  描述:    ContentProvider
 */

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.support.annotation.Nullable;
import android.util.Log;

public class BookProvider extends ContentProvider {

    public static final String TAG = "BookProvider";

    public static final String AUTHORITY = "com.liuguilin.contentprovidersampler.BookProvider";

    public static final Uri BOOK_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/book");

    public static final Uri USER_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/user");

    public static final int BOOK_URI_CODE = 0;

    public static final int USER_URI_CODE = 1;

    public static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    static {
        sUriMatcher.addURI(AUTHORITY, "book", BOOK_URI_CODE);
        sUriMatcher.addURI(AUTHORITY, "user", USER_URI_CODE);
    }

    private Context mContext;
    private SQLiteDatabase mDb;

    @Override
    public boolean onCreate() {
        Log.i(TAG, "onCreate,current thread:" + Thread.currentThread().getName());
        mContext = getContext();
        initProviderDate();
        return true;
    }

    private void initProviderDate() {
        mDb = new DbOPenHelper(mContext).getWritableDatabase();
        mDb.execSQL("delete from " + DbOPenHelper.BOOK_TABLE_NAME);
        mDb.execSQL("delete from " + DbOPenHelper.USER_TABLE_NAME);

        mDb.execSQL("insert into book values(3,'Android');");
        mDb.execSQL("insert into book values(4,'IOS');");
        mDb.execSQL("insert into book values(5,'Html5');");
        mDb.execSQL("insert into book values(1,'jake',1);");
        mDb.execSQL("insert into book values(2,'Jasmine',0);");
    }


    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        Log.i(TAG, "query,current thread:" + Thread.currentThread().getName());
        String table = getTableName(uri);
        if (table == null) {
            throw new IllegalArgumentException("Unsupported URI:" + uri);
        }
        return mDb.query(table, projection, selection, selectionArgs, null, null, sortOrder, null);
    }

    @Nullable
    @Override
    public String getType(Uri uri) {
        Log.i(TAG, "getType");
        return null;
    }

    @Nullable
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        Log.i(TAG, "insert");
        String table = getTableName(uri);
        if (table == null) {
            throw new IllegalArgumentException("Unsupported URI:" + uri);
        }
        mDb.insert(table, null, values);
        mContext.getContentResolver().notifyChange(uri, null);
        return uri;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        Log.i(TAG, "delete");
        String table = getTableName(uri);
        if (table == null) {
            throw new IllegalArgumentException("Unsupported URI:" + uri);
        }
        int count = mDb.delete(table, selection, selectionArgs);
        if (count > 0) {
            getContext().getContentResolver().notifyChange(uri, null);
        }
        return count;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        Log.i(TAG, "update");
        String table = getTableName(uri);
        if (table == null) {
            throw new IllegalArgumentException("Unsupported URI:" + uri);
        }
        int row = mDb.update(table, values, selection, selectionArgs);
        if (row > 0) {
            getContext().getContentResolver().notifyChange(USER_CONTENT_URI, null);
        }
        return row;
    }

    private String getTableName(Uri uri) {
        String tableName = null;
        switch (sUriMatcher.match(uri)) {
            case BOOK_URI_CODE:
                tableName = DbOPenHelper.BOOK_TABLE_NAME;
                break;
            case USER_URI_CODE:
                tableName = DbOPenHelper.USER_TABLE_NAME;
                break;
        }
        return tableName;
    }
}

需要注意的是,增删查改四大方法是存在多线程并发访问的,因此方法内部要做好线程同步的工作,在本例中,由于采取了sqlite并且只有一个SQLiteDatabase内部对数据库的操作式同步处理的,但是如果多个SQLiteDatabase对象来操作数据库就无法保证线程同步了,因为SQLiteDatabase对象之间无法进程线程同步,如果ContentProvider的底层数据集是一块内存的话,比如List,在这种情况下同List的遍历,插入,删除操作就需要进行线程同步了,否则会引发错误,这点尤其需要注意的,到这里BookProvider已经完成了,接着我们来外部访问他,看看他能否继续工作

package com.liuguilin.contentprovidersampler;

/*
 *  项目名:  ContentProviderSampler 
 *  包名:    com.liuguilin.contentprovidersampler
 *  文件名:   ProviderActivity
 *  创建者:   LGL
 *  创建时间:  2016/10/20 13:55
 *  描述:    ContentProvider类
 */

import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;

public class ProviderActivity extends AppCompatActivity{

    public static final String TAG = "ProviderActivity";

    @Override
    protected void onCreate( Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_provider);

        Uri bookUri = Uri.parse("content//com.liuguilin.contentprovidersampler.BookProvider/book");

        ContentValues values = new ContentValues();
        values.put("_id",6);
        values.put("name","程序设计的艺术");
        getContentResolver().insert(bookUri,values);
        Cursor bookCursor = getContentResolver().query(bookUri,new String[]{"_id","name"},null,null,null);
        while (bookCursor.moveToNext()){
            Book book = new Book();
            book.id = bookCursor.getInt(0);
            book.name = bookCursor.getString(1);
        }
        bookCursor.close();

        Uri userUri = Uri.parse("content//com.liuguilin.contentprovidersampler.BookProvider/user");
        Cursor userCursor = getContentResolver().query(userUri,new String[]{"_id","name","sex"},null,null,null);
        while (userCursor.moveToNext()){
            User user = new User();
            user.id= userCursor.getInt(0);
            user.name = userCursor.getString(1);
            user.isMale = userCursor.getInt(2) == 1;
        }
        userCursor.close();

    }
}

默认情况下,BookProvider的数据库中有三本书和两个用户,在上面的代码中,我们首先添加一本书:“程序设计的艺术”。接着查询所有的图书,这个时候应该查询出四本书,,因为我们刚刚添加了一本。然后查询所有的用户,这个时候应该查询出两个用户。是不是这样呢?我们运行一下程序,从log可以看到,我们的确查询到了4本书和2个用户,这说明BookProvider已经能够正确地处理外部的请求了,读者可以自行验证一下update和delete操作,这里就不再验证了。同时,由于ProviderActivity和BookProvider运行在两个不同的进程中,因此,这也构成了进程间的通信。ContentProvider除了支持对数据源的增删改查这四个操作,还支持自定义调用,这个过程是通ContentResolver的Call方法和ContentProvider的Call方法来完成的。关于使用ContentProvider来进行IPC就介绍到这里,ContentProvider本身还有一些细节这里并没有介绍,读者可以自行了解,本章侧重的是各种进程间通信的方法以及它们的区别,因此针对某种特定的方法可能不会介绍得面面俱到。另外,ContentProvider在后续章节还会有进一步的讲解,主要包括细节问题和工作原理,读者可以阅读后面的相应章节

二.使用Socket

在本节,我们通过Socket来实现进程通信,Socket也叫做套接字,是网络通信中的概念,他分为流式套接字和用户数据报套接字两种,分别是应用于网络的传输控制层中的Tcp和UDP协议,TCP面向的连接协议,提供稳定的双向通讯功能,TCP连接的建立需要经过“三次握手”才能完成,为了提供稳定的数据传输功能,其本身提供了超时重传机制,因此具有很高的稳定性:而UDP是无连接的,提供不稳定的单向通信功能,当然UDP也可以实现双向通信功能。在性能上,UDP具有更好的效率,其缺点是不保证数据一定能够正确传输,尤其是在网络拥塞的情况下。关于TCP和UDP的介绍就这么多,更详细的资料请查看相关网络资料。接下来我们演示一个跨进程的聊天程序,两个进程可以通过Socket来实现信息的传输,Socket本身可以支持传输任意字节流,这里为了简单起见,仅仅传输文本信息,很显然,这是一种IPC方式。

使用Socket来进行通信,有两点需要注意,首先需要声明权限:

 <uses-permission android:name="android.permission.INTERNET"/>
 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

其次要注意不能在主线程中访问网络,因为这会导致我们的程序无法在Android4.0及其以上的设备中运行,会抛出如下异常:android.os NetworkOnMainThreadException。而且进行网络操作很可能是耗时的,如果放在主线程中会影响程序的响应效率,从这方面来说,也不应该在主线程中访问网络。下面就开始设计我们的聊天室程序了,比较简单,首先在远程Service建立一个TCP服务,然后在主界面中连接TCP服务,连接上了以后,就可给服务端发消息。对于我们发送的每一条文本消息,服务端都会随机地回应我们一句话为了更好地展示Socket的工作机制,在服务端我们做了处理,使其能够和多个客户端同时连接建立连接并响应。

先看一下服务端的设计,当Service启动时,会在线程中建立TCP服务,这里监听的是8688端口,然后就可以等待客户端的连接请求。当有客户端连接时,就会生成一个新的Socket,通过每次新创建的Socket就可以分别和不同的客户端通信了。服务端每收到一次客户端的消息就会随机回复一句话给客户端。当客户端断开连接时,服务端这边也会相应的关闭对应Socket并结束通话线程,这点是如何做到的呢?方法有很多,这里是通过判断服务端输入流的返回值来确定的,当客户端断开连接后,服务端这边的输入流会返回null,这个时候我们就知道客户端退出了。服务端的代码如下所示。

package com.liuguilin.contentprovidersampler;

/*
 *  项目名:  ContentProviderSampler 
 *  包名:    com.liuguilin.contentprovidersampler
 *  文件名:   TCPServerService
 *  创建者:   LGL
 *  创建时间:  2016/10/22 15:16
 *  描述:    服务端
 */

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Random;

public class TCPServerService extends Service {

    private boolean mIsServiceDestoeyed = false;
    private String[] mDefinedMessages = {"你好呀", "你叫神马?", "今天的天气", "汽车站怎么走?"};

    @Override
    public void onCreate() {
        new Thread(new TcpServer()).start();
        super.onCreate();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onDestroy() {
        mIsServiceDestoeyed = true;
        super.onDestroy();
    }

    private class TcpServer implements Runnable{

        @Override
        public void run() {
            ServerSocket serverSocket = null;
            try {
                //监听本地8868端口号
                serverSocket = new ServerSocket(8688);
            } catch (IOException e) {
                e.printStackTrace();
                return;
            }

            while (!mIsServiceDestoeyed){
                try {
                    //接收客户端的请求
                    final Socket client = serverSocket.accept();
                    new Thread(){
                        @Override
                        public void run() {
                            try {
                                responseClient(client);
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }.start();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void responseClient(Socket client) throws IOException{
        //用于接收客户端的信息
        BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
        //用于给客户端发送消息
        PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())),true);
        out.print("欢迎来到聊天室");
        while (!mIsServiceDestoeyed){
            String str = in.readLine();
            if(str == null){
                break;
            }
            int i = new Random().nextInt(mDefinedMessages.length);
            String msg = mDefinedMessages[i];
            out.print(msg);
        }
        in.close();
        out.close();
        client.close();
    }
}

接下来看一下客户端,客户端Activity启动时,会在onCreate中开启一个线程去连接服务端的socket,至于为什么要用线程我们前面已经说了,为了确定能够连接成功,这里采用了超时重连的机制,每次连接失败后都会重新连接,当然,为了降低重试机制的开销,我们加入了休眠机制,每次重试的事件间隔为1000毫秒

     try {
            //接收服务端的消息
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            while (!TCPClientActivity.this.isFinishing()) {
                String msg = br.readLine();
                if (msg != null) {
                    String time = formatDateTime(System.currentTimeMillis());
                    String showMsg = "server " + time + ":" + msg + "\n";
                    handler.obtainMessage(MESSAGE_RECEIVE_NEW_MSG, showMsg).sendToTarget();
                }
            }

当然,你的activity在退出的时候,就要关闭socket了

  @Override
    protected void onDestroy() {
        if (mClientSocket != null) {
            try {
                mClientSocket.shutdownInput();
                mClientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        super.onDestroy();
    }

接收发送消息的整个过程,这个就很简单了,看完整代码:

package com.liuguilin.contentprovidersampler;

/*
 *  项目名:  ContentProviderSampler 
 *  包名:    com.liuguilin.contentprovidersampler
 *  文件名:   TCPClientActivity
 *  创建者:   LGL
 *  创建时间:  2016/10/22 15:31
 *  描述:    客户端
 */

import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;

public class TCPClientActivity extends AppCompatActivity implements View.OnClickListener {

    public static final int MESSAGE_RECEIVE_NEW_MSG = 1;
    public static final int MESSAGE_SOCKET_CONNECTED = 2;

    private Button mSendButton;
    private TextView mMessageTextView;
    private EditText mMessageEditText;

    private PrintWriter mPrintWriter;
    private Socket mClientSocket;

    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_RECEIVE_NEW_MSG:
                    mMessageTextView.setText(mMessageTextView.getText() + (String) msg.obj);
                    break;
                case MESSAGE_SOCKET_CONNECTED:
                    mSendButton.setEnabled(true);
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_tcp_client);
        initView();
    }

    private void initView() {
        mMessageTextView = (TextView) findViewById(R.id.msg_container);
        mSendButton = (Button) findViewById(R.id.send);
        mSendButton.setOnClickListener(this);
        mMessageEditText = (EditText) findViewById(R.id.msg);

        Intent intent = new Intent(this, TCPServerService.class);
        startService(intent);

        new Thread() {
            @Override
            public void run() {
                connectTCPServer();
            }
        }.start();
    }


    @Override
    protected void onDestroy() {
        if (mClientSocket != null) {
            try {
                mClientSocket.shutdownInput();
                mClientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        super.onDestroy();
    }

    @Override
    public void onClick(View v) {
        if (v == mSendButton) {
            String msg = mMessageEditText.getText().toString();
            if (!TextUtils.isEmpty(msg)) {
                mPrintWriter.println(msg);
                mMessageEditText.setText("");
                String time = formatDateTime(System.currentTimeMillis());
                String showesMsg = "self" + time + ":" + msg + "\n";
                mMessageTextView.setText(mMessageTextView.getText() + showesMsg);
            }
        }
    }

    private String formatDateTime(long l) {
        return new SimpleDateFormat("(HH:mm:ss)").format(new Date(l));
    }

    private void connectTCPServer() {
        Socket socket = null;
        while (socket == null) {
            try {
                socket = new Socket("localhost", 8688);
                mClientSocket = socket;
                mPrintWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
                handler.sendEmptyMessage(MESSAGE_SOCKET_CONNECTED);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        try {
            //接收服务端的消息
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            while (!TCPClientActivity.this.isFinishing()) {
                String msg = br.readLine();
                if (msg != null) {
                    String time = formatDateTime(System.currentTimeMillis());
                    String showMsg = "server " + time + ":" + msg + "\n";
                    handler.obtainMessage(MESSAGE_RECEIVE_NEW_MSG, showMsg).sendToTarget();
                }
            }

            mPrintWriter.close();
            br.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

上述代码就是通过Socket来进行进程间的通信实例,除了采用套接字,还可以用UDP套接字,OK

三.Binder连接池

上面我们介绍了不同的IPC方式,我们知道,不同的IPC方式有不同的特点和适用场景,当然这个问题会在26节进行介绍,在本节中要再次介绍一下AIdL,原因是AIDL是一种最常用的进程间通信方式,是日常开发中涉及进程间通信时的首选,所以我们需要额外强调一下.

如何使用AIDL在上面的一节中已经进行了介绍,这里在回顾一下大致流程:首先创建一个Service和一个AIDL接口,接着创建一个类继承自AIDL接口中的Stub类并实现Stub中的抽象方法,在Service的onBind方法中返回这个类的对象,然后客户端就可以绑
定服务端Service,建立连接后就可以访问远程服务端的方法了。上述过程就是典型的AIDL的使用流程。这本来也没什么问题,但是现在考虑一种情况;公司的项目越来越庞大了,现在有10个不同的业务模块都需要使用AIDL来进行进程间通信,那我们该怎么处理呢?也许你说:“就按照AIDL的实现方式一个个来吧”,这是可以的,如果用这种方法,首先我们需要创建10个Service,这好像有点多啊!如果有100个地方需要用到AIDL呢,先创建100个Servlce?到这里,读者应该明白问题所在了,随着AIDL数量的增加,我们不能无限制地增Service,Service是四大组件之一,本生是一种系统资源。而且太多的Serice会使得我们的应用看起来很重量级,因为正在运行的Service可以在应用详情页看到,当我们的应用详情显示有10个个服务正在运行时,这看起来并不是什么好事。针对上述问题,我们需要减少Service的数量,将所有的AIDL放在同个Service中去管理。

在这种模式下,整个工作机制是这样的;每个业务模块创建自己的AIDL接口并实现此接口,这个时候不同业务模块之间是不能有耦合的,所有实现细节我们要单独开来,然后向服务端提供自己的唯一标识和其对应的Binder对象;对于服务端来说,只需要一个Service就可以了,服务端提供一个queryBinder接口,这个接口能够根据业务模块的特征来返回相应的Binder对象给它们,不同的业务模块拿到所需的Binder对象后就可以进行远程方法调用了。由此可见,Binder连接池的主要作用就是将每个业务模块的Binder请求统一转发到远程Service中去执行,从而避免了重复创建Service的过程,它的工作原理如图所示。

这里写图片描述

通过上面的理论介绍,也许还有点不好理解,下面对Binder连接池的代码实现做一说明。首先,为了说明问题,我们提供了两个AIDL接口(ISecurityCenter和ICompute)来模拟上面提到的多个业务模块都要使用AIDL的情况,其中ISecurityCenter接口提供解密功能,声明如下:

interface ISecurityCenter {
    String encrypt(String content);
    String decrypt(String password);
}

而ICompute提供了计算加法的功能:

interface ICompute {

    int add(int a,int b);
}

虽然说上面的两个接口的功能都比较简单,但是用于分析Binder池的工作原理还是足够的,读者可以写出更加复杂的例子,接下来我们来看一下部分

package com.liuguilin.contentprovidersampler;

/*
 *  项目名:  ContentProviderSampler 
 *  包名:    com.liuguilin.contentprovidersampler
 *  文件名:   SecurityCenterImpl
 *  创建者:   LGL
 *  创建时间:  2016/10/22 17:50
 *  描述:    TODO
 */

import android.os.RemoteException;

public class SecurityCenterImpl extends ISecurityCenter.Stub{

    private static  final  char SECRET_CODE = '^';

    @Override
    public String encrypt(String content) throws RemoteException {
        char [] chars = content.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            chars[i] ^= SECRET_CODE;
        }
        return new String(chars);
    }

    @Override
    public String decrypt(String password) throws RemoteException {
        return encrypt(password);
    }
}


package com.liuguilin.contentprovidersampler;

/*
 *  项目名:  ContentProviderSampler 
 *  包名:    com.liuguilin.contentprovidersampler
 *  文件名:   ComputeImpl
 *  创建者:   LGL
 *  创建时间:  2016/10/22 17:52
 *  描述:    TODO
 */

import android.os.RemoteException;

public class ComputeImpl extends ICompute.Stub {

    @Override
    public int add(int a, int b) throws RemoteException {
        return a + b;
    }
}

现在的业务模块的AIDL接口定义和实现都已经完成了,注意的是这里并没有为每个模块的AIDL创建单独的Service,接下来就是服务端和Binder连接池的工作了


interface IBinderPool {

    IBinder queryBinder(int binderCode);
}

接着,为Binder连接池创建远程Service并实现IBnderPool,下面是queryBinder的具体实现,可以看到请求转达的实现方法,当Binder连接池连接上远程服务时,会根据不同的模块的标识binderCode返回不同的Binder对象,通过这个对象就可以操作全部发生在远程服务端上:

 public static class BinderPoolImpl extends IBinderPool.Stub{

        public BinderPoolImpl(){
            super();
        }

        @Override
        public IBinder queryBinder(int binderCode) throws RemoteException {
            IBinder binder = null;
            switch (binderCode){
                case BINDER_SECURUITY_CENTER:
                    binder = new SecurityCenterImpl();
                    break;
                case BINDER_COMPUTE:
                    binder = new ComputeImpl();
                    break;
            }
            return binder;
        }
    }

远程service的实现比较简单,如下:

package com.liuguilin.contentprovidersampler;

/*
 *  项目名:  ContentProviderSampler 
 *  包名:    com.liuguilin.contentprovidersampler
 *  文件名:   BinderPoolService
 *  创建者:   LGL
 *  创建时间:  2016/10/22 18:01
 *  描述:    Binder
 */

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;

public class BinderPoolService extends Service{

    private  static final String  TAG = "BinderPoolService";

    private Binder mBinderPool = new BinderPool.BinderPoolImpl();

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinderPool;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }
}

下面还剩下Binder连接池的具体实现了,在他的内部首先他要去绑定远程服务,绑定成功后,客户端就课堂通过他的queryBinder方法来获取对应的Binder,拿到所需的Binder之后,不同业务模块就可以各自操作了

package com.liuguilin.contentprovidersampler;

/*
 *  项目名:  ContentProviderSampler 
 *  包名:    com.liuguilin.contentprovidersampler
 *  文件名:   BinderPool
 *  创建者:   LGL
 *  创建时间:  2016/10/22 18:04
 *  描述:    TODO
 */

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;

import java.util.concurrent.CountDownLatch;

public class BinderPool {

    private static final String TAG = "BinderPool";
    public static final int BINDER_NONE = -1;
    public static final int BINDER_COMPUTE = 0;
    public static final int BINDER_SECURUITY_CENTER = 1;

    private Context mContext;
    private IBinderPool mIBinderPool;
    private static volatile BinderPool sInstance;
    private CountDownLatch mConnectBinderPoolCopuntDownLacth;

    private BinderPool(Context context) {
        mContext = context.getApplicationContext();
        connectBinderPoolService();
    }

    public static BinderPool getInstance(Context context) {
        if (sInstance == null) {
            synchronized (BinderPool.class) {
                if (sInstance == null) {
                    sInstance = new BinderPool(context);
                }
            }
        }
        return sInstance;
    }

    private synchronized void connectBinderPoolService() {
        mConnectBinderPoolCopuntDownLacth = new CountDownLatch(1);
        Intent service = new Intent(mContext, BinderPoolService.class);
        mContext.bindService(service, mBinderPoolConnection, Context.BIND_AUTO_CREATE);
        try {
            mConnectBinderPoolCopuntDownLacth.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public IBinder queryBinder(int binderCode) {
        IBinder binder = null;
        try {
            if (mIBinderPool != null) {
                binder = mIBinderPool.queryBinder(binderCode);
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        return binder;
    }

    private ServiceConnection mBinderPoolConnection  = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mIBinderPool = IBinderPool.Stub.asInterface(service);
            try {
                mIBinderPool.asBinder().linkToDeath(mBinderPoolDeathRecipient,0);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            mConnectBinderPoolCopuntDownLacth.countDown();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

   private IBinder.DeathRecipient mBinderPoolDeathRecipient  = new IBinder.DeathRecipient() {
       @Override
       public void binderDied() {
           mIBinderPool.asBinder().unlinkToDeath(mBinderPoolDeathRecipient,0);
           mIBinderPool = null;
           connectBinderPoolService();
       }
   };

    public static class BinderPoolImpl extends IBinderPool.Stub{

        public BinderPoolImpl(){
            super();
        }

        @Override
        public IBinder queryBinder(int binderCode) throws RemoteException {
            IBinder binder = null;
            switch (binderCode){
                case BINDER_SECURUITY_CENTER:
                    binder = new SecurityCenterImpl();
                    break;
                case BINDER_COMPUTE:
                    binder = new ComputeImpl();
                    break;
            }
            return binder;
        }
    }
}

Binder连接池就具体的分析完了,他的好处显而易见,针对上面的例子,我们只需要创建一个Service就可以完成多个AIDL的工作,我们现在可以来验证一下他的功能,新创建一个Activity,在线程中执行如下的操作

package com.liuguilin.contentprovidersampler;

/*
 *  项目名:  ContentProviderSampler 
 *  包名:    com.liuguilin.contentprovidersampler
 *  文件名:   BinderActivity
 *  创建者:   LGL
 *  创建时间:  2016/10/22 18:26
 *  描述:    TODO
 */

import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;

public class BinderActivity extends AppCompatActivity{

    private ISecurityCenter mSecurityCenter;
    private ICompute mCompute;

    @Override
    protected void onCreate( Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_binder);

        new Thread(new Runnable() {
            @Override
            public void run() {
                dowork();
            }
        }).start();
    }

    private void dowork() {
        BinderPool binderPool = BinderPool.getInstance(BinderActivity.this);
        IBinder securityBinder = binderPool.queryBinder(BinderPool.BINDER_SECURUITY_CENTER);
        mSecurityCenter = SecurityCenterImpl.asInterface(securityBinder);
        String msg = "Android";
        try {
            String password = mSecurityCenter.encrypt(msg);
            System.out.print(mSecurityCenter.decrypt(password));
        } catch (RemoteException e) {
            e.printStackTrace();
        }

        IBinder computeBinder =  binderPool.queryBinder(BinderPool.BINDER_COMPUTE);
        mCompute = ComputeImpl.asInterface(computeBinder);
        try {
            System.out.print(mCompute.add(3,5));
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

这里需要额外说明一下,为什么要在线程中去执行呢?这是因为在Binder连接池的实现中,我们通过CountDownLatch将bindService这一异步操作转换成了同步操作,这就意味着他有可能是耗时的,然后就是Binder方法的调用过程也可能是耗时的,因此不建议放在主线程中执行。注意到BindePool是一个单例实现,因此在同一个进程中只会初始化一次,所以如果我们提前初始化BinderPool,那么可以优化程序的体验,比如我们可以放在Application中提前对BinderPool进行初始化,虽然这不能保证当我们调用BinderPool时它一定是初始化,好的,但是在大多数情况下,这种初始化工作(绑定远程服务)的时间开销(如果Binderpool没有提前初始化完成的话)是可以接受的。另外,BinderPool中有断线重连的机制,当远程服务意外终止时,BinderPool会重新建立连接,这个时候如果业务模块中的Binder调用出了异常,也需要手动去重新获取最新的Binder对象,这个是需要注意的。

有了BinderPool可以大大方便日常的开发工作,比如如果有一个新的业务模块需要添加新的AIDL,那么在他实现自己的AIDL接口后,只需要修改BinderPoolImpl中的queryBinder方法,给自己添加新的binderCode并返回对应的Binder对象就可以,不需要做其他的修改,野不需要创建新的Service,由此可见,BinderPool能够极大的提高对AIDL的开发效率,并且可以避免大量的Service创建,因此比较建议使用

四.选用是个自己的IPC方式

在上面的一节中,我们介绍了各种各样的IPC方式,那么到底它们有什么不同呢?我到底该使用哪一种呢?本节就为读者解答这些问题,具体内容如表所示。通过表可以明确地看出不同IPC方式的优缺点和适用场景,那么在实际的开发中,只要我们选适的IPC方式就可以轻松完成多进程的开发场景。

这里写图片描述

太长了,真是太痛苦了,默默的赞一下主席!

有兴趣的可以加群:555974449

PPT:http://download.csdn.net/detail/qq_26787115/9661071

MakeDown:http://pan.baidu.com/s/1o7Z4Djs 密码:xdgt

Sample:http://download.csdn.net/detail/qq_26787115/9661066

作者:qq_26787115 发表于2016/10/22 19:23:12 原文链接
阅读:711 评论:2 查看评论
Viewing all 5930 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>