參考文章:html
強推博客:java
按照參考文章裏說的,咱們最好先學習如下AIDL。android
參考文章:安全
文章思路:bash
AIDL 是 Android 接口定義語言。數據結構
用來解決跨進程通訊,其實跨進程通訊還可使用
BroadcastReceiver
和Messenger
,可是BroadcastReceiver
佔用的系統資源比較多,若是是頻繁的跨進程通訊是不可取的。Messenger
進行跨進程通訊時只能同步,不能異步。app
下面是例子。異步
上面的5個文件就是咱們編寫的文件了。 2. 先編寫AIDL文件夾裏的文件 直接右鍵,new 一個 AIDL文件便可。 ide
Book.aidl 函數
BookManager.aidl
這裏要確保Book.java文件和Book.aidl文件在同一文件夾下,同時又能被編譯器找到,因此咱們要在build.gradle中加入:
sourceSets {
main {
java.srcDirs = ['src/main/java', 'src/main/aidl']
}
}
複製代碼
public class AIDLService extends Service {
public final String TAG = this.getClass().getSimpleName();
//包含Book對象的list
private List<Book> mBooks = new ArrayList<>();
//由AIDL文件生成的BookManager
private final BookManager.Stub mBookManager = new BookManager.Stub() {
@Override
public List<Book> getBooks() throws RemoteException {
synchronized (this) {
Log.e(TAG, "invoking getBooks() method , now the list is : " + mBooks.toString());
if (mBooks != null) {
return mBooks;
}
return new ArrayList<>();
}
}
@Override
public void addBook(Book book) throws RemoteException {
synchronized (this) {
if (mBooks == null) {
mBooks = new ArrayList<>();
}
if (book == null) {
Log.e(TAG, "Book is null in In");
book = new Book();
}
//嘗試修改book的參數,主要是爲了觀察其到客戶端的反饋
book.setPrice(2333);
if (!mBooks.contains(book)) {
mBooks.add(book);
}
//打印mBooks列表,觀察客戶端傳過來的值
Log.e(TAG, "invoking addBooks() method , now the list is : " + mBooks.toString());
}
}
};
@Override
public void onCreate() {
super.onCreate();
Book book = new Book();
book.setName("Android開發藝術探索");
book.setPrice(28);
mBooks.add(book);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.e(getClass().getSimpleName(), String.format("on bind,intent = %s", intent.toString()));
return mBookManager;
}
}
複製代碼
在清單文件中進行配置:
<service
android:name=".service.AIDLService"
android:exported="true">
<intent-filter>
<action android:name="com.lypeer.aidl"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>
複製代碼
public class AIDLActivity extends AppCompatActivity {
//由AIDL文件生成的Java類
private BookManager mBookManager = null;
//標誌當前與服務端鏈接情況的布爾值,false爲未鏈接,true爲鏈接中
private boolean mBound = false;
//包含Book對象的list
private List<Book> mBooks;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_aidl);
}
/**
* 按鈕的點擊事件,點擊以後調用服務端的addBookIn方法
*
* @param view
*/
public void addBook(View view) {
//若是與服務端的鏈接處於未鏈接狀態,則嘗試鏈接
if (!mBound) {
attemptToBindService();
Toast.makeText(this, "當前與服務端處於未鏈接狀態,正在嘗試重連,請稍後再試", Toast.LENGTH_SHORT).show();
return;
}
if (mBookManager == null) return;
Book book = new Book();
book.setName("APP研發錄In");
book.setPrice(30);
try {
mBookManager.addBook(book);
Log.e(getLocalClassName(), book.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
/**
* 嘗試與服務端創建鏈接
*/
private void attemptToBindService() {
Intent intent = new Intent();
intent.setAction("com.lypeer.aidl");
intent.setPackage("com.lypeer.ipcserver");
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStart() {
super.onStart();
if (!mBound) {
attemptToBindService();
}
}
@Override
protected void onStop() {
super.onStop();
if (mBound) {
unbindService(mServiceConnection);
mBound = false;
}
}
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.e(getLocalClassName(), "service connected");
mBookManager = BookManager.Stub.asInterface(service);
mBound = true;
if (mBookManager != null) {
try {
mBooks = mBookManager.getBooks();
Log.e(getLocalClassName(), mBooks.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e(getLocalClassName(), "service disconnected");
mBound = false;
}
};
}
複製代碼
以後就能夠開始通訊啦。 若是發現有問題,檢查下面配置:
其實在寫完AIDL文件後,編譯器會幫咱們自動生成一個同名的 .java 文件。(圖是截圖參考文章的,寫的時候忘記截了~~~)
public void onServiceConnected(ComponentName name, IBinder service)
mBookManager = BookManager.Stub.asInterface(service);
}
複製代碼
這裏的傳遞給了咱們一個service
對象,通過Debug,發現它是AIDLService
:
不深究它是怎麼來的。咱們接着看。 咱們看到這裏調用了asInterface
方法,咱們點擊查看一下源碼:
public static com.lypeer.ipcclient.BookManager asInterface(android.os.IBinder obj) {
//驗空
if ((obj == null)) {
return null;
}
//DESCRIPTOR = "com.lypeer.ipcclient.BookManager",搜索本地是否已經
//有可用的對象了,若是有就將其返回
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.lypeer.ipcclient.BookManager))) {
return ((com.lypeer.ipcclient.BookManager) iin);
}
//若是本地沒有的話就新建一個返回
return new com.lypeer.ipcclient.BookManager.Stub.Proxy(obj);
}
複製代碼
返回一個proxy
對象。這裏proxy
對象就是客戶端跟服務端進行溝通的橋樑了。 2. proxy
中方法作了什麼 咱們接着看proxy
方法中作了什麼:
@Override
public java.util.List<com.lypeer.ipcclient.Book> getBooks() throws android.os.RemoteException {
//很容易能夠分析出來,_data用來存儲流向服務端的數據流,
//_reply用來存儲服務端流回客戶端的數據流
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.lypeer.ipcclient.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
//調用 transact() 方法將方法id和兩個 Parcel 容器傳過去
mRemote.transact(Stub.TRANSACTION_getBooks, _data, _reply, 0);
_reply.readException();
//從_reply中取出服務端執行方法的結果
_result = _reply.createTypedArrayList(com.lypeer.ipcclient.Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
//將結果返回
return _result;
}
複製代碼
經過一個方法能夠總結一下proxy方法的工做流程:
生成_data和_reply數據流,將它傳給服務端
經過transact
方法傳遞給服務端,並請求服務端調用指定方法
接收_reply數據流,並從中取出服務端返回的數據(服務端是如何返回_reply數據流回來的,這點是底層封裝好的,咱們沒必要要知道)
咱們把數據分紅Pacel傳給服務端了,服務端幹了些啥? 前面說了客戶端經過調用 transact() 方法將數據和請求發送過去,那麼理所固然的,服務端應當有一個方法來接收這些傳過來的東西:在 BookManager.java 裏面咱們能夠很輕易的找到一個叫作 onTransact() 的方法。咱們看看這個方法是怎麼寫的:
@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_getBooks: {
//省略
return true;
}
case TRANSACTION_addBook: {
//省略
return true;
}
}
return super.onTransact(code, data, reply, flags);
複製代碼
能夠看到,它在接收了客戶端的 transact() 方法傳過來的參數後,什麼廢話都沒說就直接進入了一個 switch 選擇:根據傳進來的方法 ID 不一樣執行不一樣的操做。接下來看一下每一個方法裏面它具體作了些什麼,以 getBooks() 方法爲例:
case TRANSACTION_getBooks: {
data.enforceInterface(DESCRIPTOR);
//調用 this.getBooks() 方法,在這裏開始執行具體的事務邏輯
//result 列表爲調用 getBooks() 方法的返回值
java.util.List<com.lypeer.ipcclient.Book> _result = this.getBooks();
reply.writeNoException();
//將方法執行的結果寫入 reply ,
reply.writeTypedList(_result);
return true;
}
複製代碼
很簡單,調用服務端的具體實現,而後獲取返回值寫入到reply流。這裏的this.getBooks()
方法就是由子類去實現的,也就是咱們的AIDLService
中的bookManager
:
好了,整個流程結束了,如今咱們來總結一下:
整個流程的序列圖是:
這張圖,跟咱們以前ActivityThread
那張圖很像,由於本質上二者都是採用了Binder進行通訊。
若是要使用Binder進行通訊,那麼你首先須要定義一個協議
BookManager
,而後你須要有兩個代理,一個是服務端代理,也是最終真正作事的類--AIDLService
,還須要一個本地代理類--BookMangerProxy
,它只負責封裝數據傳遞給服務端,而後服務端在onTransact
中拿到數據進行處理便可。
Android系統基於Linux內核,即Linux Kernel。Linux Kernel是操做系統的核心,獨立於普通的應用程序,能夠訪問受保護的內存空間,也有訪問底層硬件設備的全部權限。爲了給Kernel提供必定的保護機制,因而就把Kernel和上層的應用程序抽象的隔離開,分別稱之爲內核空間(Kernel Space)和用戶空間(User Space)
Android使用的Linux內核擁有着很是多的跨進程通訊機制,好比管道,System V,Socket等,可是出於安全和性能的考慮,採用了一種全新的通訊方式——Binder。Binder自己並非Linux內核的一部分,可是通過Linux的動態可加載內核模塊機制(LKM),Binder模塊在運行時被連接到內核做爲內核的一部分在內核空間運行。
模塊是具備獨立功能的程序,它能夠被單獨編譯,可是不能獨立運行。它在運行時被連接到內核做爲內核的一部分在內核空間運行。
在Android系統中,這個運行在內核空間的,負責各個用戶進程經過Binder通訊的內核模塊叫作Binder驅動;
總結:
傳統的進程通訊方式對於通訊雙方的身份並無作出嚴格的驗證,只有在上層協議上進行架設;好比Socket通訊ip地址是客戶端手動填入的,均可以進行僞造;而Binder機制從協議自己就支持對通訊雙方作身份校檢,於是大大提高了安全性。這個也是Android權限模型的基礎。
在移動設備上,普遍地使用跨進程通訊確定對通訊機制自己提出了嚴格的要求;Binder相對出傳統的Socket方式,更加高效
比喻:打電話
SM : 通訊錄,記錄着你要找的人的電話號碼 Binder:基站
整個通訊流程如圖:
上文Binder通訊圖給出了四個角色:Client
,Server
,SM
,driver
。可是咱們仍然不清楚他們是怎麼幹活的? 看下圖:
畫畫太爛,看一下原來的圖吧。
整個流程簡述爲:
object
,能夠add
zhangsan
的object
add
方法object
對象的add
方法,並返回結果一句話總結就是:
Client進程只不過是持有了Server端的代理;代理對象協助驅動完成了跨進程通訊。
總結四個角色的做用:
IBinder/IInterface/Binder/BinderProxy/Stub是什麼?
繼承自Binder
,說明他是Binder的本地對象;實現IInterface
接口,說明具備遠程Server承諾給Client的能力;Stub是抽象類,說明具體的實現須要咱們本身完成,這裏使用了策略模式。知道了這一點後,咱們能夠分析AIDL中各個類的做用了。
先從AIDL文件夾,也就是服務端中文件看起。
package com.example.test.app;
interface ICompute {
int add(int a, int b);
}
複製代碼
而後用編譯工具編譯以後,能夠獲得對應的ICompute.java類:
public interface ICompute extends android.os.IInterface
複製代碼
繼承自IInterface,說明ICompute是用來定義了Server端具備的能力。
/**
* Cast an IBinder object into an com.example.test.app.ICompute interface,
* generating a proxy if needed.
*/
public static com.example.test.app.ICompute asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.example.test.app.ICompute))) {
return ((com.example.test.app.ICompute) iin);
}
return new com.example.test.app.ICompute.Stub.Proxy(obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
複製代碼
Stub類繼承自Binder,意味着這個Stub其實本身是一個Binder本地對象,而後實現了ICompute接口,ICompute自己是一個IInterface,所以他攜帶某種客戶端須要的能力(這裏是方法add)。
asInterface
方法/**
* Cast an IBinder object into an com.example.test.app.ICompute interface,
* generating a proxy if needed.
*/
public static com.example.test.app.ICompute asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.example.test.app.ICompute))) {
return ((com.example.test.app.ICompute) iin);
}
return new com.example.test.app.ICompute.Stub.Proxy(obj);
}
複製代碼
咱們在bind一個Service以後,在onServiceConnecttion的回調裏面,就是經過這個方法拿到一個遠程的service的,這個方法作了什麼呢?
首先看函數的參數IBinder類型的obj,這個對象是驅動給咱們的,若是是Binder本地對象,那麼它就是Binder類型,若是是Binder代理對象,那就是BinderProxy類型;而後,正如上面自動生成的文檔所說,它會試着查找Binder本地對象,若是找到,說明Client和Server都在同一個進程,這個參數直接就是本地對象,直接強制類型轉換而後返回,若是找不到,說明是遠程對象(處於另一個進程)那麼就須要建立一個Binde代理對象,讓這個Binder代理實現對於遠程對象的訪問。通常來講,若是是與一個遠程Service對象進行通訊,那麼這裏返回的必定是一個Binder代理對象,這個IBinder參數的其實是BinderProxy;
咱們知道,對於遠程方法的調用,是經過Binder代理完成的,在這個例子裏面就是Proxy類;Proxy對於add方法的實現以下:
Override
public int add(int a, int b) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(a);
_data.writeInt(b);
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
複製代碼
它首先用Parcel把數據序列化了,而後調用了transact方法.
transact
方法的做用:將Client線程掛起等待返回;驅動完成操做後調用Serer的onTransact
方法,這個方法被結果返回給驅動,驅動以後喚醒掛起的Client並將結果返回。
整個過程結束。
IInterface
,繼承Binder
(is a Binder)IInterace
,並持有IBinder
引用(has a Binder)比喻來講就是一個是真正的皇帝,一個是挾天子以令諸侯。
Activity的啓動流程中涉及如下類:
再進行講解Activity的啓動過程以前,咱們首先要知道各個類之間的關係,以及各個類的做用,另外還須要知道這些類是屬於Server端仍是Client端的。咱們知道,Android IPC 通訊採用的Binder,用到Binder就須要區分Server和Client.
以getService()
方法舉例,說明調用順序,Activity採用的是遠程代理
,ActivityManagerProxy
是AMS
在Client端的代理,同時,它也是個Binder
對象,傳遞時將它傳遞給AMS
,之後,AMS
想對Activity
作啥,就能夠經過它。
總的來講,就是:
遠端代理ActivityManagerNative
獲得本地代理ActivityManagerProxy
本地代理ActivityManagerProxy
的成員變量ActivityManagerNative
去調用,其實就是將Binder對象ActivityManagerNative
傳遞過去。若是沒了解過Activity的啓動流程,請參考文章Activity啓動流程
IAcivityManger
:是一個IInterface
,表明遠程Service有什麼能力;ActivityManagerNative
:Binder本地對象(相似Stub
)AtivityMangerService
:真正的服務端, ActivityManagerNative
的實現類ActivityManagerProxy
:Bidner代理對象ActivityManager
:不過是一個管理類而已,能夠看到真正的操做都是轉發給ActivityManagerNative
進而交給他的實現ActivityManagerService
完成的。----------------------------- 進程間通訊步驟 -----------------------------
1.Client 發起遠程調用請求 也就是RPC 到Binder。同時將本身掛起,掛起的緣由是要等待RPC調用結束之後返回的結果
2.Binder 收到RPC請求之後 把參數收集一下,調用transact方法,把RPC請求轉發給service端。
3.service端 收到rpc請求之後 就去線程池裏 找一個空閒的線程去走service端的 onTransact方法 ,實際上也就是真正在運行service端的 方法了,等方法運行結束 就把結果 寫回到binder中。
4.Binder 收到返回數據之後 就喚醒原來的Client 線程,返回結果。至此,一次進程間通訊 的過程就結束了
----------------------------- 進程間通訊步驟 -----------------------------