第二章 IPC机制详解(3)
2017-02-13 10:46
381 查看
本文为Android开发艺术探索的笔记,仅供学习
本节主要讲解AIDL和ContentProvider的使用
服务端要创建一个Service去监听客户端发来的消息,然后建立一个AIDL的文件夹,将暴露给客户端的接口在AIDL文件夹里声明,最后在Service去实现这个AIDL即可。
客户端,需要绑带Service,绑带成功后把服务端返回的Binder转化为AIDL所属的类型,接着调用AIDL里的方法即可。
我们来举个例子,大致的业务逻辑就是主要有三个功能,1.客户端可以向服务端添加Book 2.客户端可以向服务端获取Book信息 3.向服务端添加Book监听,监听每次添加新Book的信息
接下来附上服务端的代码,正如前面Binder机制一样,创建AIDL业务的接口IBookManager.Stub这是运行在客户端的,然后就会自动生成方法。
下面是客户端的代码
通过服务端返回的Binder对于也就是 IBookManager.Stub()对象,里面的asInterface方法可以返回对于的AIDL接口,没印象的可以看看IPC机制详解(1),从而去调用相应的方法
那么我们就来自定义个ContentProvider,首先去建一个类叫BookProvider 继承ContentProvider,
接着我们要去注册ContentProvider
建一个Activity
我们可以看一下输出
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去创建数据库
然后我们在对ContentProvider进行修改
对Activity进行修改
意思代码完成了ContentProvider的基本使用
对上述代码中可能大家会对Uri和UriMatcher的使用不是很了解 那么我来举个例子
第一部初始化
第二部将Uri和UriMatcher配对
第三步我们就可以通过请求的Uri进行操作
返回的结果就是”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,进行一些判断和操作。
本节主要讲解AIDL和ContentProvider的使用
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", "people", PEOPLE); 这里的“people”就是Path 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机制详解(2)
- 第二章 IPC机制详解(1)
- 第二章 IPC机制详解(4)
- Android开发艺术探索笔记_第二章 IPC机制
- Android的IPC机制Binder的详解汇总
- IPC机制详解
- Android的IPC机制Binder的详解(转发)
- Android IPC机制 详解
- Android IPC机制(五):详解Bundle与“信使”——Messenger
- Android的IPC机制Binder的详解(转发)
- (转)Android IPC机制详解
- IPC机制 - Android开发艺术探索读书笔记(第二章)
- Android IPC机制详解
- Android IPC机制——Binder详解
- Android开发艺术-第二章 IPC 机制
- Android AIDL IPC机制详解
- Android IPC 机制详解
- Android IPC机制详解
- Android IPC机制详解
- Android的IPC机制Binder的详解汇总