preface

Componentized development now basically belongs to the basic operation, everyone will generally use ARouter, LiveDataBus as a componentized communication solution, then why choose ARouter, ARouter is how to achieve it? This article mainly builds the componentized development preparation work, the componentized jump analysis, if understood this article, for the view ARouter source code should be a great help. As for ARouter and other analysis, there are a lot of explanation on the Internet, here will not analyze ARouter source code, the end of the article will give ARouter source sequence diagram and summary, can be ignored.

Ps: Why write this article, because I was recently asked, why use ARouter, ARouter is to solve what problem, can you analyze just one point? Asked the question? I answered from its jump, after all, jump is simple. Just record it and remember it.

The resources

Android APT practice

Talk about some tips and points to use APT and JavaPoet

directory

One, componentization advantage

The advantages of componentization, as we all know, can be summed up in four points

  • Compilation speed We can test a single business module according to the requirements, rather than the whole package run, saving practice, effectively improve our development speed

  • Decoupling greatly reduces the coupling between modules, which facilitates later maintenance and update. When a new business is proposed in the product, a new business component can be completely created, and integration and abandonment are very convenient

  • Functionality Reuse of a piece of functionality for use in another componentized project by relying solely on that module

  • Team development efficiency Componentized architecture is the inevitable choice of a team development method, it can effectively make the team better collaboration

2. Componentized development preparation

Componentized development can be generally divided into three layers, namely shell engineering, business components, and basic dependency library. Business components are not related to each other, and business components need to be able to run tests separately. The whole process is carried out around decoupling

1. Package name and resource file name conflict

Specifications should be formulated to standardize the division of package names and project modules. Different modules should not have class files with the same name to avoid conflicts such as package failure

2, Gradle version good unified management

The next one is the most common one, but it has a slight flaw: auto-replenishment of AS is not supported, and auto-tracing of code is not available, so consider using buildSrc. BuildSrc is a special project in Android, and you can write Groovy language in buildSrc.

Some of the modules we create, such as compileSdkVersion, buildToolsVersion, or integrated third-party dependency libraries, have a version number. If we don’t manage them in a unified way, it is difficult to manually change the version of each module. So we can add configuration in gradle.properties file, for example

Gradle.properties CompileSdkVersion = 30 Complains module build. Gradle android {compileSdkVersion compileSdkVersion. ToInteger ()}Copy the code

All module version numbers are written as above, and each time you change the version number, change it as defined in gradle.properties. However, careful you will find that, now online examples, these write very few, since this writing can also achieve unified management, why not recommend it? The answer lies in CompileSdkVersion. ToInteger () here, after obtaining CompileSdkVersion here also need to transform, if the practice of using the following to create gradle file, can eliminate completely.

Create a new conffig.gradle file in the project root directory, the same level as the global build.gradle

Config. gradle ext{android=[compileSdkVersion:29, buildToolsVersion:'29.0.2', targetSdkVersion:29, ] dependencies = [ appCompact : 'androidx. Appcompat: appcompat: 1.0.2']} the root directory of the build. Gradle, The top to apply the from: "config. Gradle" when using the following compileSdkVersion rootProject.ext.android.com pileSdkVersion implementation rootProject.ext.dependencies.appCompactCopy the code

Notice that you can write this in implementation Dependencies

Implementation 'androidx. Test. Ext: junit: 1.1.0', 'androidx. Test. Espresso: espresso - core: 3.1.1'Copy the code

But you should never write something like this in config.gradle

