手动实现bindview
Posted 爱炒饭
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手动实现bindview相关的知识,希望对你有一定的参考价值。
以前android开发中用到过xUtils3和butterknife框架来动态注册控件id,节省繁琐的findViewById操作,今天就来试着自己写一个findViewById操作包装库。
首先需要知道xUtils3和butterknife使用注解在编译时自动生成模板代码来实现的,那么就需要注解,注解之前在《注解》一文中有介绍; 同时为了方便生成java文件可以使用javapoet,javapoet是由squareup公司开源的java代码生成器,可以去github上查看详细文档。
1、总览
总的工程结构如下,settings.gradle内容如下,其中processor和annotation是java库,bindLib是android库,app是主moudle。这里需要注意由于AbstractProcessor在android中不能直接使用,一堆爆红,这里processor及其依赖库annotation必须是java library。
//settings.gradle
include ':processor'
include ':annotation'
include ':app'
rootProject.name = "BindApp"
include ':bindLib'
2、annotation
annotation库作为基础库,供其他模块调用,annotation库主要定义注解元数据,《注解》一文中查看具体用法,本文annotation库只有一个BindView.java。
//BindView.java
package com.shan.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
int value();
}
//build.gradle
plugins {
id 'java-library'
}
java {
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
}
3、bindLib
bindLib定义一些模板类,用于定义一些公共方法以及辅助主module绑定注解。本文主要有三个类,Bind.java用于绑定注解类,Utils.java定义了findViewById操作,Viewbinder.java定义了绑定对象的解绑接口。一个正常的流程是先使用Bind.java获取注解activity实例,然后Utils.java调用activity实例的findViewById方法,最后需要在activity的onDestroy方法中调用Viewbinder.java声明的解绑接口实现。
//Bind.java
package com.shan.bind;
import android.app.Activity;
import java.lang.reflect.Constructor;
public class Bind {
public static Viewbinder bind(Activity activity){
try{
Class<? extends Viewbinder> bindClass= (Class<? extends Viewbinder>) Class.forName(activity.getClass().getName()+"_ViewBinding");
Constructor<? extends Viewbinder> bindConstructor= bindClass.getDeclaredConstructor(activity.getClass());
return bindConstructor.newInstance(activity);
}catch (Exception e){
e.printStackTrace();
return Viewbinder.EMPTY;
}
}
}
//Utils.java
package com.shan.bind;
import android.app.Activity;
import android.view.View;
public class Utils {
public static <T extends View>T findViewById(Activity activity, int viewId){
return activity.findViewById(viewId);
}
}
//Viewbinder.java
package com.shan.bind;
import androidx.annotation.UiThread;
public interface Viewbinder {
@UiThread
void unbind();
Viewbinder EMPTY = new Viewbinder() {
@Override
public void unbind() {
}
};
}
//build.gradle
plugins {
id 'com.android.library'
}
android {
compileSdkVersion 30
defaultConfig {
minSdkVersion 24
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
4、processor
processor库用于使用AbstractProcessor接口动态生成java代码
//BindProcessor.java
package com.shan.processor;
import com.google.auto.service.AutoService;
import com.shan.annotation.BindView;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
@AutoService(Processor.class)
public class BindProcessor extends AbstractProcessor {
private Filer mFiler;
private Elements mElementUtils;
private static final String BINDER_NAME = "Viewbinder";
private static final String PACKAGE_NAME = "com.shan.bind";
private static final String METHOD_NAME_PRE = "_ViewBinding";
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
System.out.print("11111");//这里老是不打印,不晓得为啥子
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
Map<Element, List<Element>> elementListMap = new LinkedHashMap<>();
for (Element element : elements) {
Element element1 = element.getEnclosingElement();
List<Element> viewBindElements = elementListMap.get(element1);
if (viewBindElements==null) {
viewBindElements = new ArrayList<>();
elementListMap.put(element1,viewBindElements);
}
viewBindElements.add(element);
}
for (Map.Entry<Element, List<Element>> elementListEntry : elementListMap.entrySet()) {
Element element = elementListEntry.getKey();
List<Element> viewBindElements = elementListEntry.getValue();
ClassName className = ClassName.bestGuess(element.getSimpleName().toString());
//根据注解动态生成类
TypeSpec.Builder classBuilder = TypeSpec.classBuilder(element.getSimpleName().toString()+METHOD_NAME_PRE)
.addModifiers(Modifier.FINAL,Modifier.PUBLIC)
.addSuperinterface(ClassName.get(PACKAGE_NAME,BINDER_NAME)) //实现接口
.addField(className,"mTarget",Modifier.PRIVATE); //添加成员变量
//解绑方法
MethodSpec.Builder unbindMethodBuilder = MethodSpec.methodBuilder("unbind")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.addStatement("if (this.mTarget ==null) throw new IllegalStateException(\\"Bindings already cleared.\\")");
//生成类的构造函数
MethodSpec.Builder constructMethodBuilder = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(className,"target")
.addStatement("this.mTarget = target");
for (Element viewBindElement : viewBindElements) { //遍历所有注解的控件
String fileName = viewBindElement.getSimpleName().toString();
ClassName utilsClassName = ClassName.get(PACKAGE_NAME,"Utils");
int resId = viewBindElement.getAnnotation(BindView.class).value(); //根据注解回去resId
constructMethodBuilder.addStatement("this.mTarget.$L = $T.findViewById(target,$L)",fileName,utilsClassName,resId);
constructMethodBuilder.addStatement("System.out.println(\\"mybind,bind init\\")");
unbindMethodBuilder.addStatement("this.mTarget.$L = null",fileName);
}
unbindMethodBuilder.addStatement("this.mTarget = null");
classBuilder.addMethod(constructMethodBuilder.build());
classBuilder.addMethod(unbindMethodBuilder.build());
String packageName = mElementUtils.getPackageOf(element).getQualifiedName().toString();
try {
JavaFile.builder(packageName,classBuilder.build())
.build()
.writeTo(mFiler);
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
@Override
public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton(BindView.class.getCanonicalName());
}
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mFiler=processingEnvironment.getFiler();
mElementUtils = processingEnvironment.getElementUtils();
}
}
//build.gradle
plugins {
id 'java-library'
}
java {
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
}
dependencies {
implementation project(path: ':annotation')
implementation 'com.google.auto.service:auto-service:1.0-rc2'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7'
implementation 'com.squareup:javapoet:1.12.1'
}
5、主module
主module显示下activity中自动bindview
//MainActivity.java
package com.shan.bindapp;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;
import com.shan.annotation.BindView;
import com.shan.bind.Bind;
import com.shan.bind.Viewbinder;
public class MainActivity extends AppCompatActivity {
@BindView(R.id.txt)
TextView textView;
Button button;
private Viewbinder mViewbinder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//开始绑定activity
mViewbinder =Bind.bind(this);
textView.setText("天王盖地虎");
}
@Override
protected void onDestroy() {
//解绑,防止内存activity内存泄漏
mViewbinder.unbind();
super.onDestroy();
}
}
//build.gradle
plugins {
id 'com.android.application'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.2"
defaultConfig {
applicationId "com.shan.bindapp"
minSdkVersion 24
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation project(path: ':bindLib')
annotationProcessor project(path: ':processor')
implementation project(path: ':annotation')
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
编译执行下可以看到"天王盖地虎"显示在TextView中了,在app\\build\\generated\\ap_generated_sources\\debug\\out\\com\\shan\\bindapp下生成MainActivity_ViewBinding.java。
//MainActivity_ViewBinding.java
package com.shan.bindapp;
import com.shan.bind.Utils;
import com.shan.bind.Viewbinder;
import java.lang.Override;
public final class MainActivity_ViewBinding implements Viewbinder {
private MainActivity mTarget;
public MainActivity_ViewBinding(MainActivity target) {
this.mTarget = target;
this.mTarget.textView = Utils.findViewById(target,2131231118);
System.out.println("mybind,bind init");
}
@Override
public void unbind() {
if (this.mTarget ==null) throw new IllegalStateException("Bindings already cleared.");
this.mTarget.textView = null;
this.mTarget = null;
}
}
以上是关于手动实现bindview的主要内容,如果未能解决你的问题,请参考以下文章
Dagger MVVM - ViewModel注入为null