Android中的跨程序通信地方式很多,比如通過在Intent中附加extras來傳遞資訊,或者通過共享檔案的方式來共享資料,還可以采用Binder方式來實作,另外ContentProvider天生就是支援跨程序通信的,此外通過網絡通信也就是Socket也可以實作IPC。
1.1 使用Bundle
在Android中四大元件中的三大元件(Activity、Service、Receiver)都是支援在Intent中傳遞Bundle資料的,由于Bundle實作了Parcelable接口,是以他可以友善地在不同的程序間傳輸。當我們在一個程序中啟動了另一個程序中的activity和service和Receiver,我們就可以在Bundle中附加我們需要傳輸給遠端程序的資訊并資訊并通過Intent發送出去。當然我們傳輸的資料必須是能夠序列化。
使用Bundle實作程序間通信地另一種使用場景,A中正在進行計算,計算完成後需要啟動B程序,并且将結果傳遞給B,然後結果的類型Bundle不支援,如果再加入其他的方式會顯得麻煩,故而你需要通過Intent去啟動B中的一個Service元件,開啟一個IntentService去完成這個計算任務,由于Service元件也運作在B中,是以B可以直接獲得結果。這樣就成功避免了程序間通信的問題。
1.2 使用檔案共享
使用檔案共享來實作程序間通信的核心在于兩個程序通過讀/寫同一個檔案來交換資料,這裡最大的問題就是Android基于Linux,使得其并發讀寫檔案可以沒有限制的進行,是以兩個程序同時對一個檔案進行操作是有可能出現的,這樣的話會出現資料丢失。但是使用檔案最大的好處就是簡單實用,除了可以交換文本,我們也可以将一些序列化一個對象到檔案中,然後另一個程序恢複這個對象。在下面我們可以實作一個demo,實作程序間的通信。
建立一個項目,然後再MainActivity中将序列化後的對象通過流的方式寫到文本中,然後在清單檔案中SecondActivity後通過process實作開啟另一程序。然後從文本中将user對象恢複。
MainActivity
@Override
protected void onResume() {
super.onResume();
persistToFile();
}
private void persistToFile(){
new Thread(new Runnable() {
@Override
public void run() {
User user=new User("1","james");
File dir=new File(MyContstants.CACHE_File_PATH);
if(!dir.exists()){
dir.mkdirs();
}
File cacheFile=new File(MyContstants.CHAPTER_2_PATH);
ObjectOutputStream outputStream=null;
try {
outputStream=new ObjectOutputStream(new FileOutputStream(cacheFile));
outputStream.writeObject(user);
Log.e(TAG,"persist user:"+user);
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if(outputStream!=null){
outputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}).start();
}
SecondActivity
@Override
protected void onResume() {
super.onResume();
removeFromFile();
}
private void removeFromFile(){
new Thread(new Runnable() {
@Override
public void run() {
User user=null;
File cacheFile=new File(MyContstants.CHAPTER_2_PATH);
ObjectInputStream inputStream=null;
try {
inputStream=new ObjectInputStream(new FileInputStream(cacheFile));
user= (User) inputStream.readObject();
Log.e(TAG,"removeFromFile user:"+user);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
if(inputStream!=null)
{
inputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}).start();
}
下面看一下log日志:
- :: -/com.byd.text E/MainActivity: persist user:User{userId='1', usereName='james'}
- :: -/com.byd.text.remote E/SecondActivity: removeFromFile user:User{userId='1', usereName='james'}
可以看到反序列化之後得到的對象内容上和序列化後的對象是一緻的,但是他們本質上還是兩個對象。通過檔案共享這種資料來共享資料對檔案的格式是沒有具體要求的,隻要讀寫雙方約定好資料的格式即可。但是這個方式最大的局限性就在于并發寫時會丢失資料,是以使用這種方式來實作程序間通信适合再對資料同步要求不高的程序之間通信,并且能夠處理好并發讀寫的問題。
特例:SharePreference
SharePreference是Android中提供的輕量級存儲方案,它是通過鍵值對的方式來存儲資料,在底層實作上它采用Xml檔案來存儲鍵值對,每個應用的SharePreference檔案都可以在目前包的所在的data目錄下找到,從本質上說,它也屬于檔案共享的一種,但是由于系統對它的讀寫有一定的緩存政策,即在記憶體中會有一份它的緩存,是以在多程序模式下,系統對它的讀寫就會變得不可靠,也就是說面對高并發的讀寫操作,它有可能會丢失資料。是以,不建議在程序間通信時使用SharePreference。
1.3 使用Messenger實作程序間通信
Messenger可以翻譯為信使,通過它可以在不同的程序中傳遞Message對象,在Message中放入我們要傳遞的資料,Messenger是一種輕量級的程序間通信方案,他的底層是對AIDL進行了分裝,由于其一次隻能執行一個請求,是以也不需要在服務端開路程序同步的問題。
1.3.1 使用Messenger實作程序間通信的步驟
1.服務端程序
首先,我們需要在服務端建立一個Service來處理用戶端的請求,同時建立一個Handler并通過它來建立一個Message對象,然後在Service的onBind中傳回這個Messenger對象那個底層的Binder。
2.用戶端程序
用戶端中,首先要綁定服務端的Service,綁定成功後用服務端傳回的IBinder對象建立一個Messenger,通過這個Messenger就可以向服務端發送消息了,發送的消息類型是Message對象。如若要實作服務端也能回複用戶端,我們還需要在建立一個Handler并建立一個新的Messenger,并把這個Messenger對象通過Message的replyTo參數傳遞給服務端,服務端通過replyTo參數就可以回應用戶端。
3.服務端的代碼
/**
* 作者:byd666 on 2017/10/12 15:28
* 郵箱:[email protected]
*/
public class MessengerService extends Service {
private static final String TAG="MessengerService";
/**
* 用來處理用戶端發來的資訊,并從中取出文本資訊列印。
*/
private static class MessengerHandler extends Handler{
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case MyContstants.MSG_FROM_CLIENT:
Log.e(TAG,msg.getData().getString("msg"));
//接收message中傳遞過來的Messenger的對象,并通過其回複用戶端
Messenger client=msg.replyTo;
Message message=Message.obtain(null,MyContstants.MSG_FROM_SERVICE);
Bundle bundle=new Bundle();
bundle.putString("reply","got it,i am service");
message.setData(bundle);
try {
client.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
}
}
}
/**
* 将用戶端發送來的消息傳遞給MessengerHandler處理
*/
private final Messenger mMessenger=new Messenger(new MessengerHandler());
@Nullable
@Override
public IBinder onBind(Intent intent) {
//傳回的是Messenger中的Binder對象
return mMessenger.getBinder();
}
}
注冊Service,讓其運作在單獨的程序中。
<service
android:name=".service.MessengerService"
android:process=":messenger"/>
4.用戶端的代碼
public class MessengerActivity extends AppCompatActivity {
private static final String TAG="MessengerActivity";
private Messenger mService;
/**
* 處理服務端接回複過來的消息
*/
private static class MessengerHandler extends Handler{
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case MyContstants.MSG_FROM_SERVICE:
Log.e(TAG,msg.getData().getString("reply"));
break;
default:
super.handleMessage(msg);
}
}
}
/**
* 将messenger傳遞過來的消息交給Handler處理
*/
private Messenger messenger=new Messenger(new MessengerHandler());
private ServiceConnection mConnection=new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//根據服務端傳回的binder對象建立Messenger對象。
mService=new Messenger(service);
Message message=Message.obtain(null, MyContstants.MSG_FROM_CLIENT);
//一個可以類似Map集合的存儲資料的容器
Bundle bundle=new Bundle();
bundle.putString("msg","hello my server,i am client.");
message.setData(bundle);
//當用戶端發送消息的時候,需要将接收服務端回複的Messenger通過Message的replyTo參數傳給服務端
message.replyTo=messenger;
try {
//使用此對象向服務端發送消息
mService.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_messenger);
//綁定遠端MessengerService
Intent intent=new Intent(this, MessengerService.class);
bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
//銷毀時解綁
unbindService(mConnection);
super.onDestroy();
}
}
log日志:
- :: -/com.byd.text.messenger E/MessengerService: hello my server,i am client.
- :: -/com.byd.text E/MessengerActivity: got it,i am service
注意:通過以上例子我們可以看出,在Messenger中進行資料傳遞必須将資料放入到Message中,而二者都實作了Parcelable接口,是以可以跨程序傳輸。Message所支援的資料類型就是Messenger所支援的傳輸類型。
1.3.2 使用Messenger實作程序間通信的原理圖

最後ICP機制系列完事之後會将之前的demo放到github上,大家可以clone下來自己實作看一下。附一下位址:項目在git上面的位址