您好,登錄后才能下訂單哦!
為了弄懂IPC的來龍去脈,我將從以下三個方面為大家來講解,希望對大家理解IPC會有幫助
IPC是Inter Process Communication的縮寫,其意思就是進程間的通信,也就是兩個進程之間的通信過程。我們都知道在Android系統中,每個應用都運行在一個進程上,具有自己的DVM實例,而且進程之間是相互隔離的,也就是說各個進程之間的數據是互相獨立,互不影響的,而如果一個進程崩潰了,也不會影響到另一個進程。
采取這樣的設計是有一定道理的,例如這樣的前提下將互相不影響的系統功能分拆到不同的進程里面去,有助于提升系統的穩定性,畢竟我們都不想自己的應用進程崩潰會導致整個手機系統的崩潰。
進程之間隔離是不錯的選擇,可是如果進程之間想要互相通信,進行數據交互的時候那該怎么辦呢?例如我們在自己的應用中想要訪問手機通訊錄中的聯系人,很顯然這是兩個不同的進程,如果Android沒有提供一種進程之間交流的機制,那么這種功能將無法實現。
不過由于Android系統使用的是Linux內核,而在Linux系統中進程之間的交互是有一套機制的,所以Android也借鑒了其中的一些機制,從而形成了Android的IPC機制。
上面只是粗略的講解了IPC是啥,關于它的使用和原理我將一一為大家呈上。
上一點中我舉了訪問手機通訊錄的例子。但你可能覺得我不需要用到這種功能,那么我就不用管IPC啦!其實不然,IPC在我們的應用開發過程中隨處可見,下面我將舉一個例子來說明他的重要性。
我們在MainActivity中修改一個靜態變量,接著在另一個進程的SecondActivity中去訪問該變量,看看能否讀取已經修改過的變量。
1、新建一個Student類,并聲明一個靜態變量
public class Student { public static String name="BOB"; }
2、在MainActivity的onCreate方法中修改name的值,并打印log
@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Student.name = "JACK"; Log.d("MainActivity:Sname=", Student.name); }
3、將SecondActivity設置為新進程,并在其onCreate方法中訪問name
<!-- 在清單文件中通過android:process屬性為SecondActivity指定特定的進程:com.bob.aidltest:second --><activity android:name=".SecondActivity" android:process=":second"></activity>
public class SecondActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.second_activity); Log.d("SecondActivity:Sname=" , Student.name); } }
4、通過log,可以看到在MainActivity中修改了name的值,但是在SecondActivity中卻無法讀取修改后的值
通過以上的實驗,大家應該明白了一點:在不同的進程之間訪問同一個靜態變量是行不通的。其原因是:每一個進程都分配有一個獨立的虛擬機,不同的虛擬機在內存分配上有不同的地址空間,這就導致在不同的虛擬機上訪問同一個對象會產生多個副本。例如我們在MainActivity中訪問的name的值只會影響當前進程,而對其他進程不會造成影響,所以在SecondActivity中訪問name時依舊只能訪問自己進程中的副本。
上面也講到,為了解決這些跨進程的問題,Android沿用了一些Linux的進程管理機制,使得進程之間能夠進行交互,下面我將列出一些常見的IPC方式,需要指出的是本文主要講解Binder機制,所以會注重講解AIDL,其他方式請讀者自行查閱相關資料。
名稱 | 特點 | 使用場景 |
---|---|---|
Bundle | 只能傳輸實現了Serializable或者Parcelable接口或者一些Android支持的特殊對象 | 適合用于四大組件之間的進程交互 |
文件 | 不能做到進程間的即時通信,并且不適合用于高并發的場景 | 適合用于SharedPreference以及IO操作 |
ContentProvider | 可以訪問較多的數據,支持一對多的高并發訪問,因為ContentProvider已經自動做好了關于并發的機制 | 適合用于一對多的數據共享并且需要對數據進行頻繁的CRUD操作 |
Socket | 通過網絡傳輸字節流,支持一對多的實時通信,但是實現起來比較復雜 | 適合用于網絡數據共享 |
Messenger | 底層原理是AIDL,只是對其做了封裝,但是不能很好的處理高并發的場景,并且傳輸的數據只能支持Bundle類型 | 低并發的一對多的即時通信 |
AIDL | 功能強大,使用Binder機制(接下來會講解),支持一對多的高并發實時通信,但是需要處理好線程同步 | 一對多并且有遠程進程通信的場景 |
終于來到這篇文章的重頭戲了,上面講到Android解決IPC的方法中有一種是AIDL,它使用的原理就是Binder,只有理解了Binder,我們才算是理解了Android跨進程通信的原理。在這里我會帶大家看看Android中有哪一些重要的地方使用到了Binder,接著我們會通過一個實例來了解如何使用Binder,最后我們會分析Binder的源碼來理解他的工作流程。
說起Binder在Android的使用場景,可以說是無處不在,我列出一些最常見的場景:
四大組件的生命周期都是使用Binder機制進行管理的
View的工作原理也使用了Binder
WindowManager的工作機制同樣使用了Binder
以上三個方面只是最常見的場景,但是卻幾乎包括了我們開發的整個流程。我們開發的應用都離不開四大組件,而四大組件也正是依靠Binder機制運行的;對于我們最常見的View,他是如何顯示的,View又是如何響應我們的動作的,這其中也用到了Binder(關于這些內容我會在后續的文章中為大家分析)。可以說了解Binder對于我們的開發是很有幫助的,那接下來我們就來看看我們該如何使用Binder進行進程間的通信吧!
現在我們需要實現這樣的功能:客戶端與服務端位于不同的進程,客戶端需要向服務端添加學生,同時客戶端還可以向服務端發起查詢學生列表的請求。
Student.java
package com.bob.aidltest.aidl;import android.os.Parcel;import android.os.Parcelable;/** * Created by bob on 17-7-3. * 所有需要在Binder傳遞的數據類型都需要實現Parcelable接口 */public class Student implements Parcelable{ public static String name="BOB"; public int s_id; public String s_name; public String s_gender; public Student(Parcel in) { s_id = in.readInt(); s_name = in.readString(); s_gender = in.readString(); } public Student(int s_id, String s_name, String s_gender) { this.s_id = s_id; this.s_name = s_name; this.s_gender = s_gender; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(s_id); dest.writeString(s_name); dest.writeString(s_gender); } 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]; } }; @Override public String toString() { return String.format("[StudentID: %s , StudentName: %s , StudentGender: %s]", s_id, s_name, s_gender); } }
Student.aidl
// Student1.aidlpackage com.bob.aidltest.aidl;parcelable Student;
IStudentManager.aidl
// IStudentManager.aidlpackage com.bob.aidltest.aidl;import com.bob.aidltest.aidl.Student;interface IStudentManager { List<Student> getStudentList(); void addStudent(in Student student); }
創建完畢之后手動編譯項目(Build-->ReBuild Project),接著就會在app/build/generated/source/aidl/debug/com/bob/aidltest/aidl/IStudentManager.java中看到自動生成的IStudentManager接口,如下圖:
先來看看自動生成的代碼:
public interface IStudentManager extends android.os.IInterface{ /** 內部類Stub,繼承自Binder并且實現了IStudentManager接口,因此他也是一個Binder對象,這個內部類是需要在服務端手動實現的,并且會通過onBind方法返回給客戶端 */ public static abstract class Stub extends android.os.Binder implements com.bob.aidltest.aidl.IStudentManager { private static final java.lang.String DESCRIPTOR = "com.bob.aidltest.aidl.IStudentManager"; /** 構造方法 */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * 將服務端的Binder對象轉換為客戶端的所需的AIDL接口類型的對象,客戶端拿到這個對象就可以通過這個對象遠程訪問服務端的方法 */ public static com.bob.aidltest.aidl.IStudentManager asInterface(android.os.IBinder obj) { if ((obj==null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin!=null)&&(iin instanceof com.bob.aidltest.aidl.IStudentManager))) { return ((com.bob.aidltest.aidl.IStudentManager)iin); } return new com.bob.aidltest.aidl.IStudentManager.Stub.Proxy(obj); } @Override public android.os.IBinder asBinder() { return this; } /** * 運行在服務端進程的Binder線程池中;當客戶端進程發起遠程請求時,遠程請求會要求系統底層執行回調該方法 * @param code 客戶端進程請求方法標識符。服務端進程會根據該標識確定所請求的目標方法 * @param data 目標方法的參數,他是客戶端進程傳進來的,當我們調用addStudent(Student student)方法時,參數就是Student對象 * @param reply 目標方法執行后的結果,將會返回給客戶端,例如當我們調用getStudentList,返回的就是一個Student的列表 */ @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR); return true; } case TRANSACTION_getStudentList: { data.enforceInterface(DESCRIPTOR); java.util.List<com.bob.aidltest.aidl.Student> _result = this.getStudentList(); reply.writeNoException(); reply.writeTypedList(_result); return true; } case TRANSACTION_addStudent: { data.enforceInterface(DESCRIPTOR); com.bob.aidltest.aidl.Student _arg0; if ((0!=data.readInt())) { _arg0 = com.bob.aidltest.aidl.Student.CREATOR.createFromParcel(data); } else { _arg0 = null; } this.addStudent(_arg0); reply.writeNoException(); return true; } } return super.onTransact(code, data, reply, flags); } /** * 代理的內部類,他實現了IStudentManager接口,這個代理類就是服務端返回給客戶端的AIDL接口對象,客戶端可以通過這個代理類訪問服務端的方法 */ private static class Proxy implements com.bob.aidltest.aidl.IStudentManager { 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.bob.aidltest.aidl.Student> getStudentList() throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); java.util.List<com.bob.aidltest.aidl.Student> _result; try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_getStudentList, _data, _reply, 0); _reply.readException(); _result = _reply.createTypedArrayList(com.bob.aidltest.aidl.Student.CREATOR); } finally { _reply.recycle(); _data.recycle(); } return _result; } @Override public void addStudent(com.bob.aidltest.aidl.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); } mRemote.transact(Stub.TRANSACTION_addStudent, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } } static final int TRANSACTION_getStudentList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_addStudent = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); } public java.util.List<com.bob.aidltest.aidl.Student> getStudentList() throws android.os.RemoteException; public void addStudent(com.bob.aidltest.aidl.Student student) throws android.os.RemoteException; }
可能看了上面的注釋大家還是一頭霧水,那就先看看這個類的結構圖吧:
有關這個類的細節我們待會講,現在只需要知道我們需要在服務端手動實現Proxy類并實現其中的方法。
/** * Created by bob on 17-7-3. * 服務端代碼 */public class StudentManagerService extends Service { private static final String TAG = "StudentManagerService"; //判斷Service是否銷毀 private AtomicBoolean mIsServiceDestroyed = new AtomicBoolean(false); //適合用于進程間傳輸的列表類 private CopyOnWriteArrayList<Student> mStudentList = new CopyOnWriteArrayList<Student>(); @Override public void onCreate() { super.onCreate(); //在服務端手動添加兩位默認的學生 mStudentList.add(new Student(1, "BOB", "man")); mStudentList.add(new Student(2, "MAY", "woman")); } @Override public IBinder onBind(Intent intent) { return mBinder; } @Override public void onDestroy() { mIsServiceDestroyed.set(false); super.onDestroy(); } private Binder mBinder = new IStudentManager.Stub() { @Override public List<Student> getStudentList() throws RemoteException { SystemClock.sleep(5000);//休眠5s模擬耗時操作 return mStudentList; } @Override public void addStudent(Student student) throws RemoteException { mStudentList.add(student); } }; }
在清單文件中指定服務的進程
<service android:name=".StudentManagerService" android:process=":remote"></service>
可以看到這個服務類跟普通的服務類相差并不大,唯一的區別在于它創建了一個IStudentManager.Stub的匿名內部類并且實現了其中的方法,在onBind方法中將這個IBinder對象返回給客戶端。這里需要說明一下:Binder是實現了IBinder接口的,所以他同時也是一個IBinder對象。
public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity_Client"; private static final int MESSAGE_QUERY_STUDENTLIST=1; private int student_size = 3; private IStudentManager mRemoteStudentManager; private ServiceConnection mConnection=new ServiceConnection() { //onServiceConnected與onServiceDisconnected都是在主線程中的,所以如果里面如果涉及到服務端的耗時操作那么需要在子線程中進行 @Override public void onServiceConnected(ComponentName name, IBinder service) { //獲取到IStudentManager對象 final IStudentManager studentManager =IStudentManager.Stub.asInterface(service); mRemoteStudentManager = studentManager; } @Override public void onServiceDisconnected(ComponentName name) { mRemoteStudentManager = null; Log.d(TAG, "onServiceDisconnected.threadname:" + Thread.currentThread().getName()); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Intent intent = new Intent(this, StudentManagerService.class); bindService(intent, mConnection, BIND_AUTO_CREATE); } @Override protected void onDestroy() { unbindService(mConnection); super.onDestroy(); } //將服務端返回的數據顯示在界面上 private Handler mHandler=new Handler(){ @Override public void handleMessage(Message msg) { switch (msg.what) { case MESSAGE_QUERY_STUDENTLIST: Toast.makeText(MainActivity.this, msg.obj.toString(),Toast.LENGTH_SHORT).show(); default: super.handleMessage(msg); } } }; /** * 在客戶端向服務端添加一名學生 * @param view */ public void addStudent(View view) { if (mRemoteStudentManager != null) { try{ int student_id = student_size+ 1; Student newStudent; if (student_id % 2 == 0) { newStudent= new Student(student_id, "新學生" + student_id, "man"); } else { newStudent= new Student(student_id, "新學生" + student_id, "woman"); } mRemoteStudentManager.addStudent(newStudent); Log.d(TAG, "添加一位學生:" + newStudent.toString()); }catch(Exception e){ e.printStackTrace(); } } } /** * 在客戶端向服務端發起查詢學生的請求 * @param view */ public void get_student_list(View view) { Toast.makeText(this, "正在獲取學生列表", Toast.LENGTH_SHORT).show(); //由于服務端的查詢操作是耗時操作,所以客戶端需要開啟子線程進行工作 new Thread(new Runnable() { @Override public void run() { if (mRemoteStudentManager != null) { try{ final List<Student> students = mRemoteStudentManager.getStudentList(); student_size = students.size(); Log.d(TAG, "從服務器成功獲取到學生列表:" + students.toString()); mHandler.obtainMessage(MESSAGE_QUERY_STUDENTLIST, students).sendToTarget(); }catch(Exception e){ e.printStackTrace(); } } } }).start(); } }
可以看到我們在客戶端只需要綁定遠程的服務端,服務端就會返回一個IBinder對象,接著我們需要調用IStudentManager.Stub.asInterface()方法,將這個IBinder對象轉換為我們客戶端可用的AIDL接口對象,拿到這個對象之后我們就可以遠程調用服務端的方法了。是不是很容易?
但是需要注意的一點是為了模擬耗時操作,我們在服務端的getStudentList的方法中使用休眠以模擬耗時操作,所以客戶端在調用該方法時不能直接在主線程中調用,而是應該開啟一個子線程,在子線程中調用這個耗時的操作。
首先我們獲取學生列表,接著連續添加4個學生,再次查看學生列表,最終的結果如下圖,可以看到我們已經實現了兩個進程之間的交互,接下來我們將分析Binder的原理。
首先我們需要了解進程之間為什么不能直接進行通信,以下是兩個進程的示意圖:
從上面的圖我們可以得到以下幾點:
一個進程空間分為:用戶態和內核態,即把進程內用戶和內核隔離開來
進程之間,由于Android系統為每個進程分配了一個獨立的虛擬機,用戶空間和內核空間的數據不可交互
Binder作為進程間的介質,充當了中介,使得進程間的內核態可以通過Binder進行數據交互
圖中總共有四個元素,分別是充當客戶端的Activity,服務端的StudentManagerService,充當服務管理者的IStudentManager以及充當訪問介質的Binder驅動。他們的職責如下:
StudentManagerService: 服務提供者,這里面會有許多我們常用的服務,在本例中提供的服務就是添加學生以及獲取學生列表。而在系統中則包括有ActivityService 、 WindowMananger等服務,這些系統服務提供的功能,對四大組件以及Window的工作提供的保障。
Activity: 服務調用者,一般就是我們的應用,在這里我們通過調用StudentManagerService的服務來完成工作。
IStudentManager: 他是負責管理服務的,在其內部通過map集合來存儲Service與Binder的映射關系,這樣客戶端在向其請求服務的時候就能夠返回特定的Binder。
Binder驅動: 他是IStudentManager連接各種Service的橋梁,同時也是客戶端與服務端交流的橋梁。
總結起來說,應用程序(Activity)首先向IStudentManager發送請求StudentManagerService的服務,IStudentManager查看已經注冊在里面的服務的列表,找到相應的服務后,通過Binder驅動將其中的Binder對象返回給客戶端,從而完成對服務的請求。
我們主要分析的就是IStudentManager這個類,從上面得到講解我們已經知道它包含了兩個類:Stub和Proxy。先來看看Proxy類
//Proxy.javapublic java.util.List<com.bob.aidltest.aidl.Student> getStudentList() throws android.os.RemoteException{ android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); java.util.List<com.bob.aidltest.aidl.Student> _result; try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_getStudentList, _data, _reply, 0); _reply.readException(); _result = _reply.createTypedArrayList(com.bob.aidltest.aidl.Student.CREATOR); } finally { _reply.recycle(); _data.recycle(); } return _result; }public void addStudent(com.bob.aidltest.aidl.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); } mRemote.transact(Stub.TRANSACTION_addStudent, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } }
上面截取了Proxy的兩個方法,其中Proxy是運行在客戶端的,他是用服務端返回來的Binder對象調用了public static IStudentManager asInterface(IBinder obj)方法返回來的。
既然Proxy運行在客戶端,那么客戶端也是通過Proxy來調用遠程服務端的方法的,也就是說我們將調用方法需要用到的參數傳遞給Proxy,接著由Proxy來訪問服務端,所以我們能夠看到,Proxy將我們的參數寫進了_data,而_reply則代表從服務端返回來的結果。
從代碼中我們還看到客戶端在將數據傳遞給服務端之后就處于阻塞狀態,直到服務端返回結果,所以如果調用的服務端方法是一個耗時方法,那么我們就需要在子線程中進行工作了。
數據準備好之后當然是需要傳遞了,可以看到Proxy通過transact方法講數據傳遞出去了,接下來就來看transact方法:
//Binder#transactpublic final boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { if (false) Log.v("Binder", "Transact: " + code + " to " + this); if (data != null) { data.setDataPosition(0); } //調用了Binder的onTransact boolean r = onTransact(code, data, reply, flags); if (reply != null) { reply.setDataPosition(0); } return r; }
可以看到transact方法實際上調用了Binder的onTransact,而這里的Binder就是指Stub了,我們看一下Stub的定義:
public static abstract class Stub extends android.os.Binder implements com.bob.aidltest.aidl.IStudentManager
可以看到Stub確實繼承了Binder并且也實現了IStudentManager接口,接下來我們繼續看Stub中的onTransact方法:
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException{ switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR); return true; } case TRANSACTION_getStudentList: { data.enforceInterface(DESCRIPTOR); java.util.List<com.bob.aidltest.aidl.Student> _result = this.getStudentList(); reply.writeNoException(); reply.writeTypedList(_result); return true; } case TRANSACTION_addStudent: { data.enforceInterface(DESCRIPTOR); com.bob.aidltest.aidl.Student _arg0; if ((0!=data.readInt())) { _arg0 = com.bob.aidltest.aidl.Student.CREATOR.createFromParcel(data); } else { _arg0 = null; } this.addStudent(_arg0); reply.writeNoException(); return true; } } return super.onTransact(code, data, reply, flags); }
可以看到,服務端通過客戶端傳遞過來的code常量來判斷客戶端需要調用的是哪個方法,接著就執行該方法,執行完之后如果有數據返回則將結果寫入reply,接著Proxy就可以收到結果了。而整個通信過程也就結束了。
最后我借用Carson_Ho的一張流程圖來描述這個完整的流程:
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。