replugin宿主与插件通信小结

42 篇文章 1 订阅

近来replugin开发中遇到宿主和插件间需要通信的情形,思来只有进程间通信(IPC)才是比较好的宿主与插件的通信方式。而Android进程间通信主要有2种方式:Messenger和AIDL。

AIDL(Android Interface Definition Language)是Android接口定义语言。AIDL设计用于实现Android进程间通信。Android中每个进程都拥有自己的Dalvik虚拟机(4.4之后是ART虚拟机),以及独立的内存区域,通过AIDL制定的规则,使不同进程可以进行数据交流。

宿主与插件间通信又可细分为,插件主动向宿主发起通信以及宿主主动向插件发起通信2种情况。为了模拟这2中可能的情况,同时对加深对Messenger和AIDL的理解。在宿主和插件中分别使用这两种方式,宿主作为服务端使用Messenger通信,以及插件作为服务端使用AIDL进行通信,分别查看2种方式的使用效果。

一、使用Messenger

1.1 Messenger服务端

1)服务端定义一个Service

<service android:name=".PluginComService"    android:enabled="true"    android:exported="true">  
<intent-filter>        
<action android:name="com.hk.daijun.messengerclient"></action>        
<category android:name="android.intent.category.DEFAULT" /> 
</intent-filter>

 在清单文件中配置Service,exported要配置为true,否则别的进程不能绑定该Service,在intent-filter中设置action。

2) 创建Messenger和Handler实例

创建Messenger和Handler实例,通过Handler来处理消息。

private static class MessengerHandler extends Handler {    
@Override    public void handleMessage(Message msg) {        Log.e(TAG, "handleMessage: what="+msg.what); 
  switch (msg.what) {            case 1001:                
  Log.e(TAG, "handleMessage: 服务端收到消息"+msg.getData().getString("msg"));                
  Log.e(TAG, "handleMessage: 服务端收到消息"+msg.getData().getString("msgJson"));                
  String msgJson = msg.getData().getString("msgJson");               
  Gson gson = new Gson();                
  PushMsgContent pushMsgContent = gson.fromJson(msgJson,PushMsgContent.class);         
  if (pushMsgContent != null) {                    
    Log.e(TAG, "handleMessage: PushMsgContent="+pushMsgContent.sendTime+"  "+pushMsgContent.msgContent.alarmTime); 
  }                
  Messenger mesgerReply = msg.replyTo;                
  Message msgReply = Message.obtain();                
  Bundle bundle = new Bundle();                
  bundle.putString("service_reply","hello,服务端已收到信息,想做什么");                
  msgReply.what = 2001;                
  msgReply.setData(bundle); 
  try {                    
    mesgerReply.send(msgReply);                
  } catch (RemoteException e) {                    
    Log.e(TAG, "handleMessage: 消息发给客户端,发送失败");                   
   e.printStackTrace();                
}                
break; 
}        
super.handleMessage(msg);    
}}
private Messenger messenger = new Messenger(new MessengerHandler());

 

消息传递时可以考虑使用字符串,然后服务端和客户端都用Gson进行解析,因为宿主是第三方开发,插件是自己开发,省去频繁变更接口的麻烦。

3)重写onBind方法

在Service的onBind方法中,用Messenger创建一个IBinder,Service将IBinder在onBind方法中返回给客户端。

1.2 Messenger客户端

1)创建ServiceConnection,绑定服务

客户端创建ServiceConnection实例,绑定服务端第1步中的Service,action要与服务端app中AndroidManifest中的一致,包名(服务端app包名,即AndroidManifest中查看到的包名)也要一致。

private void connectToService(){        
  Intent intent = new Intent();        
  intent.setAction("com.hk.daijun.messengerclient");  
  intent.setPackage("com.hk.daijun.replugintest");//        
  bindService(intent,serviceConnection,BIND_AUTO_CREATE);  
  RePlugin.getHostContext().bindService(intent,serviceConnection,BIND_AUTO_CREATE);    
}

此处需要主要要用宿主的上下文来绑定服务,如果直接用bindService来启动服务,能启动服务,但解绑时会报错服务未注册。

