How to realize automatic management of module life cycle in Android componentization

Keywords: Java Android Apache

preface

Each application initializes an application class when it is started. We can do some initialization operations in this class, such as the initialization of a third-party sdk. After the component project is implemented, different business modules also need to do some initialization operations when the application is started. This involves the life cycle of the module. When the application is started, Each module can get the application. When the life cycle method inside the application is called, each module also has the corresponding life cycle method to be called

Preliminary implementation

1. Extract an IModuleLifeCycle class

The code is as follows

public interface IModuleLifeCycle {

    int MAX_PRIORITY = 12;
    int MIN_PRIORITY = 1;
    int DEFAULT_PRIORITY = 6;

    void onCreate(Application application);

    void onTerminate();

    /**
     * Returns the priority of the component. The priority range is [1-12]. 12 is the highest and 1 is the lowest. The default priority is 1
     * @return
     */
    int getPriority();

}

2. Implement the IModuleLifeCycle class

Create a new class in each module to implement the IModuleLifeCycle class. We regard this as the application class of the module. In this class, we can do some initialization operations, obtain the application object and do some initialization of global variables

3. How to manage the life cycle of components

Suppose there are ModuleA, moduleb and modulec, and the three modules correspond to modulealifecycle, moduleblifecycle and moduleclifecycle respectively. The IModuleLifeCycle class is used to realize the life cycle of the module. In order to realize the life cycle of the three modules, the simple way is to obtain the instances of these three classes in the application class, Then we call the onCreate method of ModuleALifeCycle, ModuleBLifeCycle and ModuleCLifeCycle in the onCreate method of application. The code is as follows:

@Override
public void onCreate() {
    super.onCreate();
    IModuleLifeCycle moduleA = new ModuleALifeCycle();
    IModuleLifeCycle moduleB = new ModuleBLifeCycle();
    IModuleLifeCycle moduleC = new ModuleCLifeCycle();
    moduleA.onCreate(this);
    moduleB.onCreate(this);
    moduleC.onCreate(this);
}        

This way is very simple, how many modules, initialization of how many module lifecycle classes, and then call onCreate(), but this method is not conducive to maintenance. Every time a new module is added, you need to create a new module class, and then use this class in application. Moreover, in the modular development mode, when each module runs independently, the shell project does not rely on each module, which will lead to the above code compilation error, so it is very undesirable. Therefore, there are two methods to optimize:

  • 1. define the path of each module lifecycle class in the configuration file, configure a json file in assets, define the path of each module's lifecycle class, then get these classes by reflection during the initialization of application, then call the life cycle method to complete the initialization of the module's life cycle. However, this method is not flexible enough, because the configuration file needs to be maintained, so the better method is not to maintain the configuration file

  • 2. Use APT + javapool to add annotations to module life cycle classes, then use APT to scan specific annotations, and then use javapool to generate life cycle proxy classes, which are placed in a specific directory. These proxy classes have instances of module life cycle classes. Each time the application is initialized, they will scan the classes with specific paths under dex to obtain the paths of these classes, Then get the instances of classes through reflection. These classes are the proxy classes generated by javapool. We will call the internal methods of these proxy classes, so as to call the methods of module life cycle classes to complete the initialization of each module life cycle

Final realization

It is implemented by apt + javapool

1. Implementation ideas

  1. Create a new class in the project to implement the IModuleLifeCycle class, and add specific annotations. The code is as follows
@ModuleLifeCycle(moduleName = "main", desc = "Main module")
public class MainLifeCycle implements IModuleLifeCycle {
    @Override
    public void onCreate(Application application) {
       //.. Will be called here when initializing the Application of the main project
       //.. Application is the application of the whole application
       // .. Complete the initialization of its own module here
        System.out.println("====>onCreate:"+application);
    }
    @Override
    public void onTerminate() {
       //.. Application destroyed
    }
    @Override
    public int getPriority() {
        return DEFAULT_PRIORITY;
    }
}

  1. APT will scan this specific annotation when compiling, and then use javapool to generate the proxy class of the module life cycle and put it in a specific path. The proxy class code is as follows:
public class MainLifeCycleProxy implements IModuleLifeCycle {
  public MainLifeCycle mModuleLifeCycle;

  public MainLifeCycleProxy() {
    mModuleLifeCycle = new MainLifeCycle();
  }

  @Override
  public void onCreate(Application application) {
    mModuleLifeCycle.onCreate(application);
  }

  @Override
  public int getPriority() {
    return mModuleLifeCycle.getPriority();
  }

  @Override
  public void onTerminate() {
    mModuleLifeCycle.onTerminate();
  }
}

  1. During the initialization of application, you can scan the classes under the specific path under dex to obtain the path of the class, then obtain the proxy class instance through reflection, and call the onCreate method of the proxy class, so as to call the onCreate method of the lifecycle class of each module to realize the module lifecycle. The scanning code is as follows:
