Android的IPC機制(下)
文章目錄
1.4 Android中的IPC方式
- Android中IPC的方式有很多:Intent中附加extras傳遞信息、共享文件方式共享數據、Binder方式跨進程通信、ContentProvider天生跨進程通信、網絡通信Socket等。
1.4.1 使用Bundle
Activity、Service、Receiver支持在Intent中傳遞Bundle數據;Bundle實現了Parcelable接口,可以在不同進程間傳輸。通過Bundle傳輸的數據必須能夠序列化,比如:基本類型、實現Parcelable接口的對象、實現Serializable接口的對象、Android支持的特殊對象。
Bundle使用示例:
//Bundle保存的數據,是以key-value(鍵值對)的形式存在的。 Bundle bundle = new Bundle(); bundle.putXXXXX(); Intent intent = new Intent(); intent.putExtra("name",bundle);
思路:修改跨進程程序。舉例A進程執行計算,計算完成後啓動B進程的一個組件傳遞計算結果給B進程,如果計算結果不支持放入Bundle,無法通過Intent傳輸,更換其他IPC方式會複雜很多。我們可以通過Intent啓動進程B的一個Service組件,讓Service在後臺計算,計算完成後啓動B進程的目標組件,由於Service和B進程同處一個進程,可直接獲取計算結果。這樣我們只用了很小的代價就避免了進程間的通信問題。
1.4.2 使用文件共享
通過兩個進程讀/寫同一文件來交換數據。但存在一個問題:Android系統基於Linux,使得其併發讀/寫文件可以沒有限制地進行,可能會出現問題。雖然可能存在問題但這仍是一種好用的IPC方式。
文件共享舉例:
Activity A序列化Person對象到一個文件裏;Activity B從該文件中讀取對象並進行反序列化:
//Activity A(在onResume中執行) private void persistToFile(){ new Thread(new Runnable() { @Override public void run() { Person person = new Person("Tom",22,true,info); File dir = new File(FILE_PATH); if (!dir.exists()){ dir.mkdir(); } File cachedFile = new File(FILE_PATH); ObjectOutputStream objectOutputStream = null; try{ objectOutputStream = new ObjectOutputStream(new FileOutputStream(cachedFile)); objectOutputStream.writeObject(person); }catch (IOException e){ e.printStackTrace(); }finally { if (null!=objectOutputStream){ try { objectOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } } }).start(); }
//Activity B(在onResume中執行) private void recoverFromFile() { new Thread(new Runnable() { @Override public void run() { Person person = null; File cachedFile = new File(FILE_PATH); if (cachedFile.exists()) { ObjectInputStream objectInputStream = null; try { objectInputStream = new ObjectInputStream(new FileInputStream(cachedFile)); person = (Person) objectInputStream.readObject(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } finally { try { objectInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } } }).start(); }
文件共享方式適合在對數據同步要求不高的進程之間進行通信,並且要妥善處理併發讀/寫的問題。
SharedPreference是Android提供的輕量級存儲方案,底層實現上採用XML文件存儲鍵值對;雖然SharedPreference也是文件讀/寫的形式,但是他的讀寫有一定的緩存策略,在內存中會有一份SharedPreference文件的緩存,這使得在多進程模式下,系統對它的讀/寫就變得不可靠,高併發的讀/寫訪問會使SharedPreference有很大機率丟失數據。不建議在進程間通信中使用SharedPreference。
1.4.3 使用Messenger
Messenger是輕量級的IPC方案,底層實現是AIDL,可以在不同進程中傳遞Message對象。Messenger一次處理一個請求,因此在服務端不用考慮線程同步問題,服務端中不存在併發執行的情形。
服務端進程:
在服務端創建一個Service來處理客戶端的連接請求,同時創建一個Handler並通過它創建一個Messenger對象,然後在Service的onBind中返回這個Messenger對象底層的Binder即可。
服務端典型代碼:
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 MyConstants.MSG_FROM_CLIENT: Log.d(TAG, "receive msg from Client: " + msg.getData().get("msg")); break; default: super.handleMessage(msg); } } } private final Messenger mMessenger = new Messenger(new MessengerHandler()); @Nullable @Override public IBinder onBind(Intent intent) { return mMessenger.getBinder(); } }
<application> <!--Manifest中註冊服務--> <service android:name=".MessengerService" android:enabled="true" android:exported="true" android:process=":remote"/> </application>
客戶端進程:
首先綁定服務端的Service,綁定成功後服務端返回的IBinder對象創建一個Messenger對象,通過Messenger就可以向服務端發送類型爲Message類型的對象。
如果需要服務端能夠迴應客戶端,客戶端就需要像服務端一樣,創建一個Handler並通過它創建一個Messenger對象,並把這個Messenger對象通過通過Message的replyTo參數傳遞給服務端,服務端通過這個replyTo參數就可以迴應客戶端。
客戶端典型代碼:
public class MessengerActivity extends AppCompatActivity { private static final String TAG = "MessengerActivity"; private Messenger mService; private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mService=new Messenger(service); Message msg = Message.obtain(null,MyConstants.MSG_FROM_CLIENT); Bundle data = new Bundle(); data.putString("msg","client"); msg.setData(data); try { mService.send(msg); }catch (RemoteException e){ e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { } }; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_messenger); Intent intent = new Intent(this,MessengerService.class); bindService(intent,mConnection, Context.BIND_AUTO_CREATE); } @Override protected void onDestroy() { unbindService(mConnection); super.onDestroy(); } }
Messenger中傳遞的數據需要放入Message中,Messenger和Message都實現了Parcelable接口。Message中可以使用的載體有what、arg1、arg2、Bundle、Object和replyTo。
在上述例子上修改,使得服務端收到客戶端消息後返回一個消息,客戶端根據這個消息在進行處理:
//服務端 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 MyConstants.MSG_FROM_CLIENT: Log.d(TAG, "receive msg from Client: " + msg.getData().get("msg")); Messenger client = msg.replyTo; Message relplyMessage = Message.obtain(null,MyConstants.MSG_FROM_SERVICE); Bundle bundle = new Bundle(); bundle.putString("reply","I am Service !"); relplyMessage.setData(bundle); try { client.send(relplyMessage); } catch (RemoteException e) { e.printStackTrace(); } break; default: super.handleMessage(msg); } } } private final Messenger mMessenger = new Messenger(new MessengerHandler()); @Nullable @Override public IBinder onBind(Intent intent) { return mMessenger.getBinder(); } }
//客戶端 public class MessengerActivity extends AppCompatActivity { private static final String TAG = "MessengerActivity"; private Messenger mService; private Messenger mGetReplyMessenger = new Messenger(new MessengerHandle()); private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mService = new Messenger(service); Message msg = Message.obtain(null, MyConstants.MSG_FROM_CLIENT); Bundle data = new Bundle(); data.putString("msg", "I am client !"); msg.setData(data); //注意:這裏添加了replyTo msg.replyTo = mGetReplyMessenger; try { mService.send(msg); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { } }; private static class MessengerHandle extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case MyConstants.MSG_FROM_SERVICE: Log.d(TAG, "receive msg from Service: " + msg.getData().get("reply")); break; default: super.handleMessage(msg); } } } @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_messenger); Intent intent = new Intent(this, MessengerService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } @Override protected void onDestroy() { unbindService(mConnection); super.onDestroy(); } }
- Messenger工作原理示意圖:
1.4.4 使用AIDL
Messenger是以串行的方式處理客戶端發來的信息,如果大量的信息同時發送到服務端,服務端仍然只能一個一個處理,如果有大量併發請求,Messenger不再合適。Messenger的主要作用是傳遞消息,當我們需要跨進程調用服務端的方法,Messenger無法做到,但我們可以使用AIDL實現跨進程方法調用。
服務端:
- 服務端要創建一個Service監聽客戶端的連接請求,然後創建一個AIDL文件,將暴露給客戶端的接口在這個AIDL文件中聲明,之後在Service中實現這個AIDL接口即可。
客戶端:
- 綁定服務端的Service,成功後將服務端返回的Binder對象轉成AIDL接口所屬的類型,之後調用AIDL中的方法即可。
AIDL接口的創建:
AIDL文件支持的數據類型:
- 1.基本數據類型。
- 2.String和CharSequence。
- 3.List:只支持ArrayList,且其中每個元素都能被AIDL支持。
- 4.Map:只支持HashMap,且其中每個元素都能被AIDL支持,包括key和value。
- 5.Parcelable:所有實現了Parcelable接口的對象。
- 6.AIDL:所有的AIDL接口本身也可以在AIDL文件中使用。
自定義Parcelable對象和AIDL對象要顯式import進來。
自定義Parcelable對象要新建一個同名的AIDL文件,聲明它爲Parcelable類型。
AIDL中除了基本數據類型,其他類型的參數必須標上方向:in、out或者inout。in表示輸入型參數;out表示輸出型參數;inout表示輸入輸出型參數。
AIDL接口中只支持方法,不支持聲明靜態常量。
推薦將AIDL相關的類和AIDL文件放入同一個包中,方便複製轉移。AIDL的包結構要在服務端和客戶端保持一致,否則反序列化的時候會出錯。
RemoteCallbackList:
通過AIDL實現註冊和解綁時傳遞listener時會出現無法解綁的問題。這是因爲Binder會將客戶端傳遞過來的對象重新轉化並生成一個新的對象;當我們註冊和解綁時,雖然從客戶端傳過去的是同一個對象,但是在服務端Binder將會產生兩個新的對象。注意:對象是不能跨進程直接傳輸的,對象的跨進程傳輸本質上都是反序列化的過程。
RemoteCallbackList是系統專門提供用於刪除跨進程listener的接口。RemoteCallbackList是一個泛型,支持管理任意的AIDL接口。
public class RemoteCallbackList<E extends IInterface>
RemoteCallbackList工作原理:在內部有一個Map結構專門用來保存所有的AIDL回調,這個Map的key是IBinder類型,calue是Callback類型:
Arraymap<IBinder,Callback> mCallbacks = new ArrayMap<IBinder,Callback>();
其中Callback中封裝了真正的遠程listener。當客戶端註冊listener的時候,他會把這個listener的信息存入mCallbacks中。
key和value分別通過以下方式獲得:
IBinder key = listener.asBinder(); Callback value = new Callback(listener,cookie);
當客戶端解綁時,只需遍歷服務端所有的listener,找到與需要解綁的客戶端listener具有相同Binder對象的服務器端的listener,將其刪除即可。
使用RemoteCallbackList,當客戶端進程終止後,可以自動移除客戶端所註冊的listener。
RemoteCallbackList內部自動實現了線程同步的功能,使用其註冊和解綁時,不需要做額外的線程同步工作。
在AIDL中使用權限驗證功能:
1.在onBind中進行驗證,驗證不通過返回null。
<!--定義權限--> <permission android:name="com.virtual.aidltest.ACCESS_PERSON_SERVICE" android:protectionLevel="normal" />
public IBinder onBind(Intent intent){ int check = checkCallingOrSelfPermission("com.virtual.aidltest.ACCESS_PERSON_SERVICE"); if(check==PackageManager.PERMISSION_DENIED){ return null; } }
<!--註冊權限--> <uses-permission android:name="com.virtual.aidltest.ACCESS_PERSON_SERVICE"/>
2.在服務端的onTransact方法中驗證,失敗返回false。
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { int check = checkCallingOrSelfPermission("com.virtual.aidltest.ACCESS_PERSON_SERVICE"); if (check == PackageManager.PERMISSION_DENIED) { return false; } String packageName = null; String[] packages = getPackageManager().getPackagesForUid(getCallingUid()); if (packages != null && packages.length > 0) { packageName = packages[0]; } if (!packageName.startsWith("com.virtual")) { return false; } return super.onTransact(code, data, reply, flags); }
需要注意的一些補充:
客戶端調用遠程服務的方法,被調用的方法在服務端的Binder線程池中,同時客戶端線程會被掛起。如果是耗時操作,且客戶端線程是UI線程,就會導致客戶端ANR。客戶端onServiceConnected和onServiceDisconnected方法運行在UI線程中,注意不要在其中調用耗時方法。
服務端方法運行在Binder線程池中,服務端本身可執行大量耗時操作,不需要在服務端開線程去執行異步任務。
Binder死亡的處理方式:
1.給Binder設置DeathRecipient監聽,Binder死亡時,收到binderDied方法回調。已在Binder介紹中提及。
2.onServiceDisconnected回調。
兩種方法,區別在於:onServiceDisconnected在客戶端的UI線程中被回調;binderDied在客戶端的Binder進程池中回調(不能訪問UI)。
補充一個完整的使用AIDL例子:點擊這裏
1.4.5 使用ContentProvider
ContentProvider的底層實現是Binder。
ContentProvider舉例:
創建DbOpenHelper.java
/** * 實現一個數據庫來管理信息 */ public class DbOpenHelper extends SQLiteOpenHelper { private static final String DB_NAME = "person_provider.db"; public static final String PERSON_TABLE_NAME = "person"; private static final int DB_VERSION=1; //人員表和年齡表 private String CREATE_PERSON_TABLE = "CREATE TABLE IF NOT EXISTS "+PERSON_TABLE_NAME+"(_id INTEGER PRIMARY KEY,"+"name TEXT,"+"sex INT)"; public DbOpenHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } @Override public void onCreate(SQLiteDatabase sqLiteDatabase) { sqLiteDatabase.execSQL(CREATE_PERSON_TABLE); } @Override public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) { } }
創建PersonProvider.java
public class PersonProvider extends ContentProvider { private static final String TAG = "PersonProvider"; public static final String AUTHORITY = "com.virtual.PersonProvider"; /** * 爲了明確外界訪問的是具體哪一張表,需要爲表定義單獨的Uri和Uri_Code,並使用UriMatcher將Uri與Uri_Code關聯 * 當外界訪問ContentProvider時,根據Uri得到Uri_Code;通過Uri_Code明確訪問的表 */ public static final Uri PERSON_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/person"); public static final int PERSON_URI_CODE = 0; private static final UriMatcher mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); static { mUriMatcher.addURI(AUTHORITY, "person", PERSON_URI_CODE); } private Context mContext; private SQLiteDatabase mDb; private String getTableName(Uri uri) { String tableName = null; switch (mUriMatcher.match(uri)) { case PERSON_URI_CODE: tableName = DbOpenHelper.PERSON_TABLE_NAME; break; default: break; } return tableName; } /** * onCreate()在主線程運行,不要做耗時操作。 * query()、update()、insert()、delete()在Bidner線程。 */ @Override public boolean onCreate() { Log.d(TAG, "onCreate, current thread:" + Thread.currentThread().getName()); mContext = getContext(); //演示代碼,實際過程中不推薦在主線程進行耗時的數據庫操作 initProviderData(); return true; } /** * query()、update()、insert()、delete()方法是存在多線程併發的,需要內部做好線程同步; * 示例使用SQLite且只有一個SQLiteDatabase的連接,可以正確應對多線程情況。 * 在SQLiteDatabase內部對數據庫的操作是有同步處理的。 * 但多個SQLiteDatabase之間無法保證線程同步,因爲SQLiteDatabase對象之間無法進行線程同步。 */ private void initProviderData() { mDb= new DbOpenHelper(mContext).getWritableDatabase(); mDb.execSQL("delete from "+DbOpenHelper.PERSON_TABLE_NAME); mDb.execSQL("insert into person values(1,'Tom',21); "); mDb.execSQL("insert into person values(2,'Jack',17); "); } @Override public Cursor query(Uri uri, String[] strings, String s, String[] strings1, String s1) { Log.d(TAG, "query, current thread:" + Thread.currentThread().getName()); String table = getTableName(uri); if (null == table){ throw new IllegalArgumentException("Unsupported URI:"+uri); } return mDb.query(table,strings,s,strings1,null,null,s1,null); } @Override public String getType(Uri uri) { Log.d(TAG, "getType"); return null; } @Override public Uri insert(Uri uri, ContentValues contentValues) { Log.d(TAG, "insert"); String table = getTableName(uri); if (null == table){ throw new IllegalArgumentException("Unsupported URI:"+uri); } mDb.insert(table,null,contentValues); mContext.getContentResolver().notifyChange(uri,null); return uri; } @Override public int delete(Uri uri, String s, String[] strings) { Log.d(TAG, "delete"); String table = getTableName(uri); if (null == table){ throw new IllegalArgumentException("Unsupported URI:"+uri); } int count = mDb.delete(table,s,strings); if (count>0){ mContext.getContentResolver().notifyChange(uri,null); } return count; } @Override public int update(Uri uri, ContentValues contentValues, String s, String[] strings) { Log.d(TAG, "update"); String table = getTableName(uri); if (null == table){ throw new IllegalArgumentException("Unsupported URI:"+uri); } int row = mDb.update(table,contentValues,s,strings); if(row>0){ mContext.getContentResolver().notifyChange(uri,null); } return row; } }
創建ProviderActivity.java
public class ProviderActivity extends AppCompatActivity { private static final String TAG = "ProviderActivity"; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_provider); Uri uri = Uri.parse("content://com.virtual.PersonProvider/person"); //添加 ContentValues values = new ContentValues(); values.put("_id",3); values.put("name","Jone"); values.put("sex",0); getContentResolver().insert(uri,values); //查看 Cursor personCursor=getContentResolver().query(uri,new String[]{"_id","name","sex"},null,null); while (personCursor.moveToNext()){ StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(personCursor.getInt(0)); stringBuilder.append(personCursor.getString(1)); stringBuilder.append(personCursor.getInt(2)); Log.d(TAG, "query person: "+stringBuilder.toString()); } personCursor.close(); } }
Manifest註冊
<!--註冊活動--> <activity android:name=".ProviderActivity"/> <!--註冊ContentProvider--> <!--authorities是ContentProvider的唯一標識--> <!--permission是訪問該ContentProvider需要聲明的權限名--> <provider android:authorities="com.virtual.PersonProvider" android:name="com.virtual.taskcontentprovider.PersonProvider" android:permission="com.virtual.PROVIDER" android:process=":provider"/>
1.4.6 使用Socket
Socket(套接字)支持跨進程傳輸任意字節流。
Socket舉例:
服務端:
public class TCPServerService extends Service { private boolean mIsServiceDestroyed = false; private String[] mDefinedMessages = new String[]{ "Hello", "hi", "Morning", "Well", "Cool" }; @Override public void onCreate() { new Thread(new TcpServer()).start(); super.onCreate(); } @Override public IBinder onBind(Intent intent) { return null; } @Override public void onDestroy() { mIsServiceDestroyed = true; super.onDestroy(); } private class TcpServer implements Runnable{ @Override public void run() { ServerSocket serverSocket = null; try { //監聽8688端口 serverSocket=new ServerSocket(8688); } catch (IOException e) { System.err.println("establish tcp server failed, port:8688"); e.printStackTrace(); return; } while (!mIsServiceDestroyed){ try { //接收客戶端請求 final Socket client = serverSocket.accept(); System.out.println("accept"); new Thread(){ @Override public void run() { try { responseClient(client); } catch (IOException e) { e.printStackTrace(); } } }.start(); } catch (IOException e) { e.printStackTrace(); } } } private void responseClient(Socket client) throws IOException{ //用於接收客戶端消息 BufferedReader in =new BufferedReader(new InputStreamReader(client.getInputStream())); //用於向客戶端發送消息 PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())),true); out.println("Welcome!"); while (!mIsServiceDestroyed){ String str = in.readLine(); System.out.println("msg from client:"+str); if (str == null){ //斷開客戶端 break; } int i =new Random().nextInt(mDefinedMessages.length); String msg= mDefinedMessages[i]; out.println(msg); System.out.println("send:"+msg); } System.out.println("client quit."); //關閉流 out.close(); in.close(); client.close(); } } }
客戶端:
public class TCPClientActivity extends AppCompatActivity implements View.OnClickListener { private static final int MESSAGE_RECEIVE_NEW_MSG = 1; private static final int MESSAGE_SOCKET_CONNECTED = 2; private Button mSendButton; private TextView mMessageTextView; private EditText mMessageEditView; private PrintWriter mPrintWriter; private Socket mClientSocket; private Handler mHandler=new Handler(){ @Override public void handleMessage(@NonNull Message msg) { switch (msg.what){ case MESSAGE_RECEIVE_NEW_MSG:{ mMessageTextView.setText(mMessageTextView.getText()+(String)msg.obj); break; } case MESSAGE_SOCKET_CONNECTED:{ mSendButton.setEnabled(true); break; } default: break; } } }; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_tcpclient); mMessageTextView=findViewById(R.id.tv_client); mSendButton=findViewById(R.id.bt_client); mSendButton.setOnClickListener(this); mMessageEditView=findViewById(R.id.et_client); Intent service = new Intent(this,TCPServerService.class); startService(service); new Thread(){ @Override public void run() { connectTCPServer(); } }.start(); } @Override protected void onDestroy() { if (mClientSocket !=null){ try { mClientSocket.shutdownInput(); mClientSocket.close(); } catch (IOException e) { e.printStackTrace(); } } super.onDestroy(); } @Override public void onClick(View view) { if (view == mSendButton){ final String msg = mMessageEditView.getText().toString(); if(!TextUtils.isEmpty(msg) && mPrintWriter!=null){ new Thread(new Runnable() { @Override public void run() { mPrintWriter.println(msg); } }).start(); mMessageEditView.setText(""); String time = formatDateTime(System.currentTimeMillis()); final String showMsg = "self "+time+":"+msg+"\n"; mMessageTextView.setText(mMessageTextView.getText()+showMsg); } } } private String formatDateTime(long time){ return new SimpleDateFormat("(HH:mm:ss)").format(new Date(time)); } private void connectTCPServer(){ Socket socket=null; while (socket==null){ try { socket=new Socket("localhost",8688); mClientSocket = socket; mPrintWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),true); mHandler.sendEmptyMessage(MESSAGE_SOCKET_CONNECTED); System.out.println("connect server success"); } catch (IOException e) { SystemClock.sleep(1000); System.out.println("connect tcp server failed, retry ..."); e.printStackTrace(); } } try { //接收服務端的消息 BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); while (!TCPClientActivity.this.isFinishing()){ String msg = br.readLine(); System.out.println("receive: "+msg); if (msg!=null){ String time = formatDateTime(System.currentTimeMillis()); final String showedMsg = "server "+time+":"+msg+"\n"; mHandler.obtainMessage(MESSAGE_RECEIVE_NEW_MSG,showedMsg).sendToTarget(); } } //斷開連接 System.out.println("quit..."); mPrintWriter.close(); br.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } } }
權限聲明:
<!--權限聲明--> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
1.5 Binder連接池
AIDL是一種最常用的進程間通信方式,是日常開發中涉及進程間通信時的首選。
AIDL使用流程:
- 1.創建一個Service和一個AIDL接口。
- 2.創建一個類繼承AIDL接口中的Stub類並實現Stub中的抽象方法。
- 3.在Service的onBind方法中返回這個類的對象。
- 4.客戶端綁定服務端Servioce,建立連接。
隨着AIDL數量的增加,不能無限制增加Service(Service本身消耗一定系統資源)。我們可以將所有的AIDL放在一個Service中管理。
每個業務模塊創建自己的AIDL接口並實現此接口,然後向服務端提供自己的唯一標識和其對應的 Binder 對象;服務端只需要一個 Service即可,服務端提供一個queryBinder 接口,該接口能夠根據業務模塊的特徵來返回相應的Binder對象。
Binder連接池示例:
需要的AIDL文件:
// ISecurityCenter.aidl package com.virtual.bindertest; interface ISecurityCenter { String encrypt(String content); }
// ICompute.aidl package com.virtual.bindertest; interface ICompute { int add(int a,int b); }
// IBinderPool.aidl package com.virtual.bindertest; interface IBinderPool { IBinder queryBinder(int binderCode); }
兩個不同功能的AIDL的接口實現:
public 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); } }
public class ComputeImpl extends ICompute.Stub { @Override public int add(int a, int b) throws RemoteException { return a + b; } }
進程池:
/** * BinderPool是一個單例實現,在同一個進程中只會初始化一次。 * 提前初始化BinderPool,比如在Application中提前初始化,可以優化程序。 */ public class BinderPool { private static final String TAG = "BinderPool"; public static final int BINDER_NONE = -1; public static final int BINDER_COMPUTE = 0; public static final int BINDER_SECURITY_CENTER = 1; private Context mContext; private IBinderPool mBinderPool; private static volatile BinderPool sInstance; //countDownLatch是一個計數器,線程完成一個記錄一個,計數器遞減,只能只用一次。 private CountDownLatch mConnectBinderPoolCountDownLatch; private BinderPool(Context context) { mContext = context.getApplicationContext(); connectBinderPoolService(); } public static BinderPool getInstance(Context context) { if (sInstance == null) { synchronized (BinderPool.class) { if (sInstance == null) { sInstance = new BinderPool(context); } } } return sInstance; } /** * BinderPool要在線程中執行: * CountDownLatch將bindService這一異步操作轉換成了同步操作,即其可能爲耗時操作。 * 耗時操作不能在主線程進行。 */ private synchronized void connectBinderPoolService() { mConnectBinderPoolCountDownLatch = new CountDownLatch(1); Intent service = new Intent(mContext, BinderPoolService.class); mContext.bindService(service, mBinderPoolConnect, Context.BIND_AUTO_CREATE); try { mConnectBinderPoolCountDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } } public IBinder queryBinder(int binderCode) { IBinder binder = null; try { if (mBinderPool != null) { binder = mBinderPool.queryBinder(binderCode); } } catch (RemoteException e) { e.printStackTrace(); } return binder; } private ServiceConnection mBinderPoolConnect = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mBinderPool = IBinderPool.Stub.asInterface(service); try { mBinderPool.asBinder().linkToDeath(mBinderPoolDeathRecipient, 0); } catch (RemoteException e) { e.printStackTrace(); } mConnectBinderPoolCountDownLatch.countDown(); } @Override public void onServiceDisconnected(ComponentName name) { } }; private IBinder.DeathRecipient mBinderPoolDeathRecipient = new IBinder.DeathRecipient() { @Override public void binderDied() { Log.d(TAG, "binder died."); mBinderPool.asBinder().unlinkToDeath(mBinderPoolDeathRecipient, 0); mBinderPool = null; connectBinderPoolService(); } }; public static class BinderPoolImpl extends IBinderPool.Stub { public BinderPoolImpl() { super(); } @Override public IBinder queryBinder(int binderCode) throws RemoteException { IBinder binder = null; switch (binderCode) { case BINDER_SECURITY_CENTER: { binder = new SecurityCenterImpl(); break; } case BINDER_COMPUTE: { binder = new ComputeImpl(); break; } default: break; } return binder; } } }
進程池服務:
public class BinderPoolService extends Service { private static final String TAG = "BinderPoolService"; private Binder mBinderPool = new BinderPool.BinderPoolImpl(); @Override public void onCreate() { super.onCreate(); } @Nullable @Override public IBinder onBind(Intent intent) { Log.d(TAG, "onBind"); return mBinderPool; } @Override public void onDestroy() { super.onDestroy(); } }
MainActivity中測試:
public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; ISecurityCenter mSecurityCenter; ICompute mCompute; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); new Thread(new Runnable() { @Override public void run() { doWork(); } }).start(); } private void doWork() { BinderPool binderPool = BinderPool.getInstance(MainActivity.this); //AIDL 1 Log.d(TAG, "visit ISecurityCenter"); IBinder securityBinder = binderPool.queryBinder(BinderPool.BINDER_SECURITY_CENTER); mSecurityCenter = SecurityCenterImpl.asInterface(securityBinder); String msg = "hello world"; try { Log.d(TAG, "encrypt:" + mSecurityCenter.encrypt("Hello")); } catch (RemoteException e) { e.printStackTrace(); } //AIDL 2 Log.d(TAG, "visit ICompute"); IBinder computebinder = binderPool.queryBinder(BinderPool.BINDER_COMPUTE); mCompute = ComputeImpl.asInterface(computebinder); try { Log.d(TAG, "compute:" + "3+5=" + mCompute.add(3, 5)); } catch (RemoteException e) { e.printStackTrace(); } } }
1.6 選擇合適的IPC方式
名稱 | 優點 | 缺點 | 適用場景 |
---|---|---|---|
Bundle | 簡單易用 | 只能傳輸Bundle支持的數據類型 | 四大組件間的進程間通信 |
文件共享 | 簡單易用 | 不適合高併發場景,無法做到進程間即時通信 | 無併發訪問情形,交換簡單的數據且實時性不高的情況 |
AIDL | 功能強大,支持一對多併發通信,支持實時通信 | 使用稍複雜,需要處理好線程同步 | 一對多通信且有RPC需求 |
Messenger | 功能強大,支持一對多串行通信,支持實時通信 | 不能很好處理高併發情形,不支持RPC,數據通過Message進行傳輸,因此只能傳輸Bundle支持的數據類型 | 低併發的一對多即時通信,無RPC需求,或者無須要返回結果的RPC需求 |
ContentProvider | 在數據源訪問方面功能強大,支持一對多併發數據共享,可通過Call方法擴展其他操作 | 可以理解爲受約束的AIDL,主要提供數據源的CRUD操作 | 一對多的進程間的數據共享 |
Socket | 功能強大,可以通過網絡傳輸字節流,支持一對多併發實時通信 | 實現細節稍微有點煩瑣,不支持直接的RPC | 網絡數據交換 |
1.7 其他
1.7.1 運行環境
- Compile Sdk Version 28
- Build Tools Version 28.0.3
- Source Compatibility 1.8
- Target Compativility 1.8
1.7.2 過程中可能產生的問題
產生錯誤 :… aidl.exe finished with non-zero exit value …
- 可能原因1:運行環境問題,Build Tools Version和Build Tools Version不匹配。
- 可能原因2:使用的自定義對象沒有正確配置,需要手動import、in、out、inout。
1.7.3 參考資料
- Android開發藝術與探索