1. Android中的IOC(DI)框架
1.1 ViewUtils简介(xUtils中的四大部分之一)
-
IOC: Inverse of Controller 控制反转。
-
DI: Dependency Inject 依赖注入
-
完全注解方式就可以进行UI绑定和事件绑定。
-
无需findViewById和setClickListener等。
1.2 ViewUtils使用
compile 'org.xutils:xutils:3.5.0'
compile 'com.jiechic.library:xUtils:2.6.14'
// xUtils的view注解要求必须提供id,以使代码混淆不受影响。 @ViewInject(R.id.textView) TextView textView; //@ViewInject(vale=R.id.textView, parentId=R.id.parentView) //TextView textView; @ResInject(id = R.string.label, type = ResType.String) private String label; // 取消了之前使用方法名绑定事件的方式,使用id绑定不受混淆影响 // 支持绑定多个id @OnClick({R.id.id1, R.id.id2, R.id.id3}) // or @OnClick(value={R.id.id1, R.id.id2, R.id.id3}, parentId={R.id.pid1, R.id.pid2, R.id.pid3}) // 更多事件支持参见ViewCommonEventListener类和包com.lidroid.xutils.view.annotation.event。 @OnClick(R.id.test_button) public void testButtonClick(View v) { // 方法签名必须和接口中的要求一致 ... } ... //在Activity中注入: @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); ViewUtils.inject(this); //注入view和事件 ... textView.setText("some text..."); ... } //在Fragment中注入: @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.bitmap_fragment, container, false); // 加载fragment布局 ViewUtils.inject(this, view); //注入view和事件 ... } //在PreferenceFragment中注入: public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); ViewUtils.inject(this, getPreferenceScreen()); //注入view和事件 ... } // 其他重载 // inject(View view); // inject(Activity activity) // inject(PreferenceActivity preferenceActivity) // inject(Object handler, View view) // inject(Object handler, Activity activity) // inject(Object handler, PreferenceGroup preferenceGroup) // inject(Object handler, PreferenceActivity preferenceActivity)
1.3 反射:调用(设置)类私有成员和私有方法
定义的User类:
package cn.Douzi.Annotation_Test; public class User { @ViewInject(age=23, name="张三") private String name; private int age; private String eat(String eat) { System.out.println("eat: " + eat); return eat + "真好吃"; } @Override public String toString() { // TODO Auto-generated method stub return "User [name =" + name + ", age = " + age + "]"; } }
反射调用私有对象,设置该类对象:
Class cls = User.class; /** * 2. 将这个字节码中的name字段获取到 */ // cls.getField(name); //这个方法只能获取声明为public的字段 Field declaredField = cls.getDeclaredField("name"); Field declaredFieldAge = cls.getDeclaredField("age");
declaredField.setAccessible(true); //设置允许访问,其实就是允许暴力反射 declaredFieldAge.setAccessible(true); //给user对象的declaredField设置为name declaredField.set(user, name); declaredFieldAge.set(user, age);
反射调用私有函数,设置该类函数:
//通过反射调用User对象的eat方法 Method declaredMethod = cls.getDeclaredMethod("eat", String.class); //暴力反射调用改方法 declaredMethod.setAccessible(true); Object result = declaredMethod.invoke(user, "牛肉拉面"); # 设置函数参数列表 System.out.println(result);
1.4 反射 和 自定义注解类
自定义注解类:
package cn.Douzi.Annotation_Test; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import javax.lang.model.element.Element; /** * @Taget(ElementType.METHOD) //用于方法 * */ @Target(ElementType.FIELD) //用于限制当前自定义注解类作用的对象 //@Retention(RetentionPolicy.SOURCE) //该注解只会作用在源码阶段,当源码编译成字节码时注解信息就被清除 //@Retention(RetentionPolicy.CLASS) //该注解只会作用在字节码阶段,但是当虚拟机加载这个字节码时,注解信息被清除 @Retention(RetentionPolicy.RUNTIME) //该注解类一直保留到加载到虚拟机中 public @interface ViewInject { int age(); String name(); }
package cn.Douzi.Annotation_Test; public class User { @ViewInject(age=23, name="张三") private String name; private int age; private String eat(String eat) { System.out.println("eat: " + eat); return eat + "真好吃"; } @Override public String toString() { // TODO Auto-generated method stub return "User [name =" + name + ", age = " + age + "]"; } }
1.5 测试自定义注解类
如何获取自定义注解对象(User)中参数
Class cls = User.class; /** * 2. 将这个字节码中的name字段获取到 */ // cls.getField(name); //这个方法只能获取声明为public的字段 Field declaredField = cls.getDeclaredField("name"); Field declaredFieldAge = cls.getDeclaredField("age"); /** * 3. 将当前字段上的注解对象获取到 */ ViewInject viewInject = declaredField.getAnnotation(ViewInject.class); if (viewInject != null) { /* * 4.获取自定义注解对象中的参数 */ int age = viewInject.age(); String name = viewInject.name(); System.out.println("name: " + name + ", age = " + age); }
测试是否能获取到注解对象(完整代码)
package cn.Douzi.Annotation_Test; import java.lang.reflect.Field; public class AnnotationMainTest { public static void main(String[] args) throws NoSuchFieldException, SecurityException { //获取User类中name字段上的自定义注解的值,然后将该值的age通过反射设置给User对象的age属性 //将name设置给User对象的name属性 User user = new User(); /** * 反射: * 1. 先去获取到User的字节码 */ // user.getClass(); // User.class // Class.forName("xxxx") Class cls = User.class; /** * 2. 将这个字节码中的name字段获取到 */ // cls.getField(name); //这个方法只能获取声明为public的字段 Field declaredField = cls.getDeclaredField("name"); /** * 3. 将当前字段上的注解对象获取到 */ ViewInject viewInject = declaredField.getAnnotation(ViewInject.class); if (viewInject != null) { /* * 4.获取自定义注解对象中的参数 */ int age = viewInject.age(); String name = viewInject.name(); System.out.println("name: " + name + ", age = " + age); } else { System.out.println("字段上无自定义注解"); } } }
1.6 通过暴力反射将获取到的注解值设置给User对象 (完整代码)
package cn.Douzi.Annotation_Test; import java.lang.reflect.Field; public class AnnotationMainTest { public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { //获取User类中name字段上的自定义注解的值,然后将该值的age通过反射设置给User对象的age属性 //将name设置给User对象的name属性 User user = new User(); /** * 反射: * 1. 先去获取到User的字节码 */ // user.getClass(); // User.class // Class.forName("xxxx") Class cls = User.class; /** * 2. 将这个字节码中的name字段获取到 */ // cls.getField(name); //这个方法只能获取声明为public的字段 Field declaredField = cls.getDeclaredField("name"); Field declaredFieldAge = cls.getDeclaredField("age"); /** * 3. 将当前字段上的注解对象获取到 */ ViewInject viewInject = declaredField.getAnnotation(ViewInject.class); if (viewInject != null) { /* * 4.获取自定义注解对象中的参数 */ int age = viewInject.age(); String name = viewInject.name(); System.out.println("name: " + name + ", age = " + age); /* * 5. 通过反射将这个两个值设置给User对象 */ declaredField.setAccessible(true); //设置允许访问,其实就是允许暴力反射 declaredFieldAge.setAccessible(true); //给user对象的declaredField设置为name declaredField.set(user, name); declaredFieldAge.set(user, age); System.out.println(user.toString()); } else { System.out.println("字段上无自定义注解"); } } }
1.7 通过反射调用User对象的eat方法 (完整代码)
package cn.Douzi.Annotation_Test; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class AnnotationMainTest { public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { //获取User类中name字段上的自定义注解的值,然后将该值的age通过反射设置给User对象的age属性 //将name设置给User对象的name属性 User user = new User(); /** * 反射: * 1. 先去获取到User的字节码 */ // user.getClass(); // User.class // Class.forName("xxxx") Class cls = User.class; /** * 2. 将这个字节码中的name字段获取到 */ // cls.getField(name); //这个方法只能获取声明为public的字段 Field declaredField = cls.getDeclaredField("name"); Field declaredFieldAge = cls.getDeclaredField("age"); /** * 3. 将当前字段上的注解对象获取到 */ ViewInject viewInject = declaredField.getAnnotation(ViewInject.class); if (viewInject != null) { /* * 4.获取自定义注解对象中的参数 */ int age = viewInject.age(); String name = viewInject.name(); System.out.println("name: " + name + ", age = " + age); /* * 5. 通过反射将这个两个值设置给User对象 */ declaredField.setAccessible(true); //设置允许访问,其实就是允许暴力反射 declaredFieldAge.setAccessible(true); //给user对象的declaredField设置为name declaredField.set(user, name); declaredFieldAge.set(user, age); System.out.println(user.toString()); } else { System.out.println("字段上无自定义注解"); } //通过反射调用User对象的eat方法 Method declaredMethod = cls.getDeclaredMethod("eat", String.class); //暴力反射调用改方法 declaredMethod.setAccessible(true); Object result = declaredMethod.invoke(user, "牛肉拉面"); System.out.println(result); } }
2. 自定义ViewUtils (Android)
布局文件:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.douzi.myviewutils.MainActivity"> <TextView android:id="@+id/tv1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!!!!" android:textSize="20dp" /> <TextView android:id="@+id/tv2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World2222222222!!!!" android:textSize="20dp" /> <Button android:id="@+id/btn" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="OKKKK" /> </LinearLayout>
注解类:
package com.douzi.myviewutils; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Created by Douzi on 2019/3/11. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface ViewInject { int value(); //这里必须叫value,否则绑定的时候需要写: xx=R.id.xx }
实现 ViewUtils的inject方法(使用反射)
package com.douzi.myviewutils; import android.app.Activity; import android.view.View; import java.lang.reflect.Field; /** * Created by Douzi on 2019/3/11. */ public class ViewUtils { public static void inject(Activity activity) { //绑定控件 try { bindView(activity); } catch (IllegalAccessException e) { e.printStackTrace(); } } private static void bindView(Activity activity) throws IllegalAccessException { /** * 1. 获取Activity的字节码 */ Class clazz = activity.getClass(); /** * 2. 获取到该字节码中所有的Filed */ Field[] declaredFields = clazz.getDeclaredFields(); /** * 3. 判断哪些是我们想要的字段(只有添加了ViewInject注解的字段) */ for (Field field : declaredFields) { //获取字段上面的注解 ViewInject viewInject = field.getAnnotation(ViewInject.class); if (viewInject != null) { /** * 4. 获取当前注解的值 */ int resId = viewInject.value(); /** * 5. 通过调用Activity的findViewById方法,获取当前id为resId的控件 */ View view = activity.findViewById(resId); /** * 6. 将当前的View设置给当前的Filed */ field.setAccessible(true); //给Activity对象的filed字段设置值为view对象 field.set(activity, view); } else { // nothing } } } }
测试注解的功能
package com.douzi.myviewutils; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.TextView; public class MainActivity extends AppCompatActivity { @ViewInject(R.id.tv1) TextView textView; @ViewInject(R.id.tv2) TextView textView2; private int count; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ViewUtils.inject(this); textView.setText("成功了!!"); textView2.setText("成功了2!!"); } }
绑定点击事件实现(实现ViewUtils的OnClick实现)
注解类
package com.douzi.myviewutils; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Created by Douzi on 2019/3/11. */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface OnClick { int value(); }
ViewUtils里添加函数
public static void bindOnClick(final Activity activity) { /** * 1. 获取字节码 */ Class clazz = activity.getClass(); /** * 2. 获取字节码中所有的方法 */ Method[] methods = clazz.getDeclaredMethods(); /** * 3. 遍历方法, 找出方法上声明了OnClick注解的方法 */ for (final Method method : methods) { /** * 4. 获取当前方法的上的注解 */ OnClick onClick = method.getAnnotation(OnClick.class); if (onClick != null) { /** * 5. 获取注解中的值 */ int resId = onClick.value(); /** * 6. 获取到id为resId的View */ final View view = activity.findViewById(resId); /** * 7. 将当前的View绑定点击事件 */ view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { /** * 8. 通过反射调用当前的用户的方法 */ //设置可以 method.setAccessible(true); try { method.invoke(activity, view); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } }); } } }
public static void inject(Activity activity) { //绑定控件 try { //绑定界面 bindView(activity); //绑定方法 bindOnClick(activity); } catch (IllegalAccessException e) { e.printStackTrace(); } }
测试注解功能
package com.douzi.myviewutils; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends AppCompatActivity { @ViewInject(R.id.tv1) TextView textView; @ViewInject(R.id.tv2) TextView textView2; private int count; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ViewUtils.inject(this); textView.setText("成功了!!"); textView2.setText("成功了2!!"); } @OnClick(R.id.btn) private void clickMe(View view) { Toast.makeText(this, "点击我了", Toast.LENGTH_LONG).show(); } }