背景
一直以來都只是聽說AIDL是跨進程的,但都由於項目中也沒涉及到,所以也從來都沒徹底去了解過,最近空閒下來去了解插件化開發原理,看着看着正好涉及到Ibinder以及android用到的AIDL,於是乎按圖索驥一條條來探索到底是個怎麼回事兒,按照讀者推薦,我們就先從AIDL使用以及原理開始挖掘。
前言
對於跨進程,我們都知道android底層是linux,所以進程管理也是linux系統的那一套,即進程之間是相互獨立的互不干擾的,數據是獨享的,所以要進行進程間的通信也是通過老掉牙的方案Binder機制去搞,而android的AIDL底層也是基於Binder機制來搞的,只不過封裝的比較好,android開發不需要知道Binder機制也能使用AIDL跨進程開發,只需要遵循一定的流程,一一步步創建就好;由於android的每個app對應一個獨立進程(不是自己開啓新的進程前提),要想跨應用訪問共享數據,那就得掌握進程間的通信。接下來我們一步步教大家如何使用以及對其原理講解。
資料
這個博主的一系列進程通信的講解與比較是比較完美的,推薦想了解的要細讀深究,並手動編寫代碼
http://blog.csdn.net/hitlion2008/article/details/9773251
Binde原理詳解
http://weishu.me/2016/01/12/binder-index-for-newer/
AIDL使用簡述
1、創建client/service的aidl文件(如IRemoteService.aidl),client/service使用的是同一份,分別放置在各自的src目錄下
2、創建完後,重新build項目,會在app\build\generated\source\aidl目錄下自動生成相應的java文件,
想深究的可以對着原理扒扒裏邊的源碼,後邊會簡述
3、創建service,並實現IRemoteService.Stub,通過onBind()返回
4、創建client,並實現ServiceConnection,然後通過以下方式啓動進行綁定,並建立進程間的通道
Intent intent = new Intent();
intent.setClassName("包名", "包名+service的類名");
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
5、最後在ServiceConnection的onServiceConnected中通過IRemoteBankService.Stub.asInterface(service)獲取client的代理,通過這個代理就可以和service相互通信了。
AIDL的創建步驟
1、創建所有的aidl文件,包括實現的進程間要通信的實體類也要定義
創建aidl文件 如圖所示,名字隨意命名,這裏我命名爲銀行的aidl,名字爲IRemoteBankService
創建完成後就會在src/main/aidl下自動生成和應用包名一致的包名,打開就看到我們創建的aidl文件,打開如下
// IRemoteBankService.aidl
package com.example.zwr.myapplication;
// Declare any non-default types here with import statements
interface IRemoteBankService{
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
這樣我們就創建完畢!
2、在aidl文件中編寫自定想要的接口。
3、編輯實體類.aidl,刪除除了包名外的所有內容,寫下parcelable 類名;(如parcelable User;)parcelable 中的p必須是小寫的;
4、將IRemoteBankService.aidl定義的接口中所使用到的實體類,將對應的包名+類名導入IRemoteBankService.aidl;
(如:
import com.example.zwr.myapplication.User;
import com.example.zwr.myapplication.RemoteClientCallBack;
)
5、創建自己實現的實體類,這個類必須要是實現Parcelable
6、重新編譯:即重新build就完畢了
注意:
自定義的接口方法的返回類型以及參數類型:類型有一定的限制
(1) 支持java的基本類型:float/int/String…..
(2)支持實現Parcelable的實體類
(3)支持Stub/aidl接口
基本類型不用說,看看實體類是怎麼玩的
一般來說我們會定義自己傳遞的實體類,但在定義之前,我們要先在aidl/包名/創建實體類同名的aidl(如這裏是User.aidl),然後在創建java/包名/實體類的.java,否則如果先創建類,有可能無法創建aidl,這是as工具的問題
AIDL的Demo詳解
1、創建aidl
這裏我的demo中是創建了3個aidl,所以先將這3個aidl文件創建
<1>、IRemoteBankService.aidl
創建完後得到一個類似java的接口,同樣我們可以在裏邊自定義自己要通信的接口;比如我定義如下
這裏創建了4個方法:
//—-以下方法都是客戶端直接調用,將數據傳給服務端,順帶從服務端拿取數據—–類似,客戶端發送請求,拿取數據
/*客戶端註冊回調接口,用以服務端主動通過調用回調方法,向客戶端發送消息—類似服務端主動推送數據給客戶端/
/*存錢 存款/
boolean despoistMoney(int money);
/*取款/
int drawMoney(int money);
/*當前存取款用戶/
User getUser();
//—-以上方法都是客戶端直接調用,將數據傳給服務端,順帶從服務端拿取數據—–類似,客戶端發送請求,拿取數據
/*客戶端註冊回調接口,用以服務端主動通過調用回調方法,向客戶端發送消息—類似服務端主動推送數據給客戶端/
void registerClientOberser(RemoteClientCallBack clientCallBack);
正如註釋寫的,我們存取款以及獲取用戶信息都是通過定義的前3個方法獲取,前3個方法相當於client向service發送請求,然後獲取或者發送信息,主動方是在client,而最後一個方法是client首先向service註冊一個觀察者,然後當service有什麼更新的狀態想要通知client時都是通過這個觀察者來通知更新,即主動權在service,由service主動推送消息,這樣就達到了信息互通
以上3個aidl中,IRemoteBankService.aidl中聲明的接口方法中用到了User和RemoteClientCallBack,所以需要導包:
import com.example.zwr.myapplication.User;
import com.example.zwr.myapplication.RemoteClientCallBack;
<2>、User.aidl
接下來看看User.aidl做了什麼:
// User.aidl
package com.example.zwr.myapplication;
// Declare any non-default types here with import statements
parcelable User;
其實創建aidl時會生成和之前一模一樣的接口類型,這裏我們把它都給刪除,然後寫下parcelable 類名;(這裏是parcelable User;)
這樣就算聲明完了。
<3>RemoteClientCallBack.aidl
// RemoteClientCallBack.aidl
package com.example.zwr.myapplication;
// Declare any non-default types here with import statements
interface RemoteClientCallBack {
void transferToClientByServer(String transferData);
}
這裏聲明瞭接口方法,但是參數是普通類型,不需要導入任何包;這個方法就是service主動將消息推送後,客戶端回調的方法
以上就是aidl的玩法。
<4>、創建aidl使用參數的實體類
接下來我們要在java/aidl文件同包名/創建aidl相應的實體類User,如本demo:java\com\example\zwr\myapplication\User.java
如下所示
package com.example.zwr.myapplication;
import android.os.Parcel;
import android.os.Parcelable;
/**
* author : zhongwr on 2017/2/22
*/
public class User implements Parcelable {
private String name;
private String id;
/**進程Id*/
private String pId;
public User(String name, String id, String pId) {
this.name = name;
this.id = id;
this.pId = pId;
}
public User() {
}
@Override
public String toString() {
return "name = " + name + " id = " + id + " pid = " + pId;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.name);
dest.writeString(this.id);
dest.writeString(this.pId);
}
protected User(Parcel in) {
this.name = in.readString();
this.id = in.readString();
this.pId = in.readString();
}
public static final Creator<User> CREATOR = new Creator<User>() {
@Override
public User createFromParcel(Parcel source) {
return new User(source);
}
@Override
public User[] newArray(int size) {
return new User[size];
}
};
}
看着要生成這麼多東東,其實我們不用自己手寫,直接使用as自帶的工具:
打開當前文件右鍵-》generate-》Parcelable
這樣就會自動生成
這裏我們要注意如下:
實現的Parcelable 類(User) 必須放在src/main/java目錄下,包名與aidl的包名一致(這裏是com.example.zwr.myapplication)
如圖:
這樣前提準備工作就基本完事,最後一步,重新build項目,然後不出什麼意外的話會在pp\build\generated\source\aidl目錄下自動生成相應的java文件
裏邊的內容先不關心,後續會講解
2、服務端:創建RemoteBankService.java
這裏就跟平時創建的service一樣,繼承service之後 主要是這個方法返回的IBinder
@Override
public IBinder onBind(Intent intent) {
return mRemoteBind;
}
這裏實現的是我們創建的aidl自動生成的java文件IRemoteBankService中的IRemoteBankService.Stub
private IRemoteBankService.Stub mRemoteBind = new IRemoteBankService.Stub() {
/**存錢*/
@Override
public boolean despoistMoney(int money) throws RemoteException {
Log.d(TAG, "despoistMoney pid = " + android.os.Process.myPid());
if (money > 0) {
return true;
}
return false;
}
/**取錢*/
@Override
public int drawMoney(int money) throws RemoteException {
Log.d(TAG, "drawMoney pid = " + android.os.Process.myPid());
clientCallBackInstance.transferToClientByServer("當前用戶存錢成功
餘額 :"+money+"當前進程Id = "+android.os.Process.myPid());
return money;
}
/**
* 用戶信息
* @return
* @throws RemoteException
*/
@Override
public User getUser() throws RemoteException {
Log.d(TAG, "getUser pid = " + android.os.Process.myPid());
return new User("張三", "" + System.currentTimeMillis(),
"" + android.os.Process.myPid());
}
/**
* 註冊客戶端的觀察者,用以服務端更新後主動通知其更新
* @param clientCallBack
* @throws RemoteException
*/
@Override
public void registerClientOberser(RemoteClientCallBack clientCallBack)
throws RemoteException {
clientCallBackInstance =clientCallBack;
Message msg = Message.obtain();
msg.obj = clientCallBack;
timeHandler.sendMessageDelayed(msg, 10000);
}
};
private RemoteClientCallBack clientCallBackInstance;
看到這個實現的Stub中實現了我們在aidl定義的接口方法,我們就是通過這些方法進行通信的
由於前三個是client主動調用的方法,而最後registerxxx()是client註冊後,service主動推送消息,所以我們這裏使用handler延時模擬推送。
service的完整代碼如下
package com.example.zwr.myapplication;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.*;
import android.os.Process;
import android.util.Log;
/**
* 獨立進程運行的服務
*/
public class RemoteBankService extends Service {
private static final String TAG = "RemoteBankService";
public static void bindService(Context context, ServiceConnection connection) {
Log.d(TAG, "bindService pid = " + android.os.Process.myPid());
Intent intent = new Intent(context, RemoteBankService.class);
// intent.setClassName("com.example.zwr.myapplication","com.example.zwr.myapplication.RemoteBankService");
context.bindService(intent, connection, Context.BIND_AUTO_CREATE);
}
public static void doUnbindService(Context context, ServiceConnection connection) {
Log.d(TAG, "doUnbindService pid = " + android.os.Process.myPid());
if (connection != null) {
context.unbindService(connection);
context.stopService(new Intent(context, RemoteBankService.class));
}
}
public RemoteBankService() {
}
@Override
public void onCreate() {
Log.d(TAG, "onCreate pid = " + android.os.Process.myPid());
super.onCreate();
}
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy pid = " + android.os.Process.myPid());
super.onDestroy();
Process.killProcess(Process.myPid());
}
@Override
public IBinder onBind(Intent intent) {
return mRemoteBind;
}
private IRemoteBankService.Stub mRemoteBind = new IRemoteBankService.Stub() {
/**存錢*/
@Override
public boolean despoistMoney(int money) throws RemoteException {
Log.d(TAG, "despoistMoney pid = " + android.os.Process.myPid());
if (money > 0) {
return true;
}
return false;
}
/**取錢*/
@Override
public int drawMoney(int money) throws RemoteException {
Log.d(TAG, "drawMoney pid = " + android.os.Process.myPid());
clientCallBackInstance.transferToClientByServer("當前用戶存錢成功
餘額 :"+money+"當前進程Id = "+android.os.Process.myPid());
return money;
}
/**
* 用戶信息
* @return
* @throws RemoteException
*/
@Override
public User getUser() throws RemoteException {
Log.d(TAG, "getUser pid = " + android.os.Process.myPid());
return new User("張三", "" + System.currentTimeMillis(),
"" + android.os.Process.myPid());
}
/**
* 註冊客戶端的觀察者,用以服務端更新後主動通知其更新
* @param clientCallBack
* @throws RemoteException
*/
@Override
public void registerClientOberser(RemoteClientCallBack clientCallBack)
throws RemoteException {
clientCallBackInstance =clientCallBack;
Message msg = Message.obtain();
msg.obj = clientCallBack;
timeHandler.sendMessageDelayed(msg, 10000);
}
};
private RemoteClientCallBack clientCallBackInstance;
private TimeHander timeHandler = new TimeHander();
static class TimeHander extends Handler{
public TimeHander(){
Looper looper = Looper.myLooper();
if(null == looper){
Looper.prepare();
Looper.loop();
}
}
@Override
public void handleMessage(Message msg) {
if(null!=msg.obj){
RemoteClientCallBack clientCallBackInstance
= (RemoteClientCallBack)msg.obj;
try {
clientCallBackInstance.transferToClientByServer(
"已延期10s後發送 當前進程Id = "+ Process.myPid());
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}
}
這樣service工作已完成99%了,最後一步就是要註冊,這裏我們起一個獨立進程來跑;
AndroidManifest.xml:
<service
ndroid:name=".RemoteBankService"
android:enabled="true"
android:exported="true"
android:process=":remoteservice">
</service>
3、本應用的客戶端Client:創建RemoteActivity
實現ServiceConnection:建立連接
private IRemoteBankService iRemoteBankService;
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {//建立通信鏈接成功
Log.d(TAG, "onServiceConnected pid = " + android.os.Process.myPid());
iRemoteBankService = IRemoteBankService.Stub.asInterface(service);//跨進程的處理方式
// iRemoteBankService = (IRemoteBankService)service;//統一進程的處理方式
try {
iRemoteBankService.registerClientOberser(remoteClientCallBack);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {//斷開鏈接
Log.d(TAG, "onServiceDisconnected pid = " + android.os.Process.myPid());
iRemoteBankService = null;
}
};
當建立連接之後會回調onServiceConnected();在這裏我們獲取遠程的IBinder,由於這個是服務端的IBinder,這裏又是不同的進程所以不能直接強轉成IRemoteBankService,否則會拋出類型轉換異常所以要使用
iRemoteBankService = IRemoteBankService.Stub.asInterface(service);來獲取客戶端的Binder代理;
獲取到iRemoteBankService 後我們將客戶端的觀察者註冊到service中:
iRemoteBankService.registerClientOberser(remoteClientCallBack);
再來看看remoteClientCallBack是何方妖物:
private RemoteClientCallBack.Stub remoteClientCallBack = new RemoteClientCallBack.Stub() {
@Override
public void transferToClientByServer(final String transferData) throws RemoteException {
Log.d(TAG, "transferData = " + transferData +
" client Pid = " + android.os.Process.myPid());
//如果是service通過handler調用的這個的,由於service的進程調用,所以這個回調不是在
//主線程而是工作線程中,直接更新或toast會拋出如下異常,所以定要在主線程中更新
//Uncaught remote exception! (Exceptions are not yet supported across processes.)
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(RemoteActivity.this, transferData, Toast.LENGTH_SHORT).show();
}
});
}
};
這裏實現的就是我們定義的RemoteClientCallBack.aidl文件自動生成的RemoteClientCallBack.java文件中的Stub;到了這裏其實這裏註冊觀察者,可以理解爲把client當作service,service當作client互換角色吧。
然後通過回調就可以互相通信互相拿到消息了。
這裏你可能注意到了在回調方法中使用runOnUiThread()來Toast,這是因爲,回調後不是主線程,而是從系統線程池中獲取到一個空閒工作線程來傳遞消息的,直接Toast則報異常(Exceptions are not yet supported across processes.)
ok!主要代碼都寫完了,這下需要綁定啓動服務然後操縱看看是不是能夠通信了;完整代碼如下:package com.example.zwr.myapplication;
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.Toast;
public class RemoteActivity extends AppCompatActivity implements View.OnClickListener {
public static void startInstance(Context context){
context.startActivity(new Intent(context,RemoteActivity.class));
}
private static final String TAG = "RemoteActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_remote);
initView();
}
private void initView() {
findViewById(R.id.tv_bindService).setOnClickListener(this);
findViewById(R.id.tv_despoistMoney).setOnClickListener(this);
findViewById(R.id.tv_drawMoney).setOnClickListener(this);
findViewById(R.id.tv_getuser).setOnClickListener(this);
Log.d(TAG, "initView pid = " + android.os.Process.myPid());
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.tv_bindService:
RemoteBankService.bindService(this,serviceConnection);
break;
case R.id.tv_despoistMoney:
try {
boolean isDesMoney = iRemoteBankService.despoistMoney(5);
Log.d(TAG,"isDesMoney1 = "+isDesMoney);
isDesMoney = iRemoteBankService.despoistMoney(-5);
Log.d(TAG,"isDesMoney2 = "+isDesMoney);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
case R.id.tv_drawMoney:
try {
int drawMoney = iRemoteBankService.drawMoney(5);
Log.d(TAG,"drawMoney = "+drawMoney);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
case R.id.tv_getuser:
try {
User user = iRemoteBankService.getUser();
Log.d(TAG,"user = "+user.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
break;
}
}
private IRemoteBankService iRemoteBankService;
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {//建立通信鏈接成功
Log.d(TAG, "onServiceConnected pid = " + android.os.Process.myPid());
iRemoteBankService = IRemoteBankService.Stub.asInterface(service);//跨進程的處理方式
// iRemoteBankService = (IRemoteBankService)service;//統一進程的處理方式
try {
iRemoteBankService.registerClientOberser(remoteClientCallBack);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {//斷開鏈接
Log.d(TAG, "onServiceDisconnected pid = " + android.os.Process.myPid());
iRemoteBankService = null;
}
};
private RemoteClientCallBack.Stub remoteClientCallBack = new RemoteClientCallBack.Stub() {
@Override
public void transferToClientByServer(final String transferData) throws RemoteException {
Log.d(TAG, "transferData = " + transferData + " client Pid = "
+ android.os.Process.myPid());
//如果是service通過handler調用的這個的,由於service的進程調用,所以這個回調不是
//在主線 程而是工作線程中,直接更新或toast會拋出如下異常,所以定要在主線程中更新
//Uncaught remote exception! (Exceptions are not yet supported across processes.)
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(RemoteActivity.this, transferData, Toast.LENGTH_SHORT).show();
}
});
}
};
@Override
protected void onDestroy() {
super.onDestroy();
RemoteBankService.doUnbindService(this,serviceConnection);
}
}
來看看運行結果: 先綁定,在操作其它選項
client:
service:
通過日誌可以發現,這兩個是在獨立進程中,而且是可以正常通信的
以上是同一個應用中進行的,下邊我們看看在不同應用中的通信
4、新創建應用爲ClientAIDLDEMO :爲client
先創建完後,將創建的所有aidl文件以及相關的類全部複製到這個項目中
注意:包名也是要一致的
如圖:
然後將RemoteActivity也一同複製過來,唯一要修改的只有綁定和解綁,修改爲:通過包名隱式意圖啓動進行綁定:如下
Intent intent = new Intent();
intent.setClassName(
"com.example.zwr.myapplication", "com.example.zwr.myapplication.RemoteBankService");
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
Intent intent = new Intent();
intent.setClassName(
"com.example.zwr.myapplication", "com.example.zwr.myapplication.RemoteBankService");
unbindService(serviceConnection);
stopService(intent);
然後運行:點擊綁定,結果如同上邊,就不再重複!
跨進程通信講解完畢!,有不對的地方請不吝指正!