The author

Hello everyone, my name is Xiao Xin, you can also call me crayon xiao Xin 😊;

I graduated from Sun Yat-sen University in 2017 and joined the 37 mobile Game Android team in July 2018. I once worked in Jubang Digital as an Android development engineer.

Currently, I am the overseas head of android team of 37 mobile games, responsible for relevant business development; Also take care of some infrastructure related work.

background

If you use r.i.d.xx directly, during backcompile time, resources. Arsc will be recompiled, and the mapping between id values in R class and resources. Arsc will be abnormal. We have two solutions.

One way is to correct the value of R class during packet cutting. For details about the implementation scheme, see

Game publishing cut resource Index conflict resolution, link below:

Juejin. Cn/post / 693605…

Another solution is to use getIdentifier to obtain resource ids instead of using the R class. In this way, encoding is cumbersome. In addition, strings are written in getIdentifier, which is easy to write errors and cannot be discovered during compilation. Based on this situation, we developed a Control injection framework based on getIdentifier

I. Introduction to APT Technology

1. APT definition

APT(Annotation Processing Tool) is a Tool for Processing annotations. Specifically, it is a javAC Tool used to scan and process annotations at compile time. The annotation processor takes Java code as input and generates a. Java file as output

2. Annotation definition

Annotations are a type of metadata that can be added to Java code. Classes, methods, variables, parameters, and packages can all be modified with annotations.

Annotations have no direct effect on the code they modify

3. Introduction to APT principle

Annotation Processing is performed at compile time by reading in Java source code, parsing annotations, and generating new Java code. The newly generated Java code is eventually compiled into Java bytecode, and the Annotation Processor cannot change the Java classes read in, such as adding or removing Java methods

Second, APT actual combat use

1. Source of SqInject framework

In mobile game release, it is often necessary to cut the package, connect the game to the SDK1 package, decompile, replace smali files and other resource files, and replace the channel SDK2 package. During decomcompile-back compilation, the resource index ID (that is, the ID mapping between class R and resources.arsc) conflicts, resulting in an exception of the program. Otherwise, the channel SDK and the distribution SDK cannot use class R directly and use getIdentifier to obtain the resource ID

The need to use getIdentifier in a program can be troublesome during development. Under these conditions, we couldn’t use a framework like Butterknife. Therefore, we develop a set of Control injection framework SqInject based on getIdentifier, imitating ButterKnife. The following introduces the implementation of SqInject, first look at the simple use of ha

public class MainActivity extends AppCompatActivity {
	
    / / bind ID
    @BindView(SqR.id.tv)
    TextView hello;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        SqInject.bind(this);
        Log.e("SqInject", hello.getText().toString());
    }

    // Bind the click event
    @OnClick(SqR.id.tv)
    public void click(View view) {
        Intent intent = new Intent(MainActivity.this, TestActivity.class); startActivity(intent); }}Copy the code

2. Implementation principle of SqInject

2.1. Annotation processor module implementation

As mentioned above, APT is often used to generate codes. In the process of APT annotation processing in SqInject, the process is shown as follows:

Annotations are scanned during compilation, Java code is generated, and then compiled again

In SqInject code, the implementation is as follows:

@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class SqInjectProcessor extends AbstractProcessor {...// Core method
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        $ViewBinder = $ViewBinder = $ViewBinder = $ViewBinder = $ViewBinder = $ViewBinder
        BindViewBuilder bindViewBuilder = new BindViewBuilder(roundEnvironment, mResChecker, mElementUtils, mTypeUtils, mFiler, mMessager);
        bindViewBuilder.build();
        $IdBinder class id = $IdBinder class id = $IdBinder class id = $IdBinder
        BindIdsBuilder bindIdsBuilder = new BindIdsBuilder(roundEnvironment, mResChecker, mElementUtils, mTypeUtils, mFiler, mMessager);
        bindIdsBuilder.build();
        return false; }}Copy the code

The framework checks the validity of the resource ID before generating the code for control injection. In this framework, annotations are passed in as strings. It is possible that there is no corresponding resource for a resource name, and the framework will detect it accordingly

2.2. Resource detection

During Android resource compilation, the R class is generated, that is, only those existing in the R class can be obtained by using getIdentifier. Then we can use the R class to check whether the parameters passed in are reasonable. The code is as follows:

