加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

写一个 ButterKnife

发布时间:2020-12-14 02:10:50 所属栏目:百科 来源:网络整理
导读:ButterKnife 很多人都用过,能节省很多代码,最多的就是省去了很多 findViewById 语句。接下来自己写一个,就叫 BBKnife 吧。 分析 在使用 ButterKnife 时,需要书写下面的类似代码,以一个 Activity 为例 ExampleActivity.java class ExampleActivity exten

ButterKnife 很多人都用过,能节省很多代码,最多的就是省去了很多 findViewById 语句。接下来自己写一个,就叫 BBKnife 吧。

分析

在使用 ButterKnife 时,需要书写下面的类似代码,以一个 Activity 为例

ExampleActivity.java

class ExampleActivity extends Activity {
  @BindView(R.id.title) TextView title;
  @BindView(R.id.subtitle) TextView subtitle;
  @BindView(R.id.footer) TextView footer;

  @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.simple_activity);
    ButterKnife.bind(this);
    // TODO Use fields...
  }
}

在编译后,会自动生成一个和 Activity 同名的 ExampleActivity$ViewBinder 的辅助类文件,并且生成 findViewById 相关的代码

ExampleActivity$ViewBinder.java

public void bind(ExampleActivity activity) {
  activity.subtitle = (android.widget.TextView) activity.findViewById(2130968578);
  activity.footer = (android.widget.TextView) activity.findViewById(2130968579);
  activity.title = (android.widget.TextView) activity.findViewById(2130968577);
}

那么 BBKnife 库要做的事情就很清楚了,生成辅助类。

动手开写

步骤:

  1. 创建注解
  2. 编译期间处理注解
  3. 生成辅助类
  4. 调用辅助类

0x00 创建工程

首先创建一个新的工程

然后创建一个 module , 选择 java Library。

0x01 创建注解

这个注解作用于类的属性上面,包含一个整型的参数,类似于 @BindView(R.id.title)

/** * Created by hanks on 2016/7/31. */
@Documented
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
    int value();
}

0x02 编译期间处理注解 && 生成辅助类

/** * 编译期间处理注解 * Created by hanks on 2016/7/31. */
@SupportedAnnotationTypes("xyz.hanks.BindView")
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class BindViewProcessor extends AbstractProcessor {
    private Messager messager;
    public static final String SUFFIX = "$ViewBinder";

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

    @Override
    public boolean process(Set<? extends TypeElement> annotations,RoundEnvironment roundEnv) {
        Map<String,List<VariableElement>> map = new HashMap<>(); // key 是类名,value 是该类的注解元素
        // 遍历 BindView 注解的所有元素
        for (Element element : roundEnv.getElementsAnnotatedWith(BindView.class)) {
            if (element == null || !(element instanceof VariableElement)) {
                continue;
            }
            // 给属性添加的注解
            VariableElement variableElement = (VariableElement) element;
            // 获取属性所在的类名
            String className = element.getEnclosingElement().getSimpleName().toString();
            List<VariableElement> variableElementList = map.get(className);
            if (variableElementList == null) {
                variableElementList = new ArrayList<>();
                map.put(className,variableElementList);
            }
            variableElementList.add(variableElement);
        }

        // 生成辅助类
        generate(map);
        return true;
    }

    private void generate(Map<String,List<VariableElement>> map) {
        if (null == map || map.size() == 0) {
            return;
        }
        for (String className : map.keySet()) {
            List<VariableElement> variableElementList = map.get(className);
            if (variableElementList == null || variableElementList.size() <= 0) {
                continue;
            }
            // 获取包名
            String packageName = variableElementList.get(0).getEnclosingElement().getEnclosingElement().toString();
            StringBuilder builder = new StringBuilder()
                    .append("package ").append(packageName).append(";nn")
                    .append("public class ").append(className).append(SUFFIX).append("{n") // open class
                    .append(" public void bind(Object target) {n")
                    .append(" ").append(className).append(" activity = (").append(className).append(")target;n");

            for (VariableElement variableElement : variableElementList) {
                BindView bindView = variableElement.getAnnotation(BindView.class);
                log(bindView.toString());
                builder.append(" activity.").append(variableElement.getSimpleName().toString()).append("=(").append(variableElement.asType()).append(")activity.findViewById(").append(bindView.value()).append(");n");
            }
            builder.append(" }n}n");
            // write the file
            try {
                String bindViewClassName = packageName + "." + className + SUFFIX;
                JavaFileObject source = processingEnv.getFiler().createSourceFile(bindViewClassName);
                Writer writer = source.openWriter();
                writer.write(builder.toString());
                writer.flush();
                writer.close();
            } catch (IOException e) {
                log(e.getMessage());
            }
        }
    }

    private void log(String msg) {
        messager.printMessage(Diagnostic.Kind.WARNING,msg);
    }

}