2)实例化Messenger

服务绑定后,在ServiceConnection中的onServiceConnected方法中,用IBinder来实例化Messenger(引用服务端的Handler),用此Messenger来向服务端发送消息。

3)接收服务端回复

若客户端还想从服务端那边收到回复,则需要再定义一个Messenger,messengerAccept来,在给服务端发送消息时,在Message的replyTo字段中赋值为messengerAccept。

public void sendMessageToPlugin(){    
    Log.e(TAG, "sendMessageToHost: ");    
    Message msg = Message.obtain();    
    msg.what = 1001;    
    msg.arg1 = 1001;    
    msg.replyTo = mesgerAccept;    
    Bundle bundle = new Bundle();    
    bundle.putString("msg","我要与宿主通信");          
    bundle.putString("msgJson",getJsonString());      
    msg.setData(bundle);    
    try {        
        mesgerSend.send(msg);    
    } catch (RemoteException e) {        
        e.printStackTrace();        
        Log.e(TAG, "sendMessageToHost:消息发送异常"+e.getMessage());    
    }
}

 接收服务端的回复,创建的Messenger实例mesgerAccept处理服务端的回复。

private void initAcceptMessenger() {    
mesgerAccept = new Messenger(new Handler(){  
    @Override        
    public void handleMessage(Message msg) {            
        switch (msg.what) {                
        case 2001:                    
        Log.e(TAG, "handleMessage: 收到服务端的回复");                    
        Log.e(TAG, "handleMessage: 
        service_reply="+msg.getData().getString("service_reply"));                    
        break;            
      }            
      super.handleMessage(msg);        
    }    
  });
}

4) 实现效果

首先在宿主app中进入插件,然后在插件中绑定宿主Service,然后发送消息。

 

 

二、使用AIDL

AIDL是一个缩写,全称是Android Interface Definition Language,要使用AIDL,必须创建一个定义编程接口的.aidl文件。AIDL文件可以分为两种。一种是用来定义parcelable对象,以供其他AIDL文件使用AIDL中非默认支持的数据类型的。一类是用来定义方法接口,以供系统使用来完成跨进程通信的。

AIDL支持的数据类型,包括Java中八种基本数据类型:byte、short、int、long、double、boolean、char,以及String、List、Map,集合里边的所有元素也必须是AIDL支持的数据类型(包括支持的Java数据类型以及自定义的数据类型)。List可以使用泛型、Map不支持泛型。

自定义数据类型,必须实现parcelable接口,还要注意定向tag。定向tag包括三种:in、out、inout。AIDL中的定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。一般通常in就够了。

2.1 客户端(宿主)

1)新建自定义数据类型java bean

其中writeToParcel方法即表示支持定向tag为in,而如果要支持为 out 或者 inout 的定向 tag 的话,还需要实现 readFromParcel()方法,此方法不在Parcelable接口中,需要自己手写。

可以看到自定义的数据类型要实现parcelable序列化接口。因为进程是系统进行资源分配和调度的基本单位,不同的进程有不同的内存区域,java中传递对象一般是传递对象在内存堆上的引用,而由于不同进程间不能访问各自的内存区域,所以传递的引用对方进程是访问不到的。所以要将传递的数据进行序列化。在客户端对这个对象进行序列化的操作,将其中包含的数据转化为序列化流,然后将这个序列化流传输到服务端的内存中去,再在服务端对这个数据流进行反序列化的操作,还原其中包含的数据,从而实现进程间传递数据的目的。

2)增加aidl文件目录

在project视图下,在main目录下新建aidl目录,然后新建package,包名与AlarmMessage的包名路径一致。

3)添加aidl文件

 

添加后,aidl文件没在bean目录下,aidl的包名要和对应的javabean包名一致,手动移动aidl文件到bean包名下,然后修改包名,声明序列化类AlarmMessage。

 

 

4)增加aidl接口文件

增加aidl接口文件内容,这个文件里面主要内容是进程间通信的接口。注意要导入自定义数据类型的包。AIDL默认支持一些数据类型,在使用这些数据类型的时候是不需要导包的,但是除了这些类型之外的数据类型,在使用之前必须导包,就算目标文件与当前正在编写的 .aidl 文件在同一个包下。

