Android AIDL的用法(上)

200 阅读4分钟

这是我参与11月更文挑战的第8天,活动详情查看:2021最后一次更文挑战

文章目录

AIDL介绍

AIDL 是 Android 系统中跨进程间通信的接口定义语言。

使用 AIDL 可以实现跨进程间通信,实现方式是使用 A 应用程序绑定 B 应用程序的 Service,基于绑定 Service 的方式来实现跨进程的组件之间的通信。

在跨进程的通信中,使用了 Service 提供绑定的应用程序,通常称之为“服务端”,而绑定了其他应用的 Service 的应用程序,称之为“客户端”。

开发步骤

1、【Server】创建 WorkService 类,继承自android.app.Service,并在 AndroidManifest.mxl 中注册
2、【Server】创建IMusicPlayer.aidl接口,并在接口中定义那些由 Service 实现功能,由 Activity 调用的方法。
3、【Server】WorkService创建内部类InnerBinder,继承自IMusicPlayer.Stub,实现抽象方法。在onBind()方法中,创建InnerBinder的对象,并作为该方法的返回值。
4、【Client】MainActivity创建内部类InnerServiceConnection,实现android.content.ServiceConnection接口
5、【Client】MainActivity当需要绑定 Service 时,调用bindService()方法,第1个参数是 Intent 对象,用于指定被激活的 Service 组件;第2个参数是ServiceConnection对象,使用InnerServiceConnection的对象即可,第3个参数使用常量BIND_AUTO_CREATE
6、【Server】在AndroidManifest.mxl 中设置WorkService 可被隐式激活
7、【Client】新建与服务端 aidl 所在包同名的包,并且将服务端的 aidl 文件复制到客户端
8、【Client】MainActivity声明IMusicPlayer类型的全局变量player,并在InnerBinderConnectiononServiceConnected()方法中,调用IMusicPlayer.Stub.asInterface(iBinder)方法对player进行赋值,方法的参数就是onServiceConnected()方法的第二个参数
9、【Client】MainActivity当需要调用 Service中的方法时,直接使用 player 调用即可
10、【Client】MainActivity重写onDestroy()方法,在该方法中调用unbindService()方法解除与 Service 的绑定,并且,该语句必须在super.onDestroy()之前

按照步骤进行开发

创建两个项目 ServerProject 和 ClientProject 分别写服务端和客户端的代码。

【ServerProject】

1、创建 WorkService

public class WorkService extends Service {
    public WorkService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }
}

并在 AndroidManifest 中注册

 <service
            android:name=".WorkService"
            android:enabled="true"
            android:exported="true"/>

2、创建 AIDL 接口

在 MainActivity 同级目录下创建 IMusicPlayer.aidl
在这里插入图片描述

interface IMusicPlayer {
    void play();
    void pause();
}

这样会在 main 目录下生成一个相同包名的IMusicPlayer.aidl
在这里插入图片描述
我们运行一下程序,会在 build 文件夹下找到一个 IMusicPlayer.aidl
在这里插入图片描述
打开这个文件,我们会看到
在这里插入图片描述
Stub extends android.os.Binder。所以下一步 WorkService 创建的 InnerBinder 就不继承 Binder 了,可以直接继承 IMusicPlayer.Stub

3、WorkService 中创建 InnerBinder

public class WorkService extends Service {
    public WorkService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        InnerBinder binder = new InnerBinder();
        return binder;
    }

    private class InnerBinder extends IMusicPlayer.Stub {

        @Override
        public void play() throws RemoteException {
            Log.d("AIDL", "[Server]WorkService$InnerBinder play()");
        }

        @Override
        public void pause() throws RemoteException {
            Log.d("AIDL", "[Server]WorkService$InnerBinder pause()");
        }
    }
}

ClientProject

4、5 MainActivity 绑定 Service

布局文件 activity_main 中增加一个 Button 用来绑定 Service,再增加两个按钮分别用来调用 AIDL 的两个方法。

<Button
        android:id="@+id/btn_bind"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="绑定Service" />

    <Button
        android:id="@+id/btn_play"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="调用play()" />

    <Button
        android:id="@+id/btn_pause"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="调用pause()方法" />

