Binder : AIDL

Binder AIDL的使用

参考Demo:https://github.com/gqq519/BinderAIDL

  • Binder是Android的一个类,实现了IBinder接口
  • IPC角度来说,Binder是Android的一种跨进程通信方式,可以理解为一种虚拟的物理设备,设备驱动是/dev/binder。
  • 从Framework角度说,Binder是ServiceManager连接各种Manager(ActivityManager、WindowManager等)和相应的ManagerService的桥梁。
  • 从应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回服务端业务调用的Binder对象,客户端由此获取服务端提供的服务或数据。服务包括普通服务和基于AIDL的服务。

Android中,Binder主要用于Service中,包括AIDL和Messenger,普通Service不涉及进程间通信,不涉及Binder的核心,而Messenger底层其实也是AIDL实现的,所以拿AIDL来了解Binder的工作机制。

AIDL简述

AIDL:Android Interface Definition Language,通过编写aidl文件,系统会编译生成Binder接口,用于进程间通信。

AIDL支持的数据格式:

  • Java的基本数据类型
  • String和CharSequence
  • List和Map:
    1. 元素必须是 AIDL 支持的数据类型
    2. 具体的类里则必须是 ArrayList 或者 HashMap
  • 其他AIDL生成的接口
  • 实现Parcelable的类
创建AIDL示例

1. 创建工程

2. 创建要操作的实体类,需要实现Parcelable接口,跨进程使用

public class User implements Parcelable {

    private int id;
    private String name;

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    // 反序列化方法
    protected User(Parcel in) {
        id = in.readInt();
        name = in.readString();
    }

    // 序列化
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(id);
        dest.writeString(name);
    }

    // 内容描述
    @Override
    public int describeContents() {
        // 一般返回0,另一个特殊返回CONTENTS_FILE_DESCRIPTOR,为有FileDescriptor,放入Parcelable需指定。。
        // 然而。。好像并没有什么用,所以返回0就好了
        return 0;
    }

    // 反序列化
    public static final Creator<User> CREATOR = new Creator<User>() {
        @Override
        public User createFromParcel(Parcel in) {
            // 反序列化对象
            return new User(in);
        }

        @Override
        public User[] newArray(int size) {
            // 反序列化数组
            return new User[size];
        }
    };
}

3. 创建实体类的映射aidl文件

右键新建AIDL文件User.aidl(名称与实体类保持一致),会在main下生成aidl目录,包名与java包名一致。

User.aidl文件为实体类的映射文件,需要声明映射的实体类和类型:

// User.aidl
// 包名与实体类包名一致
package com.gqq.binderaidl;

parcelable User;

4. 创建操作接口aidl文件

在aidl目录的包名下创建AIDL文件IUserManager.aidl,内部是一个接口,主动实现了void basicTypes()方法,在接口中定义需要跨进程操作的接口:

// IUserManager.aidl
package com.gqq.binderaidl;

// Declare any non-default types here with import statements
import com.gqq.binderaidl.User;

interface IUserManager {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);

    void addUser(in User user);
    List<User> getUserList();
}

比如我们定义了两个方法用于操作:

  • addUser()添加User
  • getUserList()获取用户列表

注意:

  • 定义的Parcelable实体类型,需要导入它的全路径,比如User,需要导入import com.gqq.binderaidl.User;
  • 方法参数中,除了基本数据类型外都需要标上类型:in(输入), out(输出), inout(输入输出)

5. Make Project,生成Binder的java文件

上述操作完成后,点击Build -> Make Project,完成后可以在build/generated/source/aidl/packageName/下找到生成的java文件。

IUserManager的大致预览:

生成的代码主要给客户端使用,后续再介绍里面的内容吧~

至此,通信的媒介我们已经完成了。

注意:Make Project 出现错误可能的原因

  1. 映射的aidl:User.aidl和实体类User,名称要保持一致,包名要保持一致。
  2. 定义的AIDL接口文件的方法参数需要标上类型。
  3. AIDL接口文件中导入实体类的包名。
编写服务端代码

1. 创建Service

在项目中创建Service,需要实现onBind()方法,返回值为IBinder,根据上述生成的IUserManager.java,内部Stub继承自Binder,所以,onBind() 的返回值设置为AIDL的接口的实例。

public class UserService extends Service {

