This is the 9th day of my participation in the More text Challenge. For details, see more text Challenge

preface

Nice to meet you

In the last article in this series, we covered APT technology. If you haven’t seen the last article, you are advised to read APT for Android (Part 3) : Exploring APT Technology. Next, we will use APT technology for practical application.

Github Demo address, you can see the Demo to follow my ideas together analysis

review

In this series, I speak in the project practice in the process of doing a layout optimization, a small amount of system control in Android is created by means of the new, and most of the controls such as androidx. Appcompat. Under the widget controls, custom controls, the third party controls, etc., Are created by reflection. A lot of reflection creation can cause performance problems, so we need to solve the problem of reflection creation. My solution is:

1. Get all the controls in the Xml layout by writing an Android plug-in

2. After getting the control, create a View class using new method through APT generation

Finally, get the current class by reflection and complete the replacement in the base class

1. Prepare the files generated by the Android plug-in

For example, if you want to get all the control names in the layout and write them to a.txt file, you can use the Android plugin to get all the control names in the layout and write them to a.txt file.

In the above documents, we can see:

1, some system controls without., such as TextView, ImageView. System will default to give us new ways to create and replace to androidx. Appcompat. The widget package under control, such as: TextView -> AppCompatTextView, ImageView -> AppCompatImageView

2. Controls with. May be androidx. Appcompat. Under the widget controls, custom controls, third party controls, etc., these controls if we don’t do processing, system will through reflection to create. So we are mainly for these controls to do processing

Note: I created a file all_view_name. TXT in the root directory and put the names of some views in it, just for demo purposes. In fact, the files generated by the Android plugin are usually placed in the /build directory of the app, so that we can kill them when we clean

We need to read the file, generate the corresponding class through APT, and finally use the function of this class. If you are not familiar with APT, first learn a wave of portal

The actual operation is still based on the project in the previous article. In order to facilitate the explanation of the follow-up process, I still post the engineering drawing in the previous article:

2. Compile apt-annotation

Write a comment like this:

@Inherited
@Documented
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface ViewCreator {
    
}
Copy the code

3. Specify the generated class template to prepare for subsequent automatic code generation

In practice, we do this:

1, will need to generate class files to implement a defined interface, through the interface proxy to use

2. Specify the generated Java class template, and write the generated code logic according to the template

1, will need to generate class files to implement a defined interface, through the interface proxy to use

For interfaces, we usually put them in the Module apt-API

2. Specify the generated Java class template, and write the generated code logic according to the template

Suppose we need to generate the following Java class template:

package com.dream.aptdemo;

public class MyViewCreatorImpl implements IMyViewCreator {
  @Override
  public View createView(String name, Context context, AttributeSet attr) {
    View view = null;
    switch(name) {
      case "androidx.core.widget.NestedScrollView":
      	view = new NestedScrollView(context,attr);
      	break;
      case "androidx.constraintlayout.widget.ConstraintLayout":
      	view = new ConstraintLayout(context,attr);
      	break;
      case "androidx.appcompat.widget.ButtonBarLayout":
      	view = new ButtonBarLayout(context,attr);
      	break;
        / /...
      default:
      	break;
    }
    return view;
}
Copy the code

Based on this information, we can write the automatically generated code logic

4. Apt-processor automatically generates code

Here you are on the code template given above, through the Javapoet framework to write the corresponding code generation logic can be, not familiar to Javapoet quickly to learn a wave of portal

@AutoService(Processor.class)
@SupportedAnnotationTypes("com.dream.apt_annotation.ViewCreator")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyViewCreatorProcessor extends AbstractProcessor {

    /** File generator */
    private Filer mFiler;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        mFiler = processingEnv.getFiler();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        // Read the control name from the file and convert it to the corresponding collection
        Set<String> mViewNameSet = readViewNameFromFile();
        // If the collection of acquired control names is empty, the process is terminated
        if(mViewNameSet == null || mViewNameSet.isEmpty()){
            return false;
        }
      
        // Get the element with the annotation
        Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(ViewCreator.class);
        for (Element element : elementsAnnotatedWith) {
            System.out.println("Hello " + element.getSimpleName() + "Welcome to APT");
            startGenerateCode(mViewNameSet);
            // If there are multiple annotations, we will only read the first one
            break;
        }
        return true;
    }

