Giter Club home page Giter Club logo

hermes's Introduction

Hermes

A smart, novel and easy-to-use framework for Android Inter-Process Communication (IPC).

Chinese Readme 中文文档

Hermes is a smart, novel and easy-to-use framework for Android Inter-Process Communication (IPC). In this framework, you can use IPC even if you do not understand the underneath principle of Android IPC.

Please click HERE to see the demo.

Also, HermesEventBus is a Hermes-and-EventBus-based library which posts events between processes.

Note that maybe you will find that Hermes is still a little difficult to use. I have finished this library and promoted the performance. What to do for my next step is to simplify the usage, which will be released in version 0.7.0.

##Features

  1. Make method invocations over IPC so easy just like method invocations in a local process. The statements of method invocations are almost the same.

  2. Easy to use classes, singletons, utilities in another process.

  3. Support callbacks over IPC. You can provide the remote process with a callback to perform operations in the local process.

  4. Support GC over IPC. Hermes contains two garbage collectors to reclaim the instances created in the remote process and the local callbacks for remote processes.

##Principle

Since the main purpose of IPC is to invoke methods in another process, Hermes makes method invocation so easy that you can invoke methods in another process just like you invoke methods in a local process, and also, the statements of method invocation in another process are almost the same as the ones in a local process.

For example, the singleton pattern is always used in Android apps. Suppose an app contains two processes and they want to use a singleton. The singleton is as below:

@ClassId(“Singleton”)
public class Singleton {

    private static Singleton sInstance = null;

    private volatile String mData;

    private Singleton() {
        mData = new String();
    }

    public static synchronized Singleton getInstance() {
        if (sInstance == null) {
            sInstance = new Singleton();
        }
        return sInstance;
    }

    @MethodId(“setData”)
    public void setData(String data) {
        mData = data;
    }

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

}

Suppose the instance of Singleton is in Process A, and you want to access it from Process B. Then you write an interface as below:

@ClassId(“Singleton”)
public interface ISingleton {

    @MethodId(“setData”)
    void setData(String data);

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

}

And the statements to access the singleton in Process B is as below:

//obtain the instance of Singleton
ISingleton singleton = Hermes.getInstance(ISingleton.class);

//Set a data
singleton.setData(“Hello, Hermes!”);

//Get the data
Log.v(TAG, singleton.getData());

Amazing, isn’t it?

Specifically, you pass an interface into Hermes.getInstance() and Hermes.getInstance() returns an instance which is exactly the same as the original one in Process A. Then when you invoke a method on the instance in Process B, the method is invoked on the same instance in Process A.

Now, how to write the interface? So easy. For example, there is a class named Foo in Process A, and you want to access it from Process B. Then you write an interface named IFoo and add into the interface the signatures of the methods which you want to invoke on Foo. Then add the same @ClassId annotation with the same value on IFoo and Foo, and add the same @MethodId annotation with the same value on the corresponding methods. Done! At this time, you can use Hermes.getInstance(IFoo.class) in Process B to get the instance of Foo in Process A.

##Gradle

dependencies {
    compile 'xiaofei.library:hermes:0.7.0'
}

##Maven

<dependency>
  <groupId>xiaofei.library</groupId>
  <artifactId>hermes</artifactId>
  <version>0.7.0</version>
  <type>pom</type>
</dependency>

##Usage

The following will tell you how to let the default process of the app to provide methods for other processes to invoke.

Hermes also allows method invocation between arbitrary processes. For more information about how to do this, please see HERE.

###AndroidManifest.xml

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

###Initialization

Always, an app has a default process in which most components run. Name this default process Process A.

Process A should initialize Hermes in Application.OnCreate() or Activity.OnCreate(), as the following does:

Hermes.init(getApplicationContext());

###Connection

Suppose there is another process, named Process B. Process B wants to invoke methods in Process A. Then Process B should initialize Hermes at the beginning.

You can do this in Application.OnCreate() or Activity.OnCreate() in Process B. The Corresponding API is Hermes.connect(Context). Before initialization, a HermesListener can be set to do some callbacks.

Hermes.connect(getApplicationContext());

You can call Hermes.isConnected() to see whether the process you are communicating with is still alive.

###Registration

In Process A, classes to be accessed by Process B should be registered before being accessed. There are two APIs to register classes: Hermes.register(Class<?>) and Hermes.register(Object). Hermes.register(object) is equivalent to Hermes.register(object.getClass()).

Registration is, however, not necessary if you do not add annotations on the classes. See Point 3 of Notice.

###Instance Creation

