美文网首页
Android开发— APT之ButterKnife的简单功能实

Android开发— APT之ButterKnife的简单功能实

作者: wenzhihao123 | 来源:发表于2019-05-05 14:51 被阅读0次

    假设已经看完上一篇关于APT知识介绍了, 本文介绍下如何实现最简单的Butterknife的功能,主要分为以下几个步骤。

    Step1:

    新建Java module创建注解:

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

    Step2:

    再创建一个Java module编写注解处理器,引入需要的库并引入注解module:

    apply plugin: 'java-library'
    
    dependencies {
        implementation fileTree(include: ['*.jar'], dir: 'libs')
        implementation 'com.google.auto.service:auto-service:1.0-rc2'
        implementation 'com.squareup:javapoet:1.8.0'
        implementation project(':annotationLib')
    }
    
    sourceCompatibility = "7"
    targetCompatibility = "7"
    

    编写注解处理器去处理注解:

    @AutoService(Processor.class)
    public class BindProcessor extends AbstractProcessor {
    
        //输出日志信息
        private Messager messager;
        //文件写入
        private Filer filer;
        //获取类型工具类
        private Types types;
        //模块名称
        private String moduleName = null;
        //Element的工具
        private Elements elementUtils;
        // View所在包名
        private static final String ViewClassName = "android.view.View";
        private static final String ActivityClassName = "android.app.Activity";
    
        /**
         * 初始化各种变量
         *
         * @param processingEnvironment
         */
        @Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            super.init(processingEnvironment);
            messager = processingEnvironment.getMessager();
            filer = processingEnvironment.getFiler();
            elementUtils = processingEnvironment.getElementUtils();
            //获取通过Gradle文件给处理器传递的参数
            moduleName = processingEnvironment.getOptions().get("route_module_name");
    
            types = processingEnvironment.getTypeUtils();
            LoggerInfo("moduleName = " + moduleName);
        }
    
        /**
         * 设置支持的注解类型
         *
         * @return
         */
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            Set<String> annotataions = new HashSet<String>();
            annotataions.add(BindView.class.getCanonicalName());
            return annotataions;
        }
    
        /**
         * 日志输出信息
         *
         * @param msg
         */
        public void LoggerInfo(String msg) {
            messager.printMessage(Diagnostic.Kind.NOTE, ">> " + msg);
        }
    
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return SourceVersion.latestSupported();
        }
    
        /**
         * 注解处理的核心方法
         *
         * @param set
         * @param roundEnvironment
         * @return
         */
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            if (roundEnvironment.processingOver()) {
                return false;
            }
            LoggerInfo("process start");
            // 获取所有被 @BindView 注解的对象
            Set<? extends Element> bindViewElements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
            // 把所有被注解的成员变量根据类搜集起来
            Map<TypeElement, Set<Element>> routesMap = new HashMap<>();
            // 存放注解成员变量
            Set<Element> bindViews = null;
    
            try {
                if (bindViewElements != null && bindViewElements.size() > 0) {
                    //遍历每个 @BindView注解的元素
                    for (Element element : bindViewElements) {
                        //获取注解元素所在的类对象
                        TypeElement typeElement = (TypeElement) element.getEnclosingElement();
                        Set<Element> elements = routesMap.get(typeElement);
                        if (elements != null) {
                            elements.add(element);
                        } else {
                            bindViews = new HashSet<>();
                            bindViews.add(element);
                            routesMap.put(typeElement, bindViews);
                        }
                    }
                    //根据类,生成每个类对应的文件
                    for (Map.Entry<TypeElement, Set<Element>> entry : routesMap.entrySet()) {
                        writeFile(entry.getKey(), entry.getValue());
                    }
                }
            } catch (Exception e) {
                LoggerInfo(e.getMessage());
            }
            return true;
        }
    
        /**
         * 根据注解元素及所在类的信息,生成文件并写入
         *
         * @param typeElement
         * @param routes
         */
        public void writeFile(TypeElement typeElement, Set<Element> routes) {
            //Activity的类型
            TypeMirror activityMirror = elementUtils.getTypeElement(ActivityClassName).asType();
            TypeMirror viewMirror = elementUtils.getTypeElement(ViewClassName).asType();
    
            //获取所在类文件的类型
            TypeMirror targetMirror = elementUtils.getTypeElement(typeElement.getQualifiedName()).asType();
    
            //需要的View参数
            ParameterSpec sourceView = ParameterSpec.builder(TypeName.get(viewMirror), "sourceView")
                    .build();
            //需要的Target参数
            ParameterSpec target = ParameterSpec.builder(TypeName.get(targetMirror), "target")
                    .build();
    
            LoggerInfo("start inject");
    
            //是否是Activity
            boolean isActivity = types.isSubtype(typeElement.asType(), activityMirror);
    
            // 构建构造方法,处理Activity
            MethodSpec injectForActivity = null;
            if (isActivity) {
                injectForActivity = createConstructorForActivity(target);
            }
            // 构建构造方法,处理指定的View
            MethodSpec injectView = createConstructorForView(target, sourceView, routes);
            //创建类
            TypeSpec.Builder bindBuilder = TypeSpec.classBuilder(typeElement.getSimpleName().toString() + "$BindView")
                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                    .addMethod(injectView);
            //target是Activity类型的生成对应的bind方法
            if (isActivity) {
                bindBuilder.addMethod(injectForActivity);
            }
            TypeSpec bindHelper = bindBuilder.build();
            JavaFile javaFile = JavaFile.builder("com.wzh.annotation", bindHelper)
                    .build();
    
            //写入文件
            try {
                javaFile.writeTo(filer);
            } catch (IOException e) {
                LoggerInfo(e.toString());
                e.printStackTrace();
            }
        }
    
        /**
         * 真正处理逻辑
         *
         * @param target
         * @param sourceView
         * @param routes
         * @return
         */
        private MethodSpec createConstructorForView(ParameterSpec target, ParameterSpec sourceView, Set<Element> routes) {
            MethodSpec.Builder methodBuilder = MethodSpec.constructorBuilder()
                    .addModifiers(Modifier.PUBLIC)
                    .addParameter(target)
                    .addParameter(sourceView);
            // 在方法里插入代码
            CodeBlock.Builder codeBlock = CodeBlock.builder();
            codeBlock.addStatement("if(target == null) { return; }");
            codeBlock.addStatement("if(sourceView == null) { return; }");
            methodBuilder.addCode(codeBlock.build());
            for (Element element : routes) {
                //变量名
                String fieldName = element.getSimpleName().toString();
                //变量类型
                String fieldType = element.asType().toString();
                //控件ID
                int resId = element.getAnnotation(BindView.class).value();
                methodBuilder.addStatement("target.$L = ($N)sourceView.findViewById($L)", fieldName, fieldType, resId);
            }
            return methodBuilder.build();
        }
    
        /**
         * 处理Activity注入,省去传递View的操作
         *
         * @param target
         * @return
         */
        private MethodSpec createConstructorForActivity(ParameterSpec target) {
            MethodSpec.Builder methodBuilder = MethodSpec.constructorBuilder()
                    .addModifiers(Modifier.PUBLIC)
                    .addStatement("this(target, target.getWindow().getDecorView())")
                    .addParameter(target);
            return methodBuilder.build();
        }
    }
    
    

    主要流程就是扫描到被注解的View, 然后分不同文件去存储,每个文件生成一个辅助类,完成赋值操作。
    生成文件如下:

    public final class MainActivity$BindView {
      public MainActivity$BindView(MainActivity target, View sourceView) {
        if(target == null) { return; };
        if(sourceView == null) { return; };
        target.mButton = (android.widget.Button)sourceView.findViewById(2131230800);
        target.mTextView = (android.widget.TextView)sourceView.findViewById(2131230904);
        target.mImageView = (android.widget.ImageView)sourceView.findViewById(2131230806);
      }
    
      public MainActivity$BindView(MainActivity target) {
        this(target, target.getWindow().getDecorView());
      }
    }
    
    public final class MainFragment$BindView {
      public MainFragment$BindView(MainFragment target, View sourceView) {
        if(target == null) { return; };
        if(sourceView == null) { return; };
        target.fragmentText = (android.widget.TextView)sourceView.findViewById(2131230795);
      }
    }
    

    这里区分Activity和Fragment,Actvitiy不用传递View就可以,Fragment需要传View对象。

    Step3:

    API调用赋值辅助类:

    public class BindHelper {
        public static void bind(Activity context){
            String canonicalName = context.getClass().getCanonicalName();
            try {
                Class<?> clz = context.getClassLoader().loadClass(canonicalName+"$BindView");
                Constructor constructor = clz.getConstructor(context.getClass());
                constructor.newInstance(context);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 注入View
         * @param context
         * @param view
         */
        public static void bind(Object context, View view){
            String canonicalName = context.getClass().getCanonicalName();
            try {
                Class<?> clz = context.getClass().getClassLoader().loadClass(canonicalName+"$BindView");
                Constructor constructor = clz.getConstructor(context.getClass(),View.class);
                constructor.newInstance(context, view);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }
    
    

    这里直接利用反射调用相关生成辅助文件的构造方法完成赋值操作。

    Step4

    实际使用

    public class MainActivity extends AppCompatActivity {
        @BindView(R.id.hello)
        protected Button mButton;
    
        @BindView(R.id.title)
        protected TextView mTextView;
    
        @BindView(R.id.image)
        protected ImageView mImageView;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            BindHelper.bind(this);
    
            inflateView();
            addFragment();
        }
    
        private void inflateView() {
            mImageView.setImageResource(R.drawable.icon_test);
            mTextView.setText("我是通过@BindView(R.id.title)找到的");
            mButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(MainActivity.this,"我是通过@BindView找到的", Toast.LENGTH_SHORT).show();
                }
            });
        }
    
        private void addFragment() {
            getSupportFragmentManager()
                    .beginTransaction()
                    .add(R.id.fragmentContainer,new MainFragment())
                    .commit();
        }
    }
    
    public class MainFragment extends Fragment {
        @BindView(R.id.fragmentText)
        TextView fragmentText;
    
        @Nullable
        @Override
        public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
            super.onCreateView(inflater, container, savedInstanceState);
            View view = inflater.inflate(R.layout.fragment_main, container, false);
            return view;
        }
    
        @Override
        public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            BindHelper.bind(this,view);
            fragmentText.setText("我是Fragment里面添加注解获取的");
        }
    }
    

    运行起来,没啥问题,可以直接用我们的view对象。


    运行图

    这里只是演示了Butterknife的基本原理,其中还有很多细节需要更进一步处理,如:生成文件命名要能区分module、加载的时候最好缓存起来避免每次加载等等。

    相关文章

      网友评论

          本文标题:Android开发— APT之ButterKnife的简单功能实

          本文链接:https://www.haomeiwen.com/subject/woycoqtx.html