    private List<User> users;

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        users = new ArrayList<>();
        // 返回AIDL生成的Binder实例
        return new UserServiceImpl();
    }

    // 创建AIDL生成的Binder实例
    public class UserServiceImpl extends IUserManager.Stub {

        // 实现的AIDL接口的方法
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

        }

        @Override
        public void addUser(User user) throws RemoteException {
            user.name += "-Service";
            users.add(user);
        }

        @Override
        public List<User> getUserList() throws RemoteException {
            return users;
        }
    }
}

2. 清单注册Service

Service创建好之后在清单文件注册:

        <service android:name=".UserService"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.gqq.binderaidl.IUserManager" />
            </intent-filter>
        </service>

至此,服务端工作已完成。

编写客户端代码

1. 新建Client工程

客户端可以做为一个单独的App,或者跟服务端在不同的进程都可。

2. 拷生成文件到Client工程

在客户端工程下,将服务端生成的AIDL的java文件以及实体类User.java 一起拷过来,更改client的目录与Server保持一致(拷过来会报错)。

3. 绑定服务

public class MainActivity extends AppCompatActivity {

    private IUserManager userManager;

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

        // 通过Server对外的接口操作数据并显示等
        final TextView textView = findViewById(R.id.tv_show);
        findViewById(R.id.btn_client).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (userManager == null) return;
                try {
                    userManager.addUser(new User(1, "gqq"));
                    List<User> userList = userManager.getUserList();
                    textView.setText(userList.toString());
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });

        Intent intent = new Intent();
        intent.setAction("com.gqq.binderaidl.IUserManager");
        // Android 5.0 以后必须显式启动,参考:https://blog.csdn.net/vrix/article/details/45289207
        intent.setPackage("com.gqq.binderaidl");
        bindService(intent, connection, Context.BIND_AUTO_CREATE);
    }

    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 绑定成功之后获取服务对象
            userManager = IUserManager.Stub.asInterface(service);
            Toast.makeText(MainActivity.this, "onServiceConnected", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            userManager = null;
        }
    };

运行

首先运行服务端,再运行客户端,就可以通过客户端操作了。

升级:设置监听

假如现在用户希望服务端当有新用户时实时的告诉我,这个是一个典型的观察者模式,在实际中也用到很多。

1. 提供AIDL接口,作为监听

提供一个AIDL接口,客户端需要实现这个接口并注册提醒的功能,也可以随时取消这个提醒。使用AIDL接口是因为AIDL中无法使用普通接口。

服务端创建一个aidl文件:

    package com.gqq.binderaidl;

    import com.gqq.binderaidl.User;

    // Declare any non-default types here with import statements

    interface IOnNewUserArrivedListener {
        /**
         * Demonstrates some basic types that you can use as parameters
         * and return values in AIDL.
         */
        void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
                double aDouble, String aString);
        void onNewUserArrived(in User user);
    }

2. 在原用的IUserManager.aidl接口中添加注册和取消注册的方法

添加注册和反注册的方法,以便于客户端可以监听。

    package com.gqq.binderaidl;

    // Declare any non-default types here with import statements
    import com.gqq.binderaidl.User;
    import com.gqq.binderaidl.IOnNewUserArrivedListener;

    interface IUserManager {
        /**
         * Demonstrates some basic types that you can use as parameters
         * and return values in AIDL.
         */
        void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
                double aDouble, String aString);

        void addUser(in User user);
        List<User> getUserList();
        void registerListener(IOnNewUserArrivedListener listener);
        void unregisterListener(IOnNewUserArrivedListener listener);
    }

写完make project。

3. 完善服务端的Service

