服務端實現
接下來的過程演示了服務端怎么實現Android復雜的AIDL通信實例
首先需要明白的一個事情,調用一個方法,涉及到三個重要概念:參數的個數,參數類型,和方法輸出類型
AIDL支持的參數類型
1. 簡單數據類型
- Java基本類型,即int、long、char等;
- String;
- CharSequence;
- List
List中的所有元素都必須是AIDL支持的數據類型、其他AIDL接口或你之前聲明的Parcelable實現類。 - Map
Map中的所有元素都必須是AIDL支持的數據類型、其他AIDL接口或你之前聲明的Parcelable實現類。
2. 復雜數據類型
在AIDL中傳遞的復雜參數類型對象必須要實現Parcelable接口,這是因為Parcelable允許Android系統將復雜對象分解成基本類型以便在進程間傳輸。
若客戶端組件和服務分開在不同APP,必須要把該Parcelable實現類.java文件拷貝到客戶端所在的APP,包路徑要一致。
另外需要為這個Parcelable實現類定義一個相應的.aidl文件。
復雜數據類型,必須要有import語句,即使它跟.aidl是同一個包下。
方法可有零、一或多個參數,可有返回值或void。
將復雜數據類型作為AIDL接口的形參時,記得加上in。
所有非基本類型的參數都需要標簽來表明這個數據的去向:
in,表示此變量由客戶端設置;
out,表示此變量由服務端設置;
inout,表示此變量可由客戶端和服務端設置;
而基本類型只能是in。
只expose方法,不會expose靜態變量。
3. 回調類參數類型
回調類型的參數類型和復雜數據類型用法是一樣的,但是它對應的是binder類型的參數,而不是單純的數據類型。回調類型的參數允許回調客戶端代碼及結果,相當于服務端調用客戶端當中的方法,同樣的 回調類參數類型也屬于非基本類型參數,也需要用標簽標明設置情況
需要定義一個AIDL服務需要以下幾個步驟
- 創建.aidl文件
- SDK生成對應.java文件和Stub內部類
- 通過Service子類將接口暴露給外界
1. 創建.aidl文件
-
創建對象類型的AIDL文件
簡單類型對象并不需要單獨創建一個aidl文件表示,但是復雜對象需要創建一個對應的實體類型的aidl文件,如下:
//AIDL復雜對象定義
//注意點:這個包名需要和你的實體類com.visualing.application.RequestEntity包名一致
//同時也意味著,你需要頂一個實現Parcelable接口的實體類
package com.visualing.application;
//parcelable化的實體
parcelable RequestEntity;
該aidl文件對應的實體類實現了Parcelable接口,這是一個常規的實現了Parcelable接口的實例
package com.visualing.application;
import android.os.Parcel;
import android.os.Parcelable;
public class RequestEntity implements Parcelable {
public String name;
public long money;
public int age;
public byte sex;
protected RequestEntity(Parcel in) {
name = in.readString();
money = in.readLong();
age = in.readInt();
sex = in.readByte();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeLong(money);
dest.writeInt(age);
dest.writeByte(sex);
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<RequestEntity> CREATOR = new Creator<RequestEntity>() {
@Override
public RequestEntity createFromParcel(Parcel in) {
return new RequestEntity(in);
}
@Override
public RequestEntity[] newArray(int size) {
return new RequestEntity[size];
}
};
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getMoney() {
return money;
}
public void setMoney(long money) {
this.money = money;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public byte getSex() {
return sex;
}
public void setSex(byte sex) {
this.sex = sex;
}
}
-
創建方法類型的AIDL文件
以下是創建一個供客戶端調用的aidl文件的實例
// IMyAidlInterface.aidl
package com.visualing.application;
//導入需要別的進程訪問的類對象,這些內容在客戶端需要有一份拷貝,生成一模一樣的內容
import com.visualing.application.RequestEntity;
import com.visualing.application.AidlCallback;
// Declare any non-default types here with import statements
interface IMyAidlInterface {
/**
* 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);
//復雜類型演示接口
int addValues(int anInt,int bnInt,in RequestEntity request);
//計算回調類型演示接口
int progressCallback(int anInt,int bnInt,in AidlCallback callback,in RequestEntity request);
}
我們可以看到上面有一個計算回調類型演示接口
int progressCallback(int anInt,int bnInt,in AidlCallback callback,in RequestEntity request);
那其中的AidlCallback是怎么實現的呢,如下,是AidlCallback一個回調類型的方法的aidl文件的實例,和本身調用者的定義方式是一樣的
- 創建回調類型的AIDL文件
// AidlCallback.aidl
package com.visualing.application;
//此處需要主動導入你寫入的復雜的實體類
import com.visualing.application.RequestEntity;
// Declare any non-default types here with import statements
//回調的AIDL寫法,和普通的調用方法寫法一致,只是這個表示從服務端調用客戶端的方法而已
interface AidlCallback {
/**
* 方法可有零、一或多個參數,可有返回值或void。所有非基本類型的參數都需要標簽來表明這個數據的去向:
in,表示此變量由客戶端設置;
out,表示此變量由服務端設置;
inout,表示此變量可由客戶端和服務端設置;
基本類型只能是in。
*/
//注意復雜對象的參數是,前面需要帶in,或者out,表明自己是輸入還是輸出
void callback(in RequestEntity entity);
}
由上,已經定義了程序所需要的所有的aidl文件。
2. SDK生成對應.java文件和Stub內部類
當編譯APP時,SDK工具會將項目/src/<SourceSet>/aidl目錄下的.aidl文件一個個在項目/build/generated/source/aidl目錄下生成IBinder接口.java文件。兩個文件名一樣,只是后綴不同。如AidlCallback.aidl生成AidlCallback.java。
AidlCallback.java
package com.visualing.application;
// Declare any non-default types here with import statements
//回調的AIDL寫法,和普通的調用方法寫法一致,只是這個表示從服務端調用客戶端的方法而已
public interface AidlCallback extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.visualing.application.AidlCallback {
private static final java.lang.String DESCRIPTOR = "com.visualing.application.AidlCallback";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.visualing.application.AidlCallback interface,
* generating a proxy if needed.
*/
public static com.visualing.application.AidlCallback asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.visualing.application.AidlCallback))) {
return ((com.visualing.application.AidlCallback) iin);
}
return new com.visualing.application.AidlCallback.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 {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_callback: {
data.enforceInterface(DESCRIPTOR);
com.visualing.application.RequestEntity _arg0;
if ((0 != data.readInt())) {
_arg0 = com.visualing.application.RequestEntity.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.callback(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.visualing.application.AidlCallback {
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;
}
/**
* 方法可有零、一或多個參數,可有返回值或void。所有非基本類型的參數都需要標簽來表明這個數據的去向:
* in,表示此變量由客戶端設置;
* out,表示此變量由服務端設置;
* inout,表示此變量可由客戶端和服務端設置;
* 基本類型只能是in。
*///注意復雜對象的參數是,前面需要帶in,或者out,表明自己是輸入還是輸出
@Override
public void callback(com.visualing.application.RequestEntity entity) 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 ((entity != null)) {
_data.writeInt(1);
entity.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_callback, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_callback = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
/**
* 方法可有零、一或多個參數,可有返回值或void。所有非基本類型的參數都需要標簽來表明這個數據的去向:
* in,表示此變量由客戶端設置;
* out,表示此變量由服務端設置;
* inout,表示此變量可由客戶端和服務端設置;
* 基本類型只能是in。
*///注意復雜對象的參數是,前面需要帶in,或者out,表明自己是輸入還是輸出
public void callback(com.visualing.application.RequestEntity entity) throws android.os.RemoteException;
}
IMyAidlInterface.java
public interface IMyAidlInterface extends android.os.IInterface {
public static abstract class Stub extends android.os.Binder implements com.visualing.application.IMyAidlInterface {
@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: {
}
case TRANSACTION_basicTypes: {
}
case TRANSACTION_addValues: {
}
case TRANSACTION_progressCallback: {
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.visualing.application.IMyAidlInterface {
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException {
}
@Override
public int addValues(int anInt, int bnInt, com.visualing.application.RequestEntity request) throws android.os.RemoteException {
}
@Override
public int progressCallback(int anInt, int bnInt, com.visualing.application.AidlCallback callback, com.visualing.application.RequestEntity request) throws android.os.RemoteException {
}
//這個與服務器對應的方法標識
static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addValues = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_progressCallback = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
}
//服務接口的方法定義
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;
public int addValues(int anInt, int bnInt, com.visualing.application.RequestEntity request) throws android.os.RemoteException;
public int progressCallback(int anInt, int bnInt, com.visualing.application.AidlCallback callback, com.visualing.application.RequestEntity request) throws android.os.RemoteException;
}
從上面我們看到了一個叫做Stub內部類,里面有兩個關鍵的方法
public boolean onTransact()
和
public static com.visualing.application.AidlCallback asInterface(android.os.IBinder obj)
作用已經在上一篇文章中說明。他們是binder的橋梁和轉換,是聯系兩個進程的關鍵點。
什么是Stub內部類,使用Stub內部類需要注意的地方
Stub內部類
- .aidl文件編譯后生成的.java文件中自動生成的內部類。
- public static abstract聲明。
- extends android.os.Binder。
- 實現.aidl文件中定義的接口,且聲明其所有方法。
實現Stub內部類要注意
- 對于傳過來的調用,無法保證是在主線程中執行的。
- Service必須要考慮多線程和線程安全。
- 默認情況下,RPC都是異步的。避免在主線程中調用AIDL,不然可能會導致ANR。
- 不能給調用方回拋異常。
3. 通過Service子類將接口暴露給外界
需要創建Stub內部類的子類并實現接口的相關方法,然后通過asBinder返回給客戶端使用。
package com.visualing.application.server;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import com.visualing.application.AidlCallback;
import com.visualing.application.IMyAidlInterface;
import com.visualing.application.RequestEntity;
public class ServerApplication extends Service {
IMyAidlInterface mAidlInterface = new IMyAidlInterface.Stub() {
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
//方法實現
}
@Override
public int addValues(int anInt, int bnInt, RequestEntity request) throws RemoteException {
//具體方法實現
return anInt + bnInt + request.age;
}
@Override
public int progressCallback(int anInt, int bnInt, AidlCallback callback, RequestEntity request) throws RemoteException {
//具體方法實現
request.age = addValues(anInt, bnInt, request) * 10000;
callback.callback(request);
return request.age;
}
};
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
//簡單的類型強轉
return mAidlInterface.asBinder();
}
@Override
public boolean onUnbind(Intent intent) {
return super.onUnbind(intent);
}
}
然后在AndroidManifest.xml進行注冊
<!--關鍵點:android:exported="true"-->
<service
android:name=".ServerApplication"
android:exported="true">
<intent-filter>
<action android:name="com.visualing.application.server.MyService"/>
</intent-filter>
</service>
那么到此服務端的AIDL就已經全部實現了。
看看服務端的文件結構圖是什么樣的
客戶端實現
如果已經寫好了服務端的AIDL,那么客戶端的AIDL也就寫好了,因為它們是一一對應的,需要從服務端拷貝一份AIDL文件到客戶端然后同步相關代碼,那么就會生成相同的類對象。
同時還需要做的事情是將AIDL中涉及到的java類文件也需要拷貝一份,因為他們是相同的處理方式,都是通過Binder進行處理的。
以下是客戶端的復制文件結構圖
可以看打大部分文件都是服務端文件的復制,那么客戶端怎么和服務端進行溝通呢,請看關鍵的AidlActivity.java文件。
package com.visualing.application.client;
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.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import com.visualing.application.AidlCallback;
import com.visualing.application.IMyAidlInterface;
import com.visualing.application.RequestEntity;
public class AidlActivity extends AppCompatActivity {
IMyAidlInterface mAidlInterface;
//關鍵點服務連接器
ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//關鍵點,獲取連接服務接口
/**
* <pre>
* public static com.visualing.application.IMyAidlInterface asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.visualing.application.IMyAidlInterface))) {
return ((com.visualing.application.IMyAidlInterface)iin);
}
return new com.visualing.application.IMyAidlInterface.Stub.Proxy(obj);
}
* </pre>
*/
mAidlInterface = IMyAidlInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
mAidlInterface = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_hello_sum_aidl);
//綁定遠程服務,可能第一次無法綁上
bindRomoteService();
initActionView();
}
protected void bindRomoteService() {
final Intent intent = new Intent();
//---------關鍵點:確定服務的服務類型,或者加入其它權限
intent.setAction("com.visualing.application.server.MyService");
//---------關鍵點:確定服務所屬的包名
intent.setPackage("com.visualing.application.server");
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
//解綁遠程服務
unbindService(mServiceConnection);
super.onDestroy();
}
protected void initActionView() {
Button buttonCalc = (Button) findViewById(R.id.buttonCalc);
buttonCalc.setOnClickListener(new View.OnClickListener() {
EditText value1 = (EditText) findViewById(R.id.value1);
EditText value2 = (EditText) findViewById(R.id.value2);
TextView result = (TextView) findViewById(R.id.result);
int v1 = 0, v2 = 0, res = -1;
@Override
public void onClick(View v) {
//遠程方法操作
if (mAidlInterface != null) {
try {
v1 = Integer.parseInt(value1.getText().toString());
v2 = Integer.parseInt(value2.getText().toString());
} catch (Exception e) {
e.printStackTrace();
return;
}
//此處是則是aidl運行線程
new Thread(new Runnable() {
@Override
public void run() {
//執行遠程方法
try {
//非UI運行線程
res = mAidlInterface.addValues(v1, v2, new RequestEntity(20));
try {
result.setText(Integer.valueOf(res).toString());
} catch (Exception e) {
//執行上述代碼會報異常,因為在thread中執行
e.printStackTrace();
}
mAidlInterface.progressCallback(v1, v2, new AidlCallback.Stub() {
@Override
public void callback(RequestEntity entity) throws RemoteException {
try {
result.setText(Integer.valueOf(entity.age).toString());
} catch (Exception e) {
//執行上述代碼會報異常,因為在thread中執行
e.printStackTrace();
}
}
}, new RequestEntity(30));
result.postDelayed(new Runnable() {
@Override
public void run() {
try {
//UI運行線程
mAidlInterface.progressCallback(v1, v2, new AidlCallback.Stub() {
@Override
public void callback(RequestEntity entity) throws RemoteException {
result.setText(Integer.valueOf(entity.age).toString());
//不會有異常,會得出最后的顯示結果
}
}, new RequestEntity(50));
} catch (RemoteException e) {
e.printStackTrace();
}
}
}, 1000);
//如果也就說明一個問題,aidl是在那個線程中調用的,那么執行的回調也是在那個線程中
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
} else {
bindRomoteService();
}
}
});
}
}
執行上面的代碼執行過程是這樣的,首先是 bindRomoteService(),然后得到接口對象mAidlInterface ,然后調用相關的方法實現,最后需要早destory中解綁服務 unbindService(mServiceConnection);從上面的測試代碼也可以看出aidl是在那個線程中調用的,那么執行的回調也是在那個線程中,aidl是允許在任何線程中調用的。
由上基本上完成了復雜模式的AIDL雙向通信回調。
總結
客戶端使用
ServiceConnection mServiceConnection = new ServiceConnection()
鏈接服務端,得到服務端定義的接口
IMyAidlInterface mAidlInterface = IMyAidlInterface.Stub.asInterface(service);
,客戶端使用mAidlInterface對象進行遠程調用,該對象是一個站樁對象,通過Binder的方式調用對應的接口實例
@Override
public int progressCallback(int anInt, int bnInt, com.visualing.application.AidlCallback callback, com.visualing.application.RequestEntity request)
{
...
//mRemote虛擬機接管的遠程實例對象
mRemote.transact(Stub.TRANSACTION_progressCallback, _data, _reply, 0);
...
}
,通過transact方法將信息溝通服務端,并把相關參數信息傳到客戶端實現。
接下來由服務端中的onBind方法中返回的Stub類型的Binder對象接收處理
public class ServerApplication extends Service {
@Override
public IBinder onBind(Intent intent) {
//簡單的類型強轉
return mAidlInterface.asBinder();
}
}
但是并不是直接調用Stub子類中的對應方法,而是通過Binder中的一個onTransact方法
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
switch (code)
{
case TRANSACTION_progressCallback:
{
...
int _result = this.progressCallback(_arg0, _arg1, _arg2, _arg3);
...
}
...
}
處理后進行調用用戶實現的方法progressCallback()
@Override
public int progressCallback(int anInt, int bnInt, AidlCallback callback, RequestEntity request) throws RemoteException {
//具體方法實現
request.age = addValues(anInt, bnInt, request) * 10000;
callback.callback(request);
return request.age;
}
的處理過程,可以看到該過程中有一個回調方法 callback.callback(request);該方法則又會通過上面的一個過程調用到客戶端的callback方法,只不過這次客戶端變成了服務端,服務端相當于客戶端了。這樣他們就實現了雙向通信。