前言
     最近给公司写android的公司开发库,自写了一个注解库,用于方便公司写某些重复代码( findviewbyId、setOnClickListener、setOnTouchListener )时使用注解。同时收集了不少材料,这里总结分享下,希望与众人多多学习、交流。      
一、Android中的注解框架
     Android开发过程中,我们经常会遇到以下情况:在Activity中有一个View,我们要获得这个View的实例是要通过findViewById这个方法,然后这个方法返回的是一个Object类型,我们还需要进行强制的类型转换。当一个布局中有很多个控件的时候,每一个控件都要进行上面的这个操作,其实是没有意义的重复代码,类似的还有OnClick,OnTouch等方法。     所以许多Android开发人员会引入注解框架来配合开发,以提高开发效率。以下是 国内前500APP使用注解框架的情况:      

    Dagger框架的使用方式和Google Guice差不多,其实Dagger是Guice的一个子集,更轻量,更适合在Android平台使用在国外使用比较多,没有采取反射而使用了预编译技术,因为基于反射的DI非常占用资源和耗时。使用比较复杂,在View上的注入非常麻烦。Dagger能够注入到任何你想要的对象,只要其在module类中。或者它是构造器。但是缺少对方法和字段的注入支持。 对于Dagger我们可以把它当做应用中的一个模块, 负责为其它模块提供实例并且注入依赖关系。那是它的基本职责。模块的创建位于我们应用中的一个点上,这样我们可以拥有完全的控制权。   ButterKnife Buffer knife只是避免样板代码,findViewById,仅此而已,所以不能算是一个真正的注入。只是一个view的代言,可以在在非activity里面实施,也可以在inflate的views里面实施。  RoboGuice 为Android平台上基于Google Guice开发的一个库,方便findViewById在XML中查找一个View,并将其强制转换到所需类型,通过运行时读取annotations进行反射,所以可能影响应用性能。 AndroidAnnotations是最火的快速开发框架,大量的使用注解,不会对APP的造成不良影响,会影响到APP的执行性能。在编译器中加了一层额外的自动编译步骤,用来生成基于你源码的代码。使用AA的注解在编译期间就已经自动生成了对应的子类,运行期运行的其实就是这个子类.则不会造成任何负面的影响。AndroidAnnotations像是综合了其他的几个框架的优点,通过注解,减少了繁琐(R.id.btn)的使用,并在编译的时候,将相应的注解进行转换。即:加快了开发的速度,又不影响app的性能。

开发项目使用注解框架,我个人是比较倾向使用AndroidAnnotations,因为他的功能强大和便利性。示例代码:
import android.app.Activity; import android.widget.EditText; import android.widget.TextView;
import com.googlecode.androidannotations.annotations.Click; import com.googlecode.androidannotations.annotations.EActivity; import com.googlecode.androidannotations.annotations.ViewById;
@EActivity(R.layout.main) public class MyActivity extends Activity {
    @ViewById(R.id.myInput)     EditText myInput;
    @ViewById(R.id.myTextView)     TextView textView;
    @Click    void myButton() {         String name = myInput.getText().toString();        textView.setText("Hello " + name);     } }
    
     
