示例:Android注解实现代码注入
Posted ZhangJianIsAStark
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了示例:Android注解实现代码注入相关的知识,希望对你有一定的参考价值。
前面的博客Android中的注解中,
我们简单描述了android中注解的含义和用途。
除了基本的用法外,注解还可以帮助我们实现代码注入,达到类似IoC的效果。
本篇博客以一个简单的例子,记录一下相关的内容。
通常的情况下,我们初始化界面的代码类似于:
public class MainActivity extends AppCompatActivity implements View.OnClickListener
private Button mBtn1;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBtn1 = findViewById(R.id.test);
mBtn1.setOnClickListener(this);
@Override
public void onClick(View view)
switch (view.getId())
case R.id.test:
Log.d("ZJTest", "click test btn");
break;
default:
break;
在上面代码的基础上,我们看看如何使用注解来实现代码注入。
一、注入ContentView
首先,我们来简单地替换掉setContentView方法。
定义一个注解:
/**
* @author zhangjian on 18-3-16.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentView
int value();
其中,@Target表示该注解可以用于什么地方,其定义如下:
public enum ElementType
/**
* Class, interface or enum declaration.
*/
TYPE,
/**
* Field declaration.
*/
FIELD,
/**
* Method declaration.
*/
METHOD,
/**
* Parameter declaration.
*/
PARAMETER,
/**
* Constructor declaration.
*/
CONSTRUCTOR,
/**
* Local variable declaration.
*/
LOCAL_VARIABLE,
/**
* Annotation type declaration.
*/
ANNOTATION_TYPE,
/**
* Package declaration.
*/
PACKAGE
@Retention表示:表示需要在什么级别保存该注解信息,其定义类似于:
public enum RetentionPolicy
/**
* Annotation is only available in the source code.
*/
SOURCE,
/**
* Annotation is available in the source code and in the class file, but not
* at runtime. This is the default policy.
*/
CLASS,
/**
* Annotation is available in the source code, the class file and is
* available at runtime.
*/
RUNTIME
定义完该注解后,我们就可以这样使用了:
//ContentView注解修饰类,其值为R.layout.activity_main
@ContentView(value = R.layout.activity_main)
public class MainActivity extends AppCompatActivity
.............
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
//注入代码
ViewInjectUtils.inject(this);
.............
public class ViewInjectUtils
public static void inject(Activity activity)
injectContentView(activity);
..............
private static void injectContentView(Activity activity)
Class<? extends Activity> clazz = activity.getClass();
//获取注解
ContentView contentView = clazz.getAnnotation(ContentView.class);
if (contentView != null)
//注解的值就是layout id
int contentViewId = contentView.value();
try
activity.setContentView(contentViewId);
catch (Exception e)
e.printStackTrace();
单纯从上面的代码来看,注解注入代码实际上没有任何的优势。
这里重要的是了解使用方法。
二、注入普通View
现在我们再定义一个注解:
/**
* @author zhangjian on 18-3-16.
*/
//这是修饰成员变量的
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject
int value();
定义该注解后,就可以这么使用了:
/**
* @author zhangjian
*/
@ContentView(value = R.layout.activity_main)
public class MainActivity extends AppCompatActivity
//修饰成员变量
@ViewInject(R.id.test)
private Button mBtn1;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
ViewInjectUtils.inject(this);
..........
/**
* @author zhangjian on 18-3-16.
*/
public class ViewInjectUtils
public static void inject(Activity activity)
injectContentView(activity);
injectViews(activity);
...........
.............
private static void injectViews(Activity activity)
Class<? extends Activity> clazz = activity.getClass();
//遍历所有field,找出其中具有ViewInject注解的
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields)
ViewInject viewInject = field.getAnnotation(ViewInject.class);
if (viewInject != null)
//取出值
int viewId = viewInject.value();
if (viewId != -1)
try
//找到view并赋值
Object view = activity.findViewById(viewId);
field.setAccessible(true);
field.set(activity, view);
catch (Exception e)
e.printStackTrace();
.........
有了ViewInject注解后,定义新的View时,只需要标上注解即可,
省略了每次调用findViewById。
不过从效率来看,这仍然没有实际调用效率高。
还是那句话,重在体会这种思想。
三、注入事件监听
实现了View的注入后,我们看看如何来通过注入完成对View点击事件的监听。
我们先定义一个注解:
/**
* @author zhangjian on 18-3-16.
*/
//这个注解是用来修饰注解的
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventBase
String setListenerMethod();
Class<?> listenerClass();
String listenerCallback();
基于EventBase再来定义一个注解:
/**
* @author zhangjian on 18-3-16.
*/
//修饰method的
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
//定义该注解对应的描述
@EventBase(setListenerMethod = "setOnClickListener",
listenerClass = View.OnClickListener.class, listenerCallback = "onClick")
public @interface OnClick
int[] value();
有了上述注解后,我们之前的demo就可以修改为:
/**
* @author zhangjian
*/
@ContentView(value = R.layout.activity_main)
public class MainActivity extends AppCompatActivity
@ViewInject(R.id.test)
private Button mBtn1;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
ViewInjectUtils.inject(this);
//我们的目前就是实现,点击R.id.test对应的button后
//会调用到clickBtnInvoked函数
@OnClick(R.id.test)
public void clickBtnInvoked(View view)
switch (view.getId())
case R.id.test:
Log.d("ZJTest", "click test btn");
break;
default:
break;
/**
* @author zhangjian on 18-3-16.
*/
public class ViewInjectUtils
public static void inject(Activity activity)
injectContentView(activity);
injectViews(activity);
injectEvents(activity);
.............
private static void injectEvents(Activity activity)
Class<? extends Activity> clazz = activity.getClass();
Method[] methods = clazz.getMethods();
//遍历所有的method
for (Method method : methods)
//获取每个method对应的注解
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations)
//获取注解对应的类型
Class<? extends Annotation> annotationType = annotation.annotationType();
//若是EventBase修饰的注解,则需要注入事件监听
EventBase eventBase = annotationType.getAnnotation(EventBase.class);
if (eventBase != null)
//class name
Class<?> listenerType = eventBase.listenerClass();
//class method
String listenerSetter = eventBase.setListenerMethod();
//callback method
String methodName = eventBase.listenerCallback();
try
//获取注解对应的值
Method annotationMethod = annotationType.getDeclaredMethod("value");
int[] viewIds = (int[])annotationMethod.invoke(annotation);
//创建动态代理
DynamicHandler handler = new DynamicHandler(activity);
//关联listener的回调接口和注解实际修饰的method
handler.addMethod(methodName, method);
Object listener = Proxy.newProxyInstance(listenerType.getClassLoader(),
new Class<?>[] listenerType, handler);
//为每一个View设置listener
for (int viewId : viewIds)
View view = activity.findViewById(viewId);
Method setListenerMethod = view.getClass()
.getMethod(listenerSetter, listenerType);
setListenerMethod.invoke(view, listener);
catch (Exception e)
e.printStackTrace();
至此,事件注入实现完毕。
其中,唯一有疑点就是生成动态代理的代码。
/**
* @author zhangjian on 18-3-16.
*/
public class DynamicHandler implements InvocationHandler
private WeakReference<Object> mHandlerRef;
private final HashMap<String, Method> mMethodMap = new HashMap<>(1);
public DynamicHandler(Object handler)
mHandlerRef = new WeakReference<>(handler);
//完成关联
public void addMethod(String name, Method method)
mMethodMap.put(name, method);
public Object getHandler()
return mHandlerRef.get();
public void setHandler(Object handler)
mHandlerRef = new WeakReference<>(handler);
//这个函数其实就能很好的反映动态代理的功能
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable
Object handler = mHandlerRef.get();
if (handler != null)
//调用实际的方法
String methodName = method.getName();
Method realM = mMethodMap.get(methodName);
if (realM != null)
return realM.invoke(handler, objects);
return null;
四、总结
至此,通过注解完成代码注入的示例介绍完毕。
虽然从上述例子来看,注解的优势似乎并不明显;
但这里重点在于记录一种思路,
为分析和使用retrofit等开源库打下基础。
以上是关于示例:Android注解实现代码注入的主要内容,如果未能解决你的问题,请参考以下文章
IOC 控制反转Android 视图依赖注入 ( 视图依赖注入步骤 | 视图依赖注入代码示例 )
IOC 控制反转Android 布局依赖注入 ( 布局依赖注入步骤 | 布局依赖注入代码示例 )
Android 组件化路由组件 ( 页面跳转参数依赖注入 )