Related to the author: the author: green bamboo blog: http://www.jianshu.com/p/1b1d77f58e84 copyright owned by the author, this article is made by authorization of the author to push.

Beginning in June this year, I began to be responsible for “get the app” android code modular split, before start work, I consult a lot of componentization or modular, although there are some gains, but there are few articles can give a overall and effective solutions, most of the articles are only in the component level, separate debugging There is very little interaction between components involved, let alone the trickiest issues of the component life cycle, integration debugging, and code boundaries. With this in mind, I feel it is necessary to design a complete set of componentization scheme. After several weeks of thinking, repeated demolition and reconstruction, finally formed a complete idea, organized in my first article Android complete componentization scheme practice.

In the past two months, the Android team has started component-splitting according to this scheme. After two phases of efforts, two big business components and several low-level lib libraries have been split, and some improvements have been made to the previous scheme. From the perspective of application effect, this scheme can fully meet our previous expectations of componentization, with simple structure and low learning cost, which is very suitable for a project in urgent need of rapid componentization separation. Now open source this program out, welcome to improve together. The code address: https://github.com/mqzhangw/AndroidComponent

Although open source is an overall scheme, the amount of code is actually very small, for the sake of simplicity in the demo to do some simplification, please pay attention to the points in the practical application: (1) Component-compiled scripts are provided by a Gradle plugin, which is now published in the local repo folder. The actual use of the plugin should be published in your own maven library. (2) After the component development, publish the AAR to the public repository. In the demo, this repository is replaced by the ComponentRelease folder, which also needs to be replaced by the local Maven library (3). The solution focuses on separate debugging, integrated compilation, life cycle and code boundaries, which I think are lacking or vague in published componentization solutions. The interaction between components is realized by interface +, and the jump between UIs is realized by a central route. At present, there are some more perfect schemes in these two aspects, such as exposing services through annotations and automatically generating UI jump codes, which is also the place that needs to be optimized in the future. If you have a better plan, can replace, more welcome to recommend to me.

AndroidComponent Guide

We take a look at the demo code structure first, and then according to this chart again from a separate debugging (released), component interaction, UI jump, integrated debugging, code boundary and life cycle and so on six aspects in-depth analysis, say “again”, because we have an article about this the principle of six aspects, this article focuses on its concrete implementation more.

Each module in the code basically corresponds to the figure, from top to bottom:

  • App is the main project, responsible for integrating many components and controlling the life cycle of components

  • Reader and Share are the two components we split

  • Componentservice defines the services provided by all components

  • Basicres defines common resources such as theme and color that are globally common

  • Basiclib is a common base library, and some third-party libraries (okhttp, etc.) are also handed over to Basiclib for import

There are two modules not shown in the figure, one is ComponentLib, which is our componentized base library, such as Router/UIRouter are defined here; The other is build-Gradle, which is our componentized gradle plug-in and the core of the componentized solution.

The scenario we want to realize in the demo is as follows: The main project APP integrates reader and Share components, wherein Reader provides a fragment for app to call reading (component interaction), and Share provides an activity for Reader to call (UI jump). The main project app can dynamically add and uninstall share components (lifecycle). Integration debugging and code boundaries are implemented through build-Gradle plug-ins.

1 Debug and release them separately

The configuration for debugging alone is basically the same as in the previous article. You can differentiate between scenarios by setting an isRunAlone variable in the gradle.properties file of the component project. The only difference is that you do not need to write the following boilerboard code in the build.gradle file of the component:

if(isRunAlone.toBoolean()){    
apply plugin: 'com.android.application'}else{  
 apply plugin: 'com.android.library'}Copy the code

This plugin automatically determines whether to apply com.android.library or com.android.application. There are actually more “smart” things the plug-in can do, which are covered in the integration debugging section.

The androidmanifest.xml, application, entry activity classes that are required for standalone debugging are defined under SRC /main/runalone, so I won’t go into the details.

If the component is developed and tested and a release version of the AAR file needs to be published to the central repository, just change isRunAlone to false and run assembleRelease. This is not versioning for simplicity, so if you need to add it yourself, you can do it. It is worth noting that publishing a component is the only case where you need to change isRunAlone=false. Even if you integrate the component into your app later, you do not need to change the value of isRunAlone, just keep isRunAlone=true. So in Androidstudio, you can see three application projects, click any one can run independently, and you can introduce other dependent components according to the configuration. The com.dd.comgradle plugin does all the work behind the scenes.

2 Component Interaction

Here, the interaction of components specifically refers to the data transmission between components. In our scheme, interface + implementation is used, and interface programming is completely used between components.

In the demo we asked the Reader to provide a fragment for the app to use to illustrate this. First, the Reader component defines its own service in ComponentService

public interface ReadBookService {         Fragment getReadBookFragment();
}Copy the code

