1. 程式人生 > >AIDL踩坑之旅

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)