AIDL踩坑之旅

AIDL 踩坑之旅 ---- 通过扫雷游戏学习 AIDL 跨进程在两个APP之间进行通信

  某天下班在地铁上玩半年前写的扫雷小游戏的时候, 突然产生了一个想法, 能不能设计一套算法让游戏自动进行呢? 由程序来寻找最优解, 瞬间完成扫雷, 但是由于我对算法不太擅长, 能不能提供接口给外部, 让其他感兴趣的开发者来设计扫雷算法呢? 于是乎我想到了使用 AIDL 给外部提供接口, 由其他感兴趣的开发者来设计扫雷算法.

  先贴下最后实现的效果(扫雷游戏apk下载地址扫雷算法apk下载地址):

由 扫雷游戏 对外提供 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 文件, 客户端服务器双向绑定

AIDL 是通过 Binder 机制实现的, 但由于个人能力有限无法讲解Binder机制, 推荐一篇博文 https://blog.csdn.net/carson_ho/article/details/73560642 对Binder的讲解的非常清晰

  关于扫雷的算法我觉得还是有可以优化的地方的, 想要了解扫雷算法的可以到GitHub查看源码(文章开头的 GitHub 地址), 感兴趣的可以使用扫雷游戏提供的 aidl 接口自己写下扫雷算法. (Gif 图中刚开始随机点击比较慢是因为我调试时 将每次随机点击都 sleep 了 1000ms)

猜你喜欢

转载自blog.csdn.net/qq_27070117/article/details/83546459