Android-多进程通信(二)

四、IPC方式

4.1 Bundle

Android四大组件中的三大组件Activity、Service、Receiver都是支持在Intent中传递Bundle数据的,Bundle也实现了Parcelable接口,所以可以方便的在不同的进程间传输。当一个进程通过Intent启动另一个进程的三大组件时,就可以在Bundle中附加所需要传输的信息通过Intent传输。显然,传输的信息必然得是能够被序列化的,不管是实现的Serializable接口还是Parcelable接口。

除启动三大组件时直接传递数据外,还有一种特别的使用场景。比如A进程启动B进程的一个组件时时需要将一个数据传递过去,但是此数据并不支持放入Bundle中,也就不能通过Intent来传输,而采用其他的IPC方式会略显复杂,此时就可以考虑通过放入数据获取的必要条件的Intent并启动B进程的一个Service组件,(比如IntentService,可在执行完毕后自行销毁)。然后让Service在后台对数据进行获取,获取完毕后再启动真正想要启动的B进程组件,因为Service也同样是运行在B进程的,数据可以直接传递,此操作也就解决了跨进程的问题。该方法的核心在于将数据获取的步骤转移到另一进程进行,只传递必要条件,如果必要条件也是不支持放入Bundle的,那么再往前找必要条件,但是再这么找下去反而是适得其反了,可以考虑下其他的IPC方式。

4.2 文件共享

文件共享也是一种不错的进程间通信方式,两个进程通过读/写,A进程把数据写入文件,B进程从文件读取数据。因为Android基于Linux,所以并发读/写是没有限制的,甚至同时两个线程对同一个文件进行写操作都是允许的,当然是很容易出问题的。下面的例子展示两个进程对文件的读/写达到传递数据的目的。

数据传递方

