开发原因
目前工作需要在进行安卓开发的时候很容易写很多的findById或者事件监听方法,显得特别的臃肿。另外注解开发一贯是spring的使用风格,能不能直接用spring的思想来依赖注入安卓控件的值,答案是可以的。注解开发框架有很多,比如xUtils,但xUtils比较臃肿,它包含了数据库,网络请求,注解开发,图片加载。因为这是个比较成熟的框架,所以引入到项目中如果只使用其中的一个注解开发,会显得很笨重。所以引入了注解开发。
第一步搞清楚注解开发能减少那些工作
- setContentView布局注入
- findById控件注入
- onClick事件注入
废话少说上代码吧。
创建类选择Annotation类型
布局注入注解
package com.tydfd.annotationlibrary.iocannotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Classname ContentView
* @Description Activity使用的布局文件注解
* @Target(ElementType.TYPE) // 接口、类、枚举、注解
* * @Target(ElementType.FIELD) // 属性、枚举的常量
* * @Target(ElementType.METHOD) // 方法
* * @Target(ElementType.PARAMETER) // 方法参数
* * @Target(ElementType.CONSTRUCTOR) // 构造函数
* * @Target(ElementType.LOCAL_VARIABLE)// 局部变量
* * @Target(ElementType.ANNOTATION_TYPE)// 该注解使用在另一个注解上
* * @Target(ElementType.PACKAGE) // 包
* @Date 2019/7/17 15:41
* @Created by liudo
* @Author by liudo
* 生命周期:SOURCE < CLASS < RUNTIME
* * 1、一般如果需要在运行时去动态获取注解信息,用RUNTIME注解
* * 2、要在编译时进行一些预处理操作,如ButterKnife,用CLASS注解。注解会在class文件中存在,但是在运行时会被丢弃
* * 3、做一些检查性的操作,如@Override,用SOURCE源码注解。注解仅存在源码级别,在编译的时候丢弃该注解
*/
// 该注解作用于类,接口或者枚举类型上
@Target(ElementType.TYPE)
// 注解会在class字节码文件中存在,jvm加载时可以通过反射获取到该注解的内容
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentView {
/**
* @return int 类型布局
*/
int value();
}
控件注入
package com.tydfd.annotationlibrary.iocannotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Classname ViewInject
* @Description findById注入
* @Date 2019/7/17 16:02
* @Created by liudo
* @Author by liudo
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectView {
int value();
}
事件注入(有点复杂)运用AOP切面,使用java8的动态代理来拦截事件方法,然后将事件方法拦截替换自定义方法,随后将自定义方法代替事件方法来执行。废话少说,上代码。
定义注解上的注解基类,获取事件方法的名称进行拦截操作。
package com.tydfd.annotationlibrary.iocannotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author liudo
*/
@Target(ElementType.ANNOTATION_TYPE) // 放在注解的上面
@Retention(RetentionPolicy.RUNTIME)
public @interface EventBase {
// 事件的三个成员
// 1、set方法名
String listenerSetter();
// 2、监听的对象
Class<?> listenerType();
// 3、回调方法
String callBackListener();
}
然后定义点击注解(这只是个例子,然后依此例子开发别的事件)
package com.tydfd.annotationlibrary.iocannotation;
import android.view.View;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author liudo
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventBase(listenerSetter = "setOnClickListener", listenerType = View.OnClickListener.class, callBackListener = "onClick")
public @interface OnClick {
int[] value();
}
布局注入方法,通过java的反射。
//布局的注入
private static void injectLayout(Activity activity) {
//获取类
Class<? extends Activity> clazz = activity.getClass();
//获取类的注解
ContentView contentView = clazz.getAnnotation(ContentView.class);
if(contentView!=null){
//获取注解的值(R.layout.xxxxx)
int layoutId = contentView.value();
try {
//获取指定的方法(setContentView)
Method method = clazz.getMethod("setContentView", int.class);
Log.i(TAG, "injectLayout: "+method.getName());
//执行方法
method.invoke(activity,layoutId);
} catch (Exception e) {
e.printStackTrace();
}
}
}
控件注入方法
private static void injectViews(Activity activity) {
//获取类
Class<? extends Activity> clazz = activity.getClass();
//获取类的所有属性
Field[] fields = clazz.getDeclaredFields();
//循环,拿到每个属性
for(Field field:fields){
//获得属性上的注解
InjectView injectView = field.getAnnotation(InjectView.class);
//获得注解的值 并不是所有的属性都有注解
if(injectView != null){
int viewId = injectView.value();
try {
//获取findViewById方法,并执行
Method method = clazz.getMethod("findViewById", int.class);
Object view = method.invoke(activity, viewId);
//设置访问修饰符,访问权限private
field.setAccessible(true);
//属性的值赋给控件,在当前Activity
field.set(activity,view);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
事件注入方法(有点复杂,不过运用动态代理拦截官方方法,用自定义方法替换官方方法)
private static void injectEvents(Activity activity) {
// 获取类
Class<? extends Activity> clazz = activity.getClass();
// 获取类的所有方法
Method[] methods = clazz.getDeclaredMethods();
// 遍历方法
for (Method method : methods) {
// 获取每个方法的注解(多个控件id)
Annotation[] annotations = method.getAnnotations();
// 遍历注解
for (Annotation annotation : annotations) {
// 获取注解上的注解
// 获取OnClick注解上的注解类型
Class<? extends Annotation> annotationType = annotation.annotationType();
if (annotationType != null) {
// 通过EventBase指定获取
EventBase eventBase = annotationType.getAnnotation(EventBase.class);
if (eventBase != null) { // 有些方法没有EventBase注解
// 事件3大成员
String listenerSetter = eventBase.listenerSetter();
Class<?> listenerType = eventBase.listenerType();
String callBackListener = eventBase.callBackListener();
// 获取注解的值,执行方法再去获得注解的值
try {
// 通过annotationType获取onClick注解的value值
Method valueMethod = annotationType.getDeclaredMethod("value");
Log.i(TAG, "injectEvents: "+valueMethod.getName());
// 执行value方法获得注解的值
int[] viewIds = (int[]) valueMethod.invoke(annotation);
Log.i(TAG, "injectEvents: 333333333333333333"+ Arrays.toString(viewIds));
// 代理方式(3个成员组合)
// 拦截方法
// 得到监听的代理对象(新建代理单例、类的加载器,指定要代理的对象类的类型、class实例)
ListenerInvocationHandler handler = new ListenerInvocationHandler(activity);
// 添加到拦截列表里面
Log.i(TAG, callBackListener+"============="+method);
handler.addMethod(callBackListener, method);
// 监听对象的代理对象
// ClassLoader loader:指定当前目标对象使用类加载器,获取加载器的方法是固定的
// Class<?>[] interfaces:目标对象实现的接口的类型,使用泛型方式确认类型
// InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法
Object listener = Proxy.newProxyInstance(listenerType.getClassLoader(),
new Class[]{listenerType}, handler);
// 遍历注解的值
for (int viewId : viewIds) {
// 获得当前activity的view(赋值)
View view = activity.findViewById(viewId);
// 获取指定的方法
Method setter = view.getClass().getMethod(listenerSetter, listenerType);
// 执行方法
setter.invoke(view, listener);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
}
拦截器,将拦截的OnClick方法替换成我们自定义的方法
package com.tydfd.annotationlibrary.listener;
import android.util.Log;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
/**
* @author liudo
* // 将回调的onClick方法拦截,执行我们自己自定义的方法(aop概念)
*/
public class ListenerInvocationHandler implements InvocationHandler {
private static final String TAG = "LIUDONG";
// 需要拦截的对象
private Object target;
// 需要拦截的对象键值对
private HashMap<String, Method> methodHashMap = new HashMap<>();
public ListenerInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (target != null) {
Log.i(TAG, "invoke: "+ Arrays.toString(args)+"---------------------"+method);
// 获取需要拦截的方法名 // 假如是onClick
String methodName = method.getName();
Log.i(TAG, "invoke: "+methodName+"================="+methodHashMap.toString());
// 重新赋值,将拦截的方法换为show // 执行拦截的方法,show
method = methodHashMap.get(methodName);
if (method != null) {
return method.invoke(target, args);
}
}
return null;
}
/**
* 将需要拦截的方法添加
* @param methodName 需要拦截的方法,如:onClick()
* @param method 执行拦截后的方法,如:show()
*/
public void addMethod(String methodName, Method method) {
methodHashMap.put(methodName, method);
}
}
将注解封装成一个module,引入其中,直接可以使用。
BaseActivity
package com.tydfd.iocdemo;
import android.os.Bundle;
import android.os.PersistableBundle;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.tydfd.annotationlibrary.InjectManager;
/**
* @Classname BaseActivity
* @Description TODO
* @Date 2019/7/17 15:57
* @Created by liudo
* @Author by liudo
*/
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 帮助子类进行,布局、控件、事件的注入
InjectManager.inject(this);
}
}
MainActivity
package com.tydfd.iocdemo;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.tydfd.annotationlibrary.iocannotation.ContentView;
import com.tydfd.annotationlibrary.iocannotation.InjectView;
import com.tydfd.annotationlibrary.iocannotation.OnClick;
/**
* @author liudo
*/
@ContentView(R.layout.activity_main)
public class MainActivity extends BaseActivity {
@InjectView(R.id.btn)
private Button btn;
@InjectView(R.id.tv)
private TextView tv;
@Override
protected void onResume() {
super.onResume();
Toast.makeText(this, tv+"", Toast.LENGTH_SHORT).show();
// Toast.makeText(this, btn.getText().toString(), Toast.LENGTH_SHORT).show();
}
@OnClick({R.id.tv,R.id.btn})
public void show(View view){
switch (view.getId()){
case R.id.btn:
Toast.makeText(this, btn.getText(), Toast.LENGTH_SHORT).show();
break;
case R.id.tv:
Toast.makeText(this, tv.getText(), Toast.LENGTH_SHORT).show();
break;
}
}
}
总结
注解开发很方便但是资源消耗很大,能够后期维护迅速,开发简单。虽然现在本人做的这个demo比不上优秀的注解开发模块,但是其思路还是很棒的,减少很多代码开发。自我感觉良好。有时候思路比实现更重要。加油实习生,目标猪场。欢迎来交流,欢迎沟通。
本人微信《liudongGeek245210》欢迎骚扰。
本例子Demo链接。https://download.csdn.net/download/qq_38366111/11383221