/**
 * Scan all classnames contained under the package by specifying the package name
 *
 * @param context     U know
 * @param packageName Package name
 * @return Collection of all class es
 */
public static Set<String> getFileNameByPackageName(Context context, final String packageName) throws PackageManager.NameNotFoundException, IOException, InterruptedException {
    final Set<String> classNames = new HashSet<>();

    List<String> paths = getSourcePaths(context);
    final CountDownLatch parserCtl = new CountDownLatch(paths.size());

    for (final String path : paths) {
        DefaultPoolExecutor.getInstance().execute(new Runnable() {
            @Override
            public void run() {
                DexFile dexfile = null;

                try {
                    if (path.endsWith(EXTRACTED_SUFFIX)) {
                        //NOT use new DexFile(path), because it will throw "permission error in /data/dalvik-cache"
                        dexfile = DexFile.loadDex(path, path + ".tmp", 0);
                    } else {
                        dexfile = new DexFile(path);
                    }

                    Enumeration<String> dexEntries = dexfile.entries();
                    while (dexEntries.hasMoreElements()) {
                        String className = dexEntries.nextElement();
                        if (className.startsWith(packageName)) {
                            classNames.add(className);
                        }
                    }
                } catch (Throwable ignore) {
                    Log.e("ARouter", "Scan map file in dex files made error.", ignore);
                } finally {
                    if (null != dexfile) {
                        try {
                            dexfile.close();
                        } catch (Throwable ignore) {
                        }
                    }

                    parserCtl.countDown();
                }
            }
        });
    }

    parserCtl.await();

    return classNames;
}


2. Implementation process

1. Definition notes

Create a new Java Library module in Android studio, and then create an annotation class named modulelife cycle. The engineering structure is as follows:

The modulelife cycle code is as follows:,

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface ModuleLifeCycle {
    String moduleName();
    String desc();
}

2. Create an android module lib named module lifecycle API. The project structure is as follows:

IModuleLifeCycle is the interface of module life cycle. The code is as follows:

public interface IModuleLifeCycle {

    int MAX_PRIORITY = 12;
    int MIN_PRIORITY = 1;
    int DEFAULT_PRIORITY = 6;

    void onCreate(Application application);

    void onTerminate();

    /**
     * Returns the priority of the component. The priority range is [1-10]. 10 is the highest and 1 is the lowest. The default priority is 5
     * @return
     */
    int getPriority();

}

The getPriority() method indicates the priority of module loading. In a multi module project, the higher the priority, the first to initialize the life cycle. Modulelifcyclemanager is the management class of the life cycle, which is used to initialize each module according to the priority

3. Create a new java lib module named module lifecycle apt. the code structure is as follows:

In this module, we will use apt to scan annotations and generate proxy classes with javapool. The build.gradle configuration code of the module lifecycle apt library is as follows:

plugins {
    id 'java-library'
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    //This is an automatic service registration framework provided by Google, which needs to be used
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7'
    compileOnly 'com.google.auto.service:auto-service-annotations:1.0-rc7'
    implementation 'com.squareup:javapoet:1.10.0'
    implementation project(':module-lifeCycle-annotation')
}

java {
    sourceCompatibility = JavaVersion.VERSION_1_7
    targetCompatibility = JavaVersion.VERSION_1_7
}

Modulelife cycleprocessor calls apt's api to scan specific annotations. The code is as follows:

@AutoService(Processor.class)
public class ModuleLifeCycleProcessor extends AbstractProcessor {

    public Filer filer; //File related auxiliary classes
    public Elements elements; //Auxiliary class related to element
    public Messager messager; //Log related auxiliary classes

    private Map<String, CodeCreateFactory> codeCreateFactoryMap = new HashMap<>();

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);

        filer = processingEnv.getFiler();
        elements = processingEnv.getElementUtils();
        messager = processingEnv.getMessager();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> set = new LinkedHashSet<>();
        set.add(ModuleLifeCycle.class.getCanonicalName());
        return set;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();//After this is changed, the process method will come in twice to find out why
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        Set<? extends Element> elementSet = roundEnvironment.getElementsAnnotatedWith(ModuleLifeCycle.class);
        for (Element element : elementSet) {
            if (!element.getKind().isClass()) {
                throw new RuntimeException("ModuleLifeCycle Annotations can only be used on classes");
            }
            TypeElement typeElement = (TypeElement) element;
            if (codeCreateFactoryMap.containsKey(typeElement.getSimpleName().toString())) {
                codeCreateFactoryMap.get(typeElement.getSimpleName().toString()).generateCode();
            } else {
                CodeCreateFactory codeCreateFactory = new CodeCreateFactory(typeElement, processingEnv);
                codeCreateFactoryMap.put(typeElement.getSimpleName().toString(), codeCreateFactory);
                codeCreateFactory.generateCode();
            }
        }
        return true;
    }
}