* @param name * @param type * @return */ public Boolean isIdValid(String name, String type) { String RClassName = mPackageNeme + ".R." + type; TypeElement RClassType = mElementUtils.getTypeElement(RClassName); If (RClassType == null) {mmessager.printMessage (diagno.kind. ERROR, RClassName + "does not exist, please check for package name ERROR, or type ERROR "); } else {// iterate over the attribute for (Element Element: RClassType.getEnclosedElements()) { String fieldName = element.getSimpleName().toString(); if (name.equals(fieldName)) { return true; } } } return false; }Copy the code

2.3. Parse annotations and generate code

To parse BindView as an example, the code is as follows:

/** * Parse the BindView annotation *@return* /
    private Map<TypeElement, List<VariableElement>> parseBindView(){
        Set<Element> elements = (Set<Element>) mRoundEnvironment.getElementsAnnotatedWith(BindView.class);
        if(! Utils.isEmpty(elements)) { Map<TypeElement, List<VariableElement>> map =new HashMap<>();
            for (Element element : elements) {
                if (element instanceof VariableElement) {
                    // Get the class of the attribute
                    TypeElement targetElement = (TypeElement) element.getEnclosingElement();
                    mTargetSet.add(targetElement);
                    if (map.get(targetElement) == null) {
                        List<VariableElement> targetStringLists = new ArrayList<>();
                        targetStringLists.add((VariableElement) element);
                        map.put(targetElement, targetStringLists);
                    } else{ map.get(targetElement).add((VariableElement) element); }}}return map;
        }
        return null;
    }
Copy the code

After parsing the BindView annotations, use Javapoet to generate the code. Space is limited, so just a small part of getting the parameters and generating the code is listed below

if(mBindViewIdTargetMap ! =null&& mBindViewIdTargetMap.get(targetElement) ! =null) {
                List<VariableElement> viewElements = mBindViewIdTargetMap.get(targetElement);
                / / the method body
                for (VariableElement viewElement : viewElements) {
                    // Get the attribute name
                    String fieldName = viewElement.getSimpleName().toString();
                    // Get the type
                    TypeMirror typeMirror = viewElement.asType();
                    TypeElement fieldClassElement = (TypeElement) mTypeUtils.asElement(typeMirror);
                    mMessager.printMessage(Diagnostic.Kind.NOTE, "Annotated field type is:" + fieldClassElement.getQualifiedName().toString());
                    TypeElement fieldType = mElementUtils.getTypeElement(fieldClassElement.getQualifiedName());
                    ClassName fieldClassName = ClassName.get(fieldType);
                    // Get the value of the @bindView annotation, the name
                    String name = viewElement.getAnnotation(BindView.class).value();
                    // Check whether the name is valid
                    if(! mResChecker.isIdValid(name,"id")){
                        mMessager.printMessage(Diagnostic.Kind.ERROR, "There is no id in R file" + name + "The value of the");
                    }
                    methodBuilder.addStatement("target.$N = $T.findRequiredViewAsType(source, $S, $S, $T.class)", fieldName, JptConstants.UTILS, name, "field "+ fieldName,fieldClassName); }}Copy the code

To summarize, the two most important steps in an annotation processor are parsing annotations, retrieving parameters, and generating code using the Javapoet framework

Let’s take a look at the generated code

public class MainActivity$ViewBinder implements ViewBinder<MainActivity> {
  @Override
  public void bindView(final MainActivity target, final View source) {
    // This is where the above code is generated
    target.hello = ViewUtils.findRequiredViewAsType(source, "tv"."field hello", TextView.class);
    IdUtils.findViewByName("tv", source).setOnClickListener(new DebouncingOnClickListener() {
      public void doClick(View v) { target.click(v); }}); }}Copy the code

Here, we are done using APT to generate code ha. The framework SqInject mentioned in this article is the SqInject framework that we will use in our daily work. The framework is open source and the address is: github.com/37sy/SqInje… Welcome star if you are interested

At the same time, in the framework, there is another very important link, R class corresponding SqR class, SqR class is recorded in the project resource corresponding string name, this module is implemented through the custom Gradle plug-in, interested can view juejin.cn/post/687784…