二、JAVA的注解和分类      什么是注解(Annotation):   Annotation(注解)就是Java提供了一种元程序中的元素关联任何信息和着任何元数据(metadata)的途径和方法。Annotion(注解)是一个接口,程序可以通过反射来获取指定程序元素的Annotion对象,然后通过Annotion对象来获取注解里面的元数据。 注解(Annotation),也叫元数据。一种代码级别的说明。它是在JDK5.0及以后中引入的一种特性,与类、接口、枚举是在同一层次, 在java.lang的包中已经定义了三个注解:Override,Deprecated,SuppressWarnings. 。它可以声明在包、类、字段、方法、局部变量、方法、参数等的前面,用来对这些元素进行说明、注释。 作用分类: 1.编写文档:通过代码里标识的元数据生成文档。(生成文档) 2.代码分析:通过代码里标识的元数据对代码进行分析。(使用反射) 3.编译检查:通过代码里标识的元数据让编译器能够实现基本的编译检查。(Override)
Java注解可以在编译、类加载、运行时被读取,注解不会影响程序代码的运行,如果希望注解在运行时起到作用:需要通过反射得到相应的注解,然后在对其进行处理。
包 java.lang.annotation 中包含所有定义自定义注解所需用到的原注解和接口。如接口 java.lang.annotation.Annotation 是所有注解继承的接口,并且是自动继承,不需要定义时指定,类似于所有类都自动继承Object。
该包同时定义了四个元注解,Documented,Inherited,Target(作用范围,方法,属性,构造方法等),Retention(生命范围,源代码,class,runtime)。
注解在何种时刻可获取取决于Retention决定,RetentionPolicy.RUNTIME表示在运行时可见,它将被写入class文件的VisibleAnnotation属性中。RetentionPolicy.CLASS表示写入CLASS文件但不会在运行时获取,它们被写入字节码的invisibleAnnotation属性中。这些通常是为了方便IDE或者工具开发者的。当然,通过一些字节码库,应用程序无需了解字节码结构一样可以获取它们。剩下一个表示只在编译时可见,不会被写入CLASS文件。它们用于指示编译器行为,例如检查重载,设置过时,抑制警告等。这类注解是给编译器开发者准备的。
这里特别要指出注解的Retention的作用: 1.RetentionPolicy.SOURCE —— 这种类型的Annotations只在源代码级别保留,编译时就会被忽略
2.RetentionPolicy.CLASS —— 这种类型的Annotations编译时被保留,在class文件中存在,但JVM将会忽略
3.RetentionPolicy.RUNTIME —— 这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用.

