Moment For Technology

Android business componentization development practice

Posted on Sept. 23, 2022, 10:02 p.m. by Anthony Gifford
Category: android Tag: android
This article was originally published, please indicate the address in the form of a link:

Componentization is not a new topic, but it has been discussed for a long, long time since we started decoupling projects. But back then we were talking about functional componentization. For example, many companies are common, the network request module, login registration module out separately, to a team development, and in use only need to access the function of the corresponding module.

Today, we are going to discuss the componentization of business. Take out your mobile phone and open Taobao or Dianping to have a look. The food, film and hotel takeout services are one by one. If we are writing in a project, there will always be more or less code coupling, most typically by copying and pasting a similar piece of code in order to catch up with the line time. As a result, the source referenced in this piece of code may be a separate source or code from another module. However, if a project is run as a standalone project, this situation can be completely avoided. But this is not the biggest advantage of business componentization. I think the biggest advantage is that it greatly reduces the engineering structure and directly reduces the compile time.

Code implementation

Note that componentization is not plug-in, plug-in is at run time and componentization is at compile time. In other words, pluginization is based on multiple APKs, while componentization is essentially just one APK.

Code implementation of the core ideas to remember a word: development is application, release is library. Let's look at some gradle code:

if (isDebug.toBoolean()) {
    apply plugin: ''
} else {
    apply plugin: ''
Copy the code

It is easy to understand that we will use module if it is a library during, if it is an, we use a switch to control this state switching.

And since we need to switch between Library and application, the manifest file also needs to provide both.

What about chestnuts?

You can watch it together according to the project:

Suppose you have a project that contains a module called Explorer for file browsers and a module called memory-box for notes. Since these two functions are relatively independent, we split the two functions into two modules, plus the app Module of the original project, making a total of three. Create a properties file in the root directory of the Explorer as a switch (write a global variable, however simple) that can be used to change whether the current development state or release state (debug Release). Read the values in this file from Gradle to switch configurations that need to be called for different states. By the way, when you modify the values in the Properties file, you must sync again. The detailed configuration process can be found in the article

Problems encountered

Abramovich's project uses a lot of databinding and dagger, while ours does not use these. You can see how he climbs the pit by using these two libraries

When you use componentized development, you will encounter these problems, which can only be avoided except the third, there is no good solution:

(1) Application call in Module; (2) Activity or Fragment jump across Module; (3) repeated dependency on AAR or Library Project; (4) Resource name conflict

Resolving Application conflicts

Since the Module exists as an application during development, if the Module calls code like ((XXXApplication)getApplication()).xxx(), A class conversion exception must occur when the project is finally released. Because a Module in debug is an application, and a Module in Release is just a lib. This means that an Application object obtained at debug and Release is not of the same class. This problem is ok, as long as we try not to write method implementation in application, do not make strong operation good. If you really want to distinguish between business modules that behave differently in the Debug and Release states, you can extend the BuildConfig class to perform different logic in your code with Boolean values. Just add it to Gradle (see [line:48] for code usage) :

if (isDebug.toBoolean()) {
    buildConfigField 'boolean', 'ISAPP', 'true'
} else {
    buildConfigField 'boolean', 'ISAPP', 'false'
Copy the code

Some people like to singleton the application, write a static object, and then use the global singleton whenever you need the context in your code. In this case, I give you a utility class (actually stolen from Teacher Feng's code) : Common

public class App { public static final Application INSTANCE; static { Application app = null; try { app = (Application) Class.forName("").getMethod("getInitialApplication").invoke(null); if (app == null) throw new IllegalStateException("Static initialization of Applications must be on main thread."); } catch (final Exception e) { LogUtils.e("Failed to get current application from AppGlobals." + e.getMessage()); try { app = (Application) Class.forName("").getMethod("currentApplication").invoke(null); } catch (final Exception ex) { LogUtils.e("Failed to get current application from ActivityThread." + e.getMessage()); } } finally { INSTANCE = app; }}}Copy the code

Across the module jump

If it is an Activity jump alone, it is common to implicitly start the Activity or define a scheme jump. However, if the interface is a Fragment, it is more troublesome. I recommend jumping directly through the class name.

Start by creating a list of all interface class names

public class RList {
    public static final String ACTIVITY_MEMORYBOX_MAIN = "";
    public static final String FRAGMENT_MEMORYBOX_MAIN = "";

Copy the code

When fetching a Fragment, you can read the specified Fragment based on the class name in the list.

public class FragmentRouter { public static Fragment getFragment(String name) { Fragment fragment; try { Class fragmentClass = Class.forName(name); fragment = (Fragment) fragmentClass.newInstance(); } catch (Exception e) { throw new RuntimeException(e); } return fragment; }}Copy the code

In the same way, activities can also be jumped in this way:

public static void startActivityForName(Context context, String name) { try { Class clazz = Class.forName(name); startActivity(context, clazz); } catch (ClassNotFoundException e) { throw new RuntimeException(e); }}Copy the code

Finally, for the RList class, we can also generate it from Gradle scripts, just like R files, which makes development much easier.

Repeat rely on

Double dependencies are A common problem in development, such as when you compile an A, then compile A B in the library, and then compile the same B in your project, and then rely on it twice.

By default, if it is an AAR dependency, Gradle will automatically find the new version of the library for us and discard duplicate dependencies from the old version. However, if you are using a project dependency, Gradle does not eliminate the duplication and you end up with duplicate classes in your code.

One is to change compile to provided and compile the corresponding code only in the final project.

You can also use this scheme:

All dependencies can be written in the shell module. The shell does nothing but unify all dependencies into a single entry for the upper app to import, and all dependencies of a project can be written in the shell Module.

Resource name conflict

Because there are multiple modules, there will always be resource reference conflicts when merging projects, such as two modules defining the same resource name. This problem is not new, do SDK basic encounter, can be set to the resourcePrefix to avoid. After setting this value, all of your resource names must be prefixed with the specified string, otherwise an error will be reported. However, the resourcePrefix value only applies to resources in the XML, not to image resources, all of which still require you to manually change the resource name.

The project structure

App is the directory of the final project. Explorer and memory-Box are two functional modules, which are independent applications in the development stage and introduced into the project as library at release. The router has two functions. One is a route to redirect the interface. Another function is the shell mentioned above, which acts as a dependency set for each business module to access. Base-res are the generic code that every business module accesses, which is introduced into the Router.

The final code can be viewed:

If you think this article is good, you can also follow the public account [Zhang Tao's Open Source Lab] to receive the latest blog push

About (Moment For Technology) is a global community with thousands techies from across the global hang out!Passionate technologists, be it gadget freaks, tech enthusiasts, coders, technopreneurs, or CIOs, you would find them all here.