AIDL(Android Interface Definition Language,Android接口描述語言)用於定義Client與service進行跨進程通信的編程接口。AIDL簡化了IPC通信時數據交換的流程,而程序員只需關心接口的實現即可。
注意:只有你需要其他應用程序可以通過IPC接口調用service或者需要service可以處理多線程的情況時,纔去使用AIDL。若非如此,(1)不需要處理跨進程的併發訪問,可通過實現 Binder 接口;(2)需要使用IPC,但無需處理多線程的情況,可以選擇使用 Messager
使用AIDL進行IPC通信時,需要注意:
- 當前進程調用時,service與調用者在同一線程執行(通常這種情況,一般不使用AIDL進程IPC,而是通過使用 Binder 接口進行調用)
- 如果是其他進程進行IPC調用(系統維護的一個當前進程的Thread Pool),這時可能同時出現多個IPC調用,因此必須確保AIDL接口是支持多線程(thread-safe)
- oneway(異步,不會阻塞當前進程)關鍵字用於修改遠程調用的具體行爲。使用 oneway 時,遠程調用不會阻塞,只是發送交易數據(transaction data),然後立即返回,而實現該接口的service最終會將其當作一個來自 Binder 線程池(Thread pool)普通的調用(regular call);如果oneway用於本地調用,則不會有任何影響,該調用始終是同步的
接下來,通過一個具體的示例說明如何用AIDL定義一個service,如何綁定該service,如何進行IPC的調用.
定義AIDL接口
.aidl接口的語法跟JAVA語言一樣,其定義的方式與JAVA中的interface基本一致,主要區別是AIDL只能定義methods,不能定義數據或者static的methods。每個.aidl文件只能定義一個接口(interface)。AIDL支持以下數據類型:
- 所有JAVA中的基本數據類型(primitive types),例如int,long,char,boolean
- String/CharSequence
- List
- Map
- android.os.parcelable 用於自定義數據類型
以下示例均基於Android Studio
在AS,右擊項目,new –> ADIL,對其進行命名,可以看到在 src 目錄下,生成了一個AIDL的目錄,裏面包含了剛纔新建的.aidl文件:
package com.jason.test.systemserviceaddingtest;
// Declare any non-default types here with import statements
interface IMathService {
/**
* get current process PID
*/
int getPID();
/**
* add two value
*/
int add(int x, int y);
/**
* mutiple two value
*/
int multiply(int x, int y);
}*
實現AIDL接口
編譯時,Android SDK工具會產生一個與 .aidl 同名的 .java 接口文件,該接口文件包含了一個名爲 stub abstract class,其聲明瞭 .aidl 文件裏所有的methods,同時也定義一個 asInterface 的方法,它用 IBinder(作爲參數傳遞給調用者的 onServiceConnected() 回調函數)作爲參數,並返回一個stub接口的實例。我們可以通過擴展 .Stub 類,定義一個service:
package com.jason.test.systemserviceaddingtest.service;
import android.os.*;
import android.os.Process;
import com.jason.test.systemserviceaddingtest.IMathService;
/**
* Created by Jason on 2016/7/11.
*/
public class MathServiceImpl extends IMathService.Stub {
public static final String TAG = MathServiceImpl.class.getSimpleName();
@Override
public int getPID() throws RemoteException {
return Process.myPid();
}
@Override
public int add(int x, int y) throws RemoteException {
return x + y;
}
@Override
public int multiply(int x, int y) throws RemoteException {
return x * y;
}
}
MathServiceImpl爲 service 定義了一個RPC(Remote Process Call),接下來只需要將該實例傳遞給調用者,即可實現與service的遠程通信。
使用AIDL的注意事項
- 調用不一定在Main Thread中執行,因此開始就要考慮如何處理多線程的情況
- RPC調用默認支持同步。如果一個service花比較長的時間完成一個request,不要在主線程(UI thread)中調用,以免發生ANR(Application Not Responding),而是應該在另一個線程中進行調用
- 在service中拋出的任何異常都不會返回給調用者client
將接口提供給Clients
將上述實現了的接口提供給clients,以便clients進行綁定:
package com.jason.test.systemserviceaddingtest.service;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
/**
* Created by Jason on 2016/7/11.
*/
public class MathService extends Service{
public static final String TAG = MathService.class.getSimpleName();
private final MathServiceImpl mServiceBinder =
new MathServiceImpl();
@Override
public void onCreate(){
Log.v(TAG,"onCreate()");
super.onCreate();
}
@Override
public IBinder onBind(Intent intent) {
Log.v(TAG,"onBind()");
return mServiceBinder;
}
}
當一個client通過調用 bindService() 連接這個service時,client對應的onServiceConnected() 的回調函數會接收到傳自該service onBind() 函數的 mBinder 的實例(如果client與service在不同的應用中,則client所屬的應用必須要有之前定義的.aidl文件的拷貝)。
調用IPC接口
調用service的IPC接口前,首先需要通過 ServiceConnection 將client與service進行綁定,具體參考下列代碼:首先通過mServiceConnection將client(一個新的HandlerThread)與service進行綁定;作爲示例,爲避免APP出現ANR,創建了一個新的線程去執行service的調用。
package com.jason.test.systemserviceaddingtest;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.support.v4.app.Fragment;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.Toast;
import com.jason.test.systemserviceaddingtest.service.MathService;
/**
* A placeholder fragment containing a simple view.
*/
public class MainActivityFragment extends Fragment {
public static final String TAG = MainActivityFragment.class.getSimpleName();
public static final int EVENT_START_SERVICE = 0x01;
public static final int EVENT_STOP_SERVICE = 0x02;
public static final int EVENT_BIND_SUCCESS = 0x03;
private Context mContext;
private IMathService mMathService;
private Handler mHandler;
private Boolean mIsServiceBound = false;
private Button mStartBtn;
private Button mStopBtn;
public MainActivityFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.v(TAG,"onCreateView()");
mContext = getContext();
View fragment = inflater.inflate(R.layout.fragment_main, container, false);
mStartBtn = (Button)fragment.findViewById(R.id.start_service);
mStopBtn = (Button)fragment.findViewById(R.id.stop_service);
mStartBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// start service thread
mServiceThread.start();
Message msg = mHandler.obtainMessage(EVENT_START_SERVICE);
mHandler.sendMessage(msg);
}
});
mStopBtn.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v){
Message msg = mHandler.obtainMessage(EVENT_STOP_SERVICE);
mHandler.sendMessage(msg);
// stop the service thread
if(!mIsServiceBound) {
mServiceThread.quitSafely();
}
}
});
return fragment;
}
// 連接service後,回調函數會將IBinder傳回給client
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mMathService = IMathService.Stub.asInterface(service);
Log.d(TAG,"onServiceConnected(): success");
}
@Override
public void onServiceDisconnected(ComponentName name) {
mMathService = null;
Log.d(TAG,"onServiceDisconnected(): success");
}
};
private void getMathOperations(){
int pid = 0;
int[] val = {203,230};
try{
pid = mMathService.getPID();
val[0] = mMathService.add(val[0],val[1]);
val[1] = mMathService.multiply(val[0],val[1]);
}catch (RemoteException e){
Log.e(TAG,"remote calling exception");
}
Toast.makeText(mContext,"pid = " + pid + " result is " + val[0] + "," + val[1],
Toast.LENGTH_LONG).show();
}
// 爲防止APP出現ANR,啓動新的線程調用service接口
private HandlerThread mServiceThread = new HandlerThread("ProcessInfoGettingService"){
@Override
public void start(){
//super.start();
mHandler = new Handler(Looper.myLooper()){
@Override
public void handleMessage(Message msg){
switch (msg.what){
case EVENT_START_SERVICE:
bindService();
break;
case EVENT_STOP_SERVICE:
unbindService();
break;
case EVENT_BIND_SUCCESS:
getMathOperations();
default:
break;
}
}
};
}
@Override
public void run(){
Log.d(TAG,"MathService is running");
}
};
// 綁定service
private void bindService(){
Log.v(TAG, "bindService()");
Intent service = new Intent(getContext(), MathService.class);
getContext().bindService(service, mServiceConnection, Context.BIND_AUTO_CREATE);
mIsServiceBound = true;
Toast.makeText(getContext(),"service is bound successfully",Toast.LENGTH_LONG).show();
// notify to calling
Message msg = mHandler.obtainMessage(EVENT_BIND_SUCCESS, Integer.valueOf(mIsServiceBound ? 1 : 0));
mHandler.sendMessageDelayed(msg,1*1000);
}
// 解綁service
private void unbindService(){
if(mIsServiceBound) {
mContext.unbindService(mServiceConnection);
mIsServiceBound = false;
Toast.makeText(getContext(),"service is unbound successfully",Toast.LENGTH_LONG).show();
}
}
}
更多關於利用IPC進行數據傳遞的介紹請參考[2];
如何添加如 Telephony/Alarm/Window Manager 系統服務,請參考[1]