配置完成后,将Module plugin1进行build,就会生成AlarmMessageMgr.java文件。

 

2.2 服务端(插件)

以上都是插件工程中的配置,现在要在宿主工程中也进行相应配置。配置过程也是几乎一样,需要将java文件和aidl文件都从插件工程中复制到宿主工程中,并且包路径要一样。

在将aidl文件和java bean复制到宿主工程中后。在插件中新建Service类,此次通信以插件作为服务端,而宿主app作为客户端,宿主app主动发起通信。插件中新建Service,其中AlarmMessage.Stub就是aidl文件写完后,对插件工程进行重新编译后产生java文件,在onBind中返回给客户端,客户端据此来进行通信。

public class PluginAidlService extends Service {
    private static final String TAG = "PluginAidlService";
    private List<AlarmMessage> mMsgList = new ArrayList<>();
    private final AlarmMessageMgr.Stub mAlarmMgr = new AlarmMessageMgr.Stub() {
        @Override
        public List<AlarmMessage> getAlarmMessages() throws RemoteException {
            return mMsgList;
        }
        @Override
        public void addAlarmMessage(AlarmMessage msg) throws RemoteException {
            Log.e(TAG, "plugin service addAlarmMessage: ");
            synchronized (this) {
                if (mMsgList == null) {
                    mMsgList = new ArrayList<>();
                }
                if (msg == null) {
                    Log.e(TAG, "addAlarmMessage: msg to add is null");
                    msg = new AlarmMessage();
                    msg.alarmId = 0;
                    msg.alarmTime = "1997-10-7 12:00:00";
                    msg.alarmTitle = "空报警";
                }
                mMsgList.add(msg);
            }
            int size = mMsgList.size();
            for (int i=0;i<size;i++) {
                AlarmMessage alarmMessage = mMsgList.get(i);
                Log.e(TAG, "aidl plugin1 : "+String.format("alarmTitle=%s alarmTime=%s",alarmMessage.alarmTitle,alarmMessage.alarmTime));
            }
        }
    };

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG, "onBind: ");
        return mAlarmMgr;
    }

 而客户端即宿主app通过绑定插件中的PluginAidlService服务来进行通信,需要注意的是,由于这里涉及到宿主app启动插件中的四大组件(Activity、Service、BroadcastReceiver、ContentProvider),所以不能直接使用原生的api来启动插件中的Service,需要调用Replugin中的api来启动。

    private void connectToService(){
        Log.e(TAG, "connectToService: ");
        Intent intent1 = RePlugin.createIntent("plugin1","com.hk.daijun.plugin1.PluginAidlService");
        intent1.setAction("com.hk.daijun.replugintest");
        intent1.setPackage("com.hk.daijun.plugin1");
        try {
            PluginServiceClient.bindService(PluginCommunicateActivity.this, intent1, serviceConnection, BIND_AUTO_CREATE);
        } catch (PluginClientHelper.ShouldCallSystem e) {
            Log.e(TAG, "connectToService: "+e.getMessage());
        }
    }

同样,通过bindService()来启动插件中的服务,取消绑定也需要用Replugin的api来进行Service解绑。

PluginServiceClient.unbindService(PluginCommunicateActivity.this,serviceConnection);

 客户端在Service连接建立后,调用AlarmMessageMgr.Stub.asInterface(service)将服务端返回的Binder对象转换成AIDL接口类型的对象,进而可以调用aidl定义的接口来进行调用服务端的远程方法。

private void initServiceConnection(){
    serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.e(TAG, "aidlhost onServiceConnected: ");
            mAlarmMgr = AlarmMessageMgr.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e(TAG, "aidlhost onServiceDisconnected: ");
        }
    };
}

 插件重新build后,宿主以内置模式接入插件,需要将宿主工程后先clean下,清除build目录,再重新编译安装,否则宿主不先clean的话,插件的修改可能并不生效!

实现效果如下,在宿主中发送报警信息,插件中收到信息后,将所有报警信息打印出来。宿主app中主要是2个按钮,一个绑定插件服务,一个发送消息。

 

