AndroidIPC通信之AIDL
歡迎關注我的個人公眾號,大家一起來交流Android開發知識
一、廢話
已經有很長一段時間沒有寫博客了,好吧!我承認我頹廢了。
什么是IPC通信?IPC通信:是指兩個進程之間進行數據通信的過程。(進程是資源分配的基本單位(CPU、內存),它擁有獨立的地址空間,線程是CPU執行的最小單位,一個進程可以有很多個線程組成)。Binder機制解決了Android進程間通信的問題,當然Socket同樣也可以實現。
什么時候使用IPC通信呢?有一些原因需要采用多進程來實現,你比如說某些模塊需要在單獨的進程中運行,還有些原因比如Android早些版本對單個應用做了最大內存限制,可能是16MB左右,為了加大某個應用的使用內存。還有一種情況,是為了訪問其他應用的某些數據:比如我們通過ContentProvider訪問系統層的聯系人,短信等等信息。
二、還是廢話
如何開啟多進程模式?我們討論的是一個應用多個進程的情況下。只有一個辦法就是:四大組件在Manifest中指定android:process屬性??此坪芎唵?#xff0c;其實很復雜,這里面牽扯到數據如何進行通信。因為我們知道,進程擁有獨立的地址空間,擁有自己的一塊內存。而android會為每個應用分配一個獨立的虛擬機,而每個虛擬機會映射到分配的地址空間上,這就會導致我們在不同進程中訪問同一個對象會產生多個副本,發生數據不同步的現象。
在多進程進行數據通信往往會產生一下幾個問題:
1、靜態成員和單例完全失效
2、線程同步機制失效
3、SharedPreferencesk可靠性降低
4、Application會創建多次
其實本質問題就是不在同一個存儲空間上了。
三、言歸正傳
說了那么多廢話,我們來說一下AIDL:
1、首先我們要了解一下AIDL,它是android定義的一種接口語言。既然是語言我們就要了解它支持哪些數據類型,怎么使用。它支持java中的八中數據類型和String類型,CharSequence、List、Map類型(注意List支持泛型,Map不支持)除了這幾種數據類型之外的數據類型,都需要進行導包,不管這個類是否在同一個包文件下,這是與java不同的地方。
2、我們知道AIDL文件是以.aidl為結尾的文件。aidl文件大致可以分為兩類:
①一類是我們定義的序列化對象,供其他的aidl文件調用,因為這個對象不是aidl支持數據類型中的對象,所以必須把它序列化成一個aidl文件,在其他aidl文件中通過導包進行引用。
②還有一類就是我們定義的接口方法。
3、如何使用
這里我們以其他的數據類型為例,因為默認的數據類型不需要進行創建第一類aidl文件比較簡單,所以就不做討論。
我們以兩個應用為例進行跨進程通信,一個為server端,一個為client端。server端中創建一個Student類,Student類中有姓名和年齡,客戶端進行對Student類進行獲取Student的信息,添加信息操作。
首先在server端中我們創建一個Student對象實現Pacelable接口,代碼如下:
package com.sheca.aidlserver; import android.os.Parcel; import android.os.Parcelable; /** * @author xuchangqing * @time 2019/11/21 13:59 * @descript */ public class Student implements Parcelable { ??? private String mName; ??? private int mAge; ??? ??? public Student(){} ??? protected Student(Parcel in) { ??????? mName = in.readString(); ??????? mAge = in.readInt(); ??? } ??? public static final Creator<Student> CREATOR = new Creator<Student>() { ??????? @Override ??????? public Student createFromParcel(Parcel in) { ??????????? return new Student(in); ??????? } ??????? @Override ??????? public Student[] newArray(int size) { ??????????? return new Student[size]; ??????? } ??? }; ??? public String getName() { ??????? return mName; ??? } ??? public void setName(String name) { ??????? mName = name; ??? } ??? public int getAge() { ??????? return mAge; ??? } ??? public void setAge(int age) { ??????? mAge = age; ??? } ??? @Override ??? public String toString() { ??????? return "Student{" + ??????????????? "mName='" + mName + '\'' + ??????????????? ", mAge=" + mAge + ??????????????? '}'; ??? } ??? @Override ??? public int describeContents() { ??????? return 0; ??? } ??? @Override ??? public void writeToParcel(Parcel dest, int flags) { ??????? dest.writeString(mName); ??????? dest.writeInt(mAge); ??? } }
這里實現pacelable接口默認實現writeTopacel方法,我們先解釋一下aidl數據流向的問題,在aidl中tag有三種類型:in、out和inout,這三種類型都是站在服務端的角度考慮的, in表示數據流向從客戶端流向服務端,out表示從服務端流向客戶端,inout表示是雙向數據通道。關于這個AIDL的tag問題我們后文繼續詳述,這里只做了解即可。AIDL默認的數據類型是in類型,如果要支持out或者inout類型,還要實現readFromParcel方法。不然會報錯,這個方法readFrmoParcel其實已經在Api更新的時候取消了,不需要外部強制重寫。定義如下
public void readFromParcel(Parcel dest){ ??? mName=dest.readString(); ??? mAge=dest.readInt(); }
然后我們在這個Student類的包名下創建一個Student的aidl文件,這個文件就是我們所說的第一種aidl文件,供其他aidl文件使用。系統會自動生成一個同樣包名的aidl文件:
然后我們修改這個文件的代碼如下:
// StudentAIDL.aidl package com.sheca.aidlserver; // Declare any non-default types here with import statements parcelable Student;
僅接著我們創建第二個aidl文件StudentManager,供客戶端調用。
// StudentManager.aidl package com.sheca.aidlserver; // Declare any non-default types here with import statements import com.sheca.aidlserver.StudentAIDL; interface StudentManager { ??? List<Student> getStudent(); ??? void addStudent(in Student student); }
StudentManager中封裝了兩個方法,一個是獲取學生信息,一個添加學生信息。
注意:因為Student類不屬于AIDL支持的數據類型,我們把它轉換成了AIDL支持的數據類型即生成一個Student的AIDL文件,所以我們要導入的包是aidl文件類型的包。
服務端創建一個service
package com.sheca.aidlserver; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; import java.util.ArrayList; import java.util.List; /** * @author xuchangqing * @time 2019/11/21 16:36 * @descript */ public class StudentServer extends Service { ??? private List<Student> mStudentList= new ArrayList<>(); ??? @Override ??? public IBinder onBind(Intent intent) { ??????? return mStudentBinder; ??? } ??? StudentManager.Stub mStudentBinder=new StudentManager.Stub(){ ??????? @Override ??????? public List<Student> getStudent() throws RemoteException { ??????????? return mStudentList; ??????? } ??????? @Override ??????? public void addStudent(Student student) throws RemoteException { ??????????? mStudentList.add(student); ??????? } ??? }; }
然后在Mainfest中配置這個Service。
<service android:name=".StudentServer" ??? > ??? <intent-filter> ??????? <category android:name="android.intent.category.DEFAULT"/> ??????? <action android:name="xcq_server_service"/> ??? </intent-filter> </service>
這樣我們的服務端代碼已經全部定義好了。下面開始編寫客戶端的代碼,
客戶端需要把之前在服務端創建的所有的AIDL文件和Bean文件一同復制到客戶端的工程下,注意從服務端復制的代碼,包名不能改變。工程結構如下:
我們在客戶端代碼里面加入兩個按鈕,一個按鈕添加學生信息,一個按鈕用于獲取學生信息。
客戶端與服務端通過bindservice()建立連接,在onServiceConnected()方法中,將service轉換成定義的AIDL對象的實例,通過這個實例調用;
package com.sheca.aidlclient; 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.TextView; import com.sheca.aidlserver.Student; import com.sheca.aidlserver.StudentManager; import java.util.List; public class MainActivity extends AppCompatActivity implements View.OnClickListener { ??? private TextView mGetData; ??? private TextView mAddData; ??? private TextView mContent; ??? private StudentManager mManager; ??? private List<com.sheca.aidlserver.Student> mStudent; ??? private int i=0; ??? private TextView mBind; ??? @Override ??? protected void onCreate(Bundle savedInstanceState) { ??????? super.onCreate(savedInstanceState); ??????? setContentView(R.layout.activity_main); ??????? initService(); ??????? initView(); ??? } ??? private void initService() { ??????? Intent intent? = new Intent(); ??????? intent.setPackage("com.sheca.aidlserver"); ??????? intent.setAction("xcq_server_service"); ??????? bindService(intent,mConnection, Context.BIND_AUTO_CREATE); ??? } ??? private void initView() { ??????? mGetData=(TextView)findViewById(R.id.tv_getdata); ??????? mAddData=(TextView)findViewById(R.id.tv_setdata); ??????? mContent=findViewById(R.id.tv_content); ??????? mBind=findViewById(R.id.tv_bind); ??????? mGetData.setOnClickListener(this); ??????? mAddData.setOnClickListener(this); ??????? mBind.setOnClickListener(this); ??? } ??? @Override ??? public void onClick(View v) { ??????? switch (v.getId()){ ??????????? case R.id.tv_getdata: ??????????????? if(mManager!=null){ ??????????????????? Log.e("Connect","開始獲取數據"); ??????????????? try { ?????????????????? mStudent= mManager.getStudent(); ?????????????????? mContent.setText("mStudent"+mStudent.get(mStudent.size()-1)); ??????????????? } catch (RemoteException e) { ??????????????????? e.printStackTrace(); ??????????????? } ??????????????? }else{ ??????????????????? Log.e("Connect","未綁定"); ??????????????? } ??????????????? break; ??????????? case R.id.tv_setdata: ??????????????? if(mManager!=null) { ??????????????????? Log.e("Connect","開始添加數據"); ??????????????????? i++; ??????????????????? Student student = new Student(); ??????????????????? student.setName("張三" + i); ??????????????????? student.setAge(23); ??????????????????? try { ??????????????????????? mManager.addStudent(student); ??????????????????? } catch (RemoteException e) { ??????????????????????? e.printStackTrace(); ??????????????????? } ??????????????? }else{ ??????????????????? Log.e("Connect","未綁定"); ??????????????? } ??????????????? break; ??????????? case R.id.tv_bind: ??????????????? Intent intent? = new Intent(); ??????????????? intent.setPackage("com.sheca.aidlserver"); ??????????????? intent.setAction("xcq_server_service"); ??????????????? bindService(intent,mConnection, Context.BIND_AUTO_CREATE); ??????????????? break; ??????? } ??? } ??? ServiceConnection mConnection =new ServiceConnection(){ ??????? @Override ??????? public void onServiceConnected(ComponentName name, IBinder service) { ??????????? mManager=StudentManager.Stub.asInterface(service); ??????????? Log.e("Connect","isConnext"+name); ??????? } ??????? @Override ??????? public void onServiceDisconnected(ComponentName name) { ??????? } ??? }; ??? @Override ??? protected void onDestroy() { ??????? super.onDestroy(); ??????? unbindService(mConnection); ??? } }
這樣的一個簡單的AIDL通信就建立完成了。
為什么AIDL可以完成跨進程的通信呢?我們生成的AIDL文件,它幫助我們做了一些什么事情呢?
我們在工程的/build/generated/aidl_source_output_dir/debug/compileDebugAidl/文件夾下可以查看到系統自動編譯出的AIDL文件:
在這里,我們看一下源碼:
/* * This file is auto-generated.? DO NOT MODIFY. */ package com.sheca.aidlserver; public interface StudentManager extends android.os.IInterface { ? /** Default implementation for StudentManager. */ ? public static class Default implements com.sheca.aidlserver.StudentManager ? { ??? @Override public java.util.List<com.sheca.aidlserver.Student> getStudent() throws android.os.RemoteException ??? { ????? return null; ??? } ??? @Override public void addStudent(com.sheca.aidlserver.Student student) throws android.os.RemoteException ??? { ??? } ??? @Override ??? public android.os.IBinder asBinder() { ????? return null; ??? } ? } ? /** Local-side IPC implementation stub class. */ ? public static abstract class Stub extends android.os.Binder implements com.sheca.aidlserver.StudentManager ? { ??? private static final java.lang.String DESCRIPTOR = "com.sheca.aidlserver.StudentManager"; ??? /** Construct the stub at attach it to the interface. */ ??? public Stub() ??? { ????? this.attachInterface(this, DESCRIPTOR); ??? } ??? /** ???? * Cast an IBinder object into an com.sheca.aidlserver.StudentManager interface, ???? * generating a proxy if needed. ???? */ ??? public static com.sheca.aidlserver.StudentManager asInterface(android.os.IBinder obj) ??? { ????? if ((obj==null)) { ??????? return null; ????? } ????? android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); ????? if (((iin!=null)&&(iin instanceof com.sheca.aidlserver.StudentManager))) { ??????? return ((com.sheca.aidlserver.StudentManager)iin); ????? } ????? return new com.sheca.aidlserver.StudentManager.Stub.Proxy(obj); ??? } ??? @Override public android.os.IBinder asBinder() ??? { ????? return this; ??? } ??? @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException ??? { ????? java.lang.String descriptor = DESCRIPTOR; ????? switch (code) ????? { ??????? case INTERFACE_TRANSACTION: ??????? { ????????? reply.writeString(descriptor); ????????? return true; ??????? } ??????? case TRANSACTION_getStudent: ??????? { ????????? data.enforceInterface(descriptor); ????????? java.util.List<com.sheca.aidlserver.Student> _result = this.getStudent(); ????????? reply.writeNoException(); ????????? reply.writeTypedList(_result); ????????? return true; ??????? } ??????? case TRANSACTION_addStudent: ??????? { ????????? data.enforceInterface(descriptor); ????????? com.sheca.aidlserver.Student _arg0; ????????? if ((0!=data.readInt())) { ??????????? _arg0 = com.sheca.aidlserver.Student.CREATOR.createFromParcel(data); ????????? } ????????? else { ??????????? _arg0 = null; ????????? } ????????? this.addStudent(_arg0); ????????? reply.writeNoException(); ????????? return true; ??????? } ??????? default: ??????? { ????????? return super.onTransact(code, data, reply, flags); ??????? } ????? } ??? } ??? private static class Proxy implements com.sheca.aidlserver.StudentManager ??? { ????? private android.os.IBinder mRemote; ????? Proxy(android.os.IBinder remote) ????? { ??????? mRemote = remote; ????? } ????? @Override public android.os.IBinder asBinder() ????? { ??????? return mRemote; ????? } ????? public java.lang.String getInterfaceDescriptor() ????? { ??????? return DESCRIPTOR; ????? } ????? @Override public java.util.List<com.sheca.aidlserver.Student> getStudent() throws android.os.RemoteException ????? { ??????? android.os.Parcel _data = android.os.Parcel.obtain(); ??????? android.os.Parcel _reply = android.os.Parcel.obtain(); ??????? java.util.List<com.sheca.aidlserver.Student> _result; ??????? try { ????????? _data.writeInterfaceToken(DESCRIPTOR); ????????? boolean _status = mRemote.transact(Stub.TRANSACTION_getStudent, _data, _reply, 0); ????????? if (!_status && getDefaultImpl() != null) { ??????????? return getDefaultImpl().getStudent(); ????????? } ????????? _reply.readException(); ????????? _result = _reply.createTypedArrayList(com.sheca.aidlserver.Student.CREATOR); ??????? } ??????? finally { ????????? _reply.recycle(); ????????? _data.recycle(); ??????? } ??????? return _result; ????? } ????? @Override public void addStudent(com.sheca.aidlserver.Student student) throws android.os.RemoteException ????? { ??????? android.os.Parcel _data = android.os.Parcel.obtain(); ??????? android.os.Parcel _reply = android.os.Parcel.obtain(); ??????? try { ????????? _data.writeInterfaceToken(DESCRIPTOR); ????????? if ((student!=null)) { ??????????? _data.writeInt(1); ??????????? student.writeToParcel(_data, 0); ????????? } ????????? else { ??????????? _data.writeInt(0); ????????? } ????????? boolean _status = mRemote.transact(Stub.TRANSACTION_addStudent, _data, _reply, 0); ????????? if (!_status && getDefaultImpl() != null) { ??????????? getDefaultImpl().addStudent(student); ??????????? return; ????????? } ????????? _reply.readException(); ??????? } ??????? finally { ????????? _reply.recycle(); ????????? _data.recycle(); ??????? } ????? } ????? public static com.sheca.aidlserver.StudentManager sDefaultImpl; ??? } ??? static final int TRANSACTION_getStudent = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); ??? static final int TRANSACTION_addStudent = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); ??? public static boolean setDefaultImpl(com.sheca.aidlserver.StudentManager impl) { ????? if (Stub.Proxy.sDefaultImpl == null && impl != null) { ??????? Stub.Proxy.sDefaultImpl = impl; ??????? return true; ????? } ????? return false; ??? } ??? public static com.sheca.aidlserver.StudentManager getDefaultImpl() { ????? return Stub.Proxy.sDefaultImpl; ??? } ? } ? public java.util.List<com.sheca.aidlserver.Student> getStudent() throws android.os.RemoteException; ? public void addStudent(com.sheca.aidlserver.Student student) throws android.os.RemoteException; }
很長,不過不用著急,我們一步步分析。首先我們定義的AIDL接口繼承了android.os.IInterface 這個接口。
這個接口是做什么的呢?我們查看一下這個接口的源碼,發現只有一個方法asBinder();
package android.os; /** * Base class for Binder interfaces.? When defining a new interface, * you must derive it from IInterface. */ public interface IInterface { ??? /** ???? * Retrieve the Binder object associated with this interface. ???? * You must use this instead of a plain cast, so that proxy objects ???? * can return the correct result. ???? */ ??? public IBinder asBinder(); }
如果想獲取和該接口關聯的Binder對象。你必須使用這個方法而不是使用一個簡單的類型轉化。這樣代理對象才能返回正確的結果。我們可以看出這是一個類型轉換的接口,它將服務或者服務代理通過asBinder()轉換成一個IBinder對象。
回到StudentManager接著往下看,在這個接口中我們可以查看到我們在aidl中定義的兩個方法getStudent()和addStudent(),同時使用兩個整型的id標記這兩個方法,在transact()方法中區分調用哪個方法。接著它聲明了一個Stub類,在這個類中如果客戶端與服務端處在同一個進程中,就不會走transact過程,當兩者處于不同進程時,方法會調用transact過程,這個過程是由下面的Proxy類來判斷完成的。
我們接著往下看有一個asInterface(android.os.IBinder obj)這個方法,它是將服務端返回的binder對象轉換成客戶端所需要的aidl類型,這個轉換過程是區分進程的,如果客戶端與服務端在統一進程中那么返回的對象是Stub對象本身,否則返回的是系統封裝后的Stub.proxy對象。
另外還有一個方法是OnTranscat()方法,這個方法運行在服務端的Binder線程池中,當客戶端發起請求時,遠程請求會通過系統底層封裝后交給該方法響應。我們還可以在這個方法中進行權限校驗,這個我們后面再說。
通過以上的描述我們清楚的了解了AIDL的工作原理。在Binder中還有兩個重要的方法linkToDeath和UnlinkToDeath來判斷Binder是否死亡。
這里不做過多的描述,感興趣的同學可以寫個demo嘗試一下。另外有個注意事項需要說一下我們知道
服務務端的方法是在binder線程池中運行的,如果多個客戶端對接一個服務端可能存在并發問題。因此我們存儲學生信息的容器ArrayList可以換成CopyOnWriteArrayList<>它支持并發操作,類似的HashMap也可以更換成ConCurrentHashMap
四、AIDL接口回調回傳數據
現在我們來說一個問題:如果我們客戶端不想通過調用getStudent()方法來查詢學生的信息,而是一旦有學生被添加了就通知給客戶端,該如何做呢?
我們知道這是一個典型的觀察者模式。首先我們定義一個AIDL接口作為通知的接口回調(AIDL接口不能使用普通接口),然后讓客戶端訂閱這個接口,當然還要定義一個取消訂閱的接口,供客戶端取消訂閱。
// IStudentAddInterface.aidl package com.sheca.aidlserver; // Declare any non-default types here with import statements import com.sheca.aidlserver.StudentAIDL; interface IStudentAddInterface { ??? void isStudentAdded(in Student student); }
// StudentManager.aidl package com.sheca.aidlserver; // Declare any non-default types here with import statements import com.sheca.aidlserver.StudentAIDL; import com.sheca.aidlserver.IStudentAddInterface; interface StudentManager { ??? List<Student> getStudent(); ??? void addStudent(in Student student); ??? void registerListener(IStudentAddInterface listener); ??? void unRegisterListener(IStudentAddInterface listener); }
服務端的代碼除了實現新增的兩個注冊和取消注冊的接口,在onCreate()中開啟一個線程每隔兩秒新增一個學生,再通知注冊這個接口的客戶端。
package com.sheca.aidlserver; ??????? import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; /** * @author xuchangqing * @time 2019/11/21 16:36 * @descript */ public class StudentServer extends Service { ??? private boolean isDestroyed =true; ??? private CopyOnWriteArrayList<Student> mStudentList= new CopyOnWriteArrayList<>(); ??? private CopyOnWriteArrayList<IStudentAddInterface> mListenerList = new CopyOnWriteArrayList<>(); ??? @Override ??? public IBinder onBind(Intent intent) { ??????? return mStudentBinder; ??? } ??? StudentManager.Stub mStudentBinder=new StudentManager.Stub(){ ??????? @Override ??????? public List<Student> getStudent() throws RemoteException { ??????????? return mStudentList; ??????? } ??????? @Override ??????? public void addStudent(Student student) throws RemoteException { ??????????? mStudentList.add(student); ??????? } ??????? @Override ??????? public void registerListener(IStudentAddInterface listener) throws RemoteException { ??????????? if(!mListenerList.contains(listener)){ ??????????????? mListenerList.add(listener); ??????????? } ??????????? Log.d("CONNECTION","注冊訂閱"); ??????? } ??????? @Override ??????? public void unRegisterListener(IStudentAddInterface listener) throws RemoteException { ??????????? if(mListenerList.contains(listener)){ ??????????????? mListenerList.remove(listener); ??????????? } ??????????? Log.d("CONNECTION","取消訂閱"); ??????? } ??? }; ??? @Override ??? public void onCreate() { ??????? super.onCreate(); ??????? Log.d("CONNECTION","isDestroyed"+isDestroyed); ??????? //開啟線程每隔兩秒新增一個學生 ??????? new Thread(new Runnable() { ??????????? @Override ??????????? public void run() { ??????????????? while (isDestroyed) { ??????????????????? try { ??????????????????????? Thread.sleep(2000); ??????????????????? } catch (InterruptedException e) { ??????????????????????? e.printStackTrace(); ??????????????????? } ??????????????????? Student mStudent = new Student(); ??????????????????? mStudent.setAge(mStudentList.size() + 1); ??????????????????? Log.d("CONNECTION", "新學生" + mStudent.getAge()); ????????????????? //訂閱消息通知 ??????????????????? onNewStudentAdded(mStudent); ??????????????? } ??????????? } ??????? }).start(); ??? } ??? private void onNewStudentAdded(Student mStudent) { ??????? mStudentList.add(mStudent); ??????? for (int i = 0; i < mListenerList.size(); i++) { ??????????? try { ??????????????? IStudentAddInterface listener = mListenerList.get(i); ??????????????? listener.isStudentAdded(mStudent); ??????????????? //訂閱消息 ??????????????? Log.d("CONNECTION","listener"+listener); ??????????? } catch (RemoteException e) { ??????????????? e.printStackTrace(); ??????????? } ??????? } ??? } ??? @Override ??? public void onDestroy() { ??????? super.onDestroy(); ??????? isDestroyed=false; ??? } }
我們再更改一下客戶端的代碼,客戶端只需要在建立連接的時候注冊改訂閱即可,我們知道服務端的方法是在binder線程池中執行的,客戶端在調用的時候可能會出現線程阻塞的狀況。因此我們可以采用handler來轉換一下線程。
當然我們還要在頁面關閉的時候取消注冊。
package com.sheca.aidlclient; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.widget.TextView; import com.sheca.aidlserver.IStudentAddInterface; import com.sheca.aidlserver.Student; import com.sheca.aidlserver.StudentManager; import java.util.List; public class MainActivity extends AppCompatActivity implements View.OnClickListener { ??? private TextView mGetData; ??? private TextView mAddData; ??? private TextView mContent; ??? private StudentManager mManager; ??? private List<com.sheca.aidlserver.Student> mStudent; ??? private int i=0; ??? private TextView mBind; ??? private Handler mHandler = new Handler(Looper.myLooper()){ ??????? @Override ??????? public void handleMessage(Message msg) { ??????????? super.handleMessage(msg); ??????????? switch(msg.what){ ??????????? case 1000: ??????????????? Log.e("Connection","Student"+msg.obj); ??????????????? break; ??????????? } ??????? } ??? }; ??? private IStudentAddInterface isAddListener = new IStudentAddInterface.Stub() { ??????? @Override ??????? public void isStudentAdded(Student student) throws RemoteException { ??????????? mHandler.obtainMessage(1000, student).sendToTarget(); ??????? } ??? }; ??? @Override ??? protected void onCreate(Bundle savedInstanceState) { ??????? super.onCreate(savedInstanceState); ??????? setContentView(R.layout.activity_main); ??????? initService(); ??????? initView(); ??? } ??? private void initService() { ??????? Intent intent? = new Intent(); ??????? intent.setPackage("com.sheca.aidlserver"); ??????? intent.setAction("xcq_server_service"); ??????? bindService(intent,mConnection, Context.BIND_AUTO_CREATE); ??? } ??? private void initView() { ??????? mGetData=(TextView)findViewById(R.id.tv_getdata); ??????? mAddData=(TextView)findViewById(R.id.tv_setdata); ??????? mContent=findViewById(R.id.tv_content); ??????? mBind=findViewById(R.id.tv_bind); ??????? mGetData.setOnClickListener(this); ??????? mAddData.setOnClickListener(this); ??????? mBind.setOnClickListener(this); ??? } ??? @Override ??? public void onClick(View v) { ??????? switch (v.getId()){ ??????????? case R.id.tv_getdata: ??????????????? if(mManager!=null){ ??????????????????? Log.e("Connect","開始獲取數據"); ??????????????? try { ?????????????????? mStudent= mManager.getStudent(); ?????????????????? mContent.setText("mStudent"+mStudent.get(mStudent.size()-1)); ??????????????? } catch (RemoteException e) { ??????????????????? e.printStackTrace(); ??????????????? } ??????????????? }else{ ??????????????????? Log.e("Connect","未綁定"); ??????????????? } ??????????????? break; ??????????? case R.id.tv_setdata: ??????????????? if(mManager!=null) { ??????????????????? Log.e("Connect","開始添加數據"); ??????????????????? i++; ??????????????????? Student student = new Student(); ??????????????????? student.setName("張三" + i); ??????????????????? student.setAge(23); ??????????????????? try { ??????????????????????? mManager.registerListener(isAddListener); ??????????????????? } catch (RemoteException e) { ??????????????????????? e.printStackTrace(); ??????????????????? } ??????????????????? try { ??????????????????????? mManager.addStudent(student); ??????????????????? } catch (RemoteException e) { ??????????????????????? e.printStackTrace(); ??????????????????? } ??????????????? }else{ ??????????????????? Log.e("Connect","未綁定"); ??????????????? } ??????????????? break; ??????????? case R.id.tv_bind: ??????????????? Intent intent? = new Intent(); ??????????????? intent.setPackage("com.sheca.aidlserver"); ??????????????? intent.setAction("xcq_server_service"); ??????????????? bindService(intent,mConnection, Context.BIND_AUTO_CREATE); ??????????????? break; ??????? } ??? } ??? ServiceConnection mConnection =new ServiceConnection(){ ??????? @Override ??????? public void onServiceConnected(ComponentName name, IBinder service) { ??????????? mManager=StudentManager.Stub.asInterface(service); ??????????? Log.e("Connect","isConnext"+name); ??????????? Student student = new Student(); ??????????? student.setName("張三" + 3); ??????????? student.setAge(23); ??????????? try { ??????????????? mManager.registerListener(isAddListener); ??????????????? Log.e("Connect","注冊訂閱"+isAddListener); ??????????? } catch (RemoteException e) { ??????????????? e.printStackTrace(); ??????????? } ??????????? try { ??????????????? mManager.addStudent(student); ??????????? } catch (RemoteException e) { ??????????????? e.printStackTrace(); ??????????? } ??????? } ??????? @Override ??????? public void onServiceDisconnected(ComponentName name) { ??????? } ??? }; ??? @Override ??? protected void onDestroy() { ??????? try { ??????????? mManager.unRegisterListener(isAddListener); ??????? } catch (RemoteException e) { ??????????? e.printStackTrace(); ??????? } ??????? Log.e("Connect","取消訂閱"+isAddListener); ??????? unbindService(mConnection); ??????? super.onDestroy(); ??? } }
現在我們可以先打開服務端,再打開客戶端進行通信可以看到每隔兩秒鐘會收到訂閱消息
但是當我們關閉頁面進行取消注冊監聽的時候,卻發現之前的監聽沒有找到。
這是因為Binder 會把客戶端傳遞過來的對象重新轉化并生成一個新的對象,雖然我們在注冊和解注冊過程中使用的是同一個客戶端,但是通過 Binder 傳遞到服務端后,卻會產生兩個全新的對象。而對象是不能跨進程傳輸的,對象的跨進程傳輸本質上都是反序列化的過程,這就是為什么 AIDL 中的自定義對象都必須要實現 Parcelable 接口的原因。
那么該如何解決呢?系統給我們提供了一個RemoteCallbackList專門用于刪除跨進程listener的類。注意RemoteCallbackList它并不是一個list,它只是一個泛型
public class RemoteCallbackList<E extends IInterface>{}
從源碼可以看出它支持AIDL所有的接口,工作原理很簡單就是定義一個Map來存儲調用的接口信息,鍵為Ibinder,值為Callback
/*package*/ ArrayMap<IBinder, Callback> mCallbacks ??????? = new ArrayMap<IBinder, Callback>();
key和value的獲取方式如下
*/ public boolean register(E callback, Object cookie) { ??? synchronized (mCallbacks) { ??????? if (mKilled) { ??????????? return false; ??????? } ??????? // Flag unusual case that could be caused by a leak. b/36778087 ??????? logExcessiveCallbacks(); ??????? IBinder binder = callback.asBinder(); ??????? try { ??????????? Callback cb = new Callback(callback, cookie); ??????????? binder.linkToDeath(cb, 0); ??????????? mCallbacks.put(binder, cb); ??????????? return true; ??????? } catch (RemoteException e) { ??????????? return false; ??????? } ??? } }
注意一點RemoteCallbackList 并不是一個List ,不能像 List 一樣去操作它,遍歷RemoteCallbackList 必須要以下面的方式進行,其中 beginBroadcast 和 finishBroadcast 必須配套使用,哪怕我們僅僅是想要獲取 RemoteCallbackList 中的元素個數。
五、權限驗證
增加權限驗證有利于挺高程序的安全性,并不是所有的客戶端想要與服務端建立連接我們都能夠允許通過。
這里介紹兩種常見的做法:
1、在onBInd()方法中進行驗證,不通過直接返回null,這樣客戶端就無法綁定服務。常見的做法增加permission
我們現在服務端的Maniefest中聲明權限
<!--定義權限--> <permission ??? android:name="com.sheca.permission.ACCESS_SERVICE" ??? android:protectionLevel="normal"/>
然后在onBind()方法中進行判斷
@Override public IBinder onBind(Intent intent) { ??? int check = checkCallingOrSelfPermission("com.sheca.permission.ACCESS_SERVICE"); ??? if (check == PackageManager.PERMISSION_DENIED) { ??????? return null; ??? } ??? return mStudentBinder; }
然后客戶端使用的時候必須增加這個權限才能夠使用,做法是在客戶端的Mainefest中增加
<uses-permission android:name="com.sheca.permission.ACCESS_SERVICE"/>
2、onTransact()在這個方法里面我們可以進行權限校驗,可以采用第一種的做法進行permission校驗,我們還可以采用Uid和Pid進行驗證,通過getCallingUid和getCallingPid可以拿到客戶端的所屬應用的uid和pid。通過這兩個參數我們做驗證,比如驗證包名。當然還有其他的驗證方法,比如對service增加權限等等,就不做一一介紹了。
aidl就先介紹到這里,內容很多但不是很難。
總結
以上是生活随笔為你收集整理的AndroidIPC通信之AIDL的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python3调用js的库之execjs
- 下一篇: STP RSTP MSTP PVST+学