美文网首页
第二章 IPC机制详解(3)

第二章 IPC机制详解(3)

作者: notrynobug | 来源:发表于2017-02-13 10:44 被阅读0次

    本文为Android开发艺术探索的笔记,仅供学习

    4.4 AIDL的使用

    前面Messenger进程通信中,如果客户端有大量的消息需要发送到服务端,那么服务端也只能一个个处理,所以在处理大数据的时候使用Messenger并不是好方法。我们可以使用AIDL来实现跨进程,所以Messenger的底层是AIDL换句话说Messenger就是AIDL,只不过系统做了封装方便我们使用。有了Binder的基础我们可以更好的理解AIDL。
    服务端要创建一个Service去监听客户端发来的消息,然后建立一个AIDL的文件夹,将暴露给客户端的接口在AIDL文件夹里声明,最后在Service去实现这个AIDL即可。
    客户端,需要绑带Service,绑带成功后把服务端返回的Binder转化为AIDL所属的类型,接着调用AIDL里的方法即可。
    我们来举个例子,大致的业务逻辑就是主要有三个功能,1.客户端可以向服务端添加Book 2.客户端可以向服务端获取Book信息 3.向服务端添加Book监听,监听每次添加新Book的信息

    下面上代码
    AIDL
    // Book.aidl
    package com.example.gyh.myapplication;
    // Declare any non-default types here with import statements
    parcelable Book;
    该类主要就是用来声明Book这个Bean
    
    // IOnNewBookArrivedListener.aidl
    package com.example.gyh.myapplication;
    // Declare any non-default types here with import statements
    import com.example.gyh.myapplication.Book;
    interface IOnNewBookArrivedListener {
        /**
         * Demonstrates some basic types that you can use as parameters
         * and return values in AIDL.
         */
      void onNewBookArrived(in Book newBook);
    }
    
    
    // IBookManager.aidl
    package com.example.gyh.myapplication;
    // Declare any non-default types here with import statements
    import com.example.gyh.myapplication.Book;
    import com.example.gyh.myapplication.IOnNewBookArrivedListener;
    interface IBookManager {
        /**
         * Demonstrates some basic types that you can use as parameters
         * and return values in AIDL.
         */
      void addBook(in Book book);
      List<Book> getBookList();
      void registerListener(IOnNewBookArrivedListener listener);
      void unregisterListener(IOnNewBookArrivedListener listener);
    

    接下来附上服务端的代码,正如前面Binder机制一样,创建AIDL业务的接口IBookManager.Stub这是运行在客户端的,然后就会自动生成方法。

    public class ServiceBook extends Service {
        private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>();//CopyOnWriteArrayList支持并发的读写
        //    private CopyOnWriteArrayList<IOnNewBookArrivedListener> mlListeners = new CopyOnWriteArrayList<IOnNewBookArrivedListener>();不支持多进程对Listener的增删
        private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<IOnNewBookArrivedListener>();//为什么要用这方式的List?因为RemoteCallbackList支持多进程对Listener的增删
        String TAG = "Service";
        boolean isadd = true;
    
        private Binder binder = new IBookManager.Stub() {//里面的方法和AIDL接口一一对应
            @Override
            public void addBook(Book book) throws RemoteException {
                mBookList.add(book);
            }
    
            @Override
            public List<Book> getBookList() throws RemoteException {
                return mBookList;
            }
    
            @Override
            public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
    //            if (!mlListeners.contains(listener)) {
    //                mlListeners.add(listener);
    //            } else {
    //                Log.i(TAG, "have the same");
    //            }
                mListenerList.register(listener);
                Log.i(TAG, "register size" + mListenerList.beginBroadcast());
                mListenerList.finishBroadcast();//每一次执行完都要finish一下
    
            }
    
            @Override
            public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
    //            if (mlListeners.contains(listener)) {
    //                mlListeners.remove(listener);
    //            } else {
    //                Log.i(TAG, "no find");
    //            }
                mListenerList.unregister(listener);
                Log.i(TAG, "unregister size " + mListenerList.beginBroadcast());
                mListenerList.finishBroadcast();
    
            }
        };
    
        @Override
        public void onCreate() {
            super.onCreate();
            mBookList.add(new Book(1, "ios"));
            mBookList.add(new Book(2, "android"));
            new Thread(new ServiceWorker()).start();//定义一个线程,每隔两秒去增加一个Book,目的是为了验证监听是否成功
        }
    
        @Override
        public void onDestroy() {
            isadd = false;
    
            super.onDestroy();
    
        }
    
        void addNewbook(Book book) throws RemoteException {
            mBookList.add(book);
            final int N = mListenerList.beginBroadcast();
            for (int i = 0; i < N; i++) {
                IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i);
                if (l != null) {
                    try {
                        l.onNewBookArrived(book);//给这个回掉赋值,以并于客户端在使用时有返回值
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            }
            mListenerList.finishBroadcast();
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            // TODO: Return the communication channel to the service.
            return binder;
        }
    
        private class ServiceWorker implements Runnable {
            @Override
            public void run() {
                while (isadd) {
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    int id = mBookList.size() + 1;
                    String name = mBookList.size() + 1 + "";
                    try {
                        addNewbook(new Book(id, name));
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    

    下面是客户端的代码
    通过服务端返回的Binder对于也就是 IBookManager.Stub()对象,里面的asInterface方法可以返回对于的AIDL接口,没印象的可以看看IPC机制详解(1),从而去调用相应的方法

    public class Main2Activity extends AppCompatActivity {
        String TAG = "Main2";
        IBookManager manager;
    
        ServiceConnection connection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                IBookManager iBookManager = IBookManager.Stub.asInterface(service);/通过这个Binder去使用服务端的方法
                manager = iBookManager;
                List<Book> list = new ArrayList<>();
                try {
                    list = iBookManager.getBookList();//服务端的方法
                    Log.i(TAG, list.size() + " " + list.get(0).getName());
                    iBookManager.addBook(new Book(3, "nihao"));//服务端的方法
                    Log.i(TAG, list.size() + " " + list.get(list.size() - 1).getName());
                    iBookManager.registerListener(listener);//服务端的方法
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
    
            }
        };
        IOnNewBookArrivedListener listener = new IOnNewBookArrivedListener.Stub() {
            @Override
            public void onNewBookArrived(Book newBook) throws RemoteException {
                if (newBook != null) {
                    Log.i(TAG, "Add new Book" + newBook.getId() + "  " + newBook.getName());//因为在服务端的Addnewbook的方法里添加了回掉的数据,所以newBook是有值得
                }
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main2);
            Intent intent = new Intent(Main2Activity.this, ServiceBook.class);
            bindService(intent, connection, BIND_AUTO_CREATE);
        }
    
        @Override
        protected void onDestroy() {
            Log.i(TAG, "Destory");
            if (manager != null && manager.asBinder().isBinderAlive()) {
                try {
                    Log.i(TAG, "unregister activity" + manager);
                    manager.unregisterListener(listener);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }//当Activity销毁的时候,去接触注册
            unbindService(connection);
            super.onDestroy();
        }
    }
    

    4.5 ContentProvider的使用

    ContentProvider在Android中专门用于不同App之间的数据共享的,由此可见ContentProvider天生就可以用来实现跨进程通信。ContentProvider的底层也是用到了Binder,可见Binder在Android系统中是多么的重要。虽然ContentProvider的底层是Binder,但是系统已经为我们封装好了,使用起来也比AIDL要简单的多。

    那么我们就来自定义个ContentProvider,首先去建一个类叫BookProvider 继承ContentProvider,

    public class BookProvider extends ContentProvider {
        @Override
        //可以进行一些初始化 该方法运行在主线程里,其他五个方法运行在Binder线程池里
        public boolean onCreate() {
            return false;
        }
    
        @Nullable
        @Override
        public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
            return null;
        }
    
        @Nullable
        @Override
        //用于返回Uri请求对于的MIME类型(媒体类型)
        public String getType(Uri uri) {
            return null;
        }
    
        @Nullable
        @Override
        public Uri insert(Uri uri, ContentValues values) {
            return null;
        }
    
        @Override
        public int delete(Uri uri, String selection, String[] selectionArgs) {
            return 0;
        }
    
        @Override
        public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
            return 0;
        }
    }
    

    接着我们要去注册ContentProvider

    
    <provider
        android:name=".ContentProvider_text.BookProvider"
        android:authorities="com.example.gyh.myapplication.ContentProvider_text.BookProvider"//是ContentProvider的唯一标识
        android:permission="com.example.gyh.Provider"//访问权限
        android:process=":provider"//ContentProvider运行在单独的进程中
        android:readPermission="com.example.gyh.Provider.read"//读取权限
        android:writePermission="com.example.gyh.Provider.write" //写权限   />
    

    建一个Activity

    Uri uri = Uri.parse("content://com.example.gyh.myapplication.ContentProvider_text.BookProvider");这就是xml里生命的唯一标识符
    getContentResolver().query(uri, null, null, null, null);
    getContentResolver().query(uri, null, null, null, null);
    getContentResolver().query(uri, null, null, null, null);
    

    我们可以看一下输出
    12-08 14:05:57.775 22494-22506/com.example.gyh.myapplication:provider I/Provider: query Binder_2
    12-08 14:05:57.775 22494-22505/com.example.gyh.myapplication:provider I/Provider: query Binder_1
    12-08 14:05:57.775 22494-22506/com.example.gyh.myapplication:provider I/Provider: query Binder_2

    每次线程都不一样,因为这些方法是运作在Binder线程池里的 除了onCreate是运行在主线程里,所以在onCreate是不能进行耗时操作的。


    这样简单的ContentProvider就使用成功了,但是为了更好是使用我们需要
    结合SqliteOpenHelper去创建数据库去存储数据,所以我们又建立了类去继承SqliteOpenHelper去创建数据库

    public class DbOpenHelper extends SQLiteOpenHelper {
    
        private static final String DB_NAME = "book_provider.db";//数据库名
        public static final String BOOK_TABLE_NAME = "book";//数据表名
        public static final String USER_TALBE_NAME = "user";
    
        private static final int DB_VERSION = 3;//版本号
    
        private String CREATE_BOOK_TABLE = "CREATE TABLE IF NOT EXISTS "
                + BOOK_TABLE_NAME + "(_id INTEGER PRIMARY KEY," + "name TEXT)";
    
        private String CREATE_USER_TABLE = "CREATE TABLE IF NOT EXISTS "
                + USER_TALBE_NAME + "(_id INTEGER PRIMARY KEY," + "name TEXT,"
                + "sex INT)";
    
        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) {
        //用于版本更换的时候调用
        }
    

    然后我们在对ContentProvider进行修改

    public class BookProvider extends ContentProvider {
        String TAG = "Provider";
        static String AUTHORITY = "com.example.gyh.myapplication.ContentProvider_text.BookProvider";
        private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        public static final int BOOK_URI_CODE = 0;
        public static final int USER_URI_CODE = 1;
    
        static {
            sUriMatcher.addURI(AUTHORITY, "book", BOOK_URI_CODE);
            sUriMatcher.addURI(AUTHORITY, "user", USER_URI_CODE);
    
        }
    
        Context mContext;
        SQLiteDatabase mDb;
    
    
        @Override
        //可以进行一些初始化 该方法运行在主线程里,其他五个方法运行在Binder线程池里
        public boolean onCreate() {
            mContext = getContext();
            initProviderData();
            return true;
        }
    
        private void initProviderData() {
            mDb = new DbOpenHelper(mContext).getWritableDatabase();
            mDb.execSQL("delete from " + DbOpenHelper.BOOK_TABLE_NAME);
            mDb.execSQL("delete from " + DbOpenHelper.USER_TALBE_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');");
        }
    
        @Nullable
        @Override
        public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
            Log.i(TAG, "query " + 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
        //用于返回Uri请求对于的MIME类型(媒体类型)
        public String getType(Uri uri) {
            Log.i(TAG, "getType " + Thread.currentThread().getName());
            return null;
        }
    
        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_TALBE_NAME;
                    break;
                default:
                    break;
            }
    
            return tableName;
        }
    
        @Nullable
        @Override
        public Uri insert(Uri uri, ContentValues values) {
            Log.i(TAG, "insert " + Thread.currentThread().getName());
            String tablename = getTableName(uri);
            if (tablename == null) {
                throw new IllegalArgumentException("Unsupported URI: " + uri);
            }
            Log.i(TAG, "insert table name " + tablename);
    
            mDb.insert(tablename, null, values);
            mContext.getContentResolver().notifyChange(uri, null);
            return uri;
        }
    
        @Override
        public int delete(Uri uri, String selection, String[] selectionArgs) {
            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 " + Thread.currentThread().getName());
            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(uri, null);
            }
            return row;
        }
    }
    

    对Activity进行修改

    Uri bookUri = Uri.parse("content://com.example.gyh.myapplication.ContentProvider_text.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()) {//这里的0指的是筛选的第一个条件就是_id
        Log.d(TAG, "query book:" + bookCursor.getInt(0) + "  " + bookCursor.getString(1));
    }
    bookCursor.close();
    

    意思代码完成了ContentProvider的基本使用


    对上述代码中可能大家会对Uri和UriMatcher的使用不是很了解 那么我来举个例子
    第一部初始化
    UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
    第二部将Uri和UriMatcher配对

    matcher.addURI("com.yfz.Lesson", "person/#", PEOPLE_ID);  PEOPLE_ID是code int```
    第三步我们就可以通过请求的Uri进行操作
    ```Uri uri = Uri.parse("content://" + "com.yfz.Lesson" + "/people");  
    int match = matcher.match(uri);  
           switch (match)  
           {  
               case PEOPLE:  
                   return "vnd.android.cursor.dir/people";  
               case PEOPLE_ID:  
                   return "vnd.android.cursor.item/people";  
               default:  
                   return null;  
           }  ```
    返回的结果就是"vnd.android.cursor.dir/person".
    
    我们可以看到UriMatcher作用是可以组合Uri 这里将com.yfz.Lesson和people结合content://com.yfz.Lesson/people 所以我们后面输入的Uri不再是content://com.yfz.Lesson而是content://com.yfz.Lesson/people,我们可以通过输入的Uri判断出他们的Code,进行一些判断和操作。

    相关文章

      网友评论

          本文标题:第二章 IPC机制详解(3)

          本文链接:https://www.haomeiwen.com/subject/jamzittx.html