In Android development, it is common to adopt a multi-module project structure as projects grow in size. It is also possible to use a plug-in development model, but it is up to the developer to decide which development model to use. Here’s a look at a modular development mechanism I’m familiar with. The essence is based on Gradle multi-project construction and Java dynamic proxy mechanism.

This solution is now open source: github.com/stefanJi/An…

// settings.gradle

include ':app'.':feature_a'.':feature_b'.':feature_c'
Copy the code

When using this mechanism: For example, when developing feature_A, only feature_A code changes will be involved, so developers can make Gradle not compile code in other modules (in settings.gradle comments modules that are not needed), thus reducing the compile time for local development. It is also possible to have some code that only exists during development (such as the admin module provided separately for testing purposes).

Selective compilation

At the same time, in order to avoid the dependency between modules, resulting in individual modules not compiling the goal can not be achieved. Therefore, each module can only rely on the interface provided by a public module (such as APP module) instead of directly relying on concrete implementation.

The concrete implementation is:

Define the functionality provided by the feature_A module in the app module, such as opening an Activity in Feature_A.

public interface IFeatureA {
    @Nullable
    public Class<Activity> getActivityOfA(a);
}
Copy the code

Then implement this interface in Feature_A:

public class FeatureA implements IFeatureA {
    public Class<Activity> getActivityOfA(a) {
        returnFeatureAActivity.class; }}Copy the code

Then inject the specific implementation of IFeatureA interface into the APP:

public class FeatureRegister {

    private static IFeatureA featureA;

    @Nullable
    public static IFeatureA getFeatureA(a) {
        if (featureA == null) {
            try {
                // Reflection is used here to compile successfully even if the Feature_A module is not compiled
                featureA = (IFeatureA) Class.forName("io.github.stefanji.feature_a.FeatureA").newInstance();
            } catch (ClassNotFoundException e) {
                // If Feature_A is not compiled, an exception will be raised}}returnfeatureA; }}Copy the code

Then Feature_B needs to get feature_A’s FeatureAActivity:

IFeatureA featureA = FeatureRegister.getFeatureA();
Class<Activity> activityClass = null;
if(featureA ! =null) {
    activityClass = featureA.getActivityOfA();
};
Copy the code

Dynamic proxy is not added to the compiled module

Above we have been able to achieve some modules without compiling, and the project as a whole compiles without problems. However, every time to access the functions provided by other modules, the implementation from the APP module needs to be nulled. Not very elegant.

Dynamic proxies can then be used to return a proxy object that implements the functional interface of the target module if the target module is not compiled. Modify the code in app as follows:

@NotNull
public static IFeatureA getFeatureA(a) {
    if (featureA == null) {
        try {
            featureA = (IFeatureA) Class.forName("io.github.stefanji.feature_a.FeatureA").newInstance();
        } catch (ClassNotFoundException e) {
        }
        // If the Feature_A module is not compiled and the Feature_A class cannot be found, a Proxy class will be dynamically generated
        if (featureA == null) {
            featureA = (IFeatureA) Proxy.newProxyInstance(FeatureRegister.class.getClassLoader(),
                    new Class[]{IFeatureA.class},
                    new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            Class returnType = method.getReturnType();
                            // Make the primitive type return zero
                            if (returnType == boolean.class) {
                                return false;
                            }
                            if (returnType == int.class) {
                                return 0;
                            }
                            if (returnType == float.class) {
                                return 0f;
                            }
                            / /...
                            // Make the reference type return null
                            return null; }}); }}return featureA;
}
Copy the code

So you don’t have to bet against:

if(featureA ! =null) {
    activityClass = featureA.getActivityOfA();
};
Copy the code

automation

For each new module, we need to repeat the previous steps to register the new Feature interface in FeatureRegister. This repeated operation can of course be done by using an annotation processor or a code generation mechanism such as gradle Transform to have the compiler automatically generate the template code.