还可以模拟插件Service处理完一些逻辑后,如进行网络请求,获取第三方系统账户信息等操作,执行成功后再启动插件中的Activity,需要注意的是,在Service中启动Activity,必须设置FLAG_ACTIVITY_NEW_TASK标签,但FLAG_ACTIVITY_NEW_TASK标签必须配合taskAffinity属性使用,如果不设置taskAffinity属性值,将不会生成新task。所以不设置taskAffinity就不会生成新的task。

三、 AIDL原理

掠下aidl实现进程间通信的主要原理。AIDL的底层是Binder,其本质就是为我们提供了一种快速实现Binder的工具,通过定义AIDL,android studio编译后生成了AlarmMessageMgr.java文件,而原理的关键也就是这个文件,这个文件其实也可以通过手动编写。

public static com.hk.daijun.plugin1.AlarmMessageMgr asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.hk.daijun.plugin1.AlarmMessageMgr))) {
return ((com.hk.daijun.plugin1.AlarmMessageMgr)iin);
}
return new com.hk.daijun.plugin1.AlarmMessageMgr.Stub.Proxy(obj);
}

 asInteface方法用于将服务器的Binder对象转换成客户端所需的AIDL接口类型的对象,这种转换过程是区分进程的,如果客户端和服务端位于统一进程,那么返回服务器的Stub对象本身,否则返回的是系统封装后的Stub.proxy对象。

然后看下addAlarmMessage(AlarmMessage msg)方法的调用过程,转到方法的实现。

@Override public void addAlarmMessage(com.hk.daijun.plugin1.bean.AlarmMessage msg) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((msg!=null)) {
_data.writeInt(1);
msg.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addAlarmMessage, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}

 这个方法由客户端调用,运行在客户端,输入的参数msg定向tag是in,代表数据只能由客户端流向服务端。参数信息写入_data中,然后调用transact方法发起RPC(远程过程调用),mRemote就是Binder对象在binder驱动层对应的引用。transact方法调启后,客户端当前线程被挂起,服务端的onTransact方法会被调用。

@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getAlarmMessages:
{
data.enforceInterface(DESCRIPTOR);
java.util.List<com.hk.daijun.plugin1.bean.AlarmMessage> _result = this.getAlarmMessages();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addAlarmMessage:
{
data.enforceInterface(DESCRIPTOR);
com.hk.daijun.plugin1.bean.AlarmMessage _arg0;
if ((0!=data.readInt())) {
_arg0 = com.hk.daijun.plugin1.bean.AlarmMessage.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.addAlarmMessage(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}

 onTransact方法中4个参数根据参数code,来确定调用哪个方法,data代表输入参数,reply代表返回值。客户端调用服务端的远程方法跟调用本地方法差不多,客户端发起的请求要挂起直到服务端进程处理完,所以远程方法如果是比较耗时的,客户端最好不要在UI线程中发起这个远程方法调用,而应该放到子线程中进行。而服务端的Binder方法是运行在Binder的线程池中。

四、总结

Messenger是执行进程间通信(IPC)最简单的方式,服务端Messenger通过Handler将客户端的请求放到消息循环中排队,然后逐个取出进行处理,而客户端要接收结果需要2个Messenger,一个用于发送消息,一个用于接收消息。其底层原理也是Binder,客户端得到服务端的Binder对象在binder驱动层对应的mRemote引用,然后给服务器发消息,实现跨进程通信。这种方式缺点很明显,服务端以串行方式处理客户端的消息,消息处理结果反馈并不及时,不适合服务端、客户端经常通信的场景。而用AIDL可以并发处理客户端的远程调用,远程方法调用就像本地调用一样,立即执行后就有结果返回。但Messenger的特点是简单,不需要想AIDL一样,需要定义aidl文件,还需要将aidl文件拷贝到服务端和客户端。由于引入了Replugin,使用Messenger、AIDL启动宿主/插件的Service,启动方式有所区别,使用时需要注意。AIDL的底层是Binder,其本质只是为我们提供了一种快速实现Binder的工具。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凉亭下

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值