自己动手打造一套IOC注解框架 - (ButterKnife源码阅读)

1. 说明


这节课我们自己动手打造一套IOC注解框架,接下来我们首先看下ButterKnife源码的阅读和使用。

2. ButterKnife 源码阅读和使用


/**
 * Email: 2185134304@qq.com
 * Created by JackChen 2018/3/28 9:11
 * Version 1.0
 * Params:
 * Description:  View 注解的 Annotation
*/
@Target(ElementType.CONSTRUCTOR)  // Target表示作用在哪里 FIELD:属性  TYPE:类  CONSTRUCTOR:构造方法
@Retention(RetentionPolicy.SOURCE) // Retention表示什么时间生效  RUNTIME:运行时  CLASS:编译时  SOURCE:源码资源
public @interface ViewById {

    // 指的是  --> ViewById(R.id.test_tv)
    int value() ;
}

流程就是:

  1. 在编译的时候,用ButterKnifeProcessor 生成 .java文件 ,然后生成class文件,会把class文件打包到apk里边;
  2. 在运行时候,调用 viewBinder.bind(finder , target , source) 方法

3. 自己手写一个IOC注解框架


在这里特别说明下,之所以要自己手写一个IOC注解框架,其实不是重复造轮子,一方面是可以熟悉下反射加注解的写法,同时也可以在自己IOC注解框架中去拓展一些其他的功能,比如检测网络功能等,还有一个就是如果我们直接使用系统的findViewById()找到控件、 onClick点击事件、检测网络等这些东西的话,可能需要在每一个类中都去findViewById,每一个类中都需要去onClick点击事件、每一个类中都需要去检测网络等等,所以基于这些东西,我们才去手写一个 IOC注解框架,可以根据自己需求去扩展,同时也可以直接用到自己项目中,这些也都是可以的。

3.1:View 注解的 Annotation 用于在类中 @ViewById(R.id.test_tv) 这样用

/**
 * Email: 2185134304@qq.com
 * Created by JackChen 2018/3/28 9:11
 * Version 1.0
 * Params:
 * Description:  View 注解的 Annotation   用于在类中 @ViewById(R.id.test_tv) 这样用
*/

@Target(ElementType.FIELD)  // Target表示作用在哪里 FIELD:属性  TYPE:类  CONSTRUCTOR:构造方法
@Retention(RetentionPolicy.RUNTIME) // Retention表示什么时间生效  RUNTIME:运行时  CLASS:编译时  SOURCE:源码资源
public @interface ViewById {

    // 指的是  --> ViewById(R.id.test_tv)
    int value() ;
}

3.2: View事件 注解的 Annotation 用于在类中 @OnClick({R.id.test_tv , R.id.test_iv}) 这样用

/**
 * Email: 2185134304@qq.com
 * Created by JackChen 2018/3/28 9:17
 * Version 1.0
 * Params:
 * Description:  View事件  注解的 Annotation  用于在类中 @OnClick({R.id.test_tv , R.id.test_iv})  这样用
*/
@Target(ElementType.METHOD)  // 代表注解的位置  FIELD:属性  TYPE:类  CONSTRUCTOR:构造方法  METHOD:方法
@Retention(RetentionPolicy.RUNTIME)  // 代表什么时间生效  RUNTIME:运行时  CLASS:编译时  SOURCE:源码资源
public @interface OnClick {

    // {R.id.test_tv , R.id.test_iv}
    int[] value() ;
}

3.3: 检测网络注解的Annotation 用于在类中 @CheckNet 这样用

/**
 * Email: 2185134304@qq.com
 * Created by JackChen 2018/3/28 9:11
 * Version 1.0
 * Params:
 * Description:  检测网络注解的Annotation   用于在类中 @CheckNet 这样用
*/

@Target(ElementType.METHOD)  // Target表示作用在哪里 FIELD:属性  TYPE:类  CONSTRUCTOR:构造方法
@Retention(RetentionPolicy.RUNTIME) // Retention表示什么时间生效  RUNTIME:运行时  CLASS:编译时  SOURCE:源码资源
public @interface CheckNet {

}

3.4: ViewUtils代码如下

/**
 * Email: 2185134304@qq.com
 * Created by JackChen 2018/3/31 9:20
 * Version 1.0
 * Params:
 * Description:
*/
public class ViewUtils {


    /**
     * 用于Activity中
     */
    public static void inject(Activity activity){
        inject(new ViewFinder(activity) , activity) ;
    }


    /**
     * 用于自定义View中
     */
    public static void inject(View view){
        inject(new ViewFinder(view) , view) ;
    }


    /**
     * 在项目时有时候会用到Fragment中
     */
    public static void inject(View view  ,Object object){
        inject(new ViewFinder(view) , object) ;
    }


    /**
     * 兼容上边3个方法      object -> 反射需要执行的类
     */
    private static void inject(ViewFinder finder , Object object){
        // 动态注入属性
        injectField(finder , object) ;
        // 动态注入事件
        injectEvent(finder , object) ;
    }


