Android Binder进阶

Binder用法还很多

Android Binder分析Android Studio中AIDL的使用详解Binder类已经对Binder的基础和使用进行了比较详细的介绍,但是Binder的使用远不止这些,比如用Binder实现双工通信、Binder连接池和Binder的权限等等,接下来我们就开始介绍一些更深入的使用

Binder线程池

客户端在使用Binder可以调用服务端的方法,这里面有一些隐含的问题,如果我们服务端的方法是一个耗时的操作,那么对于我们客户端和服务端都存在风险,对于服务端,如果有很多客户端都来调用它的方法,那么是否会造成ANR呢?多个客户端调用,是否会有同步问题?如果客户端在UI线程中调用的这个是耗时方法,那么是不是它也会造成ANR?这些问题都是真实存在的,首先第一个问题不会出现,因为服务端所有这些被调方法都是在一个线程池中被执行的,不在服务端的UI线程中,因此服务端不会ANR,但是服务端会有同步问题,因此我们提供的服务端接口方法应该注意同步问题。客户端会ANR很容易解决,就是我们不要在UI线程中调用Binder的方法就可以避免了。所以记住一个点:服务端的Binder方法都是异步调用的

权限问题

上面我们已经提到,Binder可以被多个客户端获取,因此我们应该在客户端调用Binder方法时进行权限的验证,有以下三种方式:

permission验证

在服务端的AndroidManifest.xml配置权限并使用:

<permission
    android:name="com.jucongyuan.permission.GET_STR"
    android:protectionLevel="normal" />

<uses-permission android:name="com.jucongyuan.permission.GET_STR" />

在服务端Service中onBind方法返回IBinder时,进行权限验证,如果没有该权限就返回null:

@Nullable
@Override
public IBinder onBind(Intent intent) {
    int check = checkCallingOrSelfPermission("com.jucongyuan.permission.GET_STR");
    if (check == PackageManager.PERMISSION_DENIED) {
        return null;
    }
    return stub;
}

这个时候,如果客户端什么都不做,那么它就无法连接到我们的服务,如果需要正确的连接,必须在客户端的AndroidManifest.xml中使用相应的权限声明:

<uses-permission android:name="com.jucongyuan.permission.GET_STR" />

这种做法还可以精简一下,就是不用在onBind()方法中写验证代码,直接给manifest中的service使用permission,如果客户端中没有进行上面一样的权限声明,调用bindService方法时会抛出异常

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.jucongyuan.aidlserver">

    <permission
        android:name="com.jucongyuan.permission.GET_STR"
        android:protectionLevel="normal" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <activity
            android:name=".activity.ServerActivity"
            android:label="@string/app_name"
            android:launchMode="standard">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service
            android:name=".service.ServerService"
            android:permission="com.jucongyuan.permission.GET_STR">
            <intent-filter>
                <action android:name="com.jucongyuan.IConnect" />"
            </intent-filter>
        </service>

    </application>

</manifest>

onTransact方法中验证

这个方法需要我们自己实现我们Binder类,而不使用AIDL,在onTransact方法中,可以通过Binder的getCallingUid()获取到客户端Uid,然后通过Uid获取到客户端的包名,可以通过包名来验证,如果包名不符合我们的规则,那么onTransact就返回false

服务端主动和客户端通信

我们知道,客户端虽然可以通过像调用普通方法一样调用服务端的方法(可以传参,也可以有返回值),但是客户端除了主动调用服务端获取数据,服务端不能主动推送数据,可能大家会想到让客户端也成为一个服务端,然后让它被连接后通信,这是一种做法,但不是一种好的做法,好的做法是使用观察者模式,我们让客户端作为一个观察者,通过服务端的提供注册方法注册进去,然后服务端有变化时,可以通知客户端,这就实现了双工通信。首先,客户端和服务端需要同时创建一个接口,这个接口不能是普通的接口,而是一个AIDL接口:

// ChangeListener.aidl
package com.jucongyuan;

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

interface ChangeListener {
    void change(in String str);
}

在IConnect.aidl中增加方法:

// IConnect.aidl
package com.jucongyuan;

import com.jucongyuan.Student;
import com.jucongyuan.ChangeListener;