有一些源码使用运行时注解来注入Android的View中,这里并不建议,可能写法稍微简单点,但是在运行效率上会比编译时注解低。
运行时注解的例子:
public static void injectContentView(Activity activity) {
    Class a = activity.getClass();
    if (a.isAnnotationPresent(ContentView.class)) {
        // 得到activity这个类的ContentView注解
        ContentView contentView = (ContentView) a.getAnnotation(ContentView.class);
        // 得到注解的值
        int layoutId = contentView.value();
        // 使用反射调用setContentView
        try {
            Method method = a.getMethod("setContentView", int.class);
            method.setAccessible(true);
            method.invoke(activity, layoutId);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

可以很明显看出是通过“反射”来将某个setContentView注入给activity,来得到activity.setContentView(layoutId)的代码实现。

下面要介绍的编译时注解,直接在编译阶段,根据注解标识,动态生成一些类或者生成一些xml都可以,在运行时期,这类注解是没有的,会依靠动态生成的类做一些操作,因为没有反射,效率和直接调用方法没什么区别。


三、Android中编译时注解的原理和实现
原理方面:其实就是1.定义注解类,来定义自定义的注解,比如:
    @Retention(RetentionPolicy.CLASS)     @Target(ElementType.FIELD)     public @interface BindView     {         int value();     }
2.通过一个继承于javax.annotation.processing.AbstractProcessor的注解处理器类(这就要求我们将所在工程定义个java library,因为Android的工程已经不支持javax),在process()方法里面,将你需要的注解生成为需要的代码,举个例子:


@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    mAnnotatedClassMap.clear();

    try {         processBindView(roundEnv);//生成初始化“@BindView” 注入的控件的代码         processOnClick(roundEnv);//生成初始化“@OnClick” 注入的控件的代码         processOnTouch(roundEnv);//生成初始化“@OnTouch” 注入的控件的代码     } catch (IllegalArgumentException e) {
        e.printStackTrace();
        error(e.getMessage());
    }

    for (AnnotatedClass annotatedClass : mAnnotatedClassMap.values()) {
        try {
            annotatedClass.generateFile().writeTo(mFiler);
        } catch (IOException e) {
            error("Generate file failed, reason: %s", e.getMessage());
        }
    }
    return true;
}


在以往的定义注解解析器时,需要在解析器类定义过程中,做以下操作: 在解析类名前定义: @SupportedAnnotationTypes("com.bosssoft.zhangxinjin.ViewInjectProcesser") @SupportedSourceVersion(SourceVersion.RELEASE_7)
同时在java的同级目录新建resources目录, 新建META-INF/services/javax.annotation.processing.Processor文件, 文件中填写你自定义的Processor全类名,这是向JVM声明解析器。

当然幸好我们现在使用的是Android Studio,可以用auto-service来替代以上操作,只要在注解类前面加上@AutoService(Processor.class)就可以替代以上操作。 还有android-apt工具。在新IDE中是这样操作的:                      在主module的build.gradle中添加工具            dependencies { ... classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'}           在使用注解的应用module的build.gradle,添加            apply plugin: 'com.neenbedankt.android-apt'      ...      dependencies {           compile project(':bosssoft-mobile-android-compiler')           apt project(':bosssoft-mobile-android-compiler')      }
当然新版本的gradle2.2+中,我们也可以用gradle自带的annotationProcessor来替代android-apt:            dependencies {           compile project(':bosssoft-mobile-android-compiler')           annotationProcessorproject(':bosssoft-mobile-android-compiler')      }



继续贴一些代码:

private void processOnClick(RoundEnvironment roundEnv) throws IllegalArgumentException {
    for (Element element : roundEnv.getElementsAnnotatedWith(OnClick.class)) {
        AnnotatedClass annotatedClass = getAnnotatedClass(element);
        OnClickMethod onClickMethod = new OnClickMethod(element);
        annotatedClass.addClickMethod(onClickMethod);
    } }

AnnotatedClass  类 public void addClickMethod(com.bosssoft.platform.mobile.android.compiler.model.OnClickMethod method) {     onClickMethods.add(method); }


public JavaFile generateFile() {
    //generateMethod
    MethodSpec.Builder injectMethod = MethodSpec.methodBuilder("inject")
            .addModifiers(Modifier.PUBLIC)
            .addAnnotation(Override.class)
            .addParameter(TypeName.get(mTypeElement.asType()), "host", Modifier.FINAL)
            .addParameter(TypeName.OBJECT, "source")
            .addParameter(com.bosssoft.platform.mobile.android.compiler.TypeUtil.PROVIDER,"provider");

    for(BindViewField field : mFields){
        // find views
        injectMethod.addStatement("host.$N = ($T)(provider.findView(source, $L))",
                field.getFieldName(),
                ClassName.get(field.getFieldType()), field.getResId());
    }
... //一些其他注解代码 ...
    //generaClass
    TypeSpec injectClass = TypeSpec.classBuilder(mTypeElement.getSimpleName() + "$$ViewInject")
            .addModifiers(Modifier.PUBLIC)
            .addSuperinterface(ParameterizedTypeName.get(com.bosssoft.platform.mobile.android.compiler.TypeUtil.INJET, TypeName.get(mTypeElement.asType())))
            .addMethod(injectMethod.build())
            .build();

    String packgeName = mElements.getPackageOf(mTypeElement).getQualifiedName().toString();

    return JavaFile.builder(packgeName, injectClass).build();
}



最后会在使用注解module的build目录下生成: Android如何开发自定义编译时注解_第1张图片

以上就是我们注解自动生成的代码,也是我们注解最终能够成功运行的关键。

 

更多相关文章

  1. Android代码开发性能指引
  2. Android源代码分析(三) MediaScanner源码分析(下)
  3. Android当中的MVP模式(七)终篇---关于对MVP模式中代码臃肿
  4. android上通过反射,获取存储器列表
  5. android 通过lint以及android-resource-remover清楚不用的资源以
  6. Android 获取无线蓝牙MAC信息代码
  7. android 简单拨号器 代码

随机推荐

  1. ubuntu16.04 compile android L error
  2. Android(安卓)之美 从0到1 之Android(安
  3. android 设置主页面的方式
  4. Android保活相关博客大纲
  5. android精确布局图
  6. android视频教程 网盘下载
  7. android ble notify
  8. Android中使用@hide成员
  9. android p cts CtsCameraTestCases fail
  10. Android——4.2.2 源码目录结构分析