示例: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 组件化路由组件 ( 页面跳转参数依赖注入 )

IOC 控制反转Android 事件依赖注入 ( 事件三要素 | 修饰注解的注解 | 事件依赖注入步骤 )

使用APT实现Android中View的注入

Android MVP-编程思想6(依赖注入多个P层方式优化---注解,反射)