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…