In Process B, there are three ways to create instances in Hermes: Hermes.newInstance(), Hermes.getInstance() and Hermes.getUtilityClass().

  1. Hermes.newInstance(Class, Object...)

    This method creates an new instance of the specified class in Process A and returns the reference to the new instance. The second parameter will be passed into the corresponding constructor of the specified class.

    @ClassId(“LoadingTask”)
    public class LoadingTask {
    
        public LoadingTask(String path, boolean showImmediately) {
            //...
        }
    
        @MethodId(“start”)
        public void start() {
            //...
        }
    }
    
    @ClassId(“LoadingTask”)
    public class ILoadingTask {
        @MethodId(“start”)
        void start();
    }
    

    In Process B, you create the instance by Hermes.newInstance(ILoadingTask.class, “files/image.png”, true) to get the instance of LoadingTask.

  2. Hermes.getInstance(Class, Object...)

    This method creates an new instance of the specified class through its static method named “getInstance” in Process A and returns the reference to the new instance. The second parameter will be passed into the corresponding static “getInstance” method of the specified class. This is useful when the specified class is a singleton.

    @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 class IBitmapWrapper {
    
        @MethodId(“show”)
        void show();
    
    }
    

    In Process B, you create the instance by Hermes.getInstance(IBitmapWrapper.class, “files/image.png”) or Hermes.getInstance(IBitmapWrapper.class, 1001) to get the instance of BitmapWrapper.

  3. Hermes.getUtilityClass(Class)

    This method provides a way to use the utility class in another process. (This is very useful when developing plugins.)

    @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 class IMaths {
    
        @MethodId(“plus”)
        int plus(int a, int b);
    
        @MethodId(“minus”)
        int minus(int a, int b);
    }
    

    In Process B, you can do as the below:

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

##Notice

  1. Actually, if two processes are in separate apps, named App A and App B respectively, and App A wants to access a class in App B, and if the interface of App A and the corresponding class of App B has the same name and the same package name, then there is no need to add the @ClassId annotation on them. But pay more attention when you use ProGuard, since the names of the corresponding classes will be different after obfuscating.

  2. If the method in an interface and the method in the corresponding class have the same name, there is also no need to add the @MethodId annotation on them.

  3. If a class in Process A has a @ClassId annotation presented on it and its corresponding interface in Process B has also a @ClassId annotation with the same value presented on it, the class should be registered in Process A before being accessed by Process B. Otherwise, when Process B uses Hermes.newInstance(), Hermes.getInstance() or Hermes.getUtilityClass(), Hermes will not find the matching class in Process A. A class can be registered in its constructor or in Application.OnCreate().

    However, if the class and its corresponding interface do not have an annotation presented on them but have the same name and the same package name, then there is no need to register the class. Hermes finds the class according to the package and the name.

    The above also works when it comes to the method.

  4. If you want to prevent a class or a method from being accessed from outside the process, add a @WithinProcess annotation on it.

  5. The type of the parameters you pass into the method can be a subclass of the corresponding parameter type, but cannot be an anonymous class or a local class. But callback is supported. See Point 7 for more information about callbacks.

    public class A {}
    
    public class B extends A {}
    

    In Process A, there is a class as below:

    @ClassId(“Foo”)
    public class Foo {
    
        public static A f(A a) {
        }
    }
    

    Then in Process B, the interface is as below:

    @ClassId(“Foo”)
    public interface IFoo {
    
        A f(A a);
    }
    

    In Process B, you can write the followings:

    IFoo foo = Hermes.getUtilityClass(IFoo.class);
    B b = new B();
    A a = foo.f(b);
    

    You can NOT write the following:

    A a = foo.f(new A(){});
    
  6. If the parameter types and the return type of the invoked method are the primitive types or some common classes such as String, the above will work very well. However, if they are the classes you declare, as the example in Point 5 shows, and if the method invocation is between apps, then you must declare the classes in both App A and App B. What’s more, you should guarantee that the name of the class and its methods remain the same even after you use the ProGuard. Or you can add the @ClassId annotation on the class and the @MethodId annotation on the methods.

  7. If the invoked method has some callback parameters, then the parameter type must be an interface. It can NOT be an abstract class. Attention should be paid when it comes to the thread where the callback run.

    If Process A invokes a method in Process B, and passes some callbacks as the parameters of the method to let Process B perform some callback operations in Process A, the callback will run, by default, in the main thread, also known as the UI thread, of Process A. If you want to let the callback run in the background, you can add the @Background annotation before the corresponding parameter when you declare the interface.

    If the callback has an return value, you should let it run in the background. If it runs in the main thread, the return value will always be null.

    By default, Hermes holds a strong reference to the callback, which may cause the memory leak. You can add a @WeakRef annotation before the corresponding callback parameter to let Hermes hold a weak reference to the callback. If the callback in a process has been reclaimed and it is called from the other process (A process does not know the callback in another process has been reclaimed), nothing happens and if the callback has a return value, the return value will be null.

    If you want to use the @Background annotation and the @WeakRef annotation, you should add them when you declare the interface. If you add them elsewhere, it will take no effect.

    @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. Any context passed into the invoked method as the parameter will be replaced by the application context of the remote process.

  9. Data transmitting between processes is based on Json.

  10. If any error occurs, a error log will be printed by android.util.Log.e(). You can see the log for the detail of the error.

