美文网首页
Java基础回归之注解Annotation【低仿ButterKn

Java基础回归之注解Annotation【低仿ButterKn

作者: 林宥之 | 来源:发表于2016-11-21 18:01 被阅读0次

    前言

    书接上回,上回说到库里对战湖人三分10投0中,真真气煞我库也,这下把气全撒在鹈鹕身上,一口气轰下破纪录的13记三分。 上回说到Java基础回归之注解Annotation【基础篇】,这回我们来真刀真枪实战。相信很多做安卓的同学都用过至少听过ButterKnife,没错,就是大神JakeWharton的黄油刀。本篇博文将结合注解和反射,实现类似Jake Wharton大神的ButterKnife注入框架,需要说明的是ButterKnife实现方式和我们的实现方式是有区别的,它里面是用到了APT技术,他是基于编译期的注入,所以效率比我们用反射高,本文是基于运行期的。但是这并不妨碍我们造轮子。
    ps:对反射不熟悉可以看博主另外一篇博文:Java基础回归之反射Reflection,本文不讲解ButterKnife的用法。

    取名

    既然是低仿大名鼎鼎ButterKnife(黄油刀),那我们项目的名字也要低仿,就叫Shaver(剃须刀)吧..

    Shaver的功能点

    • @Bind(R.id.btn1):用于注入view,代替繁琐的findViewById操作
    • @ContentView(R.layout.activity_main):用于代替注入contentView
    • @StringRes(R.string.string_shaver):用于注入String资源文件
    • @OnClick({R.id.btn1,R.id.btn2}):用于绑定view的监听事件

    目标效果

    目标效果

    开始造轮子

    我先帮大家捋一遍思路,其实说白了就是编写上述4个注解类,然后编写一个处理这四种注解的核心类。例如要处理绑定view的@Bind注解,我们需要将activity传入到核心类中,核心类反射获取到标注有@Bind注解的成员变量field,然后获取该注解的value,即view的id,最后将id利用activity.findViewById(value)获取到view,然后反射将获取到的view赋值给改成员变量filed,这样我们就成功将view注入进去了,其他三个注解同理,下面show you the code,代码注释很详细,请仔细看。

    • 首先理所应当,我们编写4个注解类:
    • @Bind注解类
    package com.youzhi.shaver.core;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    /** * 代替findViewById的注解 */
    @Target(ElementType.FIELD)//标注目标为成员变量
    @Retention(RetentionPolicy.RUNTIME)//生命周期为运行时
    public @interface Bind {  
      int value();//用于保存控件id
    }
    
    • @ContentView注解类
    package com.youzhi.shaver.core;import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    /** * 代替setContentView的注解 */
    @Target(ElementType.TYPE)//标注目标为类前
    @Retention(RetentionPolicy.RUNTIME)//生命周期为运行时
    public @interface ContentView {
        int value();//用于保存layoutId
    }
    
    • @StringRes注解类
    package com.youzhi.shaver.core;import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    /** * 代替getResource().getString(R.string.xx) */
    @Retention(RetentionPolicy.RUNTIME)//生命周期为运行时
    @Target(ElementType.FIELD)//标注目标为成员变量
    public @interface StringRes {
        int value();//用于保存string的id
    }
    
    • OnClick注解类
    package com.youzhi.shaver.core;import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    /** * 代替setOnClickListener的注解 */
    @Retention(RetentionPolicy.RUNTIME)//生命周期为运行时
    @Target(ElementType.METHOD)//标注目标为方法上
    public @interface OnClick {
        int[] value();//用于保存控件id集
    }
    
    • 接下来是最核心的处理类,所有逻辑都在这个处理类上面。(ps:可以优化,例如用Map将view缓存起来...这里留给大家)
    package com.youzhi.shaver.core;
    import android.app.Activity;
    import android.view.View;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    /** * 注入接口 */
    public class Shaver {
        public static void bind(Activity activity) {
            try {
                bindContentView(activity);
                bindStringRes(activity);
                bindViews(activity);
                bindClicks(activity);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        /**
         * 注入contentView
         * @param activity
         */
        private static void bindContentView(Activity activity) {
            Class<? extends Activity> aClass = activity.getClass();
            ContentView annotation = aClass.getAnnotation(ContentView.class);
            if(null != annotation){
                int layoutId = annotation.value();//获得注解上的layoutId值
                activity.setContentView(layoutId);//将layoutId设置给activity
                //activity.setContentView(layoutId)也可用反射实现,但效率低且麻烦,所以直接使用上面setContentView(layoutId)方法,此处只是顺便说一下反射调方法 
               //aClass.getMethod("setContentView",int.class).invoke(activity,layoutId); 
           }
        }
        /**
         * 注入String资源
         * @param activity
         */
        private static void bindStringRes(Activity activity) throws IllegalAccessException {
            Class<? extends Activity> aClass = activity.getClass();
            Field[] fields = aClass.getDeclaredFields();
            //遍历成员变量取出被StringRes注解的field
            for (Field field : fields) {
                if(field.isAnnotationPresent(StringRes.class)){
                    StringRes annotation = field.getAnnotation(StringRes.class);
                    int stringId = annotation.value();//string资源文件id
                    String stringValue = activity.getString(stringId);//获取到相应资源文件string值
                    //反射赋值
                    field.setAccessible(true);//破封装
                    field.set(activity,stringValue);
                }
            }
        }
        /**
         * 注入view
         * @param activity
         * @throws IllegalAccessException
         */
        private static void bindViews(Activity activity) throws IllegalAccessException/*, NoSuchMethodException, InvocationTargetException */{
            //反射拿到@Bind注解的成员变量
            Class<? extends Activity> aClass = activity.getClass();
            Field[] fields = aClass.getDeclaredFields();//拿到所有成员变量
            for (Field field : fields) {
                //遍历成员变量,判断成员变量上是否有@Bind注解
                if (field.isAnnotationPresent(Bind.class)) {
                    //如果有,拿出注解的value值,即控件id
                    Bind bind = field.getAnnotation(Bind.class);
                    int viewId = bind.value();
                    View view = activity.findViewById(viewId);//获取到view对象
                    //activity.findViewById(viewId)也可用反射实现,但效率低且麻烦,所以直接使用上面find方法,此处只是顺便说一下反射调方法
                    //View view= (View) aClass.getMethod("findViewById",int.class).invoke(activity,viewId);
                    field.setAccessible(true);//破封装
                    field.set(activity, view);//将view设置给该成员变量
                }
            }
        }
    
        /**
         * 绑定监听事件
         * @param activity
         */
        private static void bindClicks(final Activity activity) {
            Class<? extends Activity> aClass = activity.getClass();
            Method[] declaredMethods = aClass.getDeclaredMethods();//反射获取方法
            //遍历方法,判断方法上是否有@OnClick注解
            for (final Method method : declaredMethods) {
                if(method.isAnnotationPresent(OnClick.class)){
                    OnClick annotation = method.getAnnotation(OnClick.class);
                    int[] viewIds = annotation.value();//拿到该方法上注解的view的id集
                    for (int viewId : viewIds) {
                        final View view = activity.findViewById(viewId);
                        if(null != view){
                            view.setOnClickListener(new View.OnClickListener() {
                                @Override
                                public void onClick(View v) {
                                    try {
                                        method.setAccessible(true);//破封装
                                        method.invoke(activity,view);//调起该带有@OnClick注解方法
                                    } catch (Exception e) {
                                        e.printStackTrace();
                                    }
                                }
                            });
                        }
                    }
                }
            }
        }
    }
    

    我们看到,Shaver类里面定义了一个入口方法bind(Activity activity) ,然后bind方法里面我们调用了4个方法,这4个方法分别是处理4个注解逻辑,其思路上面已经说了,就是通过反射扫描带有这4个注解的编程元素(类/方法/成员变量),然后获取注解上的value,拿到这些id后,我们就可以做很多事了。代码本身不复杂,这里就不多说了。

    • Shaver使用
    package com.youzhi.shaver;
    //省略各种导包
    @ContentView(R.layout.activity_main)
    public class MainActivity extends AppCompatActivity {
        @Bind(R.id.btn1)
        Button btn1;
        @Bind(R.id.btn2)
        Button btn2;
        @Bind(R.id.tv1)
        TextView tv1;
        @Bind(R.id.et1)
        EditText et1;
        @StringRes(R.string.string_shaver)
        String stringRes;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            Shaver.bind(this);
            Log.e("MainActivity", btn1.getText().toString() + " , " + btn2.getText().toString() + " , " + tv1.getText().toString() + " , " + et1.getText().toString());
            tv1.setText(stringRes);
        }
        @OnClick({R.id.btn1,R.id.btn2})
        public void onClicks(View view){
            switch (view.getId()){
                case R.id.btn1:
                    Toast.makeText(this, "点击了btn1", Toast.LENGTH_SHORT).show();
                    break;
                case R.id.btn2:
                    Toast.makeText(this, "点击了btn2", Toast.LENGTH_SHORT).show();
                    break;
            }
        }
    }
    

    然后我们在String文件中有一个string_shaver,用于例子中注入@StringRes(R.string.string_shaver)

    String资源文件.png

    布局文件:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:orientation="vertical"
        android:gravity="center_horizontal"
        android:layout_height="match_parent">
        <Button
            android:id="@+id/btn1"
            android:layout_width="150dp"
            android:background="#33ff00ff"
            android:layout_marginBottom="10dp"
            android:text="按钮1"
            android:layout_height="30dp"/>
        <Button
            android:id="@+id/btn2"
            android:text="按钮2"
            android:layout_width="150dp"
            android:background="#33ffff00"
            android:layout_marginBottom="10dp"
            android:layout_height="30dp"/>
        <TextView
            android:id="@+id/tv1"
            android:layout_width="150dp"
            android:background="#4433bb00"
            android:layout_marginBottom="10dp"
            android:gravity="center"
            android:text="文本1"
            android:layout_height="30dp"/>
        <EditText
            android:id="@+id/et1"
            android:layout_width="150dp"
            android:background="#443300ff"
            android:layout_marginBottom="10dp"
            android:gravity="center"
            android:text="输入文本框1"
            android:layout_height="30dp"/>
    
    </LinearLayout>
    
    • 运行结果及打印的log
    运行结果.png
    log.png

    我们成功获取到各个view的文本,以及设置上点击事件,说明我们的Shaver起作用了!It works!

    这里需要注意打印出来的文本框为什么运行结果是“我是字符串资源”而log是“文本1”?大家看仔细点,我们布局文件里面text是“文本1”,然后我们在MainActivity里面打印出来后,我们重新set了一次,改成string文件里的值了。

    The End

    我们的低仿ButterKnife到此结束,相信讲解的已经够仔细了,到这里我相信现在对反射及注解有更深入的理解和巩固。
    最后,转载请注明出处。

    相关文章

      网友评论

          本文标题:Java基础回归之注解Annotation【低仿ButterKn

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