使用AIDL,客户端调用和Service回调,以及一些需要注意的细节

前言

  • 模拟一个简单的音乐播放demo(未实现真实的音乐播放器),使用AIDLService进行跨进程通讯,并总结。
  • 本文资料来源网络公开资源,并根据个人实践纯手打整理,如有错误请随时指出。
  • 本文主要用于个人积累及分享,文中可能引用其他技术大牛文章(仅引用链接不转载),如有侵权请告知必妥善处理。

AIDL介绍

  • AIDLJava比较类似,但更简单,因为AIDL只是作为两个原本互不影响的进程之间相互“沟通”的工具,基本只做数据的传递,不使用AIDL做逻辑处理。

  • AIDL支持的数据类型有:

    1. 基本类型:byte、short、int、long、float、double、boolean、char
    2. Java常用数据类型:String、CharSequence、List、Map
    3. .aidl文件的interface类型(接口类,用于进程间互相调用、回调)
    4. .aidl文件的parcelable类型(数据类,需要配合同包路径的.java数据类,这个.java数据类需要实现Parcelable序列化)

    特别说明
    String、CharSequence类型的参数的定向tag默认为in,并且只能是 in

  • AIDL文件一般有两大类:数据类,接口类(包含两类:交互接口,回调接口)

AIDL使用和配置

  1. 自定义AIDL文件的存放路径,如 src/main/java/ 包下任意位置,创建一个aidl文件夹

    并任意创建一个.aidl文件

  2. build.gradle中配置 aidl.srcDirs

  3. 在aidl文件夹中,任意创建一个aidl文件,并点击Android Studio工具栏“Build–>Make Project”或“clean Project”,等候编译完成,将会在 app\build\generated\source\aidl\debug\ 下自动生成对映的.java文件,此文件请勿做任何修改

至此,AIDL文件已可正常编译

示例分析

模拟一个简单的音乐播放demo,但这里不会真正得实现音乐播放功能,只是实现App进程(客户端)和单独的Service进程(播放服务端)之间的交互和回调。

AIDL

AIDL数据文件

在aidl包中创建一个数据文件Music.aidl
package com.sp.personal.music.aidl;

parcelable Music;
在同包内创建Music.java文件,Music类必须implements Parcelable。
  • 目前比较好用的Parcelable自动生成代码插件可通过“File–>Settings–>Plugins–>Browse repositories…”,输入搜索“Android Parcelable code generator”,安装即可。
  • 这里要注意的是,Music.java需要和Music.aidl在同一个包里
  • Music.java文件就不贴代码了,比较通常的一个数据类。

AIDL接口以及回调文件

AIDL回调文件

Service进程(播放服务端)回调的接口
定义一个PlayCallback.aidl文件

package com.sp.personal.music.aidl;

interface PlayCallback {
    //state:1,onPrepare;2,onStart;3,onPlaying;4,onPause;5,onEnd
    void stateNow(int state, String songId, int position);

}
AIDL接口文件

供App进程(客户端)调用的接口AIDLPlayService.aidl

package com.sp.personal.music.aidl;
import com.sp.personal.music.aidl.Music;
import com.sp.personal.music.aidl.PlayCallback;

interface AIDLPlayService{

    void prepare(in Music music);
    void start();
    void seekTo(String id, int position);
    void pause(String id);

    void addPlayCallback(PlayCallback playCallback);
    void removePlayCallback(PlayCallback playCallback);
}

这里需要注意的是:
* import,是必须的,无论是否在同一个包中
* 自定义类型需要指定其参数“定向”,这里Music music实例由App进程(客户端)传递过来,则指定为in

服务端(Service)

用于播放音乐的单独进程的Service,以及绑定AIDL接口到Service

提供一个Service,并指定这个Service在单独的进程执行:

<service
    android:name=".code.playctrl.PlayService"
    android:process=":remote"/>

(使用remote是自定义的,可以随意。冒号:这个前缀是必须的,它将把这个名字附加到你的包所运行的标准进程名字的后面作为新的进程名称)

PlayService.java
public class PlayService extends Service {

    @Override
    public void onCreate() {
        super.onCreate();
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        //将AIDLPlayService作为IBinder返回
        return mIBinder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        return super.onUnbind(intent);
    }