hermes's People

Contributors

wfmarques avatar xiaofei-it avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

hermes's Issues

第二个APP

按照文档上写的,但是第二个APP拿不到数据
2019-03-07 22:21:20.049 1376-1762/? E/WindowManager: Performed 6 layouts in a row. Skipping
2019-03-07 22:21:20.482 10161-10161/justree.cn.otherapplication E/HERMES: Error occurs during getting instance. Error code: 16
2019-03-07 22:21:20.482 10161-10161/justree.cn.otherapplication E/HERMES: Error message: Cannot find class with ClassId annotation on it. ClassId = Singleton. Please add the same annotation on the corresponding class in the remote process and register it. Have you forgotten to register the class?
2019-03-07 22:21:20.483 10161-10161/justree.cn.otherapplication E/AndroidRuntime: FATAL EXCEPTION: main
Process: justree.cn.otherapplication, PID: 10161
java.lang.NullPointerException: Attempt to invoke interface method 'java.lang.String justree.cn.otherapplication.ISingleton.getData()' on a null object reference

启动第2个app进程的时候发现service0无法绑上

之前service0 启动后 验证 service 已经起~
public static boolean isServiceExisted(Context context, String className) {
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningServiceInfo> serviceList = activityManager.getRunningServices(Integer.MAX_VALUE);
if(!(serviceList.size() > 0)) {
return false;
}
for(int i = 0; i < serviceList.size(); i++) {
ActivityManager.RunningServiceInfo serviceInfo = serviceList.get(i);
ComponentName serviceName = serviceInfo.service;
if(serviceName.getClassName().equals(className)) {
return true;
}
}
return false;
}

但是到了 B APP进程里 总是绑不上~

androidx报错

java.lang.NoClassDefFoundError: Failed resolution of: Landroidx/appcompat/app/ActionBarActivity;
at xiaofei.library.hermes.util.TypeUtils$1.(TypeUtils.java:51)
at xiaofei.library.hermes.util.TypeUtils.(TypeUtils.java:48)

Error with java.util.Date type

Android 5.1.0 on Genymotion.

This test case is just a little bit modification from Hermes demo.
Interface:

@ClassId("UserManager")
public interface IUserManager {

    @MethodId("getUser")
    String getUser();

    @MethodId("getDate1")
    Date getDate1();

    @MethodId("getDateText")
    String getDateText();

}

Implemantation:

@ClassId("UserManager")
public class UserManager implements IUserManager {

    private static UserManager sInstance = null;

    private UserManager() {

    }

    public static synchronized UserManager getInstance() {
        if (sInstance == null) {
            sInstance = new UserManager();
        }
        return sInstance;
    }

    @MethodId("getUser")
    @Override
    public String getUser() {
        return "Xiaofei";
    }
    @MethodId("getDate1")
    @Override
    public Date getDate1() {
        return new Date();
    }
    @MethodId("getDateText")
    @Override
    public String getDateText() {
        return "DateText";
    }
}

Client side code:

IUserManager userManager = Hermes.getInstanceInService(HermesService.HermesService1.class, IUserManager.class);
                Date date = userManager.getDate1();

Exception stacktrace:

