Hermes——跨进程通信(IPC)框架,使用介绍

Posted 夜辉疾风

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Hermes——跨进程通信(IPC)框架,使用介绍相关的知识,希望对你有一定的参考价值。

概述

Hermes的源码地址

Hermes的Demo地址

  1. android进程间通信IPC框架
  2. 像调用本地函数一样调用其他进程的函数
  3. 在本地进程创建其他进程类的对象
  4. 在本进程获取其他进程的单例
  5. 在本进程使用其他进程的工具类
  6. 支持进程间函数回调,调用其他进程函数的时候可以传入回调函数,让其他进程回调本进程的方法
  7. 自带内存优化,内置两个垃圾回收器,地进程在远端进程创建的实例和本地进程传给远端进程的回调接口会被自动回收。

基本使用

compile 'xiaofei.library:hermes:0.7.0'

两个进程共享一个对象单例

//在进程A中,类使用注解标记该类的id
@ClassId(“Singleton”)
public class Singleton 
    //属性
    private volatile String mData;
    
	//实现单例,代码省略
    public static Singleton getInstance();
 
	//方法使用注解标记该方法的id
    @MethodId(“setData”)
    public void setData(String data) 
        mData = data;
    

    @MethodId(“getData”)
    public String getData() 
        return mData;
    

进程B要访问在进程A中的Singleton对象,如下:

//在进程B中,自定义接口
//该接口指定与Singleton的注解的classid一致
@ClassId(“Singleton”)
public interface ISingleton 
	//方法id与Singleton的注解的methodid一致
    @MethodId(“setData”)
    void setData(String data);

    @MethodId(“getData”)
    String getData();

//使用如下:
//获得Singleton对象
ISingleton singleton = Hermes.getInstance(ISingleton.class);
//调用方法
singleton.setData(“Hello, Hermes!);
//调用方法
Log.v(TAG, singleton.getData());

在其他进程调用主进程的函数

Hermes支持任意进程之间的函数调用

AndroidManifest.xml

<service android:name="xiaofei.library.hermes.HermesService$HermesService0"/>

主进程初始化Hermes

//在给其他进程提供函数的进程中,使用Hermes.init(Context)初始化
Hermes.init(Context);

子进程连接Hermes

//子进程链接Hermes后才可以使用Hermes的服务
//在Application.OnCreate()或Activity.OnCreate()
Hermes.connect(Context)

查看通信的进程是否还活着

Hermes.isConnected()

事先注册被调用的类

//进程A中,被进程B调用的类需要事先注册
//如果类上面没有加上注解,那么注册就不是必须的,Hermes会通过类名进行反射查找相应的类
//有两种注册类的API:
Hermes.register(Class<?>)
Hermes.register(Object)
Hermes.register(object)等价于Hermes.register(object.getClass())

设置连接监听

//在连接之前给Hermes设置监听器
Hermes.setHermesListener(new HermesListener() 
    @Override
    public void onHermesConnected(Class<? extends HermesService> service) 
        //连接成功,首先获取单例
       IUserInfo iUserinfo = Hermes.getInstance(IUserInfo.class);
        //通过单例获取UserInfo
       String name = iUserinfo.getUserName();
    
);
//连接Hermes服务
Hermes.connect(context);

连接Hermes服务

Hermes.connect(context);//需要连接服务才能得到其他进程的实例

断开Hermes服务

Hermes.disconnect(context);

创建实例

//进程B中,创建进程A中的实例有三种方法:
Hermes.newInstance(Class,Object…)
Hermes.getInstance(Class,Object…)
Hermes.getUtilityClass(Class)

Hermes.newInstance(Class,Object…)

  1. 在进程A中创建指定类的实例,并将引用返回给进程B。

  2. 函数的第二个参数将传给指定类的对应的构造器。

  3. 在进程B中,调用Hermes.newInstance(ILoadingTask.class, “xxx”, true)得到LoadingTask实例。

  4. 注意:该实例不是单例,该实例为当前线程所拥有的实例。

@ClassId(“LoadingTask”)
public class LoadingTask 

    public LoadingTask(String path, boolean showImmediately) 
        //...
    

    @MethodId(“start”)
    public void start() 
        //...
    


@ClassId(“LoadingTask”)
public interface ILoadingTask 
    @MethodId(“start”)
    void start();

Hermes.getInstance(Class , Object…)

  1. 在进程A中通过指定类的getInstance()方法创建实例,并将引用返回给进程B。
  2. 第二个参数将传给对应的getInstance()方法。
  3. 该函数适合获取单例,这样进程A和进程B就使用同一个单例。
  4. 进程B中,调用Hermes.getInstance(IBitmapWrapper.class, “XXX”)将得到BitmapWrapper的实例。
  5. 进程B中,调用Hermes.getInstance(IBitmapWrapper.class, 100)将得到BitmapWrapper的实例。