MainActivity

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private View btnBindService;
    private View btnBindService;
    private Button btn_play;
    private Button btn_pause;
    private InnerServiceConnection conn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

       	btnBindService = findViewById(R.id.btn_bind);
        btn_play = findViewById(R.id.btn_play);
        btn_pause = findViewById(R.id.btn_pause);
        btnBindService.setOnClickListener(this);
        btn_play.setOnClickListener(this);
        btn_pause.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_bind:
                Intent intent = new Intent("com.example.serviceapplication.WORK_SERVICE");
                conn = new InnerServiceConnection();
                bindService(intent,conn,BIND_AUTO_CREATE);
                break;
            case R.id.btn_play:
                break;
            case R.id.btn_pause:
                break;
            default:
                break;
        }
    }

    private class InnerServiceConnection implements ServiceConnection{

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    }
}

6、WorkService 设置为可被隐式激活
其中按钮的点击事件中,由于要激活的是 ServerProject 中的 WorkService,所以,需要在【ServiceProject】的 AndroidManifest.xml 中配置WorkService是之可以被隐式激活

<service
            android:name=".WorkService"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.example.serviceapplication.WorkService" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </service>

7、把服务端的 aidl 文件夹复制到客户端相同位置
把 ServerProject 中的 main下的 aidl 文件夹复制到 ClientProject 中同样的位置

在这里插入图片描述
然后运行下程序

8、9、10 Client 端 MainActivity 中代码修改

MainActivity

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private Button btnBindService;
    private Button btn_play;
    private Button btn_pause;

    private InnerServiceConnection conn;
    private IMusicPlayer player;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btnBindService = findViewById(R.id.btn_bind);
        btn_play = findViewById(R.id.btn_play);
        btn_pause = findViewById(R.id.btn_pause);
        btnBindService.setOnClickListener(this);
        btn_play.setOnClickListener(this);
        btn_pause.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_bind:
                Intent intent = new Intent();
                intent.setAction("com.example.serviceapplication.WorkService");
                intent.setPackage("com.example.serviceapplication");
                conn = new InnerServiceConnection();
                bindService(intent, conn, BIND_AUTO_CREATE);
                break;
            case R.id.btn_play:
                try {
                    player.play();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;
            case R.id.btn_pause:
                try {
                    player.pause();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;
            default:
                break;
        }
    }

    private class InnerServiceConnection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            player = IMusicPlayer.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    }

    @Override
    protected void onDestroy() {
        //解除与service的绑定
        unbindService(conn);
        super.onDestroy();
    }
}

首先运行【ServerProject】,确保程序安装到手机上即可。然后运行【ClientProject】,点击页面两个按钮,观察【ServerProject】日志:
在这里插入图片描述
可以看到,点击【ClientProject】中的按钮,调用了【ServerProject】中的方法。

如果我们在【ServerProject】中的 IMusicPlayer.aidl 中增加1个方法getDuration()

interface IMusicPlayer {
       void play();
       void pause();
       int getDuration();
}

然后运行【ServerProject】,WorkService 的 InnerBinder 类会报错,因为我们需要重写getDuration()

private class InnerBinder extends IMusicPlayer.Stub {
		......
        @Override
        public int getDuration() throws RemoteException {
            Log.d("AIDL","[Server]WorkService$InnerBinder getDuration()-->9527");
            return 9527;
        }
    }

这时候,【ClientProject】仍然可以调用play()pause()方法,但是不能调用新增的getDuration()方法。因此我们必须更新【ClientProject】的 IMusicPlayer.aidl,使其跟【ServerProject】中的保持一致

为了测试,在【ClientProject】的布局中增加1个按钮测试调用这个方法

<Button
        android:id="@+id/btn_getDuration"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="调用getDuration()方法" />

MainActivty

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
	......
    private Button btn_getDuration;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ......
        btn_getDuration = findViewById(R.id.btn_getDuration);
        btn_getDuration.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            ......
            case R.id.btn_getDuration:
                try {
                    int duration = player.getDuration();
                    Log.d("AIDL","[Client] getDuration()-->"+duration);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;
            default:
                break;
        }
    }
    ......
}

首先运行【ServerProject】,确保安装到手机,然后运行【ClientProject】,先绑定 Service,然后点击getDuration()方法的按钮,观察Log

D/AIDL: [Server]WorkService$InnerBinder getDuration()-->9527
D/AIDL: [Client] duration:9527

报错请注意

1、点击绑定 SERVICE,5.0 以上会报错,所以上边 MainActivity 中的代码已经修改了相关绑定 Service 的代码,关于解释,具体可以看下一篇文章。

2、如果你的手机是 Android 11,MainActivity 中的 player 变量可能为空。具体解决办法,可以查看下一篇文章。