    @Override
    public void onRebind(Intent intent) {
        super.onRebind(intent);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }


    //创建一个RemoteCallbackList用来管理PlayCallback,用来通过Broadcast发送回调到客户端
    RemoteCallbackList<PlayCallback> remoteCallbackList = new RemoteCallbackList<>();

    //服务端用来接收客户端的请求
    AIDLPlayService.Stub mIBinder = new AIDLPlayService.Stub() {

        @Override
        public void prepare(Music music) throws RemoteException {
            //接收到准备播放的音乐对象
        }

        @Override
        public void start() throws RemoteException {
            //接收到客户端的请求,开始播放
        }

        @Override
        public void pause(String id) throws RemoteException {
            //接收到客户端的请求,暂停播放

            //暂停播放的测试:暂停后客户端将收到已暂停的回调通知
            pausePlay(id);
        }

        @Override
        public void addPlayCallback(PlayCallback playCallback) throws RemoteException {
            if (playCallback != null) {
                //客户端设置回调,这里使用RemoteCallbackList注册这个PlayCallback
                remoteCallbackList.register(playCallback);
            }
        }

        @Override
        public void removePlayCallback(PlayCallback playCallback) throws RemoteException {
            if (playCallback != null) {
                //客户端注销回调,这里使用RemoteCallbackList注销这个PlayCallback
                remoteCallbackList.unregister(playCallback);
            }
        }


        @Override
        public void seekTo(String id, int position) throws RemoteException {
            //接收到客户端的请求,播放到指定位置(或时间)
        }
    };

    //举例,测试回调,将在客户端代码中打印
    private void pausePlay(String id) {
        //假如已知目前播放的位置是:position=100(秒)
        int position = 100;
        playCallback(4, id, position);
    }

    /**
     *调用回调到客户端
     * @param state    1,onPrepare;2,onStart;3,onPlaying;4,onPause;5,onEnd
     * @param songId
     * @param position
     */
    private void playCallback(int state, String songId, int position) {
        //准备开始调用最近注册的Callback并且返回,已注册playCallback数(在客户端注册),目前是1个
        int N = remoteCallbackList.beginBroadcast();
        for (int i = 0; i < N; i++) {
            try {
                remoteCallbackList.getBroadcastItem(i).stateNow(state, songId, position);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        //必须的,和beginBroadcast()成对出现
        mPlayCallback.finishBroadcast();
    }
}

客户端(App进程)

Application中启动Service,并获取到AIDLPlayService对象

public class PMApplication extends Application {

    public static AIDLPlayService aidlPlayService;

    @Override
    public void onCreate() {
        super.onCreate();

        initPlayService();
    }


    private void initPlayService() {
        Intent intent = new Intent(this, PlayService.class);
        this.startService(intent);
        this.bindService(intent, new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                aidlPlayService = AIDLPlayService.Stub.asInterface(service);

            }

            @Override
            public void onServiceDisconnected(ComponentName name) {
                aidlPlayService = null;
            }
        }, Context.BIND_AUTO_CREATE);
    }
}

Activity(或其他View)添加回调,并调用pause方法测试回调

try {
    PMApplication.aidlPlayService.addPlayCallback(new PlayCallback.Stub() {

        @Override
        public void stateNow(int state, String songId, int position) throws RemoteException {
            //测试是否收到回调,打印
            Log.d("Play","state=" + state + ";songId=" + songId + ";position=" + position);
        }
    });

    PMApplication.aidlPlayService.pause("123456");
} catch (RemoteException e) {
    e.printStackTrace();
}
  • 切记addPlayCallback只能调用一次,如不需要使用该回调时,需要注销这个回调removePlayCallback,否则可能导致回调的信息混乱或之前已完成的回调信息再次回调。其中的原因是RemoteCallbackList中的消息机制,允许注册多个回调并存(请见RemoteCallbackList源码)。

结语

以上经过测试,服务端可以收到客户端的请求,并可以回调给客户端数据,已完成了基本的跨进程AIDL数据交互。如一般的音乐播放器完全可以按照这样的交互流程进行处理。

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值