綜述
IPC(interprocess communication)是指進程間通信,也就是在兩個進程間進行數據交互。不同的操作系統都有他們自己的一套IPC機制。例如在Linux操作系統中可以通過管道、信號量、消息隊列、內存共享、套接字等進行進程間通信。那么在Android系統中我們可以通過Binder來進行進程間的通信。當然除了Binder我們還可以使用Socket來進行進程間的通信。?
既然需要進程通信,那么就必須有多個進程。當然,在兩個應用交互中必然出現多進程的情況。若是在一個應用中呢?我們可以通過給四大組件在AndroidMenifest中為他們指定android:process屬性來實現不同的組件在不同進程中運行。下面就來介紹一下Android中進程間通信的實現方式。
AIDL簡介
AIDL是 Android Interface Definition Language的縮寫。AIDL 是一種IDL 語言,用于生成可以在Android設備上兩個進程之間進行 IPC的代碼。如果在一個進程中(例如Activity)要調用另一個進程中(例如Service)對象的操作,就可以使用AIDL生成可序列化的參數。?
AIDL IPC機制是面向接口的,像COM或Corba一樣,但是更加輕量級。它是使用代理類在客戶端和實現端傳遞數據。
AIDL用法
首先我們創建一個AIDL文件,在AndroidStudio中當我們創建一個AIDL文件時會自動為我們創件一個AILD文件夾,用于存放AIDL文件。創建完之后重新rebuild會自動生成aidl實現類。?
?
在下面的例子當中,我們將Service單獨作為一個應用在系統中運行,在另一個用于訪問Service的client也單獨作為一個應用運行在系統中。這樣保證了兩個程序分別運行在兩個進程中。并且使用了butterknife進行控件綁定。
AIDL簡單用法
演示
在Service中我們對客戶端傳來的兩個整數做了一次加法運算并返回到客戶端中。??
AIDL代碼
// ICalculate.aidl
package com.ljd.aidl;// Declare any non-default types here with import statementsinterface ICalculate {/*** Demonstrates some basic types that you can use as parameters* and return values in AIDL.*/int add(int first, int second); }
服務端代碼
package com.ljd.aidl.service;import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder; import android.os.RemoteException; import com.ljd.aidl.ICalculate; public class CalculateService extends Service { public CalculateService() { } private Binder mBinder = new ICalculate.Stub(){ @Override public int add(int first, int second) throws RemoteException { return first + second; } }; @Override public IBinder onBind(Intent intent) { return mBinder; } }
客戶端代碼
package com.ljd.aidl.activity;import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.widget.Toast; import com.ljd.aidl.ICalculate; import com.ljd.aidl.client.R; import butterknife.ButterKnife; import butterknife.OnClick; public class Demo1Activity extends AppCompatActivity { private final String TAG = "DEMO1"; //是否已經綁定service private boolean mIsBindService; private ICalculate mCalculate; private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.d(TAG,"bind success"); Toast.makeText(Demo1Activity.this,"bind service success",Toast.LENGTH_SHORT).show(); mCalculate = ICalculate.Stub.asInterface(service); } @Override public void onServiceDisconnected(ComponentName name) { //重新綁定Service防止系統將服務進程殺死而產生的調用錯誤。 bindService(); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_demo1); ButterKnife.bind(this); mIsBindService = false; } @Override protected void onDestroy() { unbindService(); ButterKnife.unbind(this); super.onDestroy(); } @OnClick({ R.id.bind_demo1_btn,R.id.unbind_demo1_btn,R.id.calculate_btn}) public void onClickButton(View v) { switch (v.getId()){ case R.id.bind_demo1_btn: bindService(); break; case R.id.unbind_demo1_btn: Toast.makeText(this,"unbind service success",Toast.LENGTH_SHORT).show(); unbindService(); break; case R.id.calculate_btn: if (mIsBindService && mCalculate != null ){ try { int result = mCalculate.add(2,4); Log.d(TAG,String.valueOf(result)); Toast.makeText(this,String.valueOf(result),Toast.LENGTH_SHORT).show(); } catch (RemoteException e) { e.printStackTrace(); } } else { Toast.makeText(this,"not bind service",Toast.LENGTH_SHORT).show(); } break; } } private void bindService(){ Intent intent = new Intent(); intent.setAction("com.ljd.aidl.action.CALCULATE_SERVICE"); bindService(intent,mConnection, Context.BIND_AUTO_CREATE); mIsBindService = true; } private void unbindService(){ if(mIsBindService){ mIsBindService = false; unbindService(mConnection); } } }
AIDL高級用法
對于上面的例子,在AIDL接口中只是使用了一些Java的基本類型,對于AIDL文件并不是所有的類型都是可用的,那么在AIDL中究竟有哪些類型可以使用呢?
AIDL語法規則
默認情況下AIDL支持以下數據類型:
- 所有Java的基本數據類型(例如: int, long,double, char, boolean等)
- String和CharSequence
- List:AIDL實際接收到的是ArrayList,并且List里面所有元素都必須被AIDL支持
- Map: AIDL實際接收到的是HashMap,并且Map里面所有元素都必須被AIDL支持
如果不是上面所述類型,我們必須要顯示import進來,即使他們在同一個包中。當我們使用自定義的對象時必須實現Parcelable接口,Parcelable為對象序列化接口,效率比實現Serializable接口高。并且新建一個與該類同名的AIDL文件,聲明他為Parcelable類型。
我們定義AIDL接口還需要注意以下幾點:
- 方法可以有多個或沒有參數,可以有返回值也可以為void
- 在參數中,除了基本類型以外,我們必須為參數標上方向in, out, 或者 inout
- 在AIDL文件中只支持方法,不支持靜態常量
演示
在計算機商店中需要采購筆記本進行銷售,在服務端中我們添加兩臺筆記本,在客戶端中我們為商店加購一臺dell筆記本。??
實體類代碼
我們首先構建一個計算機實體類,包含筆記本的id,品牌,型號,并且實現Parcelable接口,在AndroidStudio中會為我們自動構造代碼。?
package com.ljd.aidl.entity;import android.os.Parcel;
import android.os.Parcelable;public class ComputerEntity implements Parcelable{ public int computerId; //id public String brand; //品牌 public String model; //型號 public ComputerEntity(int computerId, String brand, String model) { this.brand = brand; this.computerId = computerId; this.model = model; } protected ComputerEntity(Parcel in) { computerId = in.readInt(); brand = in.readString(); model = in.readString(); } public static final Creator<ComputerEntity> CREATOR = new Creator<ComputerEntity>() { @Override public ComputerEntity createFromParcel(Parcel in) { return new ComputerEntity(in); } @Override public ComputerEntity[] newArray(int size) { return new ComputerEntity[size]; } }; @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(computerId); dest.writeString(brand); dest.writeString(model); } }
AIDL代碼
在AIDL中對實體類進行聲明,包名和文件名必須與實體類一致。在AndroidStudio中新建一個與實體類同名的AIDL文件會報錯,需要先用一個其它名字,然后修改與實體類名一致即可。
package com.ljd.aidl.entity; //包名必須和對用實體類的包名一致 // Declare any non-default types here with import statements parcelable ComputerEntity;
添加兩個接口分別為添加一臺筆記本和獲取全部筆記本,在該文件中使用到了ComputerEntity類,顯示的import進來。
package com.ljd.aidl;import com.ljd.aidl.entity.ComputerEntity;
// Declare any non-default types here with import statementsinterface IComputerManager {/*** Demonstrates some basic types that you can use as parameters* and return values in AIDL.*/void addComputer(in ComputerEntity computer); List<ComputerEntity> getComputerList(); }
服務端代碼
package com.ljd.aidl.service;import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException; import com.ljd.aidl.IComputerManager; import com.ljd.aidl.entity.ComputerEntity; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; public class ComputerService extends Service { private CopyOnWriteArrayList<ComputerEntity> mComputerList = new CopyOnWriteArrayList<>(); public ComputerService() { } private final IComputerManager.Stub mBinder = new IComputerManager.Stub() { @Override public void addComputer(ComputerEntity computer) throws RemoteException { mComputerList.add(computer); } @Override public List<ComputerEntity> getComputerList() throws RemoteException { return mComputerList; } }; @Override public void onCreate() { super.onCreate(); mComputerList.add(new ComputerEntity(0,"apple","macbookpro")); mComputerList.add(new ComputerEntity(1,"microsoft","surfacebook")); mComputerList.add(new ComputerEntity(2,"dell","XPS13")); } @Override public IBinder onBind(Intent intent) { return mBinder; } }
注意:在該類中使用了CopyOnWriteArrayList,CopyOnWriteArrayList能夠自動進行線程同步。可是在AIDL中接收和返回的只能是ArrayList,其實AIDL支持的是抽象的List,在Binder中會按照List訪問數據并最終形成一個ArrayList,所以在AIDL中返回的還是一個ArrayList。
客戶端代碼
package com.ljd.aidl.activity;import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection; import android.os.IBinder; import android.os.RemoteException; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import com.ljd.aidl.IComputerManager; import com.ljd.aidl.client.R; import com.ljd.aidl.entity.ComputerEntity; import java.util.List; import butterknife.Bind; import butterknife.ButterKnife; import butterknife.OnClick; public class Demo2Activity extends AppCompatActivity{ @Bind(R.id.show_linear) LinearLayout mShowLinear; private boolean mIsBindService; private IComputerManager mRemoteComputerManager; private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { @Override public void binderDied() { if(mRemoteComputerManager != null){ mRemoteComputerManager.asBinder().unlinkToDeath(mDeathRecipient,0); mRemoteComputerManager = null; bindService(); } } }; private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mIsBindService = true; Toast.makeText(Demo2Activity.this,"bind service success",Toast.LENGTH_SHORT).show(); mRemoteComputerManager = IComputerManager.Stub.asInterface(service); try { mRemoteComputerManager.asBinder().linkToDeath(mDeathRecipient,0); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { mRemoteComputerManager = null; } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_demo2); ButterKnife.bind(this); mIsBindService = false; } @Override protected void onDestroy() { unbindService(); ButterKnife.unbind(this); super.onDestroy(); } @OnClick({R.id.bind_demo2_btn,R.id.unbind_demo2_btn,R.id.test_demo2_btn,R.id.clear_demo2_btn}) public void onClickButton(View v) { switch (v.getId()){ case R.id.bind_demo2_btn: bindService(); break; case R.id.unbind_demo2_btn: Toast.makeText(this,"unbind service success",Toast.LENGTH_SHORT).show(); unbindService(); break; case R.id.test_demo2_btn: if (!mIsBindService || mRemoteComputerManager == null){ Toast.makeText(this,"not bind service",Toast.LENGTH_SHORT).show(); return; } try { List<ComputerEntity> computerList = mRemoteComputerManager.getComputerList(); for (int i =0;i<computerList.size();i++){ String str = "computerId:" + String.valueOf(computerList.get(i).computerId) + " brand:" + computerList.get(i).brand + " model:" + computerList.get(i).model ; TextView textView = new TextView(this); textView.setText(str); mShowLinear.addView(textView); } } catch (RemoteException e) { e.printStackTrace(); } break; case R.id.clear_demo2_btn: mShowLinear.removeAllViews(); break; } } private void bindService(){ Intent intent = new Intent(); intent.setAction("com.ljd.aidl.action.COMPUTER_SERVICE"); mIsBindService = bindService(intent,mConnection, Context.BIND_AUTO_CREATE); } private void unbindService(){ if(!mIsBindService){ return; } mIsBindService = false; unbindService(mConnection); } }
由于Binder是有可能會意外死亡的,也就是Service所在進程被系統殺死,這時候我們調用Service的方法就會失敗。在第一個例子中我們通過onServiceDisconnected方法中重新綁定服務。在這個例子中我們采用了另外一種方法,由于在Binder中提供了兩個配對的方法linkToDeath和unlinkToDeath,通過linkToDeath可以給Binder設置一個死亡代理,Binder死亡時回調binderDied方法,在binderDied方法中我們重新綁定服務即可。
AIDL用法拓展
當我們需要一種筆記本的時候,由于商店缺貨,這時候我們會給賣家說一聲,我所需要的這款筆記本到貨后通知我。也就成了所謂的觀察者模式。?
在Android系統中為我們提供了一個RemoteCallbackList,RemoteCallbackList是系統專門用來刪除跨進程的listener接口,并且在RemoteCallbackList中自動實現了線程同步功能,下面看一下它的用法。
演示
客戶端注冊服務以后,服務端每隔三秒會添加一臺筆記本,并通知給客戶端顯示。?
AIDL代碼
到貨后的AIDL監聽接口?
package com.ljd.aidl;import com.ljd.aidl.entity.ComputerEntity;
// Declare any non-default types here with import statementsinterface IOnComputerArrivedListener {/*** Demonstrates some basic types that you can use as parameters* and return values in AIDL.*/void onComputerArrived(in ComputerEntity computer); }
在IComputerManager接口中添加兩個方法。顯示importIOnComputerArrivedListener ,即使在同一個包下面。?
// IComputerManagerObserver.aidl
package com.ljd.aidl;import com.ljd.aidl.entity.ComputerEntity;
import com.ljd.aidl.IOnComputerArrivedListener;
// Declare any non-default types here with import statements interface IComputerManagerObserver { /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */ void addComputer(in ComputerEntity computer); List<ComputerEntity> getComputerList(); void registerUser(IOnComputerArrivedListener listener); void unRegisterUser(IOnComputerArrivedListener listener); }
服務端代碼
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteCallbackList; import android.os.RemoteException; import com.ljd.aidl.IComputerManagerObserver; import com.ljd.aidl.IOnComputerArrivedListener; import com.ljd.aidl.entity.ComputerEntity; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicBoolean; public class ComputerObserverService extends Service{ public ComputerObserverService() { } private CopyOnWriteArrayList<ComputerEntity> mComputerList = new CopyOnWriteArrayList<>(); private RemoteCallbackList<IOnComputerArrivedListener> mComputerArrivedListenerList = new RemoteCallbackList<>(); private AtomicBoolean mIsServiceDestroy = new AtomicBoolean(false); private Binder mBinder = new IComputerManagerObserver.Stub(){ @Override public void addComputer(ComputerEntity computer) throws RemoteException { mComputerList.add(computer); } @Override public List<ComputerEntity> getComputerList() throws RemoteException { return mComputerList; } @Override public void registerUser(IOnComputerArrivedListener listener) throws RemoteException { mComputerArrivedListenerList.register(listener); } @Override public void unRegisterUser(IOnComputerArrivedListener listener) throws RemoteException { mComputerArrivedListenerList.unregister(listener); } }; @Override public void onCreate() { super.onCreate(); mComputerList.add(new ComputerEntity(0,"apple","macbookpro")); mComputerList.add(new ComputerEntity(1,"microsoft","surfacebook")); mComputerList.add(new ComputerEntity(2,"dell","XPS13")); new Thread(new Runnable() { @Override public void run() { while (!mIsServiceDestroy.get()){ try { Thread.currentThread().sleep(3000); ComputerEntity computer = new ComputerEntity(mComputerList.size(),"******","******"); mComputerList.add(computer); final int COUNT = mComputerArrivedListenerList.beginBroadcast(); //通知所有注冊過的用戶 for (int i=0;i<COUNT;i++){ IOnComputerArrivedListener listener = mComputerArrivedListenerList.getBroadcastItem(i); if (listener != null){ listener.onComputerArrived(computer); } } mComputerArrivedListenerList.finishBroadcast(); } catch (InterruptedException e) { e.printStackTrace(); } catch (RemoteException e) { e.printStackTrace(); } } } }).start(); } @Override public IBinder onBind(Intent intent) { return mBinder; } @Override public void onDestroy() { super.onDestroy(); mIsServiceDestroy.set(true); } }
注意:RemoteCallbackList并不是一個List,所以我們不能像操作List一樣操作RemoteCallbackList。并且遍歷RemoteCallbackList時,beginBroadcast和finishBroadcast是配對使用的。
客戶端代碼
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.RemoteException; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import com.ljd.aidl.IComputerManagerObserver; import com.ljd.aidl.IOnComputerArrivedListener; import com.ljd.aidl.client.R; import com.ljd.aidl.entity.ComputerEntity; import java.util.List; import butterknife.Bind; import butterknife.ButterKnife; import butterknife.OnClick; public class Demo3Activity extends AppCompatActivity { @Bind(R.id.show_demo3_linear) LinearLayout mShowLinear; private boolean mIsBindService; private static final int MESSAGE_COMPUTER_ARRIVED = 1; private IComputerManagerObserver mRemoteComputerManager; private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what){ case MESSAGE_COMPUTER_ARRIVED: ComputerEntity computer = (ComputerEntity)msg.obj; String str = "computerId:" + String.valueOf(computer.computerId) + " brand:" + computer.brand + " model:" + computer.model ; TextView textView = new TextView(Demo3Activity.this); textView.setText(str); mShowLinear.addView(textView); break; default: super.handleMessage(msg); break; } } }; private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { @Override public void binderDied() { if(mRemoteComputerManager != null){ mRemoteComputerManager.asBinder().unlinkToDeath(mDeathRecipient,0); mRemoteComputerManager = null; bindService(); } } }; private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mIsBindService = true; Toast.makeText(Demo3Activity.this,"bind service success",Toast.LENGTH_SHORT).show(); mRemoteComputerManager = IComputerManagerObserver.Stub.asInterface(service); try { mRemoteComputerManager.asBinder().linkToDeath(mDeathRecipient,0); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { mRemoteComputerManager = null; } }; private IOnComputerArrivedListener mOnComputerArrivedListener = new IOnComputerArrivedListener.Stub(){ @Override public void onComputerArrived(ComputerEntity computer) throws RemoteException { mHandler.obtainMessage(MESSAGE_COMPUTER_ARRIVED,computer).sendToTarget(); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_demo3); ButterKnife.bind(this); mIsBindService = false; } @Override protected void onDestroy() { unbindService(); ButterKnife.unbind(this); super.onDestroy(); } @OnClick({R.id.bind_demo3_btn,R.id.unbind_demo3_btn,R.id.test_demo3_btn,R.id.clear_demo3_btn}) public void onClickButton(View v){ switch (v.getId()){ case R.id.bind_demo3_btn: bindService(); break; case R.id.unbind_demo3_btn: Toast.makeText(this,"unbind service success",Toast.LENGTH_SHORT).show(); unbindService(); break; case R.id.test_demo3_btn: if (!mIsBindService || mRemoteComputerManager == null){ Toast.makeText(this,"not bind service",Toast.LENGTH_SHORT).show(); return; } try { ComputerEntity computer = new ComputerEntity(3,"hp","envy13"); mRemoteComputerManager.addComputer(computer); List<ComputerEntity> computerList = mRemoteComputerManager.getComputerList(); for (int i =0;i<computerList.size();i++){ String str = "computerId:" + String.valueOf(computerList.get(i).computerId) + " brand:" + computerList.get(i).brand + " model:" + computerList.get(i).model ; TextView textView = new TextView(this); textView.setText(str); mShowLinear.addView(textView); } mRemoteComputerManager.registerUser(mOnComputerArrivedListener); } catch (RemoteException e) { e.printStackTrace(); } break; case R.id.clear_demo3_btn: mShowLinear.removeAllViews(); break; } } private void bindService(){ Intent intent = new Intent(); intent.setAction("com.ljd.aidl.action.COMPUTER_OBSERVER_SERVICE"); mIsBindService = bindService(intent,mConnection, Context.BIND_AUTO_CREATE); } private void unbindService(){ if(!mIsBindService){ return; } if (mRemoteComputerManager != null && mRemoteComputerManager.asBinder().isBinderAlive()){ try { mRemoteComputerManager.unRegisterUser(mOnComputerArrivedListener); } catch (RemoteException e) { e.printStackTrace(); } } unbindService(mConnection); mIsBindService = false; } }