    /**
     * 动态注入属性    object -> 反射需要执行的类
     */
    private static void injectField(ViewFinder finder, Object object) {
        // 1. 获取类中所有属性           ->  private TextView mTextTV , private int mPage ; 等等这些变量属性
        Class<?> clazz = object.getClass();
        // 获取所有的属性 包括private、public、protected等所有属性
        Field[] fields = clazz.getDeclaredFields();


        // 2. 获取ViewById里边的value值  ->  R.id.test_tv这些东西
        for (Field field : fields) {
            ViewById viewById = field.getAnnotation(ViewById.class);
            if (viewById != null){
                // 获取注解里边的 id 值  -  R.id.test_tv
                int viewId = viewById.value();

                // 3. findviewbyid  找到View
                // 这里就相当于在 MainActivity中调用 TextView test_tv = (TextView) findViewById(R.id.test_tv);  是一码事
                View view = finder.findViewById(viewId);

                if(view != null) {
                    // 能够注入所有的修饰符,不管是 private、public、protected都是可以的,相当于添加权限
                    field.setAccessible(true);

                    // 4. 动态的注入属性 找到的View
                    try {
                        field.set(object, view);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }

                }
            }
        }


    }



    /**
     * 动态注入事件    object -> 反射需要执行的类
     */
    private static void injectEvent(ViewFinder finder, Object object) {
        // 1. 获取类中所有的方法
        Class<?> clazz = object.getClass();
        Method[] methods = clazz.getDeclaredMethods();

        // 2. 获取onClick里面的 value值
        for (Method method : methods) {
            OnClick onClick = method.getAnnotation(OnClick.class);
            if (onClick != null){
                int[] viewIds = onClick.value();
                for (int viewId : viewIds) {

                    // 3. findviewbyid 找到 view
                    View view= finder.findViewById(viewId);


                    // 拓展功能  检测网络  这里 !=null  代表需要检测网络
                    boolean isCheckNet = method.getAnnotation(CheckNet.class) != null;

                    if (view != null){
                        // 4. view.setOnClickListener
                        // 参数1:方法  参数2:谁去执行
                        view.setOnClickListener(new DeclaredOnClickListener(method , object , isCheckNet));
                    }

                }

            }
        }
    }


    /**
     * 这里是根据View的 onClick的源码
     */
    private static class DeclaredOnClickListener implements View.OnClickListener{

        private Method mMethod ;
        private Object mObject ;
        private boolean mIsCheckNet ;

        public DeclaredOnClickListener(Method method, Object object, boolean isCheckNet) {
            this.mMethod = method ;
            this.mObject = object ;
            this.mIsCheckNet = isCheckNet ;
        }


        /**
         * 最终我们在代码中写的点击事件会调用这个方法
         */
        @Override
        public void onClick(View v) {

            // 点击事件之前首先判断 需不需要检测网络
            if (mIsCheckNet){
                // 这里代表需要
                // 如果没网
                if (!networkAvailable(v.getContext())){
                    // 打印toast  "请检查网络" 这里写死会有问题 ,最好是可以去配置
                    Toast.makeText(v.getContext() , "请检查网络" , Toast.LENGTH_SHORT).show();
                    return;
                }
            }

            // 下边是执行有网的操作

            // 参数1:在哪一个类中执行  参数2:传递View
            try {
                // 确保所有类型方法都可以执行,包括private、public、protected等方法都可以执行
                mMethod.setAccessible(true);
                // 5. 反射注入方法
                mMethod.invoke(mObject , v) ;
            } catch (Exception e) {
                e.printStackTrace();


                // 这里是确保在类中的 onClick()方法中,不去传递View view的情况
                try {
                    mMethod.invoke(mObject , null) ;
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            }
        }
    }



    /**
     * 判断当前网络是否可用
     */
    private static boolean networkAvailable(Context context) {
        // 得到连接管理器对象
        try {
            ConnectivityManager connectivityManager = (ConnectivityManager) context
                    .getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo activeNetworkInfo = connectivityManager
                    .getActiveNetworkInfo();
            if (activeNetworkInfo != null && activeNetworkInfo.isConnected()) {
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

}

MainActivity类如下,自己用于测试


public class MainActivity extends AppCompatActivity {


    @ViewById(R.id.test_tv)
    TextView test_tv ;
    @ViewById(R.id.test_iv)
    ImageView test_iv ;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ViewUtils.inject(this);

        test_tv.setText("这里是用baselibrary注解的");

    }


    /**
     * 由于我们自己在ViewUtils中把点击事件已经try cache了,所以无论什么异常情况,程序都不会崩溃,最多是一个bug而已
     * 比如下边的 i=2/0 , 然后打印 i 的值 ,下边点击会没有反应,但是程序不会崩溃,这样就很好,体验就会很好,但是
     * 调试会比较麻烦,需要去看打印的警告 ,需要把控制台的级别点到 Warn 。
     * @param view
     */
    @OnClick({R.id.test_tv , R.id.test_iv})
    @CheckNet   // 表示如果没有网,就不要执行该方法了 ,而是直接打印没有网的toast
    public void onClick(View view){
        int i = 2/1 ;
        Toast.makeText(MainActivity.this , "执行结果是 -> " +i , Toast.LENGTH_SHORT).show();
    }
}

具体代码已上传至github:
https://github.com/shuai999/EssayJoke_day01.git

欢迎star,可以加我qq:2185134304

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353

推荐阅读更多精彩内容