@ClassId(“BitmapWrapper”)
public class BitmapWrapper 

    @GetInstance
    public static BitmapWrapper getInstance(String path) 
        //这里可以写成单例,两个进程可以获得当前这个实例
    

    @GetInstance
    public static BitmapWrapper getInstance(int label) 
        //...
    

    @MethodId(“show”)
    public void show() 
        //...
    



@ClassId(“BitmapWrapper”)
public interface IBitmapWrapper 

    @MethodId(“show”)
    void show();


Hermes.getUtilityClass(Class )

  1. 获取进程A的工具类。
  2. 这种做法在插件开发中很有用。
  3. 主app和插件app存在不同的进程中,为了维护方便,应该使用统一的工具类。
  4. 插件app可以通过这个方法获取主app的工具类。
@ClassId(“Maths”)
public class Maths 

    @MethodId(“plus”)
    public static int plus(int a, int b) 
       //模拟加法
    

    @MethodId(“minus”)
    public static int minus(int a, int b) 
        //模拟减法
    



@ClassId(“Maths”)
public interface IMaths 

    @MethodId(“plus”)
    int plus(int a, int b);

    @MethodId(“minus”)
    int minus(int a, int b);

进程B中,使用下面代码使用进程A的工具类

IMaths maths = Hermes.getUtilityClass(IMaths.class);
int sum = maths.plus(3, 5);
int diff = maths.minus(3, 5);

其他注解的使用

//@WithinProcess//不让其他进程访问
@ClassId("HermesOtherBean")
public class HermesOtherBean 

    //@WeakRef:持有当前回调的弱引用
    //@Background:不让回调函数运行在主线程
    public void customBackMethod(HermesMethodBean hermesMethodBean, IHermesOtherListener iHermesOtherListener) 
        if (hermesMethodBean != null) 
            iHermesOtherListener.getVoid();
            iHermesOtherListener.setInteger(100);
            int i = iHermesOtherListener.getBackInteger();
            iHermesOtherListener.setString("customBackMethod");
            String str = iHermesOtherListener.getBackString();
            iHermesOtherListener.setHermesMethod(hermesMethodBean);
            Log.i("appjson", "getbackInteger:" + i + ";getBackString:" + str);
        
    

@ClassId("IHermesOtherListener")
public interface IHermesOtherListener 

    void getVoid();

    int getBackInteger();

    void setInteger(int i);

    String getBackString();

    void setString(String str);

    HermesMethodBean getHermesMethod();

    void setHermesMethod(HermesMethodBean hermesMethod);

@ClassId("HermesOtherBean")
public interface IHermesOtherBean 

    //@WeakRef:持有当前回调的弱引用
    //@Background:不让回调函数运行在主线程
    void customBackMethod(HermesMethodBean hermesMethodBean, @WeakRef @Background IHermesOtherListener iHermesOtherListener);

public class HermesOtherService extends Service 
    public HermesMethodBean hermesMethodBean = new HermesMethodBean(1, "a", 1D);

    @Override
    public void onCreate() 
        super.onCreate();
        //在连接之前给Hermes设置监听器
        Hermes.setHermesListener(new HermesListener() 
            @Override
            public void onHermesConnected(Class<? extends HermesService> service) 
                //连接成功,首先获取单例
                try 
                    //静态方法没有限制,这里不清楚原因需要先调用getUtilityClass在调用newInstance才能正常使用
                    Hermes.getUtilityClass(IHermesOtherBean.class);
                    //如果是new的有限制
                    IHermesOtherBean iHermesOtherBeanNew = Hermes.newInstance(IHermesOtherBean.class);
                    if (iHermesOtherBeanNew == null)
                        Log.i("appjson", "当前IHermesOtherBean==null");
                    else 
                        //这种匿名类是错误的写法
//                        Log.i("appjson", iHermesOtherBean.customMethod(new HermesMethodBean(1, "aaaa", 1D)));
                        //这种private的对象也是错误的
//                        HermesMethodBean hermesMethodBean = new HermesMethodBean(1, "a", 1D);
//                        iHermesOtherBean.customMethod(hermesMethodBean);
                        //必须是非private且不是匿名类的对象
//                        hermesMethodBean = new HermesMethodBean(1, "a", 1D);
                        iHermesOtherBeanNew.customBackMethod(hermesMethodBean, new IHermesOtherListener() 
                            @Override
                            public void getVoid() 
                                Log.i("appjson", "getVoid()");
                            

                            @Override
                            public int getBackInteger() 
                                Log.i("appjson", "getBackInteger()");
                                return 100;
                            

                            @Override
                            public void setInteger(int i) 
                                Log.i("appjson", "setInteger(" + i + ")");

                            

                            @Override
                            public String getBackString() 
                                Log.i("appjson", "getBackString()");
                                return "getBackString-admin";
                            

                            @Override
                            public void setString(String str) 
                                Log.i("appjson", "setString(" + str + ")");
                            

                            @Override
                            public HermesMethodBean getHermesMethod() 
                                Log.i("appjson", "getHermesMethod()");
                                return new HermesMethodBean(50, "test", 99D);
                            

                            @Override
                            public void setHermesMethod(HermesMethodBean hermesMethod) 
                                Log.i("appjson", "setHermesMethod():" + hermesMethod.toString());
                            
                        );
                    
                 catch (Exception e) 
                    Log.i("appjson", "error:" + e.getMessage());
                
            
        );
        //连接Hermes服务
        Hermes.connect(this);
    

    @Override
    public void onDestroy() 
        super.onDestroy();
        //断开Hermes服务
        Hermes.disconnect(this);
    

    @Nullable
    @Override
    public IBinder onBind(Intent intent) 
        return null;
    

