背景
最近在考慮項(xiàng)目重構(gòu)的時(shí)候临梗,考慮將項(xiàng)目拆分成兩個(gè)APK胞得,一個(gè)用于數(shù)據(jù)服務(wù)荧止,一個(gè)用于UI展示。
數(shù)據(jù)服務(wù)APK向自己編寫(xiě)APK提供數(shù)據(jù),同時(shí)也可以向第三方提供數(shù)據(jù)跃巡∥:牛考慮使用這樣的方式代替向第三方提供jar形式的sdk包。
如果拆分成多個(gè)APK素邪,不得不考慮 進(jìn)程間通信(IPC)的問(wèn)題外莲。Android提供了一種IPC的實(shí)現(xiàn),就是AIDL.
在學(xué)習(xí)AIDL時(shí)編寫(xiě)示例形成本文兔朦。放在Github的demo項(xiàng)目中偷线。可以在下面的地址下載到源代碼
github: https://github.com/vir56k/demo/tree/master/aidlDemo
什么是AIDL
AIDL (Android Interface Definition Language沽甥, Android接口定義語(yǔ)言)
在不同的進(jìn)程(應(yīng)用)之間進(jìn)行數(shù)據(jù)交換淋昭,就要約定 之間的通信接口。
從面向?qū)ο蟮慕嵌葋?lái)看安接,接口設(shè)計(jì)要考慮狀態(tài)和行為翔忽。一般來(lái)說(shuō),接口定義的內(nèi)容分為:
1.方法操作(描述行為)
2.參數(shù)(描述狀態(tài)盏檐,數(shù)據(jù)的類(lèi)型歇式,數(shù)據(jù)的載體/實(shí)體)
AIDL是一種IDL,它有特有的語(yǔ)法描述胡野。我們需要編寫(xiě)一個(gè)AIDL文件作為約定材失。它的語(yǔ)法非常類(lèi)似java語(yǔ)法。
它支持基礎(chǔ)數(shù)據(jù)類(lèi)型硫豆,比如 int,String,float等龙巨。
它支持實(shí)體類(lèi),必須是實(shí)現(xiàn)了Parcelable接口熊响,支持序列化旨别。
AIDL通過(guò)服務(wù)綁定的方式來(lái)使用。你需要定義一個(gè)service,傳遞一個(gè) IBinder對(duì)象汗茄。這個(gè) IBinder對(duì)象具有我們需要的方法秸弛。
拿到這個(gè)對(duì)象后執(zhí)行具體方法。
AIDL分為 服務(wù)端和客戶(hù)端
服務(wù)端即服務(wù)提供著洪碳,提供可操作的方法和數(shù)據(jù)递览。
客戶(hù)端即調(diào)用者,使用方法和數(shù)據(jù)瞳腌。
什么時(shí)候適合使用AIDL:
官方文檔建議只有你允許客戶(hù)端從不同的應(yīng)用程序?yàn)榱诉M(jìn)程間的通信而去訪(fǎng)問(wèn)你的service绞铃,以及想在你的service處理多線(xiàn)程。
步驟說(shuō)明
服務(wù)端開(kāi)發(fā)步驟如下:
1.定義一個(gè)AIDL文件
2.實(shí)現(xiàn)描述的接口嫂侍,編寫(xiě)service
3.如果有實(shí)體類(lèi)儿捧,需要提供實(shí)體類(lèi)(jar包形式)
客戶(hù)端
1.拿到AIDL文件
2.綁定服務(wù)冷离,獲得接口持有對(duì)象。
示例
服務(wù)端開(kāi)發(fā)
1.聲明AIDL文件
Android提供的特殊的文件夾來(lái)放置AIDL文件纯命,位于 src/mian/aidl 文件夾下西剥。
由于java類(lèi)/接口是有 package(命名空間)的。我們需要定義命名空間亿汞,一般和文件位置一致瞭空。
在這里,我們?cè)?src/mian/aidl 文件夾下疗我,創(chuàng)建package咆畏,名稱(chēng)為:com.example.myserver。
對(duì)應(yīng)文件夾路徑為src/mian/aidl/com/example/myserver吴裤,我們?cè)谶@個(gè)文件下建立我們的aidl文件旧找,內(nèi)容如下:
IRemoteService.aidl
package com.example.myserver;
import com.example.myserver.Entity;
import com.example.myserver.IMyCallback;
// Declare any non-default types here with import statements
interface IRemoteService {
void doSomeThing(int anInt,String aString);
void addEntity(in Entity entity);
List<Entity> getEntity();
}
Entity.aidl,這個(gè)是實(shí)體類(lèi) 麦牺,它還需要對(duì)應(yīng)一個(gè)java class文件
// Entity.aidl
package com.example.myserver;
parcelable Entity;
2.實(shí)現(xiàn)接口,編寫(xiě)service
在src/java文件夾寫(xiě)下 MyService class,集成服務(wù)Service類(lèi).在mainifest文件中注冊(cè)這個(gè)服務(wù)類(lèi)钮蛛。
如果你的aidl描述文件編寫(xiě)無(wú)誤的話(huà),android studio 會(huì)自動(dòng)幫你生成一些輔助類(lèi)剖膳,你可以在下面的目錄找到:
build/generated/source/debug
在這個(gè)文件夾下回自動(dòng)生成有 IRemoteService類(lèi)魏颓,和它的子類(lèi) IRemoteService.Stub類(lèi)及其他。感興趣的同學(xué)可以讀讀吱晒。
IRemoteService.Stub是一個(gè)根文件甸饱,它是一個(gè)抽象類(lèi)。下面代碼演示了仑濒,一個(gè) IRemoteService.Stub 的匿名類(lèi)的實(shí)現(xiàn)叹话。
在這個(gè)服務(wù)類(lèi)的 public IBinder onBind(Intent intent) 方法中,我們r(jià)eturn 一個(gè) IRemoteService.Stub 的匿名類(lèi)實(shí)現(xiàn)墩瞳。
在客戶(hù)綁定到這個(gè)服務(wù)的時(shí)候驼壶,將可以獲得到這個(gè)實(shí)現(xiàn)的一個(gè)實(shí)例,調(diào)用它的方法矗烛。
代碼如下
package com.example.myserver;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
/**
* Created by zhangyunfei on 16/10/12.
*/
public class MyService extends Service {
public static final String TAG = "MyService";
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.e(TAG, String.format("on bind,intent = %s", intent.toString()));
return binder;
}
private final IRemoteService.Stub binder = new IRemoteService.Stub() {
public static final String TAG = "IRemoteService.Stub";
private List<Entity> data = new ArrayList<Entity>();
@Override
public void doSomeThing(int anInt, String aString) throws RemoteException {
Log.d(TAG, String.format("收到:%s, %s", anInt, aString));
}
@Override
public void addEntity(Entity entity) throws RemoteException {
Log.d(TAG, String.format("收到:entity = %s", entity));
data.add(entity);
}
@Override
public List<Entity> getEntity() throws RemoteException {
return data;
}
};
}
3.編寫(xiě)實(shí)體類(lèi)
我們上面提到辅柴,接口的參數(shù)可以是實(shí)體類(lèi)。我們?cè)谇懊娑x了一個(gè)entity.aidl瞭吃,它里面寫(xiě)了一句
parcelable Entity;
這么一句話(huà)指明它需要關(guān)聯(lián)到一個(gè)具體的實(shí)體類(lèi)。我們需要在src/java文件夾編寫(xiě)這么一個(gè)類(lèi)的實(shí)現(xiàn)涣旨,必須實(shí)現(xiàn)parcelable接口歪架。
注意我們要先建立package,這個(gè) package要和aidl接口聲明里的一致。
android studio為我們方便的提供自動(dòng)生成parcelable實(shí)現(xiàn)的快捷鍵霹陡,在mac下是 command+空格和蚪。實(shí)現(xiàn)后的代碼如下:
package com.example.myserver;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Created by zhangyunfei on 16/10/12.
*/
public class Entity implements Parcelable {
int age;
String name;
public Entity() {
}
public Entity(int age, String name) {
this.age = age;
this.name = name;
}
protected Entity(Parcel in) {
age = in.readInt();
name = in.readString();
}
public static final Creator<Entity> CREATOR = new Creator<Entity>() {
@Override
public Entity createFromParcel(Parcel in) {
return new Entity(in);
}
@Override
public Entity[] newArray(int size) {
return new Entity[size];
}
};
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(age);
dest.writeString(name);
}
@Override
public String toString() {
return String.format("age=%s, name=%s", age, name);
}
}
客戶(hù)端開(kāi)發(fā) - 調(diào)用AIDL接口
再開(kāi)始之前止状,我們可以新建一個(gè)app來(lái)做演示.步驟如下:
1.獲得AIDL,放到項(xiàng)目中
我們先拿到AIDL描述文件才用使用,將AIDL文件放到aidl文件夾下攒霹。android studio 自動(dòng)生成根文件類(lèi)怯疤。
獲得實(shí)體類(lèi)Entity.class 放入到項(xiàng)目中。
2.在activity中調(diào)用
在它的 MainActivity 下綁定服務(wù)
Intent intent = new Intent();
intent.setAction("com.example.REMOTE.myserver");
intent.setPackage("com.example.myserver");
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
指定服務(wù)的名稱(chēng)催束,bindService方法中需要傳入一個(gè) ServiceConnection對(duì)象集峦。
我們寫(xiě)一個(gè)ServiceConnection的匿名類(lèi),在它的onServiceConnected方法中抠刺,獲得 aidl定義的接口持有類(lèi)塔淤。
iRemoteService = IRemoteService.Stub.asInterface(service);
還記得剛剛編寫(xiě)服務(wù)類(lèi)返回的 binder嗎,在這里獲得的就是那個(gè)binder示例速妖。我們可以通過(guò)對(duì)這個(gè)示例進(jìn)行 轉(zhuǎn)型 后的對(duì)象來(lái)調(diào)用 接口定義的方法高蜂。
3.調(diào)用接口方法
通過(guò) iRemoteService.addEntity(entity) 方法,我們可以操作具體的實(shí)體罕容,傳入實(shí)體類(lèi)作為參數(shù)备恤。
if (!mBound) {
alert("未連接到遠(yuǎn)程服務(wù)");
return;
}
try {
Entity entity = new Entity(1, "zhang");
if (iRemoteService != null)
iRemoteService.addEntity(entity);
} catch (RemoteException e) {
e.printStackTrace();
}
完整代碼如下:
package com.example.zhangyunfei.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;
import com.example.myserver.Entity;
import com.example.myserver.IMyCallback;
import com.example.myserver.IRemoteService;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private boolean mBound = false;
private IRemoteService iRemoteService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btnAdd).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!mBound) {
alert("未連接到遠(yuǎn)程服務(wù)");
return;
}
try {
Entity entity = new Entity(1, "zhang");
if (iRemoteService != null)
iRemoteService.addEntity(entity);
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
findViewById(R.id.btnList).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!mBound) {
alert("未連接到遠(yuǎn)程服務(wù)");
return;
}
if (iRemoteService != null) {
try {
List<Entity> entityList = iRemoteService.getEntity();
StringBuilder sb = new StringBuilder("當(dāng)前數(shù)量:" + entityList.size() + "\r\n");
for (int i = 0; i < entityList.size(); i++) {
sb.append(i + ": ");
sb.append(entityList.get(i) == null ? "" : entityList.get(i).toString());
sb.append("\n");
}
alert(sb.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
});
findViewById(R.id.btnCallback).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!mBound) {
alert("未連接到遠(yuǎn)程服務(wù)");
return;
}
try {
if (iRemoteService != null) {
final String para = "canshu";
iRemoteService.asyncCallSomeone(para, new IMyCallback.Stub() {
@Override
public void onSuccess(String aString) throws RemoteException {
alert(String.format("發(fā)送: %s, 回調(diào): %s", para, aString));
}
});
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
private void alert(String str) {
Toast.makeText(this, str, 0).show();
}
@Override
protected void onStart() {
super.onStart();
if (!mBound) {
attemptToBindService();
}
}
@Override
protected void onStop() {
super.onStop();
if (mBound) {
unbindService(mServiceConnection);
mBound = false;
}
}
/**
* 嘗試與服務(wù)端建立連接
*/
private void attemptToBindService() {
Intent intent = new Intent();
intent.setAction("com.example.REMOTE.myserver");
intent.setPackage("com.example.myserver");
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
}
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.e(getLocalClassName(), "service connected");
iRemoteService = IRemoteService.Stub.asInterface(service);
mBound = true;
if (iRemoteService != null) {
try {
iRemoteService.doSomeThing(0, "anything string");
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e(getLocalClassName(), "service disconnected");
mBound = false;
}
};
}
回調(diào)
在A(yíng)IDL中,有時(shí)候需要實(shí)現(xiàn)回調(diào)锦秒,傳入一個(gè)回調(diào)callbak烘跺,或者listener類(lèi)。如何實(shí)現(xiàn)呢脂崔?
1.編寫(xiě)回調(diào)類(lèi)aidl文件
IMyCallback類(lèi)具有一個(gè) onSuccess回調(diào)方法
IMyCallback.aidl滤淳,這個(gè)文件里描述一個(gè)回調(diào)接口
// IMyCallback.aidl
package com.example.myserver;
// Declare any non-default types here with import statements
interface IMyCallback {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void onSuccess(String aString);
}
2.聲明方法,以回調(diào)類(lèi)作為參數(shù)砌左,示例:
IRemoteService.aidl
package com.example.myserver;
import com.example.myserver.Entity;
import com.example.myserver.IMyCallback;
// Declare any non-default types here with import statements
interface IRemoteService {
void asyncCallSomeone( String para, IMyCallback callback);
}
3.實(shí)現(xiàn)方法脖咐,發(fā)起回調(diào)通知
發(fā)起回調(diào)有點(diǎn)類(lèi)似廣播的方式,示例:
@Override
public void asyncCallSomeone(String para, IMyCallback callback) throws RemoteException {
RemoteCallbackList<IMyCallback> remoteCallbackList = new RemoteCallbackList<>();
remoteCallbackList.register(callback);
final int len = remoteCallbackList.beginBroadcast();
for (int i = 0; i < len; i++) {
remoteCallbackList.getBroadcastItem(i).onSuccess(para + "_callbck");
}
remoteCallbackList.finishBroadcast();
}
我們需要一個(gè) RemoteCallbackList 集合類(lèi)汇歹,把 要回調(diào)的類(lèi)的示例callback示例放到這集合內(nèi)屁擅。調(diào)用這個(gè)集合類(lèi)RemoteCallbackList的下面兩個(gè)方法:
beginBroadcast 開(kāi)始廣播,finishBroadcast 結(jié)束廣播产弹,配合使用派歌。
4.客戶(hù)端調(diào)用示例:
客戶(hù)端在獲得接口操作對(duì)象后,傳入回調(diào)類(lèi)痰哨,示例:
try {
if (iRemoteService != null) {
final String para = "canshu";
iRemoteService.asyncCallSomeone(para, new IMyCallback.Stub() {
@Override
public void onSuccess(String aString) throws RemoteException {
alert(String.format("發(fā)送: %s, 回調(diào): %s", para, aString));
}
});
}
} catch (RemoteException e) {
e.printStackTrace();
}
參考
谷歌官方文檔