interface IConnect {
    Student getStr(in Student s);
    void registerListener(in ChangeListener changeListener);
    void unregisterListener(in ChangeListener changeListener);
}

客户端和服务端都需要做上述变化,对于服务端另外的变化:

public class ServerService extends Service {

    private String str;
    private RemoteCallbackList<ChangeListener> changeListenerRemoteCallbackList = new RemoteCallbackList<>();

    IConnect.Stub stub = new IConnect.Stub() {
        @Override
        public Student getStr(Student s) throws RemoteException {
            s.setNumber(str);
            return s;
        }

        @Override
        public void registerListener(ChangeListener changeListener) throws RemoteException {
            changeListenerRemoteCallbackList.register(changeListener);
        }

        @Override
        public void unregisterListener(ChangeListener changeListener) throws RemoteException {
            changeListenerRemoteCallbackList.unregister(changeListener);
        }
    };

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        str = intent.getStringExtra("str");
        final int n = changeListenerRemoteCallbackList.beginBroadcast();
        for (int i = 0; i < n; i++) {
            ChangeListener changeListener = changeListenerRemoteCallbackList.getBroadcastItem(i);
            try {
                changeListener.change(str);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        changeListenerRemoteCallbackList.finishBroadcast();
        return Service.START_NOT_STICKY;
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return stub;
    }

}

在服务端中新增实现了registerListenerunregisterListener方法,通过RemoteCallbackList来管理接口,为什么要用这个类而不用List,直接使用List是不行的,因为在unregister时,如果是普通List,应该使用的是remove方法,而这个方法不能将changeListener移除,原因很简单,changeListener对象是客户端的,在服务端存在的changeListener是另一个对象,这点很好理解,所以我们要使用RemoteCallbackListRemoteCallbackList是Android专门提供的跨进程删除Listener的类,RemoteCallbackList的用法也比较特殊,上面onStartCommand方法中有详细使用过程,而对于客户端:

public class ClientActivity extends Activity {

    private TextView tv;
    private IConnect connect;

    private ChangeListener changeListener = new ChangeListener.Stub() {
        @Override
        public void change(String str) throws RemoteException {
            Log.e("change", "学号是" + str);
        }
    };

    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            try {
                if (service == null) {
                    return;
                }
                connect = IConnect.Stub.asInterface(service);
                connect.registerListener(changeListener);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

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

    @Override
    protected void onDestroy() {
        try {
            connect.unregisterListener(changeListener);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        super.onDestroy();
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_client);
        tv = (TextView) findViewById(R.id.tv);
        Intent intent = new Intent();
        intent.setAction(IConnect.class.getName());
        intent.setClassName("com.jucongyuan.aidlserver", "com.jucongyuan.aidlserver.service.ServerService");
        bindService(intent, conn, Context.BIND_AUTO_CREATE);
    }
}

连接上时,调用注册方法注册一个监听接口,然后在onDestroy方法中取消注册,这样我们服务端点击按钮时,就可以主动将数据传给客户端了

客户端知晓服务端状态

在服务端进程中止时,我们的客户端怎么知道呢?有三种方法:

  1. 通过Binder的isBinderAlive方法来判断
  2. 通过ServiceConnection中回调方法onServiceDisconnected来知晓
  3. 创建一个IBinder.DeathRecipient对象,在客户端中连接成功后调用service.linkToDeath(deathRecipient, 0)来获取状态,如果服务端进程中止了,IBinder.DeathRecipient中的binderDied方法会被回调

2和3有什么区别呢?2中的回调是在UI线程被调用的,而3中的方法是异步调用的

Binder连接池

现在对Binder的使用已经上升到一定的高度了,但是还没有完,现在来讨论一个实际的运用需求,如果现在程序不同的模块都有自己Binder,那么服务端是不是需要多个Service来返回我们的Binder呢?当然不是,好的方案就是使用Binder连接池,只用一个Service,提供一个统一的Binder实现,用来返回不同Binder,服务端和客户端制定一种规则,通过这个规则返回不同的Binder,比如一个Binder对应一个字符串,这样就可以实现Binder连接池的功能了

注意:

上述例子的代码都是在这里面Android Studio中AIDL的使用的代码上进行修改的

坚持原创分享,您的支持将鼓励我不断前行!