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閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374