ButterKnife解析

613 阅读2分钟

ButterKnife 基本使用

    implementation 'com.jakewharton:butterknife:8.8.0'
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.0'
public class MainActivity extends AppCompatActivity {

    @BindView(R.id.tv_main_show)
    TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        textView.setText("Butter-Knife-Test");
    }
}

ButterKnife 实现--反射

BindView

/**
 * 查找控件ID的注解
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BindView {
    int value() default -1;
}

InjectLayout

/**
 * 给Activity注入布局文件的注解
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectLayout {
    int value() default -1;
}

OnClick

/**
 * 给View设置监听事件的注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnClick {
    int[] value();
}

BindProcessor

/**
 * 使用注解注入布局文件省去setContentView
 * 使用注解省去findViewById
 * 使用注解省去setOnClickListener
 * 参考资料:
 * https://blog.csdn.net/qq_20521573/article/details/82054088
 */
public class BindProcessor {

    /**
     * 供Activity调用,用于绑定注解逻辑
     *
     * @param activity
     */
    public static void bind(Activity activity) {
        injectLayout(activity);
        bindView(activity);
        onClick(activity);
    }

    private static void injectLayout(Activity activity) {
        Class<?> activityCls = activity.getClass();
        if (activityCls.isAnnotationPresent(InjectLayout.class)) {
            InjectLayout layout = activityCls.getAnnotation(InjectLayout.class);
            int layoutId = layout.value();
            try {
                Method setContentView = activityCls.getMethod("setContentView", int.class);
                setContentView.setAccessible(true);
                setContentView.invoke(activity, layoutId);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private static void bindView(Activity activity) {
        Class<?> activityCls = activity.getClass();
        Field[] fields = activityCls.getDeclaredFields();
        for (Field field : fields) {
            if (field.isAnnotationPresent(BindView.class)) {
                BindView bindView = field.getAnnotation(BindView.class);
                int viewId = bindView.value();
                try {
                    Method findViewById = activityCls.getMethod("findViewById", int.class);
                    findViewById.setAccessible(true);
                    Object view = findViewById.invoke(activity, viewId);
                    field.setAccessible(true);
                    field.set(activity, view);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private static void onClick(final Activity activity) {
        Class<?> activityCls = activity.getClass();
        Method[] methods = activityCls.getMethods();
        for (final Method method : methods) {
            if (method.isAnnotationPresent(OnClick.class)) {
                OnClick onclick = method.getAnnotation(OnClick.class);
                int[] viewIdArray = onclick.value();
                for (int viewId : viewIdArray) {
                    final View view = activity.findViewById(viewId);
                    if (view == null) {
                        continue;
                    }
                    view.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            try {
                                method.setAccessible(true);
                                method.invoke(activity, view);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    });
                }
            }
        }
    }
}

使用 MyButterKnife

/**
 * 自定义butterknife
 * 通过注解和反射,来绑定view
 */
@InjectLayout(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    @BindView(R.id.tv_main)
    TextView tv_main;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.activity_main);
        BindProcessor.bind(this);
        tv_main.setText("注解绑定View");
        Log.i(TAG, "MainActivity onCreate");
    }

    @OnClick({R.id.btn_main_click1, R.id.btn_main_click2})
    public void onClickView(View view) {
        int viewId = view.getId();
        if (viewId == R.id.btn_main_click1) {
            Toast.makeText(this, "Btn01", Toast.LENGTH_SHORT).show();
        } else if (viewId == R.id.btn_main_click2) {
            Toast.makeText(this, "Btn02", Toast.LENGTH_SHORT).show();
        } else {
            Log.i(TAG, "onClickView unknown");
        }
    }
}

ButterKnife 实现--APT

使用 APT,需要使用 annotationProcessor 依赖 APT 编译自动生成Java代码 需要分别创建两个 module;annotation_compile 存放 APT 源码,annotation_lib 存放要使用的注解

    annotationProcessor project(path: ':annotation_compile')
    implementation project(path: ':annotation_lib')

annotation_lib

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface BindView {
    int value();
}

annotation_compile

/**
 * 注解处理器,用于生成Java代码
 */
@AutoService(Processor.class)
public class AnnotationCompiler extends AbstractProcessor {

    /**
     * 用来生成文件的对象
     */
    private Filer mFiler;

    /**
     * 支持的 Java 版本
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    /**
     * 当前APT处理的注解类型
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new HashSet<>();
        types.add(BindView.class.getCanonicalName());
        return types;
    }

    /**
     * 获取用来生成文件的对象:Filer
     * 通过 Filer 生成源文件
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        mFiler = processingEnvironment.getFiler();
    }

    /**
     * 注解处理逻辑
     * 生成的代码在 app/build/generated/ap_generated_sources
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {

        // 获取所有的 BindView 注解元素;将所有的 BindView 注解都获取到 Set 集合
        Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(BindView.class);

        // 对所有的 BindView 注解元素分类;相同的Activity中的 BindView 元素放在一起
        Map<String, List<VariableElement>> map = new HashMap<>();
        for (Element element : elementsAnnotatedWith) {
            VariableElement variableElement = (VariableElement) element;
            // 得到 VariableElement 所在的 activity 名称
            String activityName = variableElement.getEnclosingElement().getSimpleName().toString();
            // 用 List 保存所在 activity 的 VariableElement
            List<VariableElement> variableElementList = map.get(activityName);
            if (variableElementList == null) {
                variableElementList = new ArrayList<>();
                map.put(activityName, variableElementList);
            }
            variableElementList.add(variableElement);
        }

        // 对 Map 中的元素进行解析,拼接成Java文件
        if (map.size() > 0) {
            Writer writer = null;
            Iterator<String> iterator = map.keySet().iterator();
            while (iterator.hasNext()) {
                String activityName = iterator.next();
                List<VariableElement> variableElementList = map.get(activityName);
                // 获取包名
                TypeElement typeElement = (TypeElement) variableElementList.get(0).getEnclosingElement();
                String packageName = processingEnv.getElementUtils().getPackageOf(typeElement).toString();
                // 写入文件
                try {
                    // 创建文件
                    JavaFileObject sourceFile = mFiler.createSourceFile(
                            packageName + "." + activityName + "_ViewBinding");
                    // 创建输入流
                    writer = sourceFile.openWriter();
                    // 写入具体内容
                    writer.write("package " + packageName + ";\n");
                    writer.write("import " + packageName + ".IBinder;\n");
                    writer.write("public class " + activityName +
                            "_ViewBinding implements IBinder<"
                            + packageName + "." + activityName + ">{\n");
                    writer.write("@Override\n");
                    writer.write("public void bind("
                            + packageName + "." + activityName + " target){\n");
                    for (VariableElement element : variableElementList) {
                        // 获取被 BindView 注解的变量名称
                        String variableName = element.getSimpleName().toString();
                        // 获取 BindView 注解的 id
                        int id = element.getAnnotation(BindView.class).value();
                        // 获取被 BindView 注解的变量类型
                        TypeMirror typeMirror = element.asType();
                        writer.write("target." + variableName
                                + " = (" + typeMirror + ") target.findViewById(" + id + ");\n");
                    }
                    writer.write("\n}\n}");
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (writer != null) {
                        try {
                            writer.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }

        return false;
    }
}

app module

IBinder

annotation_compile 中生成的 xxx_ViewBinding 类实现了 IBinder

/**
 * 用户绑定activity
 */
public interface IBinder<T> {
    void bind(T target);
}

MyButterKnife

/**
 * 用于给用户绑定
 */
public class MyButterKnife {
    public static void bind(Activity activity) {
        String viewBindingName = activity.getClass().getName() + "_ViewBinding";
        try {
            Class<?> viewBindingClass = Class.forName(viewBindingName);
            // 执行 xxx_ViewBinding 类中的 bind 方法
            IBinder iBinder = (IBinder) viewBindingClass.newInstance();
            iBinder.bind(activity);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

使用 MyButterKnife

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.btn_main)
    Button btn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyButterKnife.bind(this);
        btn.setText("MyButterKnife");
    }
}