    /** * Start executing the generated code logic **@paramMViewNameSet Control name collection */
    private void startGenerateCode(Set<String> mViewNameSet) {
        System.out.println("Start generating Java classes...");
        System.out.println("a few moment later...");
        / / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = building methods start = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
        //1, construct method: method name, annotation, modifier, return value, parameter

        ClassName viewType = ClassName.get("android.view"."View");
        MethodSpec.Builder methodBuilder = MethodSpec
                / / the method name
                .methodBuilder("createView")
                / / comment
                .addAnnotation(Override.class)
                / / modifier
                .addModifiers(Modifier.PUBLIC)
                / / the return value
                .returns(viewType)
                // The first argument
                .addParameter(String.class,"name")
                // The second argument
                .addParameter(ClassName.get("android.content"."Context"),"context")
                // The third parameter
                .addParameter(ClassName.get("android.util"."AttributeSet"),"attr");

        //2
        methodBuilder.addStatement("$T view = null",viewType);
        methodBuilder.beginControlFlow("switch(name)");
        // Loop through the collection of control names
        for (String viewName : mViewNameSet) {
            // Handle control names that contain
            if(viewName.contains(".")) {/ / separation package name and the control name, such as: androidx. Constraintlayout. Widget. Constraintlayout
                / / packageName: androidx. Constraintlayout. Widget
                / / simpleViewName: ConstraintLayout
                String packageName = viewName.substring(0,viewName.lastIndexOf("."));
                String simpleViewName = viewName.substring(viewName.lastIndexOf(".") + 1);
                ClassName returnType = ClassName.get(packageName, simpleViewName);

                methodBuilder.addCode("case $S:\n",viewName);
                methodBuilder.addStatement("\tview = new $T(context,attr)", returnType);
                methodBuilder.addStatement("\tbreak");
            }
        }
        methodBuilder.addCode("default:\n");
        methodBuilder.addStatement("\tbreak");
        methodBuilder.endControlFlow();
        methodBuilder.addStatement("return view");

        MethodSpec createView = methodBuilder.build();
        / / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = build method end = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

        / / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = build class start = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
        TypeSpec myViewCreatorImpl = TypeSpec.classBuilder("MyViewCreatorImpl")
                // Class modifier
                .addModifiers(Modifier.PUBLIC)
                // Implement the interface
                .addSuperinterface(ClassName.get("com.dream.apt_api"."IMyViewCreator"))
                // Add the method
                .addMethod(createView)
                .build();
        / / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = build class end = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

        / / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = package specified path, the build file body start = = = = = = = = = = = = = = = = = = = = = = = = =
        // Specify the class package path
        JavaFile javaFile = JavaFile.builder("com.dream.aptdemo",myViewCreatorImpl).build();
        // Generate the file
        try {
            javaFile.writeTo(mFiler);
            System.out.println("Generated successfully...");
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("Failed to generate...");
        }
        / / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = package specified path, the build file body end = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    }

    /** * reads the control name from the file and converts it to the corresponding collection */
    private Set<String> readViewNameFromFile(a) {
        try {
            // Get the file where the control name is stored
            File file = new File("/Users/zhouying/AndroidStudioProjects/AptDemo/all_view_name.txt");
            Properties config = new Properties();
            config.load(new FileInputStream(file));
            // Get the collection of control names
            return config.stringPropertyNames();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null; }}Copy the code

The above code generation logic is commented in detail and is primarily an application of the Javapoet framework

The code has been generated, and now it needs to be made available to the upper layer

5. Apt-api service encapsulation is used by the upper layer

1. Define an interface,apt-apiapt-processorWill be used

// Define an interface
public interface IMyViewCreator {
    /** * create View ** with new@paramName Control name *@paramContext *@paramAttributeSet properties * /
    View createView(String name, Context context, AttributeSet attributeSet);
}
Copy the code

2, reflection to obtain the generated class, provide the corresponding proxy class for the upper layer to call

public class MyViewCreatorDelegate implements IMyViewCreator{
    
    private IMyViewCreator mIMyViewCreator;
    
    / / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = singleton start = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    @SuppressWarnings("all")
    private MyViewCreatorDelegate(a){
        try {
            // Retrieve the class generated by Apt by reflection
            Class aClass = Class.forName("com.dream.aptdemo.MyViewCreatorImpl");
            mIMyViewCreator = (IMyViewCreator) aClass.newInstance();
        } catch(Throwable t) { t.printStackTrace(); }}public static MyViewCreatorDelegate getInstance(a){
        return Holder.MY_VIEW_CREATOR_DELEGATE;
    }

    private static final class Holder{
        private static final MyViewCreatorDelegate MY_VIEW_CREATOR_DELEGATE = new MyViewCreatorDelegate();
    }
    / / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = singleton end = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =


    /** * Create a View ** from the generated class@paramName Control name *@paramContext *@paramAttributeSet properties *@return View
     */
    @Override
    public View createView(String name, Context context, AttributeSet attributeSet) {
        if(mIMyViewCreator ! =null) {return mIMyViewCreator.createView(name, context, attributeSet);
        }
        return null; }}Copy the code

At this point we are almost done with the layout optimization process, and the next step is the upper level call

Vi. App upper level invocation

1. Add annotations to the created MyApplication

You can add annotations elsewhere, because my annotation handler made a logical decision to only read the first annotation. To correspond, I chose to add annotations to MyApplication, as shown below:

2. Finally, add the logic to replace the View in MainActviity

As follows:

/ /...
public class MainActivity extends AppCompatActivity {

    / /...
    @Nullable
    @Override
    public View onCreateView(@NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {
        //1, use the generated class to create the View first
        View view = MyViewCreatorDelegate.getInstance().createView(name, context, attrs);
        if(view ! =null) {
            return view;
        }
        //2, some system View, then go through a system creation process
        return super.onCreateView(name, context, attrs); }}Copy the code

Note: Normally we put the replacement View logic in the base class

Vii. Effect verification

Run the project

1. First take a look at the log we printed, as shown in the figure below:

2. Take a look at the Java class file we generated, as shown below:

3. Finally, the debug project follows the following process, and the results are consistent with our expectations, as shown in the figure below:

At this point, the demand ends

Eight, summary

Some of the highlights of this article:

1, through APT read files to get all the control names and generate Java classes

2. Provide reasonable business encapsulation to the upper layer through the interface proxy

3. Annotate the Application in the upper layer and replace the View control in the Activity

4. An effect verification after actual completion

Well, that’s the end of this series of articles, hope to help you 🤝

Thanks for reading this article

Here is the full text, the original is not easy, welcome to like, collect, comment and forward, your recognition is the motivation of my creation

Follow my public account, search for sweereferees on wechat and updates will be received as soon as possible