手动实现bindview

Posted 爱炒饭

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手动实现bindview相关的知识,希望对你有一定的参考价值。

以前android开发中用到过xUtils3butterknife框架来动态注册控件id,节省繁琐的findViewById操作,今天就来试着自己写一个findViewById操作包装库。

首先需要知道xUtils3butterknife使用注解在编译时自动生成模板代码来实现的,那么就需要注解,注解之前在《注解》一文中有介绍; 同时为了方便生成java文件可以使用javapoetjavapoet是由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的主要内容,如果未能解决你的问题,请参考以下文章

片段的onPause()中的缓存数据在返回片段时为null

ButterKnife与BindView使用详解

Bindview 在 Eclipse 中不起作用

Dagger MVVM - ViewModel注入为null

SimpleCursorAdapter 在 bindView 上重复单击操作

CursorAdapter bindView 优化