07-23 02:33:25.976  11397-11414/xiaofei.library.hermestest:g W/System.errxiaofei.library.hermes.util.HermesException
07-23 02:33:25.976  11397-11414/xiaofei.library.hermestest:g W/System.errat xiaofei.library.hermes.util.TypeCenter.getClassType(TypeCenter.java:145)
07-23 02:33:25.976  11397-11414/xiaofei.library.hermestest:g W/System.errat xiaofei.library.hermes.receiver.InstanceGettingReceiver.<init>(InstanceGettingReceiver.java:44)
07-23 02:33:25.976  11397-11414/xiaofei.library.hermestest:g W/System.errat xiaofei.library.hermes.receiver.ReceiverDesignator.getReceiver(ReceiverDesignator.java:35)
07-23 02:33:25.976  11397-11414/xiaofei.library.hermestest:g W/System.errat xiaofei.library.hermes.HermesService$1.send(HermesService.java:48)
07-23 02:33:25.976  11397-11414/xiaofei.library.hermestest:g W/System.errat xiaofei.library.hermes.internal.IHermesService$Stub.onTransact(IHermesService.java:70)
07-23 02:33:25.976  11397-11414/xiaofei.library.hermestest:g W/System.errat android.os.Binder.execTransact(Binder.java:446)
07-23 02:33:25.979  11380-11380/? E/HERMESError occurs during getting instance. Error code: 16
07-23 02:33:25.979  11380-11380/? E/HERMESError message: Cannot find class with ClassId annotation on it. ClassId = UserManager. Please add the same annotation on the corresponding class in the remote process and register it. Have you forgotten to register the class?
07-23 02:33:25.979  11380-11380/? D/AndroidRuntimeShutting down VM
07-23 02:33:25.979  11380-11380/? E/AndroidRuntimeFATAL EXCEPTION: main
    Process: xiaofei.library.hermestest:h, PID: 11380
    java.lang.NullPointerException: Attempt to invoke interface method 'java.util.Date xiaofei.library.hermestest.IUserManager.getDate1()' on a null object reference
            at xiaofei.library.hermestest.DemoActivity$4.onClick(DemoActivity.java:71)
            at android.view.View.performClick(View.java:4780)
            at android.view.View$PerformClick.run(View.java:19866)
            at android.os.Handler.handleCallback(Handler.java:739)
            at android.os.Handler.dispatchMessage(Handler.java:95)
            at android.os.Looper.loop(Looper.java:135)
            at android.app.ActivityThread.main(ActivityThread.java:5254)
            at java.lang.reflect.Method.invoke(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:372)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)

java.lang.NoClassDefFoundError: Failed resolution of: Landroid/support/v7/app/ActionBarActivity;

Getting error at runtime :

java.lang.NoClassDefFoundError: Failed resolution of: Landroid/support/v7/app/ActionBarActivity;
at xiaofei.library.hermes.util.TypeUtils$1.(TypeUtils.java:51)
at xiaofei.library.hermes.util.TypeUtils.(TypeUtils.java:48)
at xiaofei.library.hermes.util.TypeCenter.register(TypeCenter.java:94)
at xiaofei.library.hermes.Hermes.register(Hermes.java:71)
at xiaofei.library.hermeseventbus.HermesEventBus.init(HermesEventBus.java:122)
at com.cloudblocks.android.user.MainApplication.onCreate(MainApplication.java:44)
at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1036)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4728)
at android.app.ActivityThread.-wrap1(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1415)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5443)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:728)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:618)

I think its due to add(ActionBarActivity.class); in > xiaofei.library.hermes.util.TypeUtils.java

For temporarily solving this issue, I created a empty class android.support.v7.app.ActionBarActivity.java and extend to AppCompatActivity and my app starting running again :)

package android.support.v7.app;

/**
 * Created by blackadmin on 07/10/17.
 */

public class ActionBarActivity extends AppCompatActivity {
}

Can't send or receive data larger then 100k

For example, I changed this code in demo UserManager class.

    @MethodId("getUser")
    @Override
    public String getUser() {
        // I tried 64k works.
        return new String(new byte[1024*100]);
    }

This got exception:

07-07 15:59:59.325 25466-25466/? E/JavaBinder: !!! FAILED BINDER TRANSACTION !!!
07-07 15:59:59.325 25466-25466/? E/HERMES_INVOCATION: Error occurs. Error 1: Remote Exception: Check whether the process you are communicating with is still alive.

I suppose this hit the limit of android IPC max data size. But 100k or 10m is not a big size for modern android device.

Activity 作为参数传递

我看了代码,如果Activity作为参数传递,接收到的是对方进程的Application context . 正常跳转是没问题,但是如果是需要返回值的startActivityForResult。就需要取得Activity的实例进行跳转。context 是不包含此方法的。我要怎么去获取传递过来的Activity 的实例?

关于这个库的个人理解~

@ClassId(“Singleton”)
public interface ISingleton {

    @MethodId(“setData”)
    void setData(String data);

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

}

@ClassId(“Singleton”)
public class Singleton {
    ...

    @MethodId(“setData”)
    public void setData(String data) {
        mData = data;
    }

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

}
  1. ISingleton 基本就是 java 版的 AIDL
  2. Singleton 可以看做 extends ISingleton.Stub (代入到AIDL视角的话)
  3. 底层由 Service + Binder 实现
  4. 使用了动态代理,某种程度上有点像Retrofit

不知道有没有理解偏了。

不理解

看了半天,还是不理解。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.