主要是实现Service中的IUserManager.Stub的实现,因为新增了两个方法。另外模拟场景:开启线程,每隔5s新增一个用户并通知客户端。

    // ------------定义的变量---------------    
        private List<IOnNewUserArrivedListener> listeners = new ArrayList<>();
        private boolean isServiceDestoryed = false;

    // ------------重写方法,开启工作线程---------
        @Override
        public void onCreate() {
            super.onCreate();
            new Thread(new ServiceWorker()).start();

        }

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

    // -------------工作的线程-----------------
        public class ServiceWorker implements Runnable {
            @Override
            public void run() {
                while (!isServiceDestoryed) {
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    int userId = users.size() + 1;
                    User user = new User(userId, "new User:"+userId);
                    try {
                        onNewUserArrived(user);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

    private void onNewUserArrived(User user) throws RemoteException {
            users.add(user);
            for (int i = 0; i < listeners.size(); i++) {
                IOnNewUserArrivedListener onNewUserArrivedListener = listeners.get(i);
                onNewUserArrivedListener.onNewUserArrived(user);
            }
        }

    // --------------重写的方法实现------------------
        @Override
        public void registerListener(IOnNewUserArrivedListener listener) throws RemoteException {

            if (!listeners.contains(listener)) {
                listeners.add(listener);
            } else {
                Log.i("TAG", "listener already exists");
            }
        }

        @Override
        public void unregisterListener(IOnNewUserArrivedListener listener) throws RemoteException {
            if (listeners.contains(listener)) {
                listeners.remove(listener);
            } else {
                Log.i("TAG", "not found");
            }
        }

服务端的修改已经完成。

4. 客户端注册监听并处理接收

把服务端新加的aidl文件生成的java文件复制到客户端项目中,客户端注册监听,并在页面退出时解除注册。同时在onUserArrived方法中接收到数据后要回到主线程显示等,所以借助Handler实现。

        private ServiceConnection connection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                Toast.makeText(MainActivity.this, "onServiceConnected", Toast.LENGTH_SHORT).show();
                userManager = IUserManager.Stub.asInterface(service);
                try {

                    // 注册监听
                    userManager.registerListener(listener);

                    service.linkToDeath(deathRecipient, 0);
                } catch (RemoteException e) {
                    Log.i("TAG", "RemoteException");
                    e.printStackTrace();
                }
            }

    // 注册的监听
            private IOnNewUserArrivedListener listener = new IOnNewUserArrivedListener.Stub() {
                @Override
                public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
                }

                @Override
                public void onNewUserArrived(User user) throws RemoteException {
                    handler.obtainMessage(MESSAGE_WHAT_ARRIVED, user).sendToTarget();
                }
            };

    // Handler的处理
            private Handler handler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    switch (msg.what) {
                        case MESSAGE_WHAT_ARRIVED:
                            Log.i("TAG", "received new user" + msg.obj);
                            List<User> userList = null;
                            try {
                                userList = userManager.getUserList();
                                textView.setText(userList.toString());
                            } catch (RemoteException e) {
                                e.printStackTrace();
                            }
                            break;
                        default:
                            super.handleMessage(msg);
                    }
                }
            };

    // 取消监听
            @Override
            protected void onDestroy() {
                if (userManager != null && userManager.asBinder().isBinderAlive()) {
                    try {
                        userManager.unregisterListener(listener);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
                unbindService(connection);
                super.onDestroy();
            }
升级:取消监听

在上述的场景设置中,当退出客户端页面,取消注册,通过日志可以查看到,当取消注册的时候会发现unregisterListener中remove的时候发生了异常,因为在多线程中,Binder会把客户端传递过来的对象重新转化并生成一个新的对象。对象是不能跨进程传递的,我们跨进程传递的时候都是把对象进行序列化和反序列化。那如何实现取消注册呢?需要借助RemoteCallbackList

RemoteCallbackList是系统专门提供的用于删除跨进程listener的接口,可以从源码中看出:

    public class RemoteCallbackList<E extends IInterface> 

    // Callback中封装了真正的远程listener
    ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>();

跨进程的对象虽然不一样,但Binder是同一个,利用Binder来实现,客户端解除注册的时候遍历所有的服务端的listener,将具有相同Binder 的listener删除即可,这个是RemoteCallbackList所做的事情。同时,还有一个功能,当客户端进行终止后,它会自动解除客户端所注册的listener。

代码改善

利用RemoteCallbackList实现解除注册:用RemoteCallbackList代替List<>

    private RemoteCallbackList<IOnNewUserArrivedListener> listeners = new RemoteCallbackList<>();

修改注册和解注册的方法:

        @Override
        public void registerListener(IOnNewUserArrivedListener listener) throws RemoteException {
            listeners.register(listener);

            // 在此处打印下注册的监听的数量
            int i = listeners.beginBroadcast();
            listeners.finishBroadcast();
            Log.i("TAG", "registerListener listener size:"+ i);
        }

        @Override
        public void unregisterListener(IOnNewUserArrivedListener listener) throws RemoteException {
            listeners.unregister(listener);

            // 注意:在此处打印下注册的监听的数量
            int i = listeners.beginBroadcast();
            listeners.finishBroadcast();
            Log.i("TAG", "unregisterListener listener size:"+ i);
        }

修改onNewUserArrived方法:

        private void onNewUserArrived(User user) throws RemoteException {
            users.add(user);
            int size = listeners.beginBroadcast();
            for (int i = 0; i < size; i++) {
                IOnNewUserArrivedListener onNewUserArrivedListener = listeners.getBroadcastItem(i);
                if (onNewUserArrivedListener != null) {
                    onNewUserArrivedListener.onNewUserArrived(user);
                }
            }
            listeners.finishBroadcast();
        }

注意:RemoteCallbackList并不是一个List,无法像操作List一样操作它,要像上述的方式一样去遍历它,其中beginBroadcast()finishBroadcast()必须配对使用,哪怕是想要获取RemoteCallbackList的元素个数。

升级:耗时处理

客户端调用服务端的方法,被调用的方法运行在服务端的Binder线程池中,同时客户端挂起。如果服务端的方法是耗时的操作,客户端在UI线程的话,就会导致ANR。客户端的onServiceConnected和onServiceDisconnected运行在UI线程中,不能直接在里面调用服务端的耗时方法。服务端的方法本身运行在Binder线程池中,可以做大量的耗时操作,不用再开启线程去进行异步操作。模拟一下耗时的操作:

    // 服务端模拟耗时操作的方法
        @Override
        public List<User> getUserList() throws RemoteException {
            SystemClock.sleep(5 * 1000);
            return users;
        }

客户端点击按钮去直接调用服务端的方法获取list数据,多次点击就会出现ANR,那么就需要把调用放到非UI线程,比如:

        findViewById(R.id.btn_client).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (userManager == null) return;
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            List<User> userList = userManager.getUserList();
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
        }

同样,我们不可以在服务端UI线程中调用客户端耗时的操作,另外AIDL接口方法都运行在Binder线程池中,访问UI需要切换线程。例如客户端的onNewUserArrived方法。

升级:服务重连

Binder在通信中可能意外死亡,往往由于服务端进程意外停止,需要重新连接服务。

1. Binder设置DeathRecipient监听

设置DeathRecipient监听,当Binder死亡时,会收到binderDied方法的回调,可以在binderDied方法中重连服务。

2. 在OnServiceDisconnected中重连远程服务

区别:onServiceDisconnected在客户端UI线程被回调,binderDied在客户端的binder线程池中被回调,即binderDied中不能访问UI。

升级:权限验证

定义权限非本节重点:定义权限参考

首先在服务端的AndroidMenifest中声明所需权限

    <permission   
    android:name="com.gqq.binderaidl.permission.ACCESS_USER_SERVICE"
    android:protectionLevel="normal" />
    <uses-permission android:name="com.gqq.binderaidl.permission.ACCESS_USER_SERVICE" />

第一种方法:在onBind中进行验证(permission验证)

      @Override
        public IBinder onBind(Intent intent) {
            int check = checkCallingOrSelfPermission("com.gqq.binderaidl.permission.ACCESS_USER_SERVICE");
            if (check == PackageManager.PERMISSION_DENIED) {
                Log.i(TAG, "onbind check=" + check);
                return null;
            }
            return mBinder;
        }

第二种方法:在onTransact中进行验证(包名验证)

    public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws RemoteException {
                // 权限验证
                int check = checkCallingOrSelfPermission("com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE");
                if(check == PackageManager.PERMISSION_DENIED){
                    L.d("Binder 权限验证失败");
                    return false;
                }
                // 包名验证
                String packageName=null;
                String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
                if(packages!=null && packages.length>0){
                    packageName = packages[0];
                }
                if(!packageName.startsWith("com.ryg")){
                    L.d("包名验证失败");
                    return false;

                }
                return super.onTransact(code, data, reply, flags);
            };

客户端AndroidMenifest中声明:

<uses-permission android:name="com.gqq.binderaidl.permission.ACCESS_USER_SERVICE" />

参考Demo

总结

主要是AIDL的整体使用流程。

再贴一遍代码地址,欢迎指正!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值