注意点:
- 添加 @SupportedAnnotationTypes @SupportedSourceVersion 注解, 原因: AbstractProcessor 中做了相关校验(看 AbstractProcessor 源码)。
- 打印消息是由 processingEnv.getMessager().printMessage 或者输出日志文件,原因:编译期间做的处理,不能使用 System.out 或者 Log.i
- 处理注解的时候需要获取类名或者包名,需要注意获取的是全路径还是简单名称。
- 依照需要生成辅助类文件。

在 main 下新建 resources > META_INF > services 目录,创建 javax.annotation.processing.Processor 文件,javac 会自动检查和读取 javax.annotation.processing.Processor 中的内容,并且注册 BindViewProcessor 作为注解处理器。

0x03 调用辅助类

当执行 BBKnife.bind(activity) 的时候调用我们生成的辅助类,辅助类内部进行 findViewById 从而进行注入。

public class BBKnife {
    // 调用我们生成的辅助类
    public static void bind(Object view){
        try {
            String cla = view.getClass().getName()+BindViewProcessor.SUFFIX;
            Class clazz = Class.forName(cla);
            Object instance = clazz.newInstance();
            Method bind = clazz.getMethod("bind",Object.class);
            bind.invoke(instance,view);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

导出 bbknife.jar

在 build/libs 目录下有自动导出的 jar 文件,

使用

复制到 app 下的 libs 进行引用。

app 下的 build.gradle

apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt' // 使用 apt

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' // 添加引用
    }
}

android {
    compileSdkVersion 24
    buildToolsVersion "24.0.1"

    defaultConfig {
        applicationId "xyz.hanks.bbknifeproject"
        minSdkVersion 15
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'
        }
    }
    // 设置java 版本
    compileOptions{
        sourceCompatibility JavaVersion.VERSION_1_7
        targetCompatibility JavaVersion.VERSION_1_7
    }
}

dependencies {
    compile fileTree(dir: 'libs',include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:24.1.1'
}

在 MainActivity 使用

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.bb_button) Button mButton;
    @BindView(R.id.bb_image)  ImageView mImage;
    @BindView(R.id.bb_text)   TextView mText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        BBKnife.bind(this);

        mButton.setText("hanks.xyz");
        mText.setText("hanks.xyz");
        mImage.setImageResource(R.mipmap.ic_launcher);
    }
}

看最后生成的辅助类 MainActivity$ViewBinder.java

public class MainActivity$ViewBinder{
    public void bind(Object target) {
        MainActivity activity = (MainActivity)target;
        activity.mButton=(android.widget.Button)activity.findViewById(2131427412);
        activity.mImage=(android.widget.ImageView)activity.findViewById(2131427414);
        activity.mText=(android.widget.TextView)activity.findViewById(2131427413);
    }
}

参考链接

[http://blog.stablekernel.com/the-10-step-guide-to-annotation-processing-in-android-studio
](http://blog.stablekernel.com/the-10-step-guide-to-annotation-processing-in-android-studio
)

文章来自: http://hanks.xyz

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读