注意事项

  1. 如果两个进程属于两个不同的app(分别叫App A和App B),App A想访问App B的一个类,并且App A的接口和App B的对应类有相同的包名和类名,那么就没有必要在类和接口上加@ClassId注解。

    1. 但是要注意使用ProGuard后类名和包名仍要保持一致。
  2. 如果接口和类里面对应的方法的名字相同,那么也没有必要在方法上加上@MethodId注解。

    1. 但是要注意使用ProGuard后接口内的方法名字必须仍然和类内的对应方法名字相同。
  3. 如果进程A的一个类上面有一个@ClassId注解,这个类在进程B中对应的接口上有一个相同的@ClassId注解,那么进程A在进程B访问这个类之前必须注册这个类。

    1. 否则进程B使用Hermes.newInstance()、Hermes.getInstance()或Hermes.getUtilityClass()时,Hermes在进程A中找不到匹配的类。
    2. 类可以在构造器或者Application.OnCreate()中注册。
    3. 但是如果类和对应的接口上面没有@ClassId注解,但有相同的包名和类名,那么就不需要注册类。
    4. Hermes通过包名和类名匹配类和接口。
    5. 对于接口和类里面的函数,上面的说法仍然适用。
  4. 如果不想让一个类或者函数被其他进程访问,可以在上面加上@WithinProcess注解。

  5. 使用Hermes跨进程调用函数的时候,传入参数的类型可以是原参数类型的子类,但不可以是匿名类和局部类。

    1. 传递的参数不能是匿名类:maths.puls(new A());
    2. 传递的参数也不能是private的:private A a=new A();
    3. 但是回调函数例外,关于回调函数详见第7点
  6. 如果被调用的函数的参数类型和返回值类型是int、double等基本类型或者String这样的Java通用类型,上面的说法可以很好地解决问题。但如果类型是自定义的类,比如第5点中的自定义类A,并且两个进程分别属于两个不同app,那么你必须在两个app中都定义这个类,且必须保证代码混淆后,两个类仍然有相同的包名和类名。

    1. 不过你可以适用@ClassId和@MethodId注解,这样包名和类名在混淆后不同也不要紧了。
  7. 如果被调用的函数有回调参数,那么函数定义中这个参数必须是一个接口,不能是抽象类。请特别注意回调函数运行的线程。

    1. 如果进程A调用进程B的函数,并且传入一个回调函数供进程B在进程A进行回调操作,那么默认这个回调函数将运行在进程A的主线程(UI线程)。如果你不想让回调函数运行在主线程,那么在接口声明的函数的对应的回调参数之前加上@Background注解。

    2. 如果回调函数有返回值,那么你应该让它运行在后台线程。如果运行在主线程,那么返回值始终为null。

    3. 默认情况下,Hermes持有回调函数的强引用,这个可能会导致内存泄漏。你可以在接口声明的对应回调参数前加上@WeakRef注解,这样Hermes持有的就是回调函数的弱引用。

    4. 如果进程的回调函数被回收了,而对方进程还在调用这个函数(对方进程并不会知道回调函数被回收),这个不会有任何影响,也不会造成崩溃。

    5. 如果回调函数有返回值,那么就返回null。

    6. 如果你使用了@Background和@WeakRef注解,你必须在接口中对应的函数参数前进行添加。

    7. 如果加在其他地方,并不会有任何作用。

    8. @ClassId(“Foo”)
      public class Foo 
          public static void f(int i, Callback callback) 
          
      
      
      @ClassId(“callback”)
      public interface Callback 
          void callback();
      
      
      @ClassId(“Foo”)
      public interface IFoo 
          void f(int i, @WeakRef @Background Callback callback);
      
      
  8. 调用函数的时候,任何Context在另一个进程中都会变成对方进程的applicationContext。

  9. 数据传输是基于Json的。

  10. 使用Hermes的时候,有任何的错误都会使用android.util.Log.e()打出错误日志。

以上是关于Hermes——跨进程通信(IPC)框架,使用介绍的主要内容,如果未能解决你的问题,请参考以下文章

IPC进程间通信/跨进程通信

IPC进程间通信/跨进程通信

Swoole基于IPC通信的跨进程连接池

Hermes跨进程通讯——源码解析

Android面试Android跨进程通信IPC

Android IPC跨进程通讯的几种方式作用

AIDL跨进程通信

linux 单机跨进程通信

Android IPC机制用Socket实现跨进程聊天程序

饿了么开源项目Hermes:新颖巧妙易用的Android进程间通信IPC框架