Dependencies = [appCompact: '\' androidx appcompat: appcompat: 1.0.2 \ ', \ 'androidx. Test. The espresso: espresso - core: 3.1.1 \']Copy the code

Because in build.gradle you put all the dependencies after implementation, separated by a comma, which is not the same as the string comma, What you’re writing in config.gradle is just like writing in build.gradle implementation dependencies

Implementation 'androidx. Test. Ext: junit: 1.1.0, androidx. Test. The espresso: espresso - core: 3.1.1'Copy the code

You may ask, how can I centralize the management of common dependencies for all modules in config.gradle? I could write it like this

ext { .... dependencies = [ publicImplementation: [' androidx. Test. Ext: junit: 1.1.0 ', 'androidx. Test. Espresso: espresso - core: 3.1.1'], appCompact: 'androidx. Appcompat: appcompat: 1.0.2']} implementation rootProject. Ext dependencies. PublicImplementation // Write this sentence for each moduleCopy the code

Is that all? In addition, we also need to centrally manage our own public libraries, which we usually write one by one in the module build.gradle

implementation project(path: ':basic')
Copy the code

Now we manage it through Gradle, as follows

ext {
    ....
    dependencies = [
            other:[
                ':basic',
            ]
    ]
}
​
rootProject.ext.dependencies.other.each{
    implementation project(it)
}
Copy the code

3. Components switch between Application and Library at will

The Library cannot have applicationId in Gradle files

Androidmanifest.xml file distinction

During development, you need independent testing, and you can’t avoid switching between Application and Library frequently. There can be only one Application class when modules, including the shell project APP module, are running.

First of all we need to configure it in config.gradle, why not in gradle.properties, as mentioned earlier

Ext {android = [compileSdkVersion: 29, buildToolsVersion: '29.0.2', targetSdkVersion: 29, isApplication:false, ] .... }Copy the code

Then add the following judgments at the top of the build.gradle file for each module

if(rootProject.ext.android.isApplication) {
    apply plugin: 'com.android.application'
}else{
    apply plugin: 'com.android.library'
}
​
Copy the code
  • The Library cannot have applicationId in Gradle files

    Android {defaultConfig {the if (rootProject. Ext. Android. IsApplication) {applicationId “com. Cov. Moduletest” / / as a dependent libraries, No applicationId}…. }

  • Gradle for app modules also needs to be differentiated

    dependencies { ….. if(! RootProject. Ext. Android. IsApplication) {implementation project (path: ‘: the customer’) / / only when business module is dependent on to rely on, look at the business requirements}}

  • Androidmanifest.xml file distinction

    Distinguish between modules in build.gradle

    sourceSets {
        main{
            if(rootProject.ext.android.isApplication){
                manifest.srcFile '/src/main/AndroidManifest.xml'
            }else{
                manifest.srcFile "src/main/manifest/AndroidManifest.xml"
            }
        }
    }
    Copy the code
  • Application configuration

Since we do some initialization in the Application, if the module runs separately, these operations need to be placed in the Application of the module, so we need to configure this separately. Create a new module folder. When you have configured the following files, create a custom Application class. Then specify Application in the manifest file in the manifest folder. When run as a dependency, the files in the Module folder will not compile.

          main{
              if(rootProject.ext.android.isApplication){
                  manifest.srcFile '/src/main/AndroidManifest.xml'
              }else{
                  manifest.srcFile "src/main/manifest/AndroidManifest.xml"
                  java.srcDirs 'src/main/module','src/main/java'
              }
          }
Copy the code

When configuring a single module, you can write the Application in this way. However, you need to consider the initialization problem of the Application. After the Application initialization of the shell project is completed, you need to initialize the Application of the dependent components. I could write it this way

Public interface IApp{void init(Application app); Public AModuleApplication implements IApp{public void init(Application app)}} In the Application of engineering to maintain an array {com. CV. AModuleApplication. "class", "xx"} but that is not elegant, Suggestions on basic build a kind of special maintenance Next, run as an independent AP P, All you need to do is to reflect all the classes of the array in the onCreate method of the shell project Application and call init.Copy the code

The only downside is that you need to maintain a class that contains modules that need to be initialized when they are used as a Library. Is there a better way? ** The answer is certainly yes, using annotations, each module, need to be called in the Application initializer, that is, the array of classes maintained above, add annotations, compile time to collect, Application onCreate method call, you can see the following component jump analysis, similar to the reason.

4. Determine whether to run independently or as an integration in Java code

At run time, each module will generate a corresponding BuildConfig class, and the package path may be different, so how do we do that?

Add the following code to the basic module build.gradle

buildTypes {
        release {
            buildConfigField 'boolean', 'isApplication', rootProject.ext.android.isApplication.toString()
        }
        debug {
            buildConfigField 'boolean', 'isApplication', rootProject.ext.android.isApplication.toString()
        }
}
Copy the code

Why do you add it under the BASIC module? Since BuildConfig is available for every module, you can’t add this phrase to all modules. After the basic module is added, other modules rely on this module, and then add a method to obtain this value in the BaseActivity defined in the BASIC module. Other modules inherit BaseActivity, and then obtain the parent method for judgment. This is just one kind, and the specific analysis depends on the business.

Third, componentized jump analysis

1. Customize the componentized jump module

With this configuration in place, the first step is to solve componentized communication problems, the first of which is jump dependent. Because business components cannot be coupled to each other, we can only customize a new router module, inherit the dependency within each business component, and then achieve the jump.

We only need to define an ARouter container class in the router module, and then each module registers the Activity

public class ARouter { private static ARouter aRouter = new ARouter(); private HashMap<String, Class<? extends Activity>> map = new HashMap<>(); private Context mContext; private ARouter(){ } public static ARouter getInstance(){ return aRouter; } public void init(Context context){ this.mContext = context; @param clazz */ public void registerActivity(String key,Class<? extends Activity> clazz){ if(key ! = null && clazz ! = null && ! map.containsKey(key)){ map.put(key,clazz); } } public void navigation(String key){ navigation(key,null); } public void navigation(String key, Bundle bundle){ if(mContext == null){ return; } Class<? extends Activity > clazz = map.get(key); if(clazz ! = null){ Intent intent = new Intent(mContext,clazz); if(bundle ! = null){ intent.putExtras(bundle); } mContext.startActivity(intent); }}}Copy the code

Arouter.getinstance ().navigation(“key”) allows you to jump, but only if you register each Activity and its path with a registerActivity call. It’s not possible to call this method on every Activity to add class objects to the ARouter routing table. We might think of adding an abstract method to the BasicActivity, returning all the class objects, and then calling the registerActivity method to register them when you get them, but that would only happen if the class you inherited from BasicActivity was already created and instantiated. So it is not possible to register without starting the Activity. How do we add all class objects to the ARouter container when the Activity is not started? Is there any way to collect all unstarted activities at Application creation time?

You might also want to create a new ActivityUtils class in each module, and then define a method called arouter. registerActivity that registers all classes that need to be registered in the module, and then fire that method in the Application class. Module is less easy to say, you can manually knock one by one, a module, each module has to write, maintenance is too troublesome, can you automatically generate such a method, automatically find the need to register the class, collect it?

This requires the use of APT technology to achieve, by annotating the Activity that needs to jump, and then generate class files and class methods during compilation. In this class method, Map is used to collect the corresponding annotated classes. When Application is created, the related methods of these class files are executed and collected into the ARouter container.

2. Upgrade of componentized jump implementation scheme

If you don’t know how to operate APT, can you refer to it

Android APT practice

Talk about some tips and points to use APT and JavaPoet

To do this, you need to take a look at APT (Annotation Processing Tool), a Javac Tool that scans and processes annotations at compile time.

  • Create annotations that mark the Activity class that needs to be registered with annotations (the annotation module)

    @target declares the scope of the annotation

    @Retention Life Specifies the life cycle of annotations

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ActivityPath {
        String value();
    }
    Copy the code
  • Create annotation handlers and generate classes (annotation_compiler module)

    @autoService (processor.class) AnnotationCompiler = AnnotationCompiler;} @autoService (processor.class); The AutoService annotation handler can be automatically generated by @autoService in auto-service. To register used to generate the meta-inf/services/javax.mail annotation. Processing. The Processor file

    @supportedSourCeversion (SourceVersion.release_7) Specifies the compiled JDK version

    @supporteDANNotationTypes ({constant.activity_path}) specifies the annotation, where the fully qualified name package name of the ActivityPath class is entered. ActivityPath

    Filer object, a tool used to generate Java files

    Element officially represents a program Element, such as a package, class, or method. TypeElement represents a class or interface program Element. VariableElement represents a field, an enumeration constant or constructor parameter, a local variable. A TypeParameterElement represents a formal type parameter for a generic class, interface, method, or constructor element, as a simple example

    package com.example //PackageElement public class A{ //TypeElement private int a; //VariableElement private A mA; // ExecuteableElement public A(){} // ExecuteableElement public void setA(int A){// ExecuteableElement is A VariableElement }}Copy the code

    One more thing to note, in order not to appear at compile timeGBK coding errorNeed to be added in Gradle

    tasks.withType(JavaCompile) {
        options.encoding = 'UTF-8'
    }
    Copy the code

    Now we’re going to actually implement it, and now add to the dependencies on annotation_compile

    Implementation 'com. Google. Auto. Services: auto - service: 1.0 rc4' AnnotationProcessor 'com. Google. Auto. Services: auto - service: 1.0 rc4' implementation 'com. Squareup: javapoet: 1.11.1'Copy the code

    Then implement the annotation handler class

    @autoService (processor.class) @SupporteDANNotationTypes ({constant.activity_path}) // Annotate the parameters received by the Processor @supportedOptions (constant.module_name) public class AnnotationCompiler extends AbstractProcessor private Filer filer; private String moudleName; @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); filer = processingEnv.getFiler(); moudleName = processingEnv.getOptions().get(Constant.MODULE_NAME); } / get the latest Java version * * * * * @ return * / @ Override public SourceVersion getSupportedSourceVersion () {return processingEnv.getSourceVersion(); ** @override public Boolean process(set <? extends TypeElement> set, RoundEnvironment roundEnvironment) { if (moudleName == null) { return false; } // Get ActivityPath Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ActivityPath.class); Map<String, String> Map = new HashMap<>(); For (Element Element: elements) {TypeElement TypeElement = (TypeElement) Element; ActivityPath activityPath = typeElement.getAnnotation(ActivityPath.class); String key = activityPath.value(); String activityName = typeElement.getQualifiedName().toString(); // Get the fully qualified name of the element of this type, map.put(key, activityName + ".class"); If (map.size() > 0) {createClassFile(map); } return false; } private void createClassFile(Map<String, String> map) { //1. MethodBuilder = MethodSpec. MethodBuilder ("registerActivity").addmodifiers (Modifier.public) .returns(void.class); Iterator<String> iterator = map.keySet().iterator(); while (iterator.hasNext()) { String key = iterator.next(); String className = map.get(key); / / 2. Add methods body methodBuilder. AddStatement (Constant) AROUTER_NAME + ". The getInstance (). RegisterActivity (\ "" + key +" \ ", "+ className + ")"); } //3. Generate MethodSpec MethodSpec = methodBuilder.build(); ClassName iRouter = classname.get (constant.package_name, constant.irouter); //4. //5. Create utility class TypeSpec TypeSpec = TypeSpec. ClassBuilder (constant.class_name + "? + moudleName).addmodifiers (Modifier.public).addsuperinterface (iRouter) // parent class.addmethod (methodSpec) // addMethod.build(); JavaFile = Javafile.builder (constant.package_name, typeSpec).build(); Try {javafile.writeto (filer); //7. } catch (IOException e) { } } }Copy the code

    The resulting file is as follows

    public class RouterGroup$$moduletest implements IRouter { public void registerActivity() { com.cv.router.ARouter.getInstance().registerActivity("/main/login",com.cv.moduletest.LoginActivity.class); }}Copy the code
  • Implement init method in ARouter, which triggers the method of the class file

    public void init(Context context){ this.mContext = context; //1. Obtain the generated RouterGroup? . Try {List<String> clazzes = getClassName(); if(clazzes.size() > 0){ for(String className:clazzes){ Class<? > activityClazz = Class.forName(className); if(IRouter.class.isAssignableFrom(activityClazz)){ //2. Whether the IRouter subclass IRouter router = (IRouter) activityClazz. NewInstance (); router.registerActivity(); } } } } catch (Exception e) { e.printStackTrace(); } } private List<String> getClassName() throws IOException { List<String> clazzList = new ArrayList<>(); / / load the apk storage paths to DexFile DexFile df = new DexFile (mContext. GetPackageCodePath ()); Enumeration<String> enumeration = df.entries(); while (enumeration.hasMoreElements()){ String className = enumeration.nextElement(); if(className.contains(Constant.CLASS_NAME)){ clazzList.add(className); } } return clazzList; }Copy the code

    That’s where you automate the collection of class information.

4. Summary of ARouter

When you understand the above method, you look at the source ARouter, will be easy point, jump implementation principle, are similar. Of course, ARouter also supports interception and other functions, want to view the source ARouter, you can search on the nuggets. Here is the note made when looking at ARouter, only for the client using ARouter time sequence diagram and text description, may not be a good summary, don’t spray if you don’t like

  1. GetInstance ().init() calls the init() method of _ARouter, and then calls the after method, which is the interceptor Service obtained by byName.

  2. This is init(), which builds a Handler that starts the application component jump and displays the prompt in debug mode, and a pool of threads that are used to perform subsequent interceptors. The most important thing is logisticscenter.init (), where it gets all the class names under the arouter.router package name, loads them into a Set, and iterates over them. Root-related classes are loaded into the groupIndex collection by calling the loadInto method, Interceptors related classes are loaded into the interceptorsIndex, Providers related classes are loaded into the providersIndex. These class files are generated by arouter-compile according to the annotation, and the file name rule is arouter Root? Is the module name ARouter? Provider module name, or ARouter? Group ? For example, the loadInto method of the Root related class matches the group value with the group related class in the groupIndex, and then adds the group related class information when needed.

  3. When we use arouter.getInstance ().build().navigation to get a Fragment or a jump, it starts with _ARouter’s build method, and in that method, it calls PathReplaceService in the bayType form, Make changes to the path passed in the build() method. Then, if the RouterPath annotation does not specify a group, the first string after the/in the path will be taken as a group and a Poscard will be returned with an internal bundle to receive the passed arguments. Then we call our own navigation method, and finally we call back to _ARouter’s navigation() method. This method will retrieve the class information that is loaded with the specified path. First, we need the class information that is loaded with the group name from groupIndex. Then, the loadInto method is called through reflection to save all Path mappings under the name of the group into the Routes Map. Then, the RouteMeta information corresponding to the passed Path is improved. Finally, the corresponding information is constructed according to the type of meta information. Specify the provider and fragment to open the green channel. Then, if the green channel is not open, all interceptors will be processed as needed using CountDownlaunch and thread pool. After passing, according to the meta information type, the parameters will be constructed, the Activity will be started, or the reflection will be built.

@Path(path)

  • ARouter$$Root$$module name. Class. The loadTo method is generated internally to store the mapping between the path group and the corresponding class in a MAP. Interceptors are similar to providers. In the case of providers, a map is a mapping between the interface name and a RouteMeta. A RouteMeta contains information about the implementation class, path, and so on.

  • ARouter$$Group$$Group name. Class. There is also an internal loadTo method, which stores all routing paths and RouteMeta information in a Map

Navigation jumps or retrives the instance

  • It looks to see if the path to jump to is at least level 2, and then it looks to see if the PathReplaceService path replacement interface has an implementation class and needs to be replaced. There is only one

  • It then checks to see if pre-processing, or pre-jump processing, is needed, for example for a path, to handle the jump itself

  • Then go to the group map and find the class that corresponds to the path group name. Then call the method of that class and store all the pathnames of that group in the new Routes map along with a RouteMeta containing information about the target Activity or some of the implementation classes of the IProvider. Then delete the group information from the previous group map to save memory.

  • Providers store the providers Map of the Provider implementation Class and the instance of the reflection constructor, and then call the init method.

  • By default, the green channel is enabled for fragments and providers and the interceptor is not executed.

  • Interceptors are interceptors that get the system Settings by default when ARouter is initialized

  • Within this interceptor, all of the developer’s custom interceptors are invoked using thread pools and CountDownLatch. Control whether to release

The longest common substring

Asked a question this week, the android list shows all the data, how to find the longest public child tags, I immediately thought of dynamic programming, but always feel there will be a better way to accomplish this, after all the LCS problem mostly is given two strings, not every two (O (n2)) after comparison, then compare with the third, fourth, That’s not a very good time complexity. In the end, if you think about it, you should think about it this way, through the system API, you should also compare.

/** * str1 = M, str2 = N, Str1 [0.... I] and str2[0......j] have the same subsequence length as 1 * @author XXX * */ Public class DemoFive {//dp[I][j] is the length of str1[0.... I] and str2[0......j] public static String findLCS(String A,int) n,String B,int m) { char[] arrA = A.toCharArray(); char[] arrB = B.toCharArray(); // n * m matrix A * B int[][] dp = new int[n][m]; int length = 0; int start = 0; for(int i = 1; i<n; i++) { for(int j = 1; j<m; j++) { if(arrA[i]== arrB[j] ) { dp[i][j] = dp[i-1][j-1]+1; if(dp[i][j] > length) { length = dp[i][j]; start = i - length+1 ; }}}} String result = a.substring (start,start + length); return result; }}Copy the code

Note 8