ButterKnife 很多人都用过,能节省很多代码,最多的就是省去了很多 findViewById
语句。接下来自己写一个,就叫 BBKnife
吧。
分析
在使用 ButterKnife 时,需要书写下面的类似代码,以一个 Activity 为例
ExampleActivity.java
1 2 3 4 5 6 7 8 9 10 11 12
| 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); } }
|
在编译后,会自动生成一个和 Activity 同名的 ExampleActivity$ViewBinder
的辅助类文件,并且生成 findViewById 相关的代码
ExampleActivity$ViewBinder.java
1 2 3 4 5 6
| 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
库要做的事情就很清楚了,生成辅助类。
动手开写
步骤:
- 创建注解
- 编译期间处理注解
- 生成辅助类
- 调用辅助类
0x00 创建工程
首先创建一个新的工程
然后创建一个 module , 选择 java Library。
0x01 创建注解
这个注解作用于类的属性上面,包含一个整型的参数,类似于 @BindView(R.id.title)
1 2 3 4 5 6 7 8 9
|
@Documented @Retention(RetentionPolicy.CLASS) @Target(ElementType.FIELD) public @interface BindView { int value(); }
|
0x02 编译期间处理注解 && 生成辅助类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
|
@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<>(); 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(";\n\n") .append("public class ").append(className).append(SUFFIX).append("{\n") .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"); 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
从而进行注入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| 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 使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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
1 2 3 4 5 6 7 8 9 10
| 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); } }
|
参考链接
https://blog.stablekernel.com/the-10-step-guide-to-annotation-processing-in-android-studio
文章来自: https://hanks.pub