• 开发自己的山寨Android注解框架


    开发自己的山寨Android注解框架

    参考

    Github黄油刀

    Overview

    在上一章我们学习了Java的注解(Annotation),但是我想大家可能感觉,虽然理解了也会学会,但是不知道干什么用,那么请继续忍受我这枯燥乏味的文风继续向下看吧。

    在下面我们将会模仿(山寨)一把 黄油刀

    第零步

    我想许多 Android Coder 都非常讨厌findViewById 这种操作,既乏味无趣有没有代码的优雅感,实在是让人厌恶至极。 我们著名的黄油刀框架就是为了解决这一问题的。

    优雅的黄油刀
    class ExampleActivity extends Activity {
    //通过注解找到我们想要的控件
      @BindView(R.id.user) EditText username;
      @BindView(R.id.pass) EditText password;
    
      @BindString(R.string.login_error) String loginErrorMessage;
    //绑定OnClick事件
      @OnClick(R.id.submit) void submit() {
        // TODO call server...
      }
    
      @Override public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.simple_activity);
        ButterKnife.bind(this);
        // TODO Use fields...
      }
    }
    

    优雅的代码扑面而来~

    我们就是准备模仿这么一个东西,当然我们要做出来的东西,肯定远不及人家,可能就有人会说,既然已经有了这么好的框架,为什么我们还有做一些重复造轮子的工作呢?

    对此我想说,我们不能做一个只会用别人的轮子的开发者,总归我们是要能够造出来自己的轮子的。

    第一步,我们要实现的东西

    我们在这里要实现两个功能

    1. 通过注解,来替换掉,以前的findViewByid的操作
    2. 通过注解,来完成绑定OnCLick事件的操作。
    编程思路

    实现该功能的思路如下:

    1. 在Activity/Fragment初始化的时候,进行绑定操作
    2. 绑定操作后,通过遍历所有的字段和方法
    3. 遍历字段,找出所有的被注解标记的字段
    4. 根据注解中的值,找到相应的View,然后赋值给字段
    5. 遍历所有的方法,找出被注解的方法
    6. 通过注解的值找到对应的View
    7. 为找到的View注册事件
    8. 回收占用的资源

    第二步,做准备工作

    建立项目

    首先建立一个项目,我叫他为Finder ,在项目中建立一个FinderLibrary Module, 我们会在这个Model中写代码。

    创建我们需要的注解

    @BindView 注解,用于绑定视图

    /**
     * 用于绑定视图的注解
     */
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface BindView {
        int value();
    }
    
    

    @ClickEvent注解,用于注册OnClick事件

    @Documented
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ClickEvent {
        int[] value();
    }
    

    主体代码

    Finder类的主要方法

    建立一个Find类来写我们的核心代码。下面的代码中列出了finder类中的主要的方法,后面我们会一步步地实现他们。

    public final class Finder {
    	/**
    	* 私有化构造方法,禁止从外部创建对象
    	*/
        private Finder() throws Exception {
            throw new Exception("Invalidate constructor");
        }
    	
        /**
         * 用于存储所有的已经绑定的对象的集合
         */
        static Map<Unbinder, View> Pot = new HashMap<>();
    
        /**
         * 用于绑定活动
         */
        public static Unbinder bind(@NonNull Activity target) {}
        
        /**
         * 用于绑定碎片
         */
        public static Unbinder bind(Fragment target) {}
    	
        private static void apply(Unbinder unbinder) {}
    
        /**
         * 绑定View,为标记的字段
         */
        private static void bindView(Object holder, View view) throws Exception {}
    
        /**
         * 绑定OnClick方法
         */
        private static void bindMethod(Object holder, View view) throws Exception {}
    
        /**
         * 为View绑定ONClick事件
         */
        private static void bindOnClickListener
          (final Object holder, View view, final Method method) {}
    
        /**
         * 从集合释放掉占用的对象
         */
        public static void unbind(Unbinder unbinder) {}
    }
    
    
    Unbinder类

    这个类的作用很简单,当我们在Activity/Fragment中绑定了Finder以后,会返回一个此对象,那么当Activity/Fragment 生命周期结束的时候,通过此对象释放掉Finder所占用的资源。

    package little_david.finderlibrary;
    
    public class Unbinder {
        public Unbinder(Object c) {
            this.holder = c;
        }
    
        /**
         * 当前Unbinde对象的持有者
         */
        Object holder;
    	
        /**
         * 解绑操作
         */
        public void unbind() {
            Finder.unbind(this);
        }
    }
    
    bind方法

    在此方法中我们需要获取Activity/Fragment的View,通过调用apply 方法,并将其存储起来。

    /**
     * 用于绑定活动
     */
    public static Unbinder bind(@NonNull Activity target) {
        Unbinder unbinder = new Unbinder(target);
        //获取Activity的顶级布局,并存储
        Pot.put(unbinder, target.getWindow().getDecorView());
        apply(unbinder);
        return unbinder;
    }
    /**
     * 用于绑定碎片
     */
    public static Unbinder bind(Fragment target) {
        Unbinder unbinder = new Unbinder(target);
        //获取Fragment的顶级布局,并存储
        Pot.put(unbinder, target.getView());
        apply(unbinder);
        return unbinder;
    }
    
    apply方法

    通过此方法来调用,最主要的两个核心方法。这个方法并没有什么特殊,只是起到了一个过渡的作用。

    private static void apply(Unbinder unbinder) {
        try {
            //视图对象
            View view = Pot.get(unbinder);
            bindView(unbinder.holder, view);
            bindMethod(unbinder.holder, view);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    bindView 方法

    在此方法中做的事情是,遍历被注解了的字段,然后根据注解中的值,进行反射赋值。

    /**
     * 绑定View,为标记的字段
     */
    private static void bindView(Object holder, View view) throws Exception {
        //拿到持有者的Class
        Class cls = holder.getClass();
        //获取到所有的字段
        Field[] fields = cls.getFields();
        //遍历字段
        for (Field field : fields) {
            //过滤没有被@BindView标记的字段
            if (!field.isAnnotationPresent(BindView.class))
                continue;
            //获取我们注解的详细的对象,并赋值
            BindView bindView = field.getAnnotation(BindView.class);
            int viewResId = bindView.value();
            View targetView = view.findViewById(viewResId);
            //为字段赋值
            field.set(holder, targetView);
        }
    }
    
    bindMethod

    在此方法中遍历所有的方法,找出被注解标识的方法并且满足我们要求的方法,作为View的OnClick事件的处理方法。

    /**
     * 绑定OnClick方法
     */
    private static void bindMethod(Object holder, View view) throws Exception {
        Class cls = holder.getClass();
        Method[] methods = cls.getMethods();
        for (Method method : methods) {
            //过滤没有被注解的方法
            if (!method.isAnnotationPresent(ClickEvent.class))
                continue;
            //获取方法参数
            Class[] parameterClsArray = method.getParameterTypes();
            //根据方法参数过滤,过滤被注解了但是不合法的方法
            if (parameterClsArray.length != 1 || parameterClsArray[0] != View.class)
                continue;
            //绑定点击事件
            ClickEvent event = method.getAnnotation(ClickEvent.class);
            /**
             * 因为@ClickEvent注解是支持多选的,所以我们需要遍历所有的值来进行设置OnClick事件操作
             * */
            for (int resId : event.value()) {
                View eventView = view.findViewById(resId);
                bindOnClickListener(holder, eventView, method);
            }
        }
    }
    
    /**
     * 为View绑定ONClick事件
     */
    private static void bindOnClickListener(final Object holder, View view, final Method method) {
        view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                  	//执行方法
                    method.invoke(holder, v);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        });
    }
    
    unbind

    此方法的左右就是释放掉所占用的资源。

    /**
     * 从集合释放掉占用的对象
     */
    public static void unbind(Unbinder unbinder) {
        unbinder.holder = null;
        Pot.remove(unbinder);
    }
    

    第三步, 实验我们自己框架

    应用于Activity

    public class MainActivity extends AppCompatActivity {
    
        @BindView(R.id.btn1)
        Button btn1;
    
        @BindView(R.id.btn2)
        Button btn2;
        private Unbinder mUnbinder;
    	
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            //进行绑定操作
            mUnbinder = Finder.bind(this);
            btn1.setText("Hello world!");
        }
    
        /**
         * 可以绑定多个View
         */
        @ClickEvent({R.id.btn1, R.id.btn2})
        public void btn1Click(View view) {
            Toast.makeText(this, "My id is: " + view.getId(), Toast.LENGTH_SHORT).show();
        }
    
        /**
         * 进行解绑操作,释放资源
         */
        @Override
        protected void onDestroy() {
            mUnbinder.unbind();
            super.onDestroy();
        }
    }
    

    应用于Fragment

    public class TestFragment extends Fragment {
    
        private Unbinder mUnbinder;
    
        @BindView(R.id.btn3)
        Button btn3;
        @BindView(R.id.tvTest)
        TextView tvTest;
    
        @Nullable
        @Override
        public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
            return inflater.inflate(R.layout.fragment_test, container, false);
        }
    
        /**
         * 绑定
         */
        @Override
        public void onActivityCreated(@Nullable Bundle savedInstanceState) {
            super.onActivityCreated(savedInstanceState);
            mUnbinder = Finder.bind(this);
        }
    
        /**
         * 释放资源
         */
        @Override
        public void onDestroyView() {
            mUnbinder.unbind();
            super.onDestroyView();
        }
    
        @ClickEvent(R.id.btn3)
        public void btn3Click(View view) {
            Toast.makeText(getContext(), "Btn3.text=" + this.btn3.getText().toString(), Toast.LENGTH_SHORT).show();
        }
    }
    

    源码下载

    虽然,我们做的这个框架现在还漏洞百出,但是,我们已经跨出了我们造轮子的第一步,总有一天我们也会能够写出非常好的轮子让别人用。

    好了,现在本节的内容已经结束了,我想还是比较容易理解的,毕竟没什么难度。源码已经上传至github,欢迎大家吐槽。

    https://github.com/1258730808/Finder

  • 相关阅读:
    spring学习之模拟spring(spring原理解析)-01.xml文件的解析
    存储过程学习
    对象的深浅拷贝
    JavaScript 面向对象编程思想(二)
    深层剖析JavaScript 一
    深入剖析 css
    Vuex 总结
    h5 微信授权
    LeetCode
    echarts-liquidfill
  • 原文地址:https://www.cnblogs.com/slyfox/p/8677380.html
Copyright © 2020-2023  润新知