Then, in your component project, provide the concrete implementation class ReadBookServiceImpl:

public class ReadBookServiceImpl implements ReadBookService { @Override public Fragment getReadBookFragment() { return new ReaderFragment(); }}Copy the code

After providing the implementation class, you need to register the implementation class with the Router when the component is loaded. The specific code is in ReaderAppLike. ReaderAppLike is equivalent to the application class of the component. Load and uninstall corresponding components.

public class ReaderAppLike implements IApplicationLike { Router router = Router.getInstance(); @Override public void onCreate() { router.addService(ReadBookService.class.getSimpleName(), new ReadBookServiceImpl()); } @Override public void onStop() { router.removeService(ReadBookService.class.getSimpleName()); }}Copy the code

How to use a ReaderFragment like the reader component in an app? Note that the app doesn’t see any implementation classes for the component. It only sees ReadBookService defined in ComponentService, so it can only program for ReadBookService. The specific example code is as follows:

Router router = Router.getInstance(); if (router.getService(ReadBookService.class.getSimpleName()) ! = null) { ReadBookService service = (ReadBookService) router.getService(ReadBookService.class.getSimpleName()); fragment = service.getReadBookFragment(); ft = getSupportFragmentManager().beginTransaction(); ft.add(R.id.tab_content, fragment).commitAllowingStateLoss(); }Copy the code

It is important to note that since components can be loaded and unloaded dynamically, nullifying is required when using ReadBookService. We can see that data transmission is realized through a central Router. The implementation of this Router is actually very simple. Its essence is a HashMap.

The above steps make it easy to interact with components, which are completely decoupled because they are interface oriented. As for how to make components invisible at compile time, this is done with the com.dd.comgradle mentioned above. This was covered in the first article, and detailed code will be posted later.

3 the UI jump

Page jumps are also implemented through a central route, UIRouter, with the added concept of priority. (This piece of code refers to the implementation ideas of my former technology boss in netease. Thank you here, boss, you will always be my boss.) The specific implementation will not be described here, the code is very clear.

To jump to a page via a short chain, for example, to jump to the share page, just call

UIRouter.getInstance().openUri(getActivity(), "componentdemo://share", null);Copy the code

Which component responds to the short chain ComponentDemo ://share? This depends on which component handles the schme and host. In the demo, the share component declares itself to handle the short chain in its ShareUIRouter implementation, as follows:

private static final String SCHME = "componentdemo"; private static final String SHAREHOST = "share"; public boolean openUri(Context context, Uri uri, Bundle bundle) { if (uri == null || context == null) { return true; } String host = uri.getHost(); if (SHAREHOST.equals(host)) { Intent intent = new Intent(context, ShareActivity.class); intent.putExtras(bundle == null ? new Bundle() : bundle); context.startActivity(intent); return true; } return false; }Copy the code

In this case, return true if an existing component has already responded to the short chain, so that lower priority components do not receive the short chain.

The current logic for jumping to schme and host is written by the developers themselves, which will be modified to generate from annotations later. There are already some excellent open source projects to look at in this section, such as ARouter and others.

4 Integration Debugging

Integration debugging can be thought of as app or other components acting as host, introducing other related components to participate in compilation, so as to test the whole interaction process. Both app and Reader can act as hosts in the demo. Let’s take app as an example.

First we need to add a variable mainModulename to the root project’s gradle.properties, whose value is the main project in the project, in this case app. Module set to mainModulename whose isRunAlone is always true.

Then add two variables to your app project’s gradle.properties file:

debugComponent=readercomponent,com.mrzhang.share:sharecomponent
compileComponent=readercomponent,sharecomponentCopy the code

The debugComponent is imported when you run debug, and the compileComponent is imported when you run Release. We can see that the two components introduced by the debugComponent are written differently because component imports support two syntax, module or modulePackage:module, which refers directly to the Module project. The latter uses aArs already published in ComponentRelease.

Note that reader and Share components introduced during integration debugging do not need to change their isRunAlone values to false. We know that one application project cannot directly compile another application project, so if both app and component are isRunAlone=true, it will not compile properly under normal circumstances. The secret is that Gradle automatically identifies which component is being debugged and modifies the rest of the components as library projects. This change only takes effect when compiling.

How do you know if you’re running an app or which component? This is determined by task according to the following rules:

  • AssembleRelease – app

  • App :assembleRelease or :app:assembleRelease → app

  • Sharecomponent: assembleRelease or: sharecomponent: assembleRelease – sharecomponent

The goal of the above is that each component can be run directly in Androidstudio, or packaged using commands, without any configuration changes, while importing dependent components automatically. This can greatly speed up productivity in development.

5 Code Boundaries

