AIDL踩坑之旅
AIDL 踩坑之旅 ---- 通過掃雷遊戲學習 AIDL 跨程序在兩個APP之間進行通訊
某天下班在地鐵上玩半年前寫的掃雷小遊戲的時候, 突然產生了一個想法, 能不能設計一套演算法讓遊戲自動進行呢? 由程式來尋找最優解, 瞬間完成掃雷, 但是由於我對演算法不太擅長, 能不能提供介面給外部, 讓其他感興趣的開發者來設計掃雷演算法呢? 於是乎我想到了使用 AIDL 給外部提供介面, 由其他感興趣的開發者來設計掃雷演算法.
由 掃雷遊戲 對外提供 aidl 介面, aidl 檔案如下:
注意: 掃雷遊戲與掃雷演算法中的aidl檔案必須完全一致, 最好直接複製過去, 否則有可能導致呼叫aidl介面得到錯誤的結果
// IMineAidlInterface.aidl
package com.eshel.mine.aidl;
/**
* 掃雷對外提供 AIDL 介面
*/
interface IMineAidlInterface {
String test();
/**
* 遊戲開始與否
*/
boolean gameStarted();
/**
* 獲取一行多少雷
*/
int getMinesWidth();
/**
* 獲取一列多少雷
*/
int getMinesHeight ();
/**
* 獲取一個格子周圍有幾個未知格子
* 被標記旗子不計數, 被標記問號計數
*/
int getUnKnown(int x, int y);
/**
* 點選掃雷
* @return 變化的雷的集合, 格式為: x=y=mineNumber
* 角標為0的元素對應點選的那個
*/
List<String> clickMine(int x, int y);
/**
* 長按標記
*/
void longClickMine (int x, int y);
/**
* 0 - 8 代表 周圍雷的數量
* -1代表本身是雷
* 10 代表 未知
* 11 代表被標記
* 12 代表 ? 標記
* -2 代表錯誤
*/
int lookMineType(int x, int y);
/**
* 0 遊戲未開始
* 1 遊戲進行中(已開始)
* -1 遊戲結束 , Game Over You Lose
* 2 遊戲結束, You Win
*/
int seeGameState();
}
掃雷遊戲(資料提供者):
在AndroidStudio中通過右鍵 --> New --> AIDL --> AIDL File建立aidl檔案, 檔案建立完成點選同步生成java檔案, 然後建立 Service檔案, 通過 AIDL 生成的檔案 IMineAidlInterface 使用匿名內部類的方式來建立 IBinder 物件, 並在此實現 aidl 介面, 固定寫法, 程式碼如下:
注意: 在 MineAidlService 中實現的介面(如 gameStarted()等), 都會在子執行緒中被呼叫, 所以UI操作需要通過 Handler 發訊息
AIDL預設支援8大基本型別, String型別, List Map型別, JavaBean需要實現 Parcelable , 其中 clickMine 方法由於資料結構簡單所以直接使用了String型別, 想了解JavaBean怎麼使用的自行百度 AIDL.
public class MineAidlService extends Service {
private IBinder mineBinder = new IMineAidlInterface.Stub() {
@Override
public boolean gameStarted() throws RemoteException {
boolean gameStarted = MineDataProvider.gameState == 1;
Log.i("Mine",gameStarted+"");
return gameStarted;
}
@Override
public int getMinesWidth() throws RemoteException {
return MineDataProvider.getMinesWidth();
}
@Override
public int getMinesHeight() throws RemoteException {
return MineDataProvider.getMinesHeight();
}
@Override
public int getUnKnown(int x, int y) throws RemoteException {
return MineDataProvider.getUnKnown(x, y);
}
@Override
public List<String> clickMine(int x, int y) throws RemoteException {
return MineDataProvider.clickMine(x,y);
}
@Override
public void longClickMine(int x, int y) throws RemoteException {
MineDataProvider.longClickMine(x,y);
}
@Override
public int lookMineType(int x, int y) throws RemoteException {
return MineDataProvider.lookMineType(x, y);
}
@Override
public int seeGameState() throws RemoteException {
return MineDataProvider.gameState;
}
@Override
public String test(){
return "掃雷 Demo";
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mineBinder;
}
}
接著, 需要在清單檔案中註冊這個服務:
注意: exported 代表是否可以被其他程序呼叫, 必須為 true, 意圖過濾器是為了由其他APP通過隱式意圖繫結服務
<service
android:exported="true"
android:name=".aidl.MineAidlService" >
<intent-filter>
<action android:name="com.eshel.mine.MineAidlService"/>
</intent-filter>
</service>
掃雷遊戲的業務邏輯就不講解了, 感興趣的可以去 GitHub 檢視原始碼 點這前往GitHub
提供資料的我稱之為 服務端, 獲取使用資料的稱之為 客戶端
到此為止服務端已經完成了AIDL, 接下來需要在客戶端中使用 由服務端提供的AIDL介面.
首先要將服務端的aidl檔案複製到客戶端相同位置, 同步生成java程式碼.
然後在客戶端的Activity中繫結遠端服務:
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//遠端服務以及連線成功, 初始化 mineProvider, 之後就可以使用 mineProvider 通過 aidl 呼叫伺服器的資料了
mineProvider = IMineAidlInterface.Stub.asInterface(service);
Log.i(TAG, "onServiceConnected: success");
connected();
}
@Override
public void onServiceDisconnected(ComponentName name) {
mineProvider = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//掃雷遊戲 Service 中配置的 Action
Intent intent1 = new Intent("com.eshel.mine.MineAidlService");
//掃雷遊戲包名(Android 5.0 之後使用隱式意圖呼叫服務必須加包名)
intent1.setPackage("com.eshel.mine");
//呼叫此行後如果目標App存在將會執行ServiceConnection中的onServiceConnected方法, 如果沒有執行需檢查配置是否有誤
bindService(intent1, mConnection, BIND_AUTO_CREATE);
}
AIDL 使用方法都是固定程式碼, 需要注意的地方(我遇到的問題)再總結一遍:
- 1 客戶端 Android 5.0 之後通過隱式意圖繫結服務必須加包名
- 2 服務端與客戶端 aidl 檔案必須完全一致
- 3 服務端清單檔案中註冊服務時 必須新增屬性 exported=true ,即可跨程序訪問
- 4 伺服器服務中介面實現處由客戶端呼叫, 伺服器通過阻塞子執行緒中呼叫具體實現, 因此不能直接在此更新 UI.
- 5 AIDL 支援 List 集合但不能直接寫 ArrayList, 其實現中必須使用ArrayList.
- 6 想要非同步呼叫 AIDL 的需要兩個 AIDL 檔案, 客戶端伺服器雙向繫結
關於掃雷的演算法我覺得還是有可以優化的地方的, 想要了解掃雷演算法的可以到GitHub檢視原始碼(文章開頭的 GitHub 地址), 感興趣的可以使用掃雷遊戲提供的 aidl 介面自己寫下掃雷演算法. (Gif 圖中剛開始隨機點選比較慢是因為我除錯時 將每次隨機點選都 sleep 了 1000ms)