public void onIPCFileMode(View view) {
    SerializableBean bean = new SerializableBean();
    new Thread(new Runnable() {
        @Override
        public void run() {
            String dataSavePath = getCacheDir().getPath() + File.separator + "ipc_data_save";
            File dataFile = new File(dataSavePath);
            ObjectOutputStream objectOutputStream = null;
            try {
                objectOutputStream = new ObjectOutputStream(new FileOutputStream(dataFile));
                objectOutputStream.writeObject(bean);
                objectOutputStream.flush();
                startActivity(new Intent(RemoteActivity1.this, RemoteActivity2.class));
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                if (objectOutputStream != null) {
                    try {
                        objectOutputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }).start();
}

数据接收方

private void onIPCFileMode() {
    new Thread(new Runnable() {
        @Override
        public void run() {
            String dataSavePath = getCacheDir().getPath() + File.separator + "ipc_data_save";
            File dataFile = new File(dataSavePath);
            if (!dataFile.exists()) {
                return;
            }
            ObjectInputStream objectInputStream = null;
            Object result = null;
            try {
                objectInputStream = new ObjectInputStream(new FileInputStream(dataFile));
                result = objectInputStream.readObject();
                if (result instanceof SerializableBean) {
                    Log.e(TAG, "onIPCFileMode run: " + result.toString());
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (objectInputStream != null) {
                    try {
                        objectInputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }).start();
}

可以在接收方进程看到这样的log,表明数据传递成功。

E/RemoteActivity2: onIPCFileMode run: SerializableBean{mString='ABC', mInt=21, mBoolean=true, mLong=221, mShort=2, mByte=96, mChar=a}

通过这种方式共享数据对文件格式是没有具体要求的,可以文本文件,也可以是XML文件等,只要读/写双方约定数据格式即可。文件共享的方式也是有局限性的,比如并发读/写,如果并发读/写,那么读出的数据有可能不是祖新的,如果是并发写那就更完蛋了。尽量避免此情况发生或者使用线程同步限制多个线程的写操作。

既然任何文件格式都可以,那么SharedPreferences应该也是可行的。SharedPreferences是Android提供的轻量级的存储方案,通过键值对的方式存储数据,在底层是XML文件来存储键值对,理论上是完全支持的。但是由于系统对它的读/写有一定的缓存策略,即在内存中也会有一份SharedPreferences文件的缓存,因此在多进程模式下系统对其读/写就变得不可靠,当面对高并发的读/写访问时,SharedPreferences有很大几率丢失数据,因此不适合在进程间通信用SharedPreferences。SharedPreferences中是有多进程MODE的MODE_MULTI_PROCESS,但现在已经被弃用了,在Android-SharedPreferences中会有所阐述。

4.3 Messenger

介绍

Messenger轻量级的多进程通信方法,可以通过它在不同的进程中传递Message对象,而需要传递的数据就可以放在Message中。其底层实现是AIDL,可以在Messenger的构造方法中发现,与章节三中的AIDL方式一模一样。

public Messenger(Handler target) {
    mTarget = target.getIMessenger();
}

public Messenger(IBinder target) {
    mTarget = IMessenger.Stub.asInterface(target);
}

Messenger的使用方法也很简单,对AIDL作了封装使得更简单了。尤其是Messenger一次只能处理一个请求,不能并发请求,也不需要作线程同步的考虑。

使用

  1. 服务端

    在Service中创建一个继承Handler的内部类MessengerHandler,此类的作用就是处理从客户端传过来的消息,以Handler作参数创建一个Messenger,并在Service的onBind方法返回此Messenger的Binder供客户端构建Messenger使用。服务端和客户端的信息传递是用Message对象作载体,在MessengerHandler中的handleMessage方法进行处理。

    Message是肯定实现了Parcelable接口的,作为载体,Message能支持的对象传输类型,Messenger肯定也是能支持的,包括能被序列化的对象及基本类型。Message中有几个属性可以携带信息,what、arg1、arg2、Bundle、replyTo、object,其中what、arg1、arg2都是int类型可携带一些标识符啥的;replyTo是Messenger类型作用在完成一次客户端到服务端的信息传递后需要将信息再回传客户端,在让客户端将Message封装时附带上客户端的Messenger;Bundle是主要的信息传输,毕竟支持序列化对象及其他对象;object对象在跨进程传输时作用不是很大,因为object不能传递自定义的序列化对象,不光是Parcelable对象,传输Serializable对象也会导致java.lang.RuntimeException: Can't marshal non-Parcelable objects across processes.这样的异常。

    服务端代码如下:

    public class MessengerService extends Service {
    
        private static final String TAG = "MessengerService";
    
        public static final int OPERATION_GET_BOOK_LIST = 1;
        public static final int OPERATION_ADD_BOOK = 2;
    
        private ArrayList<Book> mBookList = new ArrayList<>();
        private Messenger mMessenger = new Messenger(new MessengerHandler(this));
    
        public MessengerService() {
            mBookList.add(new Book(123, "Love & Peace"));
            mBookList.add(new Book(321, "Play Boy"));
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            // TODO: Return the communication channel to the service.
    //        throw new UnsupportedOperationException("Not yet implemented");
            return mMessenger.getBinder();
        }
    
        private static class MessengerHandler extends Handler {
    
            private WeakReference<MessengerService> mWeakReference;
    
            MessengerHandler(MessengerService service) {
                mWeakReference = new WeakReference<>(service);
            }
    
            @Override
            public void handleMessage(Message msg) {
                Log.e(TAG, "handleMessage: " + msg.what);
                switch (msg.what) {
                    case OPERATION_GET_BOOK_LIST:
                        if (mWeakReference.get() != null) {
                            Bundle data = new Bundle();
                            data.putParcelableArrayList("result_get_list", mWeakReference.get().mBookList);
    
                            Message message = Message.obtain();
                            message.what = IPCActivity.RESULT_MESSENGER_BOOK_LIST;
                            message.setData(data);
    
                            if (msg.replyTo != null) {
                                try {
                                    msg.replyTo.send(message);
                                } catch (RemoteException e) {
                                    e.printStackTrace();
                                }
                            }
                        }
                        break;
                    case OPERATION_ADD_BOOK:
                        if (mWeakReference.get() != null) {
                            Bundle data = msg.getData();
                            data.setClassLoader(getClass().getClassLoader());
                            Parcelable parcelable = data.getParcelable("add_book");
                            if (parcelable instanceof Book) {
                                mWeakReference.get().mBookList.add((Book) parcelable);
                            }
                        }
                        break;
                    default:
                        super.handleMessage(msg);
                }
            }
        }
    
    }
    
  1. 客户端

    跟服务端一样,同样创建一个Handler,并作为参数传递创建一个客户端Messenger,如果客户端传递给服务端消息后需要有回执的话就将此Messenger设置在Message的replyTo属性。其他的就是正常的绑定Service,并在onServiceConnected方法中创建一个客户端持有的服务端Messenger,用来向服务端传递Message。

    传递就很简单了,不管是示例中onGetBookListByMessenger或onAddBookToListByMessenger方法都是先获取一个Message,然后塞入需要传递的对象,有回执需求就配置上replyTo,传递完就在Handler中等服务端回传的Message。有一个非常需要注意的点是从Bundle中获取自定义Parcelable对象时需要对Bundle设置一次ClassLoader,不然会找不到自定义对象报此异常android.os.BadParcelableException: ClassNotFoundException when unmarshalling: com.libok.androidnote.aidl.Book,同样的服务端的也是如此。

    客户端代码如下:

    public class IPCActivity extends AppCompatActivity {
    
        private static final String TAG = "IPCActivity";
        
        public static final int RESULT_MESSENGER_BOOK_LIST = 9876;
    
        private Messenger mServerMessenger = null;
        private ServiceConnection mMessengerConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                mServerMessenger = new Messenger(service);
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
                mServerMessenger = null;
            }
        };
    
        private Handler mMessengerHandler = new Handler(Looper.getMainLooper()) {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case RESULT_MESSENGER_BOOK_LIST:
                        Bundle data = msg.getData();
                        Log.e(TAG, "handleMessage: get class loader:" + data.getClassLoader());
                        data.setClassLoader(getClassLoader());
                        ArrayList<Parcelable> result = data.getParcelableArrayList("result_get_list");
    
                        Log.e(TAG, "handleMessage: RESULT_MESSENGER_BOOK_LIST " + result.toString());
                        break;
                    default:
                        super.handleMessage(msg);
                }
            }
        };
        private Messenger mClientMessenger = new Messenger(mMessengerHandler);
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_ipc);
        }
    
        @Override
        protected void onDestroy() {
            unbindService(mMessengerConnection);
            super.onDestroy();
        }
    
        /**
         * 绑定Messenger方式的Service
         */
        public void onBindMessengerService(View view) {
            Intent messengerServiceIntent = new Intent(this, MessengerService.class);
            bindService(messengerServiceIntent, mMessengerConnection, BIND_AUTO_CREATE);
        }
    
        /**
         * 通过Messenger添加书本
         */
        public void onAddBookToListByMessenger(View view) {
            if (mServerMessenger != null) {
                Book book = new Book(158, "声微饭否");
    
                Bundle data = new Bundle();
                data.putParcelable("add_book", book);
    
                Message addBookMessage = Message.obtain();
                addBookMessage.what = MessengerService.OPERATION_ADD_BOOK;
                addBookMessage.setData(data);
    
                try {
                    mServerMessenger.send(addBookMessage);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    
        /**
         * 通过Messenger获取所有的书本
         */
        public void onGetBookListByMessenger(View view) {
            if (mServerMessenger != null) {
                Message getListMessage = Message.obtain();
                getListMessage.what = MessengerService.OPERATION_GET_BOOK_LIST;
                getListMessage.replyTo = mClientMessenger;
    
                try {
                    mServerMessenger.send(getListMessage);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
  1. 总结

    image-20200525224623603
    graph LR
    A[Client]-->B[Message]-->C[Messenger]-->|Binder|D[Service]
    D-->F[Handler]
    D-->G[Message]-->H[replyTo Messenger]-->|Binder|A
    A-->E[Handler]
    

4.4 AIDL

在Binder章节中已经接触过AIDL,简单的接口,简单的调用,但是其实已经讲述了AIDL的基本用法,此节中除了再回顾一下基本操作还有些新东西。

AIDL简单介绍

依然是分为客户端和服务端两个部分:

服务端

创建一个Service来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在此文件中声明,最后在Service中实现AIDL接口。

客户端

需要先绑定服务端Service,在绑定成功后将服务端返回的Binder对象转成AIDL接口所属的类型,然后就能调用AIDL中的方法了。

具体使用上需要三个步骤,创建AIDL接口、实现远程服务端Service、实现客户端。

  1. 创建AIDL接口

    AIDL的创建在Binder中已经操作过,在此不再赘述,可以直接用以前的例子,这里着重讲一下AIDL本身的一些知识点。

    在AIDL中不是所有的数据类型都能用的,仅仅是以下的类型可以在AIDL中使用,包括:

    • 基本数据类型(int、long、char、Boolean、double等)
    • String和CharSequence
    • List只支持ArrayList,并且内部的所有元素都能被AIDL支持
    • Map只支持HashMap,并且内部的所有元素都能被AIDL支持,包括key和value
    • Parcelable,所有实现了Parcelable接口的对象
    • AIDL,所有的AIDL接口本身也可以在AIDL中使用
以上类型就是AIDL所支持的所有类型,其中定义的Parcelable对象和AIDL对象都需要显式import引用,不管是不是和当前AIDL文件处于同一个包下,IBookManager.aidl中有所体现。

```java
// IBookManager.aidl
package com.libok.androidnote.aidl;

// Declare any non-default types here with import statements

import com.libok.androidnote.aidl.Book;

interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
}
```



另一个需要注意的点是如果AIDL文件中用到了自定义的Parcelable对象,就必须创建一个同名的AIDL文件,并在里面声明为Parcelable类型,在Book.aidl中有所体现。

```java
package com.libok.androidnote.aidl;

parcelable Book;
```



第三点就是在AIDL文件中除了节本类型,其他类型的参数必须在参数前标上方向:in、out、inout,in表示输入型参数,out表示输出型参数,inout表示输入输出型参数。*这个留作以后有时间再作讨论*。

最后,AIDL接口有一点跟传统接口不同,就是接口中只支持方法,不支持静态常量。
  1. 实现远程服务端Service

    在Binder中也有介绍,就是一个Process属性的Service,并在其中创建一个Binder,然后在onBind方法中返回此Binder。这里着重说一下CopyOnWriteArrayList,为什么采用这个List。是因为AIDL的方法是在服务端的Binder线程池中执行的,是支持并发的,当多个线程同时访问时需要在数据端作线程同步,而CopyOnWriteArrayList本身是自带同步。

    有一个疑问是,在AIDL介绍中,AIDL只支持ArrayList但是为什么能用CopyOnWriteArrayList,是因为AIDL中所支持的是抽象的List,即List接口,因此虽然服务端返回的是CopyOnWriteArrayList,但是在Binder中会按照List的规范去访问数据并最终形成一个ArrayList传递给客户端,所以是可行的。同样的还有ConcurrentHashMap。Binder中的示例就可以在getBookList方法中打印一下返回的List确实是ArrayList。

    E/IPCActivity: onGetBookList: [Book{mBookId=123, mBookName='Love & Peace'}, Book{mBookId=321, mBookName='Play Boy'}, Book{mBookId=523, mBookName='Own Added Book523'}] ArrayList
    
  1. 实现客户端

    客户端的实现也比较简单,仅仅是绑定下服务然后挨个调用方法测试下,Binder章节中也有IPCActivity的代码。再着重重复一点的是,在客户端中,特别要注意的是尽量不要将远程调用放在主线程,因为远程调用会将当前线程先挂起,然后执行远程方法,之后再唤醒客户端线程,要是远程方法是耗时操作的话,可能会导致ANR。

进阶使用

  1. AIDL接口

    还是上面的例子,当每次addBook后只有getBookList才能获取到最新的列表,太麻烦不说还不够优雅,其实AIDL是支持AIDL接口完成Callback的。新建如下IOnNewBookListener.aidl文件。

    // IOnNewBookListener.aidl
    package com.libok.androidnote.aidl;
    
    import com.libok.androidnote.aidl.Book;
    
    interface IOnNewBookListener {
        void onNewBookArrived(in Book book);
    }
    
显然的在以前的IBookManager.aidl中需要加上注册接口和解绑接口的方法。

```java
// IBookManager.aidl
package com.libok.androidnote.aidl;

// Declare any non-default types here with import statements

import com.libok.androidnote.aidl.Book;
import com.libok.androidnote.aidl.IOnNewBookListener;

interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
    void registerListener(IOnNewBookListener listener);
    void unregisterListener(IOnNewBookListener listener);
}
```

此时就不需要用手写IBookManager的对应的Java类了,都是相似的,现在直接用系统给生成的Java类。类被改变了相应的Service中也需要做些改动,Binder中实现两个方法,新开一个Thread,每5秒添加一本书。

```java
public class BookManagerService extends Service {

    private static final String TAG = "BookManagerService";

    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
    private CopyOnWriteArrayList<IOnNewBookListener> mNewBookListeners = new CopyOnWriteArrayList<>();
    private AtomicBoolean mServiceDestroyed = new AtomicBoolean(false);

    private Binder mBinder = new IBookManager.Stub() {
        @Override
        public List<Book> getBookList() {
            Log.e(TAG, "getBookList: " + mBookList.size());
            return mBookList;
        }

        @Override
        public void addBook(Book book) {
            Log.e(TAG, "addBook: " + book.toString());
            mBookList.add(book);
        }

        @Override
        public void registerListener(IOnNewBookListener listener) throws RemoteException {
            if (!mNewBookListeners.contains(listener)) {
                mNewBookListeners.add(listener);
            }
            Log.e(TAG, "registerListener: size " + mNewBookListeners.size());
        }

        @Override
        public void unregisterListener(IOnNewBookListener listener) throws RemoteException {
            mNewBookListeners.remove(listener);
            Log.e(TAG, "unregisterListener: size " + mNewBookListeners.size());
        }
    };

    private Binder mOwnBinder = new IMyBookManager.Stub() {
        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookList;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            mBookList.add(book);
        }
    };

    public BookManagerService() {
        mBookList.add(new Book(123, "Love & Peace"));
        mBookList.add(new Book(321, "Play Boy"));
        new ServiceWorker().start();
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
//        throw new UnsupportedOperationException("Not yet implemented");
        return mBinder;
    }

    private void notifyNewBookArrived(Book newBook) throws RemoteException {
        Log.e(TAG, "notifyNewBookArrived: listener size " + mNewBookListeners.size());
        mBookList.add(newBook);
        for (IOnNewBookListener newBookListener : mNewBookListeners) {
            newBookListener.onNewBookArrived(newBook);
        }
    }

    @Override
    public void onDestroy() {
        mServiceDestroyed.set(true);
        super.onDestroy();
    }

    private class ServiceWorker extends Thread {
        @Override
        public void run() {
            super.run();
            while (!mServiceDestroyed.get()) {
                try {
                    sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                int bookId = mBookList.size();
                String bookName = ("BLEACH " + bookId);
                Book newBook = new Book(bookId, bookName);

                try {
                    notifyNewBookArrived(newBook);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
```



客户端的改动如下:

```java
public class IPCActivity extends AppCompatActivity {

    private static final String TAG = "IPCActivity";

    private static final int BOOK_START_ID = 522;
    public static final int RESULT_MESSENGER_BOOK_LIST = 9876;

    private int mBookId = BOOK_START_ID;
    private IBookManager mIBookManager = null;
    //    private IMyBookManager mOwnBookManager = null;
    private Intent mRemoteBookServiceIntent;

    private IOnNewBookListener mNewBookListener = new IOnNewBookListener.Stub() {
        @Override
        public void onNewBookArrived(Book book) throws RemoteException {
            Log.e(TAG, "onNewBookArrived: " + book);
        }
    };

    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            Log.e(TAG, "binderDied: ");
//            if (mOwnBookManager == null) {
//                return;
//            }
//            mOwnBookManager.asBinder().unlinkToDeath(this, 0);
//            mOwnBookManager = null;
            if (mIBookManager == null) {
                return;
            }
            mIBookManager.asBinder().unlinkToDeath(this, 0);
            mIBookManager = null;
            bindService(mRemoteBookServiceIntent, mBookServiceConnection, Context.BIND_AUTO_CREATE);
            // 其他操作
        }
    };

    private ServiceConnection mBookServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mIBookManager = IBookManager.Stub.asInterface(service);
//            mOwnBookManager = IMyBookManager.Stub.asInterface(service);
            try {
                service.linkToDeath(mDeathRecipient, 0);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e(TAG, "onServiceDisconnected: ");
        }
    };

    private Messenger mServerMessenger = null;
    private ServiceConnection mMessengerConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mServerMessenger = new Messenger(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mServerMessenger = null;
        }
    };

    private Handler mMessengerHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case RESULT_MESSENGER_BOOK_LIST:
                    Bundle data = msg.getData();
                    Log.e(TAG, "handleMessage: get class loader:" + data.getClassLoader());
                    data.setClassLoader(getClassLoader());
                    ArrayList<Parcelable> result = data.getParcelableArrayList("result_get_list");

                    Log.e(TAG, "handleMessage: RESULT_MESSENGER_BOOK_LIST " + result.toString());
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    };
    private Messenger mClientMessenger = new Messenger(mMessengerHandler);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ipc);
    }

    /**
     * 绑定AIDL方式的Service
     */
    public void onBindAIDLBookManagerService(View view) {
        mRemoteBookServiceIntent = new Intent(this, BookManagerService.class);
        bindService(mRemoteBookServiceIntent, mBookServiceConnection, Context.BIND_AUTO_CREATE);
    }

    public void onGetBookList(View view) {
        if (mIBookManager != null) {
            try {
                List<Book> bookList = mIBookManager.getBookList();
                Log.e(TAG, "onGetBookList: " + bookList.toString());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
//        if (mOwnBookManager != null) {
//            try {
//                List<Book> bookList = mOwnBookManager.getBookList();
//                Log.e(TAG, "onGetBookList: " + bookList.toString() + " " + bookList.getClass().getSimpleName());
//            } catch (RemoteException e) {
//                e.printStackTrace();
//            }
//        }
    }

    public void onAddBookToList(View view) {
        if (mIBookManager != null) {
            try {
                mBookId++;
                mIBookManager.addBook(new Book(mBookId, "Added Book" + mBookId));
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
//        if (mOwnBookManager != null) {
//            try {
//                mBookId++;
//                mOwnBookManager.addBook(new Book(mBookId, "Own Added Book" + mBookId));
//            } catch (RemoteException e) {
//                e.printStackTrace();
//            }
//        }
    }

    /**
     * 注册AIDL接口
     */
    public void onRegisterListener(View view) {
        try {
            mIBookManager.registerListener(mNewBookListener);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    /**
     * 解绑AIDL接口
     */
    public void onUnRegisterListener(View view) {
        try {
            mIBookManager.unregisterListener(mNewBookListener);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onDestroy() {
//        mOwnBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
        if (mIBookManager != null && mIBookManager.asBinder().isBinderAlive()) {
            mIBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
            try {
                mIBookManager.unregisterListener(mNewBookListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        unbindService(mBookServiceConnection);
        unbindService(mMessengerConnection);
        super.onDestroy();
    }

    /**
     * 绑定Messenger方式的Service
     */
    public void onBindMessengerService(View view) {
        Intent messengerServiceIntent = new Intent(this, MessengerService.class);
        bindService(messengerServiceIntent, mMessengerConnection, BIND_AUTO_CREATE);
    }

    /**
     * 通过Messenger添加书本
     */
    public void onAddBookToListByMessenger(View view) {
        if (mServerMessenger != null) {
            Book book = new Book(158, "声微饭否");

            Bundle data = new Bundle(getClassLoader());
            data.putParcelable("add_book", book);

            Message addBookMessage = Message.obtain();
            addBookMessage.what = MessengerService.OPERATION_ADD_BOOK;
            addBookMessage.setData(data);

            try {
                mServerMessenger.send(addBookMessage);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 通过Messenger获取所有的书本
     */
    public void onGetBookListByMessenger(View view) {
        if (mServerMessenger != null) {
            Message getListMessage = Message.obtain();
            getListMessage.what = MessengerService.OPERATION_GET_BOOK_LIST;
            getListMessage.replyTo = mClientMessenger;

            try {
                mServerMessenger.send(getListMessage);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }
}
```

当绑定Service点击注册Listener完成后,会收到每5秒返回的newBook。Log如下:

```java
2020-03-15 17:40:07.306 28248-28293/com.libok.androidnote E/IPCActivity: onNewBookArrived: Book{mBookId=2, mBookName='BLEACH 2'}
2020-03-15 17:40:12.308 28248-28293/com.libok.androidnote E/IPCActivity: onNewBookArrived: Book{mBookId=3, mBookName='BLEACH 3'}
2020-03-15 17:40:17.311 28248-28384/com.libok.androidnote E/IPCActivity: onNewBookArrived: Book{mBookId=4, mBookName='BLEACH 4'}
2020-03-15 17:40:22.313 28248-28384/com.libok.androidnote E/IPCActivity: onNewBookArrived: Book{mBookId=5, mBookName='BLEACH 5'}
2020-03-15 17:40:27.316 28248-28384/com.libok.androidnote E/IPCActivity: onNewBookArrived: Book{mBookId=6, mBookName='BLEACH 6'}
2020-03-15 17:40:32.318 28248-28384/com.libok.androidnote E/IPCActivity: onNewBookArrived: Book{mBookId=7, mBookName='BLEACH 7'}
2020-03-15 17:40:37.319 28248-28384/com.libok.androidnote E/IPCActivity: onNewBookArrived: Book{mBookId=8, mBookName='BLEACH 8'}
2020-03-15 17:40:42.322 28248-28384/com.libok.androidnote E/IPCActivity: onNewBookArrived: Book{mBookId=9, mBookName='BLEACH 9'}
2020-03-15 17:40:47.324 28248-28384/com.libok.androidnote E/IPCActivity: onNewBookArrived: Book{mBookId=10, mBookName='BLEACH 10'}
```



但是当点击解绑Listener时可以发现在服务端并不会删除此Listener,点击后Log如下:

```java
E/BookManagerService: unregisterListener: size 1
```

仔细想想出现这种情况也是合理的,就像是服务端Service返回的Binder,和在客户端Activity中收到的Binder并不是同一个,所以才能根据此特性在asInterface时去区分是客户端还是服务端的Binder。因此在服务端注册解绑时从客户端传过来的肯定不是同一个Listener,所以也就无法正确的解绑。要想正确的解绑就需要用到一个特殊的“List”——RemoteCallbackList。



RemoteCallbackList是系统专门提供的用于删除跨进程listener的接口。RemoteCallbackList是一个泛型,支持管理任何的AIDL接口,从此类的声明上即可看出。

```java
public class RemoteCallbackList<E extends IInterface>
```

这个类的工作原理很简单,内部有一个以IBinder作Key,以Callback作Value的ArrayMap,用来保存所有的Callback,AIDL回调最终是以Callback形式保存,在创建Callback时需要将AIDL接口传入。以Callback作Map的Value可以理解,但是为什么是IBinder作Key。*盲生你发现了华点*。这就涉及到了跨进程的另一个特性,虽然说每次从客户端传递过来的Listener都是新的,但是这些Listener都具有一个特点,那就是Listener的asBinder返回的都是同一个Binder。这是可以通过打印Binder验证的,但是并不是很想动手。

RemoteCallbackList还有一个很有用的功能就是当客户端进程终止后,会自动的将此客户端注册的Listener给删除。这是因为Callback实现了Binder的死亡代理,当进程死亡Binder也死亡时会回调binderDied方法,继而会自动删除相应的AIDL接口。最后一点RemoteCallbackList内部是是实现了线程同步的,无需做额外线程同步工作。



所以Service中mBinder又需要作以下的改动:

```java
@Override
public void registerListener(IOnNewBookListener listener) throws RemoteException {
      if (!mNewBookListeners.contains(listener)) {
          mNewBookListeners.add(listener);
      }
    mRemoteCallbackList.register(listener);
    Log.e(TAG, "registerListener: size " + mRemoteCallbackList.getRegisteredCallbackCount());
}

@Override
public void unregisterListener(IOnNewBookListener listener) throws RemoteException {
    mRemoteCallbackList.unregister(listener);
    Log.e(TAG, "unregisterListener: size " + mRemoteCallbackList.getRegisteredCallbackCount());
}
```



notifyNewBookArrived方法作以下改动:

```java
private void notifyNewBookArrived(Book newBook) throws RemoteException {
    Log.e(TAG, "notifyNewBookArrived: listener size " + mNewBookListeners.size());
    mBookList.add(newBook);
    //        for (IOnNewBookListener newBookListener : mNewBookListeners) {
    //            newBookListener.onNewBookArrived(newBook);
    //        }
    //        for (int i = 0; i < mRemoteCallbackList.getRegisteredCallbackCount(); i++) {
    //            mRemoteCallbackList.getRegisteredCallbackItem()
    //        }
    int callbackCount = mRemoteCallbackList.beginBroadcast();
    for (int i = 0; i < callbackCount; i++) {
        mRemoteCallbackList.getBroadcastItem(i).onNewBookArrived(newBook);
    }
    mRemoteCallbackList.finishBroadcast();
}
```

RemoteCallbackList的遍历有些别致,需要先调用beginBroadcast方法获取callback总数,然后调用getBroadcastItem方法获取回调,最后需要调用finishBroadcast方法清理内部的临时变量。其中**beginBroadcast方法和finishBroadcast方法必须成对出现**,否则会报异常,看内部源码就能看出所以然来,不再赘述。

**题外话:**但是有一点不是很懂,不管是获取当前RemoteCallbackList中回调的数量还是获取回调都有两种方法。获取数量可以成对的调用beginBroadcast,也可以调用getRegisteredCallbackCount,同样都是获取的内部Map的数量,但是前者是将Map中的所有回调保存到一个临时**数组变量**mActiveBroadcast中,后者是直接返回Map的size。获取回调还是可以调用beginBroadcast,然后调用getBroadcastItem方法获取回调,也可以直接调用getRegisteredCallbackItem方法获取,但是这个方法是需要API26才能使用的,前者还是先将Map中的回调保存到mActiveBroadcast中,然后从此数组中获取,后者直接从Map中获取。所以问题来了,这两种方式有什么不同,看了一顿方法注释也没看懂。。**太过愚钝,希望有懂的人给讲一讲**。



至此,Listener就可以正确的解绑了,AIDL接口也全部介绍完毕。



但是还有两点需要**注意**:方法运行在哪个线程问题,注意区分主线程和Binder线程,注意主线程中尽量不要出现的远程操作及耗时操作,注意Binder线程不要访问UI。具体的可以在不明白的方法内部打印当前线程。Binder的意外死亡问题,可以为Binder设置死亡代理,前面Binder中已经说过,在死亡代理的回调中重新绑定服务。另外一种方法是在ServiceConnection中的onServiceDisconnected方法中重新绑定服务,区别就是Binder死亡代理是运行在Binder线程池中的,onServiceDisconnected方法是运行在主线程的。
  1. 权限验证

    权限验证的方法很多,在此只介绍几种。

    • 在Service的onBind方法中通过Manifest文件的permission进行验证

      首先在Manifest文件中创建一个自定义的permission如下:

      <permission
          android:name="com.libok.androidnote.aidl.IBookManager.permission.ACCESS_BIND_SERVICE"
          android:protectionLevel="normal" />
      

      并在uses-permission中声明此权限

      <uses-permission android:name="com.libok.androidnote.aidl.IBookManager.permission.ACCESS_BIND_SERVICE" />
      

      然后就可以在Service的onBind方法进行验证了。

      @Override
      public IBinder onBind(Intent intent) {
          int checkCallingOrSelfPermission = checkCallingOrSelfPermission("com.libok.androidnote.aidl.IBookManager.permission.ACCESS_BIND_SERVICE");
          Log.e(TAG, "onBind: " + checkCallingOrSelfPermission);
          if (checkCallingOrSelfPermission == PackageManager.PERMISSION_DENIED) {
              return null;
          }
          return mBinder;
      }
      

      至于权限应该配置在客户端还是服务端以及checkCallingOrSelfPermission方法的用法在此方法的注释中已经说的很清楚了。

      Determine whether the calling process of an IPC <em>or you</em> have been
      granted a particular permission.  This is the same as
      {@link #checkCallingPermission}, except it grants your own permissions
      if you are not currently processing an IPC.  Use with care!
      

      显然需要客户端配置声明权限。

*   在服务端的onTransact方法中作权限验证

    在此方法中既可以用permission验证也可以通过getCallingUid和getCallingPid获取到客户端所属应用的UID和PID作验证。验证不成功直接返回false,表示不允许调用远程方法。以下为示例:

    ```java
    private Binder mBinder = new IBookManager.Stub() {
        @Override
        public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            int checkCallingOrSelfPermission = checkCallingOrSelfPermission("com.libok.androidnote.aidl.IBookManager.pe
            if (checkCallingOrSelfPermission == PackageManager.PERMISSION_DENIED) {
                return false;
            }
            String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
            Log.e(TAG, "onTransact: " + Arrays.toString(packages));
            String packageName = null;
            if (packages != null && packages.length > 0) {
                packageName = packages[0];
            }
            if (TextUtils.isEmpty(packageName) || !TextUtils.equals(packageName, "com.libok.androidnote")) {
                return false;
            }
            return super.onTransact(code, data, reply, flags);
        }
                                                                            
        @Override
        public List<Book> getBookList() {
            Log.e(TAG, "getBookList: " + mBookList.size());
            return mBookList;
        }
                                                                            
        @Override
        public void addBook(Book book) {
            Log.e(TAG, "addBook: " + book.toString());
            mBookList.add(book);
        }
                                                                            
        @Override
        public void registerListener(IOnNewBookListener listener) throws RemoteException {
              if (!mNewBookListeners.contains(listener)) {
                  mNewBookListeners.add(listener);
              }
            mRemoteCallbackList.register(listener);
            Log.e(TAG, "registerListener: size " + mRemoteCallbackList.getRegisteredCallbackCount());
        }
                                                                            
        @Override
        public void unregisterListener(IOnNewBookListener listener) throws RemoteException {
            mRemoteCallbackList.unregister(listener);
            Log.e(TAG, "unregisterListener: size " + mRemoteCallbackList.getRegisteredCallbackCount());
        }
    };
    ```

4.5 ContentProvider

ContentProvider是Android中提供的专门用于不同应用间进行数据共享的方式,从这一点上看,天生就适合进程间通信。和Messenger一样,ContentProvider的底层实现同样也是Binder。但使用过程要比AIDL简单些,系统已经做好了封装,可以无需关注底层即可轻松实现进程间通信。

系统预置了很多ContentProvider,比如通讯录、短信、日程表等,想要访问这些信息必然是跨进程的,而且只需要通过ContentProvider的query、update、insert、delete方法即可。接下来通过创建一个ContentProvider来看看如何进行进程间通信。

新建一个ContentProvider很简单,只需要继承ContentProvider并实现六个抽象方法即可:onCreate、getType、insert、delete、update、query。onCreate方法就是ContentProvider创建时调用;getType用来返回一个Uri请求的MIME类型,详细的参考MIME 参考手册;剩下的四个方法就是CRUD操作。根据Binder的原理,六个方法都是运行在ContentProvider进程,除onCreate方法由系统回调运行在主线程外,其余五个方法都是运行在Binder线程。

ContentProvider主要是以表格的形式组织数据,并且可以包含多个表,对于每个表来说,它们都具有行和列的层次性,行往往代表一条记录,列往往对应一条记录中的一个字段,跟数据库的表类似。除了表格形式外,ContentProvider还支持文件数据,比如图片、视频等。文件数据和表数据的结构不同,因此处理这类数据时可以在ContentProvider中返回文件的句柄给外界,让外界来访问文件信心。Android系统中所提供的MediaStore就是文件类型的ContentProvider,可供参考。另外,虽然ContentProvider的底层数据看起来很像SQLite数据库,但是ContentProvider对底层的数据存储方式并没有任何要求,既可以是SQLite数据库,也可以是普通文件,json、xml等的都可以,只要是符合需求。

ContentProvider示例:

public class BookProvider extends ContentProvider {

    private static final String TAG = "BookProvider";
    
    @Override
    public boolean onCreate() {
        Log.e(TAG, "onCreate: " + Thread.currentThread().getName());
        return true;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        Log.e(TAG, "query: " + Thread.currentThread().getName());
        return null;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        Log.e(TAG, "getType: ");
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        Log.e(TAG, "insert: " + Thread.currentThread().getName());
        return null;
    }
    
    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        Log.e(TAG, "delete: " + Thread.currentThread().getName());
        return 0;
    }
    
    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        Log.e(TAG, "update: " + Thread.currentThread().getName());
        return 0;
    }
    
}

当然除了新建类之外还得在Manifest中声明注册这个ContentProvider。

<provider
    android:name=".provider.BookProvider"
    android:authorities="com.libok.androidnote.provider"
    android:permission="com.libok.androidnote.BOOK_PROVIDER"
    android:process=":provider" />

android:authorities是ContentProvider的唯一标识,通过这个属性外部应用就可以访问我们的BookProvider,最好在命名时加上包名以示区分。

android:permission表示要想访问ContentProvider,就必须声明com.libok.androidnote.BOOK_PROVIDER这个权限。除了permission外,ContentProvider还细分的有写权限android:writePermission以及读权限android:readPermission,只有正确声明才能正确的访问。

Activity访问示例:

public class IPCActivity extends AppCompatActivity {

    private static final String TAG = "IPCActivity";

    private Uri mProviderUri = Uri.parse("content://com.libok.androidnote.provider");

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ipc);
    }

    /**
     * ContentProvider执行新增
     */
    public void onContentProviderAdd(View view) {
        getContentResolver().insert(mProviderUri, null);
    }
    
    /**
     * ContentProvider执行删除
     */
    public void onContentProviderDelete(View view) {
        getContentResolver().delete(mProviderUri, null, null);
    }
    
    /**
     * ContentProvider执行修改
     */
    public void onContentProviderUpdate(View view) {
        getContentResolver().update(mProviderUri, null, null, null);
    }
    
    /**
     * ContentProvider执行查询
     */
    public void onContentProviderQuery(View view) {
        Cursor cursor = null;
        try {
            cursor = getContentResolver().query(mProviderUri, null, null, null, null);
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
    }
}

当然现在的insert和update操作还是会引起程序异常,但是执行delete和query方法可以从log发现onCreate方法确实是在main线程,不能做耗时操作,而其他的方法都是在Binder线程。

E/BookProvider: onCreate: main
E/BookProvider: query: Binder:1399_3
E/BookProvider: delete: Binder:1399_3

流程现在是能够跑通了,现在再为ContentProvider提供一个SQLite数据库作为底层数据存储来实现ContentProvider的跨进程通信,当然前面也说过不一定非得是数据库,其他的能存储数据的能够进行数据处理的都是可以的。

MySQLiteHelper

public class MySQLiteHelper extends SQLiteOpenHelper {

    private static final String TAG = "MySQLiteHelper";

    private static final int DB_VERSION = 1;
    private static final String DB_NAME = "test_content_provider.db";
    public static final String BOOK_TABLE_NAME = "book";
    public static final String USER_TABLE_NAME = "user";

    public static final String ID = "_id";
    public static final String BOOK_ID = "book_id";
    public static final String BOOK_NAME = "name";
    public static final String USER_ID = "user_id";
    public static final String USER_NAME = "name";
    public static final String USER_SEX = "sex";

    private static final String CREATE_BOOK_TABLE = "CREATE TABLE IF NOT EXISTS " + BOOK_TABLE_NAME + "(" +
            ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
            BOOK_ID + " INTEGER, " +
            BOOK_NAME + " TEXT" +
            ")";
    private static final String CREATE_USER_TABLE = "CREATE TABLE IF NOT EXISTS " + USER_TABLE_NAME + "(" +
            ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
            USER_ID + " INTEGER, " +
            USER_NAME + " TEXT, " +
            USER_SEX + " INT" +
            ")";

    private static MySQLiteHelper sHelper = null;

    public static SQLiteDatabase getInstance(Context context) {
        if (sHelper == null) {
            synchronized (MySQLiteHelper.class) {
                if (sHelper == null) {
                    sHelper = new MySQLiteHelper(context, DB_NAME, null, DB_VERSION);
                }
            }
        }

        return sHelper.getWritableDatabase();
    }

    private MySQLiteHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, 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来操作。

相应的provider也要作相应更改:

  1. 用UriMatcher作Uri匹配,并在类初始化时添加匹配规则
  2. getTableName方法用作获取Uri匹配的表名

其他的就是对数据库的操作,并无特别之处,当Provider进行insert、update、delete时如果需要知晓数据发生了改变,可以针对操作的Uri作监听,即通过ContentResolver的registerContentObserver方法注册观察者,通过unregisterContentObserver来解除观察者。

public class BookProvider extends ContentProvider {

    private static final String TAG = "BookProvider";

    public static final String AUTHORITIES = "com.libok.androidnote.provider";
    public static final Uri CONTENT_BOOK_URI = Uri.parse("content://" + AUTHORITIES + "/book");
    public static final Uri CONTENT_USER_URI = Uri.parse("content://" + AUTHORITIES + "/user");

    public static final int CODE_BOOK = 0;
    public static final int CODE_USER = 1;

    private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    static {
        sUriMatcher.addURI(AUTHORITIES, "book", CODE_BOOK);
        sUriMatcher.addURI(AUTHORITIES, "user", CODE_USER);
    }

    private SQLiteDatabase mDatabase;
    private Context mContext;

    @Override
    public boolean onCreate() {
        Log.e(TAG, "onCreate: " + Thread.currentThread().getName());
        mContext = getContext();
        mDatabase = MySQLiteHelper.getInstance(mContext);
        return true;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        Log.e(TAG, "query: " + Thread.currentThread().getName());
        String tableName = getTableName(uri);
        if (!TextUtils.isEmpty(tableName)) {
            return mDatabase.query(tableName, projection, selection, selectionArgs, null, null, sortOrder);
        }
        return null;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        Log.e(TAG, "getType: ");
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        Log.e(TAG, "insert: " + Thread.currentThread().getName());
        String tableName = getTableName(uri);
        if (!TextUtils.isEmpty(tableName)) {
            mDatabase.insert(tableName, null, values);
            mContext.getContentResolver().notifyChange(uri, null);
            return uri;
        }
        return null;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        Log.e(TAG, "delete: " + Thread.currentThread().getName());
        String tableName = getTableName(uri);
        if (!TextUtils.isEmpty(tableName)) {
            int count = mDatabase.delete(tableName, selection, selectionArgs);
            if (count > 0) {
                mContext.getContentResolver().notifyChange(uri, null);
            }
            return count;
        }
        return -1;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        Log.e(TAG, "update: " + Thread.currentThread().getName());
        String tableName = getTableName(uri);
        if (!TextUtils.isEmpty(tableName)) {
            int row = mDatabase.update(tableName, values, selection, selectionArgs);
            if (row > 0) {
                mContext.getContentResolver().notifyChange(uri, null);
            }
            return row;
        }
        return -1;
    }

    private String getTableName(Uri uri) {
        String tableName = null;
        switch (sUriMatcher.match(uri)) {
            case CODE_BOOK:
                tableName = MySQLiteHelper.BOOK_TABLE_NAME;
                break;
            case CODE_USER:
                tableName = MySQLiteHelper.USER_TABLE_NAME;
                break;
        }

        return tableName;
    }
}

补充:具体操作ContentProvider

/**
 * ContentProvider执行新增
 */
public void onContentProviderAdd(View view) {
    ContentValues bookValues = new ContentValues();
    bookValues.put(MySQLiteHelper.BOOK_ID, mBookId);
    bookValues.put(MySQLiteHelper.BOOK_NAME, "BLEACH " + mBookId);
    getContentResolver().insert(BookProvider.CONTENT_BOOK_URI, bookValues);
    mSavedBookIdList.add(mBookId);
    Log.e(TAG, "onContentProviderAdd: " + mSavedBookIdList);
    mBookId++;
    ContentValues userValues = new ContentValues();
    userValues.put(MySQLiteHelper.USER_ID, mBookId);
    userValues.put(MySQLiteHelper.USER_NAME, "BLEACH " + mBookId);
    userValues.put(MySQLiteHelper.USER_SEX, mBookId % 2);
    getContentResolver().insert(BookProvider.CONTENT_USER_URI, userValues);
    mBookId++;
}
/**
 * ContentProvider执行删除
 */
public void onContentProviderDelete(View view) {
    if (!mSavedBookIdList.isEmpty()) {
        int bookId = mSavedBookIdList.remove(0);
        int count = getContentResolver().delete(BookProvider.CONTENT_BOOK_URI, MySQLiteHelper.BOOK_ID + " = ?", new String[]{St
        Log.e(TAG, "onContentProviderDelete: delete count " + count);
    } else {
        Log.e(TAG, "onContentProviderDelete: 当前并没有存储Book");
    }
}
/**
 * ContentProvider执行修改
 */
public void onContentProviderUpdate(View view) {
    if (!mSavedBookIdList.isEmpty()) {
        int bookId = mSavedBookIdList.remove(0);
        ContentValues updateValues = new ContentValues();
        updateValues.put(MySQLiteHelper.BOOK_NAME, "FIRE " + bookId);
        int row = getContentResolver().update(BookProvider.CONTENT_BOOK_URI, updateValues, MySQLiteHelper.BOOK_ID + " = ?", new
        Log.e(TAG, "onContentProviderUpdate: update row " + row);
    } else {
        Log.e(TAG, "onContentProviderDelete: 当前并没有存储Book");
    }
}
/**
 * ContentProvider执行查询
 */
public void onContentProviderQuery(View view) {
    Cursor bookCursor = null;
    Cursor userCursor = null;
    StringBuilder stringBuilder = new StringBuilder();
    mSavedBookIdList.clear();
    try {
        bookCursor = getContentResolver().query(BookProvider.CONTENT_BOOK_URI, new String[]{MySQLiteHelper.BOOK_ID, MySQLiteHel
        stringBuilder.append("[\n");
        if (bookCursor != null) {
            while (bookCursor.moveToNext()) {
                int bookId = bookCursor.getInt(bookCursor.getColumnIndex(MySQLiteHelper.BOOK_ID));
                mSavedBookIdList.add(bookId);
                stringBuilder.append("\tbook id=")
                        .append(bookId)
                        .append(" name=")
                        .append(bookCursor.getString(bookCursor.getColumnIndex(MySQLiteHelper.BOOK_NAME)))
                        .append("\n");
            }
            Log.e(TAG, "onContentProviderQuery: " + mSavedBookIdList);
        }
        stringBuilder.append("]\n\n");
        userCursor = getContentResolver().query(BookProvider.CONTENT_USER_URI, new String[]{MySQLiteHelper.USER_ID, MySQLiteHel
        stringBuilder.append("[\n");
        if (userCursor != null) {
            while (userCursor.moveToNext()) {
                stringBuilder.append("\tuser id=")
                        .append(userCursor.getInt(userCursor.getColumnIndex(MySQLiteHelper.USER_ID)))
                        .append(" name=")
                        .append(userCursor.getString(userCursor.getColumnIndex(MySQLiteHelper.USER_NAME)))
                        .append(" sex=")
                        .append(userCursor.getInt(userCursor.getColumnIndex(MySQLiteHelper.USER_SEX)) == 0 ? "女" : "男")
                        .append("\n");
            }
        }
        stringBuilder.append("]\n");
    } finally {
        if (bookCursor != null) {
            bookCursor.close();
        }
        if (userCursor != null) {
            userCursor.close();
        }
    }
    mShowQueryText.setText(stringBuilder.toString());
}

4.6 Socket

Socket—套接字,是网络通信中的概念,分为流式套接字和用户数据报套接字,分别对应网络的传输控制层中的TCP和UDP协议。TCP协议是面向连接的协议,提供稳定的双向通信功能,TCP连接的建立需要经过”三次握手“才能完成,为了提供稳定的数据传输功能,其本身提供了超时重传机制,因此具有很高的稳定性;而UDP是无连接的,提供不稳定的通信功能。但在性能上UDP具有更好的效率,缺点是不能够保证正确传输,尤其是在网络拥堵的情况下。

服务端Service

public class SocketService extends Service {

    private static final String TAG = "SocketService";

    public SocketService() {
        initSocketServer();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    private void initSocketServer() {
        new Thread() {
            @Override
            public void run() {
                ServerSocket serverSocket = null;
                try {
                    serverSocket = new ServerSocket(7873);
                    Log.e(TAG, "initSocketServer: wait client accept...");
                    Socket client = serverSocket.accept();
                    Log.e(TAG, "initSocketServer: client accepted");
                    handleClient(client);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }

    private void handleClient(Socket client) throws IOException {
        PrintWriter printWriter = new PrintWriter(client.getOutputStream(), true);
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(client.getInputStream()));

        String line = bufferedReader.readLine();
        Log.e(TAG, "handleClient: receive client:" + line);

        printWriter.println("Hello, this is server.");
    }
}

客户端

public class IPCActivity extends AppCompatActivity {

    private static final String TAG = "IPCActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ipc);
    }

    /**
     * 绑定socket
     *
     * @param view
     */
    public void onStartRemoteServer(View view) {
        startService(new Intent(this, SocketService.class));
    }

    /**
     * 给服务端发送消息
     *
     * @param view
     */
    public void onSendToServer(View view) {
        new Thread() {
            @Override
            public void run() {
                try {
                    Socket socket = new Socket("localhost", 7873);
                    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                    PrintWriter printWriter = new PrintWriter(socket.getOutputStream(), true);

                    printWriter.println("Hello, this is client.");

                    String line = bufferedReader.readLine();
                    Log.e(TAG, "onBindSocket: from server:" + line);

                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }.start();

    }
}

麻雀虽小五脏俱全,简单的小栗子,客户端和服务端通一次信就拉倒,也并没有对资源进行释放,仅仅是示例。当然能传递的信息并非只有字符串,只要能进行网络传输的都能行得通。

五、Binder连接池

Binder连接池的作用是在多个业务模块下需要多个AIDL文件中体现的。按照普通的思路来说是单个AIDL对应单个Service,但是当AIDL越来越多时,这么不计数量的创建Service并不是一个好办法,而且也并不是只有一个AIDL对应一个Service的方法,而且多次创建Service是重复性质的,Service的绑定也是重复的。所以可以采用一种更简洁的方法去管理多个AIDL,将AIDL升华一下,用一个AIDL总管去管理将所有的AIDL,这样就可以将所有的AIDL放在同一个Service中,并且通过识别码的形式去按需获取AIDL。

5.1 BinderPool

新建一个BinderPool AIDL文件,当Activity绑定Service时,返回的是此AIDL的Binder,然后通过调用queryBinder方法查找另一个进程真正所需的Binder。

package com.libok.androidnote.aidl;


interface IBinderPool {
    IBinder queryBinder(in int binderCode);
}

此AIDL所对应的Service跟普通的多进程Service也是如出一辙。

public class BinderPoolService extends Service {

    private static final String TAG = "BinderPoolService";

    public static final int BINDER_CODE_SECURITY = 0;
    public static final int BINDER_CODE_COMPUTE = 1;

    private BinderPoolImpl mBinderPool = new BinderPoolImpl();

    public BinderPoolService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinderPool;
    }

    private static class BinderPoolImpl extends IBinderPool.Stub {

        @Override
        public IBinder queryBinder(int binderCode) throws RemoteException {
            IBinder binder = null;
            switch (binderCode) {
                case BINDER_CODE_SECURITY:
                    binder = new SecurityCenterImpl();
                    break;
                case BINDER_CODE_COMPUTE:
                    binder = new ComputeImpl();
                    break;
            }
            return binder;
        }
    }

}

但是接下来就大相径庭了,首先最大的不同就是本来在Activity中进行的绑定Service现在需要用一个单例去绑定管理,即BinderPool。

public static class BinderPool {

    private static final String TAG = "BinderPool";

    private static BinderPool sBinderPool = null;

    private Context mContext;
    private CountDownLatch mCountDownLatch;
    private IBinderPool mBinderPool = null;

    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mBinderPool = BinderPoolImpl.asInterface(service);
            try {
                mBinderPool.asBinder().linkToDeath(mDeathRecipient, 0);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            Log.e(TAG, "onServiceConnected: bind success " + Thread.currentThread().getName() + " " + android.os.Process.myPid());
            mCountDownLatch.countDown();
            Log.e(TAG, "onServiceConnected: count down");
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };

    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            mBinderPool.asBinder().unlinkToDeath(this, 0);
            mBinderPool = null;
            bindService();
        }
    };

    private BinderPool(Context context) {
        mContext = context.getApplicationContext();
        bindService();
    }

    public static BinderPool getInstance(Context context) {
        if (sBinderPool == null) {
            synchronized (BinderPool.class) {
                if (sBinderPool == null) {
                    sBinderPool = new BinderPool(context);
                }
            }
        }

        return sBinderPool;
    }

    private synchronized void bindService() {
        mCountDownLatch = new CountDownLatch(1);
        mContext.bindService(new Intent(mContext, BinderPoolService.class), mServiceConnection, Context.BIND_AUTO_CREATE);
        Log.e(TAG, "bindService: start " + Thread.currentThread().getName());
        try {
            mCountDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Log.e(TAG, "bindService: finished " + Thread.currentThread().getName() + " " + android.os.Process.myPid());
    }

    public IBinder queryBinder(int bindCode) throws RemoteException {
        IBinder binder = null;

        if (mBinderPool != null) {
            binder = mBinderPool.queryBinder(bindCode);
        }

        return binder;
    }
}

首先,这个类是单例类,全局用一个类去管理,当然此类是运行在主进程的。

其次,获取单例之后再bindService,而且此bind非彼bind,是一个附加了些额外操作的套娃方法,在方法中创建一个同步辅助,并在真正的Context执行BindService方法后暂停当前线程,等待服务的成功绑定,因为绑定的过程可能会耗费很长时间。

最后,等mBinderPool变量被赋值后,即可向外部提供queryBinder服务。

实际提供服务的两个AIDL,这两个AIDL无需过多解释,与普通的AIDL服务大同小异,只是在需要时再进行懒加载。

package com.libok.androidnote.aidl;

interface ICompute {
    int add(in int a, in int b);
}
package com.libok.androidnote.aidl;


interface ISecurityCenter {
    String encrypt(in String content);
    String decrypt(in String password);
}

具体实现:

public static class ComputeImpl extends ICompute.Stub {
    @Override
    public int add(int a, int b) throws RemoteException {
        return a + b;
    }
}
public static 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);
    }
}

在queryBinder方法中按需返回远程Binder。

BinderPool类的所有方法都是运行在主进程,调用另外进程的是queryBinder方法中的binder = mBinderPool.queryBinder(bindCode);,mBinderPool变量在BinderPoolImpl绑定成功后赋值,并在之后提供另外进程向主进程返回Binder的服务。

5.2 使用

经过上面的代码逻辑,在使用上肯定会跟普通的多进程通信有点差别。下面是Activity的代码:

public class BinderPoolActivity extends AppCompatActivity {

    private static final String TAG = "BinderPoolActivity";

    private BinderPoolService.BinderPool mBinderPool;

    private String mContent = "QWER";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_binder_pool);
    }

    public void onBindBinderPool(View view) {
        new Thread(){
            @Override
            public void run() {
                Log.e(TAG, "run: ");
                mBinderPool = BinderPoolService.BinderPool.getInstance(BinderPoolActivity.this);
                Log.e(TAG, "run: finish");
            }
        }.start();
//        mBinderPool = BinderPoolService.BinderPool.getInstance(BinderPoolActivity.this);
    }

    public void onEncrypt(View view) {
        try {
            IBinder binder = mBinderPool.queryBinder(BinderPoolService.BINDER_CODE_SECURITY);
            ISecurityCenter securityCenter = BinderPoolService.SecurityCenterImpl.asInterface(binder);
            mContent = securityCenter.encrypt(mContent);
            Log.e(TAG, "onEncrypt: " + mContent);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    public void onDecrypt(View view) {
        try {
            IBinder binder = mBinderPool.queryBinder(BinderPoolService.BINDER_CODE_SECURITY);
            ISecurityCenter securityCenter = BinderPoolService.SecurityCenterImpl.asInterface(binder);
            mContent = securityCenter.decrypt(mContent);
            Log.e(TAG, "onDecrypt: " + mContent);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    public void onCompute(View view) {
        try {
            IBinder binder = mBinderPool.queryBinder(BinderPoolService.BINDER_CODE_COMPUTE);
            ICompute compute = BinderPoolService.ComputeImpl.asInterface(binder);
            int result = compute.add(21, 20);
            Log.e(TAG, "onCompute: " + result);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

除了onCreate方法其他的四个方法都是Button的Click方法,在绑定方法onBindBinderPool中就是5.1中所述,获取单例并绑定方法。但是有一点特别的显眼,眼尖的人已经看出来了,为什么getInstance方法需要在子线程,为什么不能在主线程。

5.1中描述了一个BindService方法中的同步变量,此变量的作用就是保证此方法的调用线程等待Service的绑定成功,会在成功后执行countDown方法释放线程锁。如果是子线程被暂停完全没有问题,但是如果是主线程被暂停,问题可就大了。要是绑定的过程相当漫长,是会引起ANR的,这是原因一;其二也是最主要的原因,服务绑定成功的回调方法onServiceConnected是会返回到主线程的,而释放线程的代码在方法里面,这就导致了一个死循环。onServiceConnected方法需要在主线程中执行,所以主线程得是畅通的而不是被锁住的,但是要想解锁得先执行onServiceConnected方法。综上,子线程是必须的。

获取到服务的Binder之后,逻辑就简单明了了,就是将Binder转化成相应的AIDL实现上即可。

至于为什么onServiceConnected会在主线程回调就是后话了,Service的工作过程 coming soon

六、选择合适的IPC方式

说完这么多IPC方式后,非常需要一个总结表格,更直观简单的描述优缺点及适用场景。

名称 优点 缺点 适用场景
Bundle 简单易用 只能传输Bundle支持的数据类型 四大组件间的进程间通信
文件共享 简单易用 不适合高并发的场景,并且无法做到进程间的即时通信 无并发访问情形,交换简单的实时性不高的数据
AIDL 功能强大,支持一对多并发通信 使用复杂,需要处理好线程同步 一对多且有RPC[1]需求
Messenger 功能一般,支持一对多串行通信,支持实时通信 不能很好的处理高并发情形,不支持RPC,数据通过Message进行传输,因此只能传输Bundle支持的数据类型 低并发的一对多即时通信,无RPC需求,或者无需要返回结果的RPC需求
ContentProvider 在数据源访问方面功能强大,支持一对多并发数据共享,可通过Call方法扩展其他操作 可以理解为受约束的AIDL,主要提供数据源的CRUD操作 一对多的进程间的数据共享
Socket 功能强大,可通过网络传输字节流,支持一对多并发实时通信 实现细节稍微有点繁琐,不支持直接的RPC 网络数据交换

  1. 远程过程调用

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