關於有許多關於 AIDL 的文章,高質量的也很多,就不打算描述了,主要是想說說實際操作,如何在客戶端與服務端搭建一個回調(觀察者模式)。
搭建同進程中的通信
第一步先搭建一個服務端以及它的 Service ,新建一個 Project。
先彆着急寫代碼,思考下,假設我們新建一個 AIDL 實現兩個簡單的功能:
- 算出兩個數字的和並返回
- 對某個字符串取大寫並返回。
那是不是應該在 app(默認 module)中直接添加 AIDL 呢,當然是可以的。
不過我們還有更好的選擇,先說說 AIDL 的定義,Application Interface Defination Language(應用程序接口定義語言) 從本質上還是一個接口吧,服務端實現了該接口的方法,由客戶端調用該方法並傳入需要使用的參數。
那既然是個接口,說明是雙方都可以共用的吧,那麼我們是不是可以使用一個公共的 Library ,讓客戶端和服務端都來依賴這個 Library 呢 ?
那我們來導入一個 Library 。
然後在 main 目錄上右鍵,添加 AIDL 文件。
添加後,發現 main 目錄下多了一個 AIDL 目錄,並且裏面生成了我們剛輸入名字的 AIDL 文件,裏面已經有一個方法,和我們需要的不太一樣,我們把它刪除,然後寫入需要的方法。
interface IMyAidl {
int onNumberPlus(int number1,int number2);
String onUpperString(String strins);
}
並且點擊 Sync Project With Gradle Files。Android Studio 會爲我們自動生成對應的文件。
不過現在好像沒法知道是不是文件生成好了呢,先不着急,讓我們先讓服務端,即 app(Module) 依賴 commonlib (Library),在 app/build.gradle 文件中添加代碼。
implementation project (':commonlib')
然後在 app Module 中的 Acitivity 中寫一個 IMyAidl;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//測試 aidl 文件是否自動生成好。
IMyAidl iMyAidl;
}
}
點擊 IMyAidl 發現是自動生成好的文件,並且上面的提示也不建議我們進行編輯,咱們先暫時不看這個自動生成的,能看見這個文件,說明前面的操作沒有問題,咱們就開始動手編寫服務端的代碼。(如果想認真看看的話,建議使用代碼對齊後查看)
新建一個 Service 類來完成服務端的邏輯。
並且添加之前我們要實現的功能,對兩個數字求和返回與求一個字符串取大寫字符返回。
這裏我們是做的兩個方法的實現,解耦了調用與實現。
public class WorkService extends Service {
private int addNumber(int a, int b){
return a + b;
}
private String toUpperString(String s){
if(s == null || s.equals("")){
return null;
}else{
return s.toUpperCase();
}
}
}
我們的目的是讓客戶端能調用服務端的代碼,現在客戶端需要拿到服務端的一個 Service 實例,那麼肯定是需要在 Service 的 onBind() 中返回一個 IBinder 對象。
在 Service 中新建一個類,並且繼承 IMyAidl.Stub 。
public class WorkBinder extends IMyAidl.Stub{
@Override
public int onNumberPlus(int number1, int number2) throws RemoteException {
//調用前面寫的實現
return addNumber(number1,number2);
}
@Override
public String onUpperString(String strins) throws RemoteException {
//調用前面寫的實現
return toUpperString(strins);
}
}
這裏也需要實現兩個代碼的邏輯,不過我們在前面已經寫了,那麼直接把前面的代碼加進來, WorkBinder 只負責調用,真正的邏輯實現是在外部。
當然還需要在 WorkService 中增加一個 WorkBinder 的對象,不然我們返回哪個對象是吧,所以在 Service 的 onCreate 中,對WorkBinder 進行初始化,然後在連接成功時返回該 WorkBinder 實例。
然後整理好 WorkService 就是這樣的。
public class WorkService extends Service {
private WorkBinder mWorkBinder;
@Override
public void onCreate() {
super.onCreate();
//初始化實例
if(mWorkBinder == null){
mWorkBinder = new WorkBinder();
}
}
//加法實現
private int addNumber(int a, int b){
return a + b;
}
//大寫實現
private String toUpperString(String s){
if(s == null || s.equals("")){
return null;
}else{
return s.toUpperCase();
}
}
public class WorkBinder extends IMyAidl.Stub{
@Override
public int onNumberPlus(int number1, int number2) throws RemoteException {
return addNumber(number1,number2);
}
@Override
public String onUpperString(String strins) throws RemoteException {
return toUpperString(strins);
}
}
//返回該 Service 實例時重要的方法。
//如果是通過 startService 方法時,可以忽略此方法。
@Override
public IBinder onBind(Intent intent) {
if(mWorkBinder == null)
return null;
}else{
return mWorkBinder;
}
}
}
好了,我們在 Activity 當中,直接開啓 Service ,並且在連接建立後,嘗試調用AIDL定義的方法。
public class MainActivity extends AppCompatActivity {
private static final String TAG = "Server.MainActivity";
//定義爲全局引用,方便後面調用
IMyAidl myAidl;
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "onServiceConnected: ");
myAidl = IMyAidl.Stub.asInterface(service);
int sum;
String string = null;
try {
sum = myAidl.onNumberPlus(10,20);
string = myAidl.onUpperString("abcde");
} catch (RemoteException e) {
e.printStackTrace();
sum = -1;
string = "Error";
}
//試着打印下結果
Log.i(TAG, "onServiceConnected: sum " + sum );
Log.i(TAG, "onServiceConnected: String " + string );
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "onServiceDisconnected: " );
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindService();
}
//建立連接
private boolean bindService(){
Intent intent = new Intent(this,WorkService.class);
return bindService(intent,mServiceConnection,BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
//結束時解除連接
if(mServiceConnection != null){
unbindService(mServiceConnection);
}
}
}
最後不要忘了,在 Manifest 文件中添加 Service。
<application
……
<activity android:name=".MainActivity">
……
</activity>
<service android:name=".WorkService"/>
</application>
好了,我們啓動來,看看 Log.
com.rambopan.aidlcallback I/MainActivity: Server.onServiceConnected:
com.rambopan.aidlcallback I/MainActivity: Server.onServiceConnected: sum 30
com.rambopan.aidlcallback I/MainActivity: Server.onServiceConnected: String ABCDE
看來沒什麼問題,當然這個 AIDL 此時的作用只是在同一個進程當中。
搭建跨進程的通信
接下來改成跨進程的通訊,搭建客戶端,新建一個 Module 。比如叫 Client。同樣讓客戶端依賴 commonlib (Library)。
客戶端也類似服務端,唯一不同的就是開啓 Service 時需要隱式打開,我們先寫客戶端代碼。
public class MainActivity extends AppCompatActivity {
private static final String TAG = "Client.MainActivity";
IMyAidl myAidl;
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "onServiceConnected: ");
myAidl = IMyAidl.Stub.asInterface(service);
int sum;
String string = null;
try {
sum = myAidl.onNumberPlus(40,47);
string = myAidl.onUpperString("client");
} catch (RemoteException e) {
e.printStackTrace();
sum = -1;
string = "Error";
}
Log.i(TAG, "onServiceConnected: sum " + sum );
Log.i(TAG, "onServiceConnected: String " + string );
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "onServiceDisconnected: " );
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindService();
}
@Override
protected void onDestroy() {
super.onDestroy();
//結束時解除連接
if(mServiceConnection != null){
unbindService(mServiceConnection);
}
}
private void bindService() {
Intent intent = new Intent("com.rambopan.commonlib.IMyAidl");
bindService(intent,mServiceConnection,BIND_AUTO_CREATE);
}
}
然後我們去服務端那邊,把隱式 Intent 所需的 Action 添加進 Manifest 文件。
<application
……
<activity android:name=".MainActivity">
……
</activity>
<service android:name=".WorkService" >
<intent-filter>
<action android:name="com.rambopan.commonlib.IMyAidl"/>
</intent-filter>
</service>
</application>
這次我們先把之前服務端的 app 重新裝一次,因爲添加了隱式的 action ,然後運行客戶端。
嗯,然後一打開發現崩潰了,咱們來瞅瞅日誌。
12-14 23:52:41.530 12552-12552/com.rambopan.client E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.rambopan.client, PID: 12552
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.rambopan.client/com.rambopan.client.MainActivity}: java.lang.IllegalArgumentException: Service Intent must be explicit: Intent { act=com.rambopan.commonlib.IMyAidl }
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2315)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2375)
at android.app.ActivityThread.access$900(ActivityThread.java:147)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1283)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5254)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:910)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:705)
Caused by: java.lang.IllegalArgumentException: Service Intent must be explicit: Intent { act=com.rambopan.commonlib.IMyAidl }
at android.app.ContextImpl.validateServiceIntent(ContextImpl.java:1686)
at android.app.ContextImpl.bindServiceCommon(ContextImpl.java:1785)
at android.app.ContextImpl.bindService(ContextImpl.java:1763)
at android.content.ContextWrapper.bindService(ContextWrapper.java:548)
at com.rambopan.client.MainActivity.bindService(MainActivity.java:52)
at com.rambopan.client.MainActivity.onCreate(MainActivity.java:47)
at android.app.Activity.performCreate(Activity.java:5993)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1105)
說需要指定爲顯示的,我們明明要通過其他應用來開啓 Service ,肯定是需要隱式的。google 一下,發現是缺少一些信息,在 5.0 之後會拋出異常,找到一個解決方案,增加自定義類的包名。
具體分析可查看這篇文章:https://www.jianshu.com/p/08902fab84d4
然後修改 bindService 代碼,添加包名。
private void bindService() {
Intent intent = new Intent("com.rambopan.commonlib.IMyAidl");
//添加目標Service的包名
intent.setPackage("com.rambopan.aidlcallback");
bindService(intent,mServiceConnection,BIND_AUTO_CREATE);
}
修改之後我們先把之前服務端開啓的應用從後臺清除,再運行客戶端,觀察下日誌。
com.rambopan.client I/Client.MainActivity: onServiceConnected:
com.rambopan.client I/Client.MainActivity: onServiceConnected: sum 87
com.rambopan.client I/Client.MainActivity: onServiceConnected: String CLIENT
沒有啓動服務端,只啓動客戶端,也成功調起了服務端的服務。
搭建一個回調
現在已經實現了基本的調用操作,那麼 …… 如果我們想實現這樣一個功能:
開啓服務端,讓服務端不斷的執行數據處理,然後回調給客戶端,假設從 1 開始 ,每兩秒發送 +1 的結果給客戶端。
按照類似 setOnClickListener 的方式,客戶端給服務端設置一個監聽,由服務端把數據回調給客戶端,我們先來試一下。
然後新加入一個 AIDL 文件 CountListener 作爲回調的接口。
interface CountListener {
//計數的接口
void onGetNumber(int number);
}
先在 Library 中的 AIDL 加入四個新的方法:開始計數、是停止計數、註冊回調、反註冊回調。
interface IMyAidl {
int onNumberPlus(int number1,int number2);
String onUpperString(String strins);
//開始計數
boolean onStartCount();
//停止計數
boolean onStopCount();
//註冊回調
void registerListener(CountListener listener);
//反註冊回調
void unregisterListener(CountListener listener);
}
因爲對 CountListener 的引用,在 IMyAidl 文件中,對 CountListener 進行導入。注意觀察註釋,注意必須手動導入。
既然修改了 AIDL 文件,那麼服務端的實現邏輯肯定會變。
主要是增加了自動生成要傳遞的數字,以及註冊接口。
public class WorkService extends Service {
private static final String TAG = "Server.WorkService";
private WorkBinder mWorkBinder;
//開啓線程來遞增數字
private Thread mThread;
//回調接口
private CountListener mCountListener;
//遞增數字
private int mNumber = 0;
//開始與結束線程
private boolean isStart = false;
@Override
public void onCreate() {
super.onCreate();
if(mWorkBinder == null){
mWorkBinder = new WorkBinder();
}
}
private int addNumber(int a, int b){
return a + b;
}
private String toUpperString(String s){
if(s == null || s.equals("")){
return null;
}else{
return s.toUpperCase();
}
}
//開始計數的實現 開啓線程,2秒+1
private boolean startCount(){
if(!isStart && mThread == null){
mThread = new Thread(new Runnable() {
@Override
public void run() {
while(isStart){
mNumber++;
if(mCountListener != null){
try {
mCountListener.onGetNumber(mNumber);
} catch (RemoteException e) {
Log.w(TAG, "deliver number Error " );
e.printStackTrace();
}
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
isStart = true;
mThread.start();
return true;
}
return false;
}
//停止計數的實現
private boolean stopCount(){
if(isStart && mThread != null){
isStart = false;
return true;
}
return false;
}
//註冊回調實現
private void registerListenerImp(CountListener listener){
Log.i(TAG, "registerListener: listener : " + listener);
if(listener != null){
mCountListener = listener;
}
}
//反註冊回調實現
private void unregisterListenerImp(CountListener listener){
Log.i(TAG, "unregisterListenerImp: listener : " + listener);
mCountListener = null;
}
//包裝一層,調用 Service 中的實現。
public class WorkBinder extends IMyAidl.Stub{
@Override
public int onNumberPlus(int number1, int number2) throws RemoteException {
return addNumber(number1,number2);
}
@Override
public String onUpperString(String strins) throws RemoteException {
return toUpperString(strins);
}
@Override
public boolean onStartCount() throws RemoteException {
return startCount();
}
@Override
public boolean onStopCount() throws RemoteException {
return stopCount();
}
@Override
public void registerListener(CountListener listener) throws RemoteException {
registerListenerImp(listener);
}
@Override
public void unregisterListener(CountListener listener) throws RemoteException {
unregisterListenerImp(listener);
}
}
//返回該 Service 實例時重要的方法。
//如果是通過 startService 方法時,可以忽略此方法。
@Override
public IBinder onBind(Intent intent) {
if(mWorkBinder == null){
return null;
} else{
return mWorkBinder;
}
}
}
服務端實現修改好了,我們繼續回到客戶端。
客戶端先增加兩個按鈕,方便我們點擊註冊與反註冊。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
<Button
android:id="@+id/btn_start_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Start_Count"
/>
<Button
android:id="@+id/btn_stop_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Stop_Count"/>
</LinearLayout>
MainActivity 當中主要是增加 CountListener.Stub 類,關於回調的傳遞、註冊、實現。
public class MainActivity extends AppCompatActivity {
private static final String TAG = "Client.MainActivity";
IMyAidl myAidl;
private CountClientStub mCountClientStub;
//新建一個繼承 CountListener.Stub的類作爲傳入的對象。
private class CountClientStub extends CountListener.Stub{
@Override
public void onGetNumber(int number) throws RemoteException {
Log.i(TAG, "onGetNumber: ---> " + number);
}
}
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "onServiceConnected: ");
myAidl = IMyAidl.Stub.asInterface(service);
int sum;
String string = null;
try {
sum = myAidl.onNumberPlus(40,47);
string = myAidl.onUpperString("client");
} catch (RemoteException e) {
e.printStackTrace();
sum = -1;
string = "Error";
}
Log.i(TAG, "onServiceConnected: sum " + sum );
Log.i(TAG, "onServiceConnected: String " + string );
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "onServiceDisconnected: " );
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindService();
//傳入一個 Listener
if(mCountClientStub == null) {
mCountClientStub = new CountClientStub();
}
Button btnStartCount = findViewById(R.id.btn_start_count);
Button btnStopCount = findViewById(R.id.btn_stop_count);
btnStartCount.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(myAidl != null){
try {
myAidl.registerListener(mCountClientStub);
myAidl.onStartCount();
} catch (RemoteException e) {
e.printStackTrace();
Log.w(TAG, "onClick: registerListener RemoteException" );
}
}
}
});
btnStopCount.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(myAidl != null){
try {
myAidl.onStopCount();
myAidl.unregisterListener(mCountClientStub);
} catch (RemoteException e) {
e.printStackTrace();
Log.w(TAG, "onClick: unregisterListener RemoteException" );
}
}
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
//結束時解除連接
if(mServiceConnection != null){
unbindService(mServiceConnection);
}
}
private void bindService() {
Intent intent = new Intent("com.rambopan.commonlib.IMyAidl");
intent.setPackage("com.rambopan.aidlcallback");
bindService(intent,mServiceConnection,BIND_AUTO_CREATE);
}
}
現在把服務端客戶端重新裝一次,再試一次。
12-15 16:09:57.543 25584-25584/com.rambopan.client I/Client.MainActivity: onServiceConnected:
12-15 16:09:57.543 25584-25584/com.rambopan.client I/Client.MainActivity: onServiceConnected: sum 87
12-15 16:09:57.543 25584-25584/com.rambopan.client I/Client.MainActivity: onServiceConnected: String CLIENT
12-15 16:10:11.276 25606-25623/com.rambopan.aidlcallback I/Server.WorkService: registerListener: listener : com.rambopan.commonlib.CountListenerProxy@cd84546
12-15 16:10:11.306 25584-25600/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 1
12-15 16:10:13.308 25584-25601/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 2
12-15 16:10:15.310 25584-25600/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 3
12-15 16:10:17.312 25584-25601/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 4
12-15 16:10:19.314 25584-25600/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 5
12-15 16:10:21.316 25584-25601/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 6
傳遞非基本類型數據
成功了,那麼我們如果回調,不只是傳基本類型,要是傳一些類的對象呢,那肯定是需要對該類實現序列化的接口。才能在一個進程中序列化寫入,另一個進程序列化讀出。
在 aidl 目錄下,新建一個新的文件,Person。只需要寫一句話。
parcelable Person;
然後在相同的包下,java 目錄下,新建一個類,也叫 Person。並且實現 Parcelable。
我們增加一個名字和一個年齡屬性,然後自動生成代碼。
實現 Parcelable 的目的,就是通過 Parcelable 能過將對象序列化和反序列化,可以從 protected Person(Parcel in) 看出。
public class Person implements Parcelable {
private String mName;
private int mAge;
protected Person(Parcel in) {
mName = in.readString();
mAge = in.readInt();
}
//新增構造函數。
public Person(String name,int age){
mName = name;
mAge = age;
}
public static final Creator<Person> CREATOR = new Creator<Person>() {
@Override
public Person createFromParcel(Parcel in) {
return new Person(in);
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mName);
dest.writeInt(mAge);
}
@Override
public String toString() {
return " mName : " + mName +
" mAge : " + mAge;
}
}
我們增加了一個兩個參數的構造函數。並且複寫了 toString 方法,方便一會打印參數。
Person 類準備好了,我們去 CountListener 當中新增加一個方法,並且加入需要導入的 Person類。
import com.rambopan.commonlib.Person;
interface CountListener {
//計數的接口
void onGetNumber(int number);
//獲取每個人的信息
void onGetPersonInfo(in Person person);
}
注意:Person 前面需要增加 in 關鍵字。
我們分別對服務端客戶端代碼進行修改。
服務端:增加構造 Person 類的方法。
Random mRandom = new Random();
//開始計數的實現 開啓線程,2秒+1
private boolean startCount(){
if(!isStart && mThread == null){
mThread = new Thread(new Runnable() {
@Override
public void run() {
while(isStart){
mNumber++;
if(mCountListener != null){
try {
mCountListener.onGetNumber(mNumber);
//隨機生成 A - Z 的字母作爲名字
String name = Character.toString((char)(mRandom.nextInt(26) + 65));
//隨機生成 0 - 20 的數字作爲年齡
int age = mRandom.nextInt(20);
Person person = new Person(name,age);
mCountListener.onGetPersonInfo(person);
} catch (RemoteException e) {
Log.w(TAG, "deliver number Error " );
e.printStackTrace();
}
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
isStart = true;
mThread.start();
return true;
}
return false;
}
客戶端增加打印的消息。
private class CountClientStub extends CountListener.Stub{
@Override
public void onGetNumber(int number) throws RemoteException {
Log.i(TAG, "onGetNumber: ---> " + number);
}
@Override
public void onGetPersonInfo(Person person) throws RemoteException {
Log.i(TAG, "onGetPersonInfo: ---> Person : " + person);
}
}
好,現在來運行一下。
12-15 16:49:46.764 26999-27017/com.rambopan.aidlcallback I/Server.WorkService: registerListener: listener : com.rambopan.commonlib.CountListenerProxy@cd84546
12-15 16:49:46.764 26963-26988/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 1
12-15 16:49:46.774 26963-26988/com.rambopan.client I/Client.MainActivity: onGetPersonInfo: —> Person : mName : I mAge : 15
12-15 16:49:48.776 26963-26988/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 2
12-15 16:49:48.786 26963-26990/com.rambopan.client I/Client.MainActivity: onGetPersonInfo: —> Person : mName : F mAge : 8
12-15 16:49:50.788 26963-26988/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 3
12-15 16:49:50.788 26963-26990/com.rambopan.client I/Client.MainActivity: onGetPersonInfo: —> Person : mName : P mAge : 7
12-15 16:49:52.789 26963-26988/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 4
12-15 16:49:52.789 26963-26990/com.rambopan.client I/Client.MainActivity: onGetPersonInfo: —> Person : mName : M mAge : 11
12-15 16:49:54.801 26963-26988/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 5
12-15 16:49:54.801 26963-26990/com.rambopan.client I/Client.MainActivity: onGetPersonInfo: —> Person : mName : K mAge : 8
嗯,成功了。
更方便的使用
現在可以把 commonlib 共享出去,給需要使用的其他工程依賴。那麼依賴很麻煩,有沒有更簡單的方法,比如打個 jar 包,然後讓其他應用直接丟進 lib 文件夾依賴。
當然是可以的,我們可以先 clean 下工程,再 rebuid ,在這個目錄下可以看見生成的 jar 文件。(不同 Android Studio 版本可能有差異)
/commonlib/build/intermediates/packaged-classes/debug/
然後把 jar 文件分別放進客戶端與服務端,再分別修改 build.gradle :註釋掉之前依賴的 commonlib。並確保下一行代碼存在。
//implementation project (':commonlib')
//依賴 libs 目錄下所有 jar 類型文件。
implementation fileTree(dir: 'libs', include: ['*.jar'])
替換了文件,然後重新同步下工程,再點擊 IMyAidl ,已經提示是通過 .class 文件反編譯的,無法修改了,說明是 jar 包生效了。
這樣保證了工程在共享過程中不會被隨意改動。
那還有沒有什麼可以優化的地方呢,要是每個客戶端用的時候,都要寫一次綁定服務的代碼,是不是也可以把這重複的部分抽出來呢 ? 當然是可以的啦。
來創建一個輔助單例類 ConnectAssist,把綁定,解綁定等做成公共的部分(當然確保客戶端不會添加新的功能)。
- 把連接相關的邏輯放進來。
- 把 AIDL 的方法包裝一層,或者做一個獲取 AIDL 的公共方法。爲了簡單,就用獲取 AIDL 好了。
public class ConnectAssist {
private static final String TAG = "ConnectAssist";
private static ConnectAssist connectAssist;
private IMyAidl myAidl;
private Context mContext;
private ConnectAssist(Context context){
mContext = context;
}
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "onServiceConnected: ");
myAidl = IMyAidl.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "onServiceDisconnected: " );
}
};
public static synchronized ConnectAssist getInstance(Context context){
if(connectAssist == null){
synchronized (ConnectAssist.class){
if(connectAssist == null){
connectAssist = new ConnectAssist(context.getApplicationContext());
}
}
}
return connectAssist;
}
public boolean bindService() {
Intent intent = new Intent("com.rambopan.commonlib.IMyAidl");
intent.setPackage("com.rambopan.aidlcallback");
return mContext.bindService(intent,mServiceConnection,BIND_AUTO_CREATE);
}
public boolean unbindService(){
if(mServiceConnection != null){
mContext.unbindService(mServiceConnection);
return true;
}else{
return false;
}
}
//獲取 AIDL 接口
public IMyAidl getMyAidl(){
return myAidl;
}
}
重新生成 jar 文件,然後替換客戶端 jar 包,替換 jar 包後同步下工程,簡單調整客戶點綁定代碼。
調整後如圖,其實如果不是兩個 OnClickListener 和 CountClientStub 類 ,還是挺簡潔的 ……
public class MainActivity extends AppCompatActivity {
private static final String TAG = "Client.MainActivity";
IMyAidl myAidl;
private CountClientStub mCountClientStub;
//新建一個繼承 CountListener.Stub的類作爲傳入的對象。
private class CountClientStub extends CountListener.Stub{
@Override
public void onGetNumber(int number) throws RemoteException {
Log.i(TAG, "onGetNumber: ---> " + number);
}
@Override
public void onGetPersonInfo(Person person) throws RemoteException {
Log.i(TAG, "onGetPersonInfo: ---> Person : " + person);
}
}
ConnectAssist connectAssist;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//bindService();
connectAssist = ConnectAssist.getInstance(this);
connectAssist.bindService();
//傳入一個 Listener
if(mCountClientStub == null) {
mCountClientStub = new CountClientStub();
}
Button btnStartCount = findViewById(R.id.btn_start_count);
Button btnStopCount = findViewById(R.id.btn_stop_count);
btnStartCount.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//嘗試在使用之前判斷一次,如果爲 null 就取一次
if(myAidl == null){
myAidl = connectAssist.getMyAidl();
}
if(myAidl != null){
try {
myAidl.registerListener(mCountClientStub);
myAidl.onStartCount();
} catch (RemoteException e) {
e.printStackTrace();
Log.w(TAG, "onClick: registerListener RemoteException" );
}
}
}
});
btnStopCount.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//嘗試在使用之前判斷一次,如果爲 null 就取一次
if(myAidl == null){
myAidl = connectAssist.getMyAidl();
}
if(myAidl != null){
try {
myAidl.onStopCount();
myAidl.unregisterListener(mCountClientStub);
} catch (RemoteException e) {
e.printStackTrace();
Log.w(TAG, "onClick: unregisterListener RemoteException" );
}
}
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
//結束時解除連接
connectAssist.unbindService();
}
}
終於圓滿了,當然在操作過程中也碰到一些需要思考的地方,比如。
- WorkBinder 爲什麼要繼承 IMyAidl.Stub ?
那我們先把 commonlib 中的 IMyAidl 點開,看看是什麼結構。
public interface IMyAidl extends android.os.IInterface {
……
public static abstract class Stub extends android.os.Binder implements com.rambopan.commonlib.IMyAidl {
……
private static class Proxy implements com.rambopan.commonlib.IMyAidl {
……
}
}
}
public class Binder implements IBinder {
……
}
可以看到 IMyAidl.Stub 繼承了 Binder 類,Binder 類對 IBinder 進行了實現。所以 Stub 可以作爲 IBinder 類型返回。
看看 服務端 ServiceConnection 代碼。
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "onServiceConnected: ");
myAidl = IMyAidl.Stub.asInterface(service);
……
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "onServiceDisconnected: " );
}
};
從名字上可以看出,是把 IBinder 類型作爲定義的接口類型返回,而 Stub 類,就是不光繼承了 IBinder 類,同時也實現了定義的 AIDL 接口類 IMyAidl 。
public static com.rambopan.commonlib.IMyAidl asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.rambopan.commonlib.IMyAidl))) {
return ((com.rambopan.commonlib.IMyAidl) iin);
}
return new com.rambopan.commonlib.IMyAidl.Stub.Proxy(obj);
}
點開 asInterface() 方法,可以看出,是對 IBinder 類進行判斷。
-
如果在本地能查詢到該類型 IInterface ,則向上轉型。
-
如果查詢不到,則使用代理類,對 IBinder 類進行代理,返回一個該類型 IInterface。
private static class Proxy implements com.rambopan.commonlib.IMyAidl { 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; } @Override public int onNumberPlus(int number1, int number2) 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(number1); _data.writeInt(number2); mRemote.transact(Stub.TRANSACTION_onNumberPlus, _data, _reply, 0); _reply.readException(); _result = _reply.readInt(); } finally { _reply.recycle(); _data.recycle(); } return _result; } …… }
點擊 Proxy 類,能看到持有一個 IBInder 類型引用,幾個方法都基本類似,我們就只分析一個 onNumberPlus。
Proxy 類當中的各種方法,就是將數據變成序列化之後,對每個方法加上唯一的描述符,然後調用 mRemote 引用中的 transact() 方法,把需要調用該方法的數據傳入,等待 mRemote 調用結束之後,再從回覆當中讀取數據,就完成了一次跨進程的調用。
此處的 mRemote ,就是外層的 Stub 類,來看看 Stub 類中對應的方法,發現並沒有找到 transact 方法,那去它的父類 IBinder 類中看看。
public final boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply,
int flags) throws RemoteException {
if (false) Log.v("Binder", "Transact: " + code + " to " + this);
if (data != null) {
data.setDataPosition(0);
}
boolean r = onTransact(code, data, reply, flags);
if (reply != null) {
reply.setDataPosition(0);
}
return r;
}
觀察 transact 執行的邏輯,主要還是在 onTransact 方法中,雖然 IBinder 當中是存在 onTransact 的,不過 Stub 類中已經重寫了該類。在我們定義的這幾種操作的情況下,會執行我們寫的邏輯,其他情況下調用 super 。
public static abstract class Stub extends android.os.Binder implements com.rambopan.commonlib.IMyAidl {
private static final java.lang.String DESCRIPTOR = "com.rambopan.commonlib.IMyAidl";
……
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
java.lang.String descriptor = DESCRIPTOR;
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(descriptor);
return true;
}
case TRANSACTION_onNumberPlus: {
data.enforceInterface(descriptor);
int _arg0;
_arg0 = data.readInt();
int _arg1;
_arg1 = data.readInt();
int _result = this.onNumberPlus(_arg0, _arg1);
reply.writeNoException();
reply.writeInt(_result);
return true;
}
……
default: {
return super.onTransact(code, data, reply, flags);
}
}
}
……
}
可以看到執行的邏輯,先判斷對應的 transact 當時寫入的每個操作對應的唯一描述符,先從序列化中讀出數據,再調用 this.onNumberPlus() ,再把結果寫入。
而 this 此時就是指每個 Stub 對象,而我們在 WorkBinder 定義當中已經複寫這幾個操作的邏輯,所以現在繞了一圈,所有線索都拼接上了。也算是把 AIDL 流程簡單熟悉了下。
- 爲什麼要在 myAidl 使用前進行一次 (myAidl == null) 的判斷?在 onCreate 當中加入獲取不行麼?
當然是可以的,不過如果直接在 bindService 之後去獲取的話,可能是 null 。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
connectAssist = ConnectAssist.getInstance(this);
connectAssist.bindService();
//測試
myAidl = connectAssist.getMyAidl();
Log.i(TAG, "ConnectAssist: ---> myAidl : " + myAidl);
……
}
我們加入 log 測試,查看一下。
11:32:13.418 12674-12674/com.rambopan.client I/Client.MainActivity: ConnectAssist: —> myAidl : null
11:32:13.448 12674-12674/com.rambopan.client I/ConnectAssist: onServiceConnected:
從結果來看,Service 連接成功還需要一點時間,所以 bindService 是異步執行的,所以等沒有等待連接成功或失敗之後再執行剩下的代碼。
- 技術淺薄,如果存在任何問題或錯誤,歡迎指出,會予以確認與改正。