第二章 IPC机制详解(3)

本文为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,进行一些判断和操作。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容