CodeCreateFactory calls the api of javapool to generate the specified code and save it to a specific path. The code is as follows:

public class CodeCreateFactory {
    TypeElement typeElement;
    ProcessingEnvironment processingEnv;

    public CodeCreateFactory(TypeElement typeElement, ProcessingEnvironment processingEnv) {
        this.typeElement = typeElement;
        this.processingEnv = processingEnv;
    }

    public void generateCode() {
        TypeSpec ModuleFeedLifeCycleManger = TypeSpec
                .classBuilder(typeElement.getSimpleName().toString() + "Proxy")
                .addModifiers(Modifier.PUBLIC)
                .addSuperinterface(ClassName.get("com.quwan.moduleLifecycleApi", "IModuleLifeCycle"))
                .addField(FieldSpec
                        .builder(ClassName.get(typeElement.getQualifiedName().toString().replace("." + typeElement.getSimpleName().toString(), ""),
                                typeElement.getSimpleName().toString()), "mModuleLifeCycle", Modifier.PUBLIC)
                        .build())
                .addMethod(MethodSpec
                        .constructorBuilder()
                        .addModifiers(Modifier.PUBLIC)
                        .addStatement("mModuleLifeCycle = new $T()", ClassName.get(typeElement.getQualifiedName().toString().replace("." + typeElement.getSimpleName().toString(), ""),
                                typeElement.getSimpleName().toString()))
                        .build())
                .addMethod(MethodSpec
                        .methodBuilder("onCreate")
                        .addAnnotation(ClassName.get("java.lang", "Override"))
                        .addModifiers(Modifier.PUBLIC)
                        .addParameter(
                                ParameterSpec
                                        .builder(ClassName.get("android.app", "Application"), "application")
                                        .build()
                        )
                        .addStatement("mModuleLifeCycle.onCreate(application)")
                        .build())
                .addMethod(MethodSpec
                        .methodBuilder("getPriority")
                        .returns(INT)
                        .addAnnotation(ClassName.get("java.lang", "Override"))
                        .addModifiers(Modifier.PUBLIC)
                        .addStatement("return mModuleLifeCycle.getPriority()")
                        .build())
                .addMethod(MethodSpec
                        .methodBuilder("onTerminate")
                        .returns(VOID)
                        .addAnnotation(ClassName.get("java.lang", "Override"))
                        .addModifiers(Modifier.PUBLIC)
                        .addStatement("mModuleLifeCycle.onTerminate()")
                        .build())
                .build();

        JavaFile javaFile = JavaFile.builder("com.component.lifeCycle", ModuleFeedLifeCycleManger).build();
        try {
            //Generate file
            javaFile.writeTo(processingEnv.getFiler());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

The above completes the implementation process. In fact, it is quite simple

3. How to use

1. call the ModuleLifeCycleManager method in the onCreate() method of application. The code is as follows:

public class App extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        ModuleLifeCycleManager.init(this);
    }

    @Override
    public void onTerminate() {
        super.onTerminate();
        ModuleLifeCycleManager.terminate();
    }
}

2. Create a new life cycle class in the module, implement the IModuleLifeCycle interface, and add comments. The code is as follows

@ModuleLifeCycle(moduleName = "main", desc = "Main module")
public class MainLifeCycle implements IModuleLifeCycle {

    @Override
    public void onCreate(Application application) {
       //.. Will be called here when initializing the Application of the main project
       //.. Application is the application of the whole application
       // .. Complete the initialization of its own module here
        System.out.println("====>onCreate:"+application);
    }

    @Override
    public void onTerminate() {
       //.. Application destroyed
    }

    @Override
    public int getPriority() {
        return DEFAULT_PRIORITY;
    }
}

3. rebuild the following project to generate the module life cycle agent class. The code is as follows:

package com.component.lifeCycle;

import android.app.Application;
import com.quwan.moduleLifecycleApi.IModuleLifeCycle;
import com.quwan.modulelifecycle.MainLifeCycle;
import java.lang.Override;

public class MainLifeCycleProxy implements IModuleLifeCycle {
  public MainLifeCycle mModuleLifeCycle;

  public MainLifeCycleProxy() {
    mModuleLifeCycle = new MainLifeCycle();
  }

  @Override
  public void onCreate(Application application) {
    mModuleLifeCycle.onCreate(application);
  }

  @Override
  public int getPriority() {
    return mModuleLifeCycle.getPriority();
  }

  @Override
  public void onTerminate() {
    mModuleLifeCycle.onTerminate();
  }
}

The above completes the initialization process of the module life cycle

Relevant video recommendations:
[Android component design] what is component design?
[Android component design] page Jump after component configuration

This article is transferred from https://juejin.cn/post/7029216556965953543 , in case of infringement, please contact to delete.

Posted by pete_bisby on Fri, 12 Nov 2021 08:29:36 -0800