Binder总结篇2-Binder使用

Binder总结篇2-Binder使用

在上一篇文章Binder总结篇1-Binder原理中,我们大概理解了Binder的运行原理,那么我们在什么样子的应用场景下会使用到Binder呢?

就我个人而言,是在IM系统开发当中使用到多进程开发,也就需要Binder来进行通信了,本文是编写实际的例子的,涉及到的点有:

  1. AIDL

包括支持的数据类型,定义以及使用等

  1. Service

包括一些启动,数据获取以及数据回传等。

本文的demo地址是:AndroidBinderSample

AIDL

关于AIDL的详细描述,可以看官网Android 接口定义语言 (AIDL)

他是Android的接口定义语言,用来具体实现Binder通信过程的数据传递,格式跟java的接口代码的编写差不多。 他是使用.aidl文件结尾,存放在man文件夹下的aidl文件夹下,当然你可以通过在gradle中配置aidl.srcDirs来指定。

AIDL支持的数据类型

  1. Java的基本类型,也包括String类型和CharSequence类型
  2. List 和Map,其中List和Map中的元素必须是AIDL支持的数据类型,而且在Server端必须使用ArrayList或者是HashMap来接收。
  3. 其他的AIDL生成的接口
  4. 实现了Parcelable接口的实体,可以看详细介绍Android中Parcelable的原理和使用方法

AIDL文件,总得来说,AIDL分为两类文件,一种是接口类型,就是需要被调用被实现的。一种是声明Parcelable数据,作用就是把对应的Java实现了Parcelable接口的类映射到AIDL中,然后被AIDL的接口文件引用,需要注意的是这个AIDL文件的包名需要与Java实现Parcelable文件对应的包名一致。 例如: 我们声明了一个JavaDomain,在包app.androidbinder.domain下,大致如下

package app.androidbinder.domain;
import android.os.Parcel;
import android.os.Parcelable;
/**
 * 作者:黎伟杰 on 2018/8/13.
 * 邮箱:liweijie@qq.com
 * description:
 * update by:
 * update day:
 *
 * @author liweijie
 */
public class UserInfo implements Parcelable {
      //省略代码
    public UserInfo() {
    }
    protected UserInfo(Parcel in) {
          //省略代码
    }
    public static final Creator<UserInfo> CREATOR = new Creator<UserInfo>() {
        @Override
        public UserInfo createFromParcel(Parcel in) {
            return new UserInfo(in);
        }
        @Override
        public UserInfo[] newArray(int size) {
            return new UserInfo[size];
        }
    };
    @Override
    public int describeContents() {
        return 0;
    }
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        //省略代码
    }
    public void readFromParcel(Parcel in) {
        //省略代码
    }
}
复制代码

然后在AIDL文件下也需要建立对应的包名,然后编写UserInfo.aidl,如下:

package app.androidbinder.domain;
/**
 * 作者:黎伟杰 on 2018/8/14.
 * 邮箱:liweijie@qq.com
 * description:
 * update by:
 * update day:
 *
 * @author liweijie
 */
parcelable UserInfo;
复制代码

这样子,在别的AIDL中就可以引用这个UserInfo了。 定义接口类型的AIDL如下:

// UserService.aidl
package app.androidbinder;
import java.util.List;
import app.androidbinder.domain.UserInfo;
// Declare any non-default types here with import statements
interface UserService {
    String getUserName(int userId);
    void saveUser(in UserInfo param);
    UserInfo getUserInfo(int userId);
    List<UserInfo> queryUser();
    //for in out inout
    UserInfo handleIn(in UserInfo info);
    UserInfo handleOu(out UserInfo info);
    UserInfo handleInOut(inout UserInfo info);
}
复制代码

这就是两种AIDL文件类型以及对应的大致编写。

in、out、inout

在官方文档中支出,所有的非原语参数需要指示数据的方向标记,可以是in、out、inout。默认的原语是in,不能是其他流向。 这里指定的非原语是指:除了Java的基本类型外的其他参数,也就是对象。我们在AIDL使用的时候需要知道这个参数的流向。 那什么是数据的方向标记呢? 首先,数据的方向标记是针对客户端中的那个传入的方法参数而言。数据流向的标识符不能使用在返回参数上,只能使用在方法参数上面。

  1. in:他表示的是这个参数只能从客户端流向服务端,比如客户端传递了一个User对象给服务端,服务端会收到一个完整的User对象,然后假如在服务端对这个对象进行操作,那么这个改变是不会反映到客户端的,这个流向也就是只能从客户端到服务端。
  2. out:他表示,当客户端传递参数到服务端的时候,服务端将会收到一个空的对象,假如服务端对该对象进行操作,将会反映到客户端。比如,客户端传递一个User对象到服务端,服务端接收到的是一个空的User对象(不是null,只是有点像new一个User对象)。当服务端对这个User对象进行改变的时候,他的值变化将会反映到客户端。
  3. inout,它具有这二者的功能,也就是客户端传递对象到服务端,可以接收到完整的对象,同时服务端改变对象,也会反映到客户端。 总结来说,in类似于传值,out类似于传引用,只是out的引用值到了服务端为空,inout则具有二者的功能,默认的是in。

Service

多进程之间的通信离不开的是Service,Android四大组件之一,这里不过多的赘述,只是需要知道我们在多进程通信当中,服务端最少提供一个Service来,在onBind()方法中返回实现AIDL接口的IBinder对象。然后客户端通过bindService()来连接,通过在ServiceConnectiononServiceConnected方法,通过调用AIDL 生成的文件中的StubasInterface()来在客户端获取服务实例,继而调用服务端的方法。需要注意,在跨app的进程调用中,对外暴露的Service需要在清单文件中把android:exported设置为true。一般而言,我们还会配置一些fillter来进行过滤。 关于如何使用Service以及他的一些生命周期,一些方法区别(比如startService和bindService)请自己另外查阅文档,这里就不描述了。以下是本文Demo中的实例,使用的还是上面的AIDL,使用之前确保成功编译出对应的aidl生成文件:

服务端的Service

package app.androidbinder2.services;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
import app.androidbinder.UserService;
import app.androidbinder.domain.UserInfo;
/**
 * 作者:黎伟杰 on 2018/8/12.
 * 邮箱:liweijie@qq.com
 * description:
 * update by:
 * update day:
 *
 * @author liweijie
 */
public class App2Service extends Service {
    /**
     * 模拟一些测试数据
     */
    private List<UserInfo> data = new ArrayList<>();
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        data.clear();
        data.add(new UserInfo(1, "A", 20));
        data.add(new UserInfo(2, "B", 30));
        data.add(new UserInfo(3, "C", 40));
        data.add(new UserInfo(4, "D", 50));
        return userService;
    }
    private Binder userService = new UserService.Stub() {
        @Override
        public String getUserName(int userId) throws RemoteException {
            for (UserInfo item : data) {
                if (item.getUserId() == userId) {
                    return item.getUserName();
                }
            }
            return null;
        }
        @Override
        public void saveUser(UserInfo param) throws RemoteException {
            data.add(param);
        }
        @Override
        public UserInfo getUserInfo(int userId) throws RemoteException {
            for (UserInfo item : data) {
                if (item.getUserId() == userId) {
                    return item;
                }
            }
            return null;
        }
        @Override
        public List<UserInfo> queryUser() throws RemoteException {
            return data;
        }
        @Override
        public UserInfo handleIn(UserInfo info) throws RemoteException {
            info.setUserName("嘿嘿嘿");
            return info;
        }
        @Override
        public UserInfo handleOu(UserInfo info) throws RemoteException {
            if (info == null) {
                Log.e("App2Service", "UserInfi server is null");
                info = new UserInfo();
            }
            info.setUserName("嘻嘻嘻");
            return info;
        }
        @Override
        public UserInfo handleInOut(UserInfo info) throws RemoteException {
            info.setUserName("哈哈哈");
            return info;
        }
    };
}
复制代码

清单文件的配置是:

     <service
            android:name=".services.App2Service"
            android:exported="true">
            <intent-filter>
                <action android:name="app.androidbinder2.user_service.action"/>
            </intent-filter>
        </service>
复制代码

客户端

主要的代码如下:

//...省略代码
    private UserService userServerService;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        //...省略代码
    }
    ServiceConnection userConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            showToast("连接成功");
            userServerService = UserService.Stub.asInterface(service);
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
            showToast("与服务器断开连接");
        }
    };
    public void startServer(View view) {
        Intent startServer = new Intent();
        startServer.setAction("app.androidbinder2.user_service.action");
        startServer.setComponent(new ComponentName("app.androidbinder2", "app.androidbinder2.services.App2Service"));
        bindService(startServer, userConnection, Service.BIND_AUTO_CREATE);
    }
    public void getUserName(View view) {
        try {
        //实际调用
            String name = userServerService.getUserName(1);
            showToast(name);
        } catch (Exception e) {
            e.printStackTrace();
            showToast("出现错误");
        }
    }
//...省略代码
复制代码

AIDL的实际使用

我们就以上面定义的AIDL文件作为我们的例子。我们需要实现的一个需求是两个App之间通过AIDL实现数据互通,至于一个App之间多进程也是类似,后面再做例子。 其实上面已经是把例子的代码罗列的差不多了。这里列一下跨APP进程调用demo一般的步骤:

  1. 假如有需要自定义的数据需要通过实现Parcelable传递,那么先编写这种java文件
  2. 在客户单编写一份引入上面编写的Parcelable实体的AIDL文件,再编写一份AIDL接口(一般而言会把所有跨进程的接口编写在一起)
  3. 通过编译,当没有问题之后,需要把上面的需要跨进程的Parcelable的Java文件以及AIDL文件复制到Server端,包名需要一致,然后编译。
  4. 在Server端,新建继承Service的服务实现,然后在onBinder()中,返回实现我们编译生成的AIDL接口文件的Stub子类。
  5. 在Client端,通过ServiceConnection获取到Server端的Binder实例,进行调用。这里主要用到的是Stub的静态方法asInterface()。客户端就可以通过这个实例进行跨进程调用了。

同一个APP中跨进程通信

我们其实在实际中,使用最多的还是这一种情况,目前我所接触到的就是自己设计的IM系统中使用到。 他其实跟跨APP没有什么区别,同一个APP中跨进程,他只是需要一份AIDL文件即可。因为系统分配给一个进程的内存是有限的,而且默认的主进程处理的事务较多,在保活方面,在数据接收处理方面,使用多进程是有优势的。 Android的APP启用多进程方式是在清单文件中添加android:process,设置该组件所处的进程名称既可。 比如

android:process=":local"//他设置所处的进程名称是包名+local,他表示的是该组件处于自己的私有进程,其他进程的组件不可以跑在同一个进程中。
android:process="com.app.sample.local"//这里的name就是进程名称。这种则是声明他是全局进程,其他应用的组件可以通过ShareUID来达到跟他位于同一个进程运行。
复制代码

以上就是AIDL的基本使用,当然我们在实际使用中,会更加的复杂,比如我们会在IM进程中开启多个线程来处理消息,接受消息,轮训消息等,而且需要通过回调的方式主动通知主进程,一般需要IM端一个Service,主进程端一个Service等,这些我们后面在做讲述。

本文如有错误或侵权还请指出或联系删除,谢谢。

参考文章

Android 接口定义语言 (AIDL)

详细介绍Android中Parcelable的原理和使用方法

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值