As for how dependent components are integrated into the host, the essence is to use compile Project (…) directly. Or compile modulePackage:module@aar. Why not build gradle directly instead of using com.dd.comgradle for complex operations? The reason for this, as discussed in the first article, is complete isolation between components, also known as code boundaries. If we compile the component directly, all of its implementation classes are fully exposed and users can program directly into the implementation classes, bypassing the constraints of interface oriented programming. The decoupling effect is completely lost.

So how to solve this problem? Our solution is to analyze the task again, and compile is introduced only when the task is assembled. This way the components are completely invisible during the development of the code, thus eliminating the opportunity to make mistakes. The specific code is as follows:

/** * Dependencies are automatically added, only when the Assemble task is running, so components are completely invisible to each other during development, which is key to complete isolation. * Two syntax are supported: Module or modulePackage:module, referring to the Module project between the former, The latter uses aar * @param assembleTask * @param Project */private void compileComponents(assembleTask) already published in ComponentRelease assembleTask, Project project) { String components; if (assembleTask.isDebug) { components = (String) project.properties.get("debugComponent") } else { components = (String) project.properties.get("compileComponent") } if (components == null || components.length() == 0) { return; } String[] compileComponents = components.split(",") if (compileComponents == null || compileComponents.length == 0) { return; } for (String str : compileComponents) { if (str.contains(":")) { File file = project.file(".. /componentrelease/" + str.split(":")[1] + "-release.aar") if (file.exists()) { project.dependencies.add("compile", str + "-release@aar") } else { throw new RuntimeException(str + " not found ! maybe you should generate a new one ") } } else { project.dependencies.add("compile", project.project(':' + str)) } } }Copy the code

6 Life Cycle

As we discussed in the last article, the only difference between componentization and plug-in is that componentization cannot dynamically add and modify components, but can dynamically load and unload components that have already been compiled, and even reduce dimension.

First let’s look at component loading. Using integration debugging in Section 5, you can compile dependent components at package time. When you decompile the APK code, you will see that the code and resources of each component are already included in the package. But since ApplicationLike, the unique entry for each component, has not yet executed the onCreate () method, the component does not register its services with the central route, so the component is effectively unreachable.

When and how do components load? Currently com.dd.com Gradle provides two methods, bytecode insertion and reflection call.

  • Bytecode insertion mode scans all ApplicationLike classes (which have a common parent) before the dex is generated, The code that calls ApplicationLike.onCreate() is then inserted in the main project application.oncreate () via Javassisit. This means that each component is loaded when the application starts.

  • The reflection calls are made manually in application.oncreate () or at other appropriate times by manually reflecting applicationLike.oncreate (). There are two reasons for this: scanning and inserting code increases compile time, especially during debugging, and this mode does not support Instant Run well. Another reason is more flexibility in controlling loading and unloading times.

The two modes are configured by configuring Com.dd.com Gradle’s Extension. The following is the configuration format for bytecode insertion mode. The purpose of adding applicatonName is to speed up the locating of applications.

combuild {
    applicatonName = 'com.mrzhang.component.application.AppApplication'
    isRegisterCompoAuto = true}Copy the code

There are two buttons on the home page of the APP, one is to load the shared component, the other is to uninstall the shared component. You can click the button at will to load or uninstall the component during running. You can run the demo to check the specific effect.

Two, the perception of componentization split

In the recent two months of componentized separation, finally realized how difficult it is to do the stripping of silk cocoons. It is important to determine a plan, but it is more important to overcome all difficulties and carry it out firmly. In split, componentized solution has been fine-tuning, finally can happy to say, this project is through the test, first it is low in cost, colleagues in the group can quickly, the second effect is obvious, it had run a need for 8 to 10 minutes, but back in the top with a MAC, speed up a lot of), Now a single component can do it in about a minute. The most important thing is that the code structure is much clearer, the later parallel development and plug-in laid a solid foundation.

In summary, if you have a huge project in front of you, it is recommended that you use this solution and start componentizing as soon as possible with minimal cost. If you are currently working on an early-stage project with a small amount of code, it is also advisable to plan for componentization as soon as possible, and not to overload your future self with useless work.

“Get APP” is developing rapidly, we are in urgent need of all Android and iOS talents to join us, as long as you meet the following points, that is what we need: 1. Passionate about mobile terminal development (Android /ios/ RN), preferably with 2-3 years of APP development experience. 2. Our office is located in a beautifully decorated single-family building along Chang ‘an Avenue in a very beautiful environment. As for the treatment, it is absolutely industry leading, as evidenced by our internal promotion award being the luxurious Apple three-piece set.

Recommended reading

Android thorough componentization scheme practice

2

Android APP Banner, this one is enough

3

Parallax Animation – Yahoo News Digest loaded

If you find this article helpful, please share it with others

Pay attention to Android technology grocery store, there are Android dry goods articles to share every day!

Long click the QR code to follow us