1. 说明
这节课我们自己动手打造一套IOC注解框架,接下来我们首先看下ButterKnife源码的阅读和使用。
2. ButterKnife 源码阅读和使用
/**
* Email: [email protected]
* 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() ;
}
流程就是:
- 在编译的时候,用ButterKnifeProcessor 生成 .java文件 ,然后生成class文件,会把class文件打包到apk里边;
- 在运行时候,调用 viewBinder.bind(finder , target , source) 方法
3. 自己手写一个IOC注解框架
在这里特别说明下,之所以要自己手写一个IOC注解框架,其实不是重复造轮子,一方面是可以熟悉下反射加注解的写法,同时也可以在自己IOC注解框架中去拓展一些其他的功能,比如检测网络功能等,还有一个就是如果我们直接使用系统的findViewById()找到控件、 onClick点击事件、检测网络等这些东西的话,可能需要在每一个类中都去findViewById,每一个类中都需要去onClick点击事件、每一个类中都需要去检测网络等等,所以基于这些东西,我们才去手写一个 IOC注解框架,可以根据自己需求去扩展,同时也可以直接用到自己项目中,这些也都是可以的。
3.1:View 注解的 Annotation 用于在类中 @ViewById(R.id.test_tv) 这样用
/**
* Email: [email protected]
* 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: [email protected]
* 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: [email protected]
* 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: [email protected]
* 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