Githup Repository:ModularizationArchitecture
My Homepage:SpinyTech
Welcome to add star and follow.



ModularizationArchitecture is a routing-based multi-process, component-based architecture on the Android platform: it communicates with different modules and processes by sharing routes without referring to other modules. It is suitable for medium-sized App architecture team collaboration, parallel development, business line decoupling, white-box testing and other scenes.

1. Definition of terms

  • Main App Module:The main module of the project, which can be run on the mobile.
  • Library Moudule:The library of the project. It is a business module or a functional component of the project.
  • Test App Module:The main module of the project, which can be run on the mobile. It used to test a library module.
  • Action:The specific implementation of Cross-module calls.
  • ActionResult:The result returned by the Action invoke.
  • Provider:Provider is a actions cluster, It make register actions easier.
  • RouterRequest:The request when invoke an action.
  • RouterResponse:The response when an action invoked completed.
  • LocalRouter:The local router.
  • WideRouter:The multiple process wide router.
  • LocalRouterConnectService:The guard service of local router, used to connect with the wide router, perform AIDL cross-process communication.

2. Getting start

2.1 Integration in the project

First of all, we need to add the following code into the build.gradle of the Main App Module and all Library Moudule.

1
compile 'com.spinytech.ma:macore:0.2.1'

2.2 Create a custom Application

Create a custom Application that inherits from the MaApplication

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class CustomApplication extends MaApplication {

// Register the local router for multiple processes
, It is described in detail in chapter 2.7.1
@Override
public void initializeAllProcessRouter() {
}

// Register the logic of application, It is described in detail in chapter 2.3
@Override
protected void initializeLogic() {
//The first argument represents the process name, the second indicates the initialization priority, and the third is the Application logic class, which is instantiated by reflection when needed.
registerApplicationLogic("com.spinytech.maindemo",999, MainApplicationLogic.class);
}

// The value of this methed returned means whether the app is multi-process, if it is a multi-process returns true, otherwise returns false.
@Override
public boolean needMultipleProcess() {
return false;
}
}

Please note:
The package name in registerApplicationLogic("com.spinytech.maindemo", 999, MainApplicationLogic.class) must be passed in constant. Please do not use the context.getPackageName () method instead, otherwise the multiprocessing can not register the application logic correctly.

Then add the following code to your AndroidManifest.xml

1
2
3
4
5
6
7
<application
...
android:name="xxx.xxx.CustomApplication"
...
>
...
</application>

2.3 Create a custom ApplicationLogic

In the initializeLogic method, you need to separate the application logic from the Application.The reason for the separation, because if the introduction of multi-process, Application will be initialized many times. If the application logic has separated, each Application initialization, for different processes, initialization of different logic.

The lifecycle of ApplicationLogic and Application are the same, they all have onCreate, onTerminate, onLowMemory method and so on.

Create a custom ApplicationLogic that inherits from the BaseApplicationLogic

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class CustomApplicationLogic extends BaseApplicationLogic {
@Override
public void onCreate() {
// Register providers. It is described in detail in chapter 2.4
LocalRouter.getInstance(mApplication).registerProvider("util",new UtilProvider());
}

@Override
public void onTerminate(){}

@Override
public void onLowMemory(){}

@Override
public void onTrimMemory(int level){}

@Override
public void onConfigurationChanged(Configuration newConfig) {}

}

2.4 Create a custom provider

Provider data structure is actually a HashMap, its function is to tie multiple Actions together, to facilitate the local router to register.

1
2
3
4
5
6
public class UtilProvider extends MaProvider {
@Override
protected void registerActions() {
registerAction("md5",new MD5EncryptAction());
}
}

When the Provider is created, then register it to local router. It is usually registered in onCreate in ApplicationLogic (see 2.3).

1
LocalRouter.getInstance(mApplication).registerProvider("util",new CustomProvider());

The first parameter “util” is the provider key, it can be customized, It will be used later when we invoke the action.

2.5 Create a custom Action

Each Action is a concrete implementation that provides to the invoke. We need to inherit and overwirte it.

There are two methods need to be overwriten, one is isAsync(), the other is invoke().

isAsync() need to tell the caller whether the action is asynchronous. If it is asynchronous(such as network, IO and so on.), then return true, if it is synchronous return false. The reason for adding this judgment is that some action may be called in the main thread, we need to determine whether the action is time-consuming, and thus decide whether to start a new thread to invoke the action.

Invoke () method is the actual implementation of the call. The method has a context Context and a set of request dataHashMap <String, String>, which returnsAcationResult as a result back to the caller.

The data structure of AcationResult

1
2
3
4
5
{
code:xxx,
msg:xxx,
data:xxx
}

The implementation of the method is similar to that of a Web application, where a parameter is a set of key-value pairs, and the result is processed after a series of processing.

Here we give a simple MD5 encryption example, create a custom MD5EncryptAction inherited fromMaAction

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class MD5EncryptAction extends MaAction {

@Override
public boolean isAsync(Context context, HashMap<String, String> requestData) {
return false;
}

@Override
public MaActionResult invoke(Context context, HashMap<String, String> requestData) {
String result = "";
if(!TextUtils.isEmpty(requestData.get("params_1"))){
result +=requestData.get("params_1");
}
if(!TextUtils.isEmpty(requestData.get("params_2"))){
result +=requestData.get("params_2");
}
// The MD5Util is a dummy util, here we do not implement it.
result = MD5Util.md5(temp);

MaActionResult result = new MaActionResult.Builder()
.code(MaActionResult.CODE_SUCCESS)
.msg("success")
.data(result)
.object(null)
.build();
return result;
}
}

When the Action is created, then register it to Provider.

1
registerAction("md5",new MD5EncryptAction());

The first parameter “md5” is the action key, it can be customized, It will be used later when we invoke the action.

2.6 Invoke

In previous chapters, we have defined everything, and we’ll invoke this chapter.

1
2
3
4
5
6
RouterResponse response = LocalRouter.getInstance(MaApplication.getMaApplication())
.route(context, RouterRequest.obtain(context)
.provider("util")
.action("md5")
.data("params_1", "Hello")
.data("params_2", "World"));

First we get the instance of LocalRouter.

1
LocalRouter.getInstance(MaApplication.getMaApplication())

Then use route to invoke.

1
route(Context context,RouterRequest request)

2.6.1 Create the RouterRequest

1
2
3
4
5
RouterRequest.obtain(context)
.provider("util")
.action("md5")
.data("params_1", "Hello")
.data("params_2", "World");

This code means that find the registration of the name “util” of the provider, and then find its registered name “md5” action. The two parameters is “params_1”, “params_2”.

Send the router request to the local router to finish the invoke.

Note: RouterRequest uses the object memory pool technology, will be recycled after use, so do not hold a reference to RouterRequest, in order to avoid unnecessary errors.

2.6.2 Deal with RouterResponse

The route() method will return the instance of RouterRequest. This method is nonblocking and returns RouterResponse immediately after invoke. The isAsync() method of RouterResponse tells you whether the call is asynchronous or not. Theget()method ofRouterResponse returns the result of that call. Please note that the get() method may cause blocking, depending on isAsync(), so please be careful when you use the get() method in main thread.

2.7 Multiple process

In chapter 2.2, overwirte the needMultipleProcess() method and set the return true.

1
2
3
4
@Override
public boolean needMultipleProcess() {
return true;
}

2.7.1 Create connect service, register local router

Because it involves multiple processes, so we need to use AIDL for inter-process communication. Therefore, each process within the app, you need to create a connection Service, used to communicate with the wide router.

Create a custom Service inherited from LocalRouterConnectService, we do not need to write any code.

1
public class MainRouterConnectService extends LocalRouterConnectService {}

Register the connect service in Androidmanifest.xml

1
<service android:name="xxx.xxx.MainRouterConnectService"/>

Then register the connect service in initializeAllProcessRouter() method.
The first parameter of WideRouter.registerLocalRouter method is the process name, the second is the class of the connect service. See the following code.

1
2
3
4
5
@Override
public void initializeAllProcessRouter() {
WideRouter.registerLocalRouter("com.spinytech.maindemo",MainRouterConnectService.class);
WideRouter.registerLocalRouter("com.spinytech.maindemo:music",MusicRouterConnectService.class);
}

2.7.2 Cross-process invoke

Cross-process invoking is similar to the local invoking. But in generating RouterRequest time, we need to add a Domain, Domain is the process name. (When you obtain the RouterRequest, if not add domain, the default domain is the current process name)

1
2
3
4
5
RouterRequest
.obtain(context)
.domain("com.spinytech.maindemo:music")
.provider("music")
.action("play");

2.8 RouterRequest with the attachment Object and RouterResponse with the return Object (Added in v0.2.0)

In the v0.2.0 version, we added a function that we can add an attach object into the RouterRequest. (only in a single process).

In RouterRequest, the object(Object attachment) method is added to add the attachment object to the request.

In the abstract class MaAction, we added two methods

1
2
Public boolean isAsync (context context, HashMap <String, String> requestData, Object object)`  
Public MaActionResult invoke (Context context, HashMap <String, String> requestData, Object object)

By judging whether or not RouterRequest carries the attachment object,LocalRouter will call the different invoke methods.

Related code:

1
2
RouterResponse.mIsAsync = attachment == null? TargetAction.isAsync (context, params): targetAction.isAsync (context, params, attachment);
MaActionResult result = attachment == null? TargetAction.invoke (context, params): targetAction.invoke (context, params, attachment);

When you return the result, you can use the object(Object returnedObject) method in MaActionResult.Builder to return an object instance to the caller, using the getObject() method of RouterResponse to get the returned Object.

Please note: Because the inter-process memory is not shared, all attachments Object can only be passed in the process. The attachment object of the process will be ignored by default, please do not cross the process with the attachment Object call.

For details, please refer to: [AttachObjectAction.java] (https://github.com/SpinyTech/ModularizationArchitecture/blob/master/maindemo/src/main/java/com/spinytech/maindemo/AttachObjectAction.java)

3. Principle

The principles of this architecture have not yet been translated into English. You can refer to the following article, and use google translate to read. If you have any questions, please email me.

《Android架构思考》

4. Defect

  • Calls between two Apks of the same ShareUid are not supported at this time.
  • Need to inherit from MaApplication, the original Application code may be changed.

5. Future plans

  • Add the Action fail switch so that it can be dynamically controlled by the server.
  • Add the Action hot fix feature.
  • Optimize the process of packaging and unpacking in the process of message passing.
  • Add SocketAction to make the Action can instant messaging.

6. Version

v0.2.1

  • Fixed the bug that In synchronization method of RouterResponse can not get code, message, data.

v0.2.0

  • Added a new method object(Object attachment) of RouterRequest that make Action can incomming a object parameter.
  • The Action can return an attachment object by MaActionResult.
  • Fixed some bugs.
  • New example is added to maindemo.

Original article, reproduced please contact the author: spiny.tech@gmail.com