首頁  >  文章  >  Java  >  Android(Java)程式碼手動實現依賴注入--技術詳解

Android(Java)程式碼手動實現依賴注入--技術詳解

php是最好的语言
php是最好的语言原創
2018-07-25 10:56:522056瀏覽

Android(Java)程式碼產生技術中的依賴注入,相信大家在平常的開發中這個詞沒少聽說過吧,比如做安卓開發的,使用的Butterknife、Greendao等等第三方庫,都是使用的一種叫做編譯期程式碼即時產生的技術,然後我們可以利用編譯產生的類別來輔助我們的開發,減少我們的工作量,這個技術聽上去感覺挺高大上的,編譯期間程式碼生成,這該怎麼做到啊,好像從來沒有從哪裡聽說編譯還能生成程式碼的,下面讓我們來看看這門神奇的技術!

編譯期程式碼產生原理

首先在這之前,我們可能或多或少了解到一個叫做JavaPoet的技術,首先我們從它開始,我們來到它的源碼,你會發現它好像並沒有做什麼高深的事情,總共才十幾個類,而且大多數只是做了一些類的封裝,提供一些接口方便使用,然後還提供了一個工具類供我們使用,如圖,它的原始碼僅僅只有下面幾個類別而已

Android(Java)程式碼手動實現依賴注入--技術詳解這時候就迷了,難道編譯期產生程式碼不是這個函式庫幫我們完成的嗎?
準確的說確實不是的,那它的功能是什麼呢?它其實只是完成了我們所說的一半的功能,就是代碼生成,而且是簡單易懂的代碼生成,因為它做了封裝,對一些常用的代碼生成需求基本上都提供了相應的方法,大大減少了程式碼複雜度。那和我們想要的需求來比,少了一個編譯期間產生,那這個怎麼做呢?

其實要實作編譯期間做一些事情,這個工作Java已經幫我們做好了,我們先來看一個類別AbstractProcessor,這個類別可能平常不會用到,但是也沒關係,我們簡單了解一下它,首先你可以把它理解為一個抽象的註解處理器,它位於javax.annotation.processing.AbstractProcessor;下面,是用於在編譯時掃描和處理註解的類別,我們要實作類似ButterKnife這樣的功能,先定義一個自己的註解處理器,然後繼承它即可,如下

public class MyProcessor extends AbstractProcessor{
    @Override
    public boolean process(Set set, RoundEnvironment roundEnvironment) {        return false;
    }
}

可以看到我們需要實作一個叫process的抽象方法,這個方法裡的內容就會在編譯期間執行。
然後,怎麼讓jvm在編譯期間調用我們自己寫的這個註解處理器呢,有一個快捷辦法就是使用谷歌的開源庫auto,然後使用它提供的AutoService註解來實現,另外一種辦法就是自己手動去建立指定的資料夾,然後配置我們的註解處理器的路徑。

做完上述工作後,在編譯時,jvm就會掃描到所有的AbstractProcessor 的實作類,這裡也就是MyProcessor ,然後呼叫實作實作類的process方法,執行對應的操作,然後我們產生程式碼的工作就可以寫在process這裡,然後具體的生成程式碼的方法再藉助JavaPoet工具來簡化操作。
因為我們產生程式碼的工具是在編譯期間執行的,最後產生的java程式碼會和普通的java程式碼一起編譯,當做普通的類別來使用,所以不會影響到最後程序運行時的效能,最多只是編譯速度慢了點,因為要產生額外的類別和程式碼。

至此我們知道怎麼在編譯期間產生程式碼,基本的實作思維也有了,不過還有一個問題存在。

怎麼使用註解處理器來處理一個註解,從而實現類似@BindView的效果呢?首先我們當然要自訂一個註解,具體自訂註解不了解的可以去學下,挺簡單的,內容不多,由於不是本章重點,所以不作過多說明,當然也可以就在文末的demo連結裡下載原始碼學習,在定義了一個註解之後,我們先看到我們的MyProcessor  註解處理器的process() 方法的annotations 參數,這個參數就是在編譯的時候,用來儲存掃描到的所有的非元註解(非元註解也就是自訂註解,元註解一共四個,除了元註解,剩下的就是自訂註解),然後我們遍歷這個集合,取到我們自訂註解時,再執行對應的邏輯。具體的程式碼如下,其中XXXAnnotation就是你的自訂註解的名稱

for (TypeElement element : annotations) {    if (element.getQualifiedName().toString().equals(XXXAnnotation.class.getCanonicalName())) {        //执行你的逻辑
    }
}

然後我們還需要重寫AbstractProcessor 類別的getSupportedAnnotationTypes() 方法和getSupportedSourceVersion() 方法,getSupportedAnnotationTypes()  方法用來指定該註解處理器是用來處理哪個註解的,getSupportedSourceVersion() 方法用來指定java版本,一般給值為SourceVersion.latestSupported()

完成以上工作后,对于自定义注解作用的对象,编译期间就会自动执行相应process() 里的逻辑,比如生成辅助类,这个辅助类其实就可以理解为依赖类,这个编译期间通过注解生成辅助类的过程,就相当于实现了注入。
到这里,一整个依赖注入的思路和实现方法已经全部打通。
下面我们来动手实现一个小例子吧!

动手实现

首先我们新建一个Android工程,按照默认的配置就好,新建完毕后,会有个默认的app的module,我们暂且不管它,然后直接新建一个java module,新建方式为file -> New -> New Module
然后选择Java Libary,这里给libary取名为javapoet
Android(Java)程式碼手動實現依賴注入--技術詳解然后在module对应的build.gradle下,加入下面两个依赖

implementation 'com.squareup:javapoet:1.11.1'implementation 'com.google.auto.service:auto-service:1.0-rc4'

第一个依赖是javaPoet的依赖,第二个是Google开源库auto的依赖
 上面提到让jvm加载我们自己的注解处理器有两种方式,这里我们先试一下用谷歌的这个开源库

这个module用来编写我们的注解处理器的实现类,先放着,待会再写。
继续新建一个java module,我这里取名为libannotation,然后在里面新建一个自定义注解,内容如下

@Retention(RetentionPolicy.CLASS)@Target(ElementType.TYPE)public @interface HelloAnnotation {}

@Retention(RetentionPolicy.CLASS)表示我们定义的这个注解会留存在class字节码文件中 ,但是在运行时是没有的,@Target(ElementType.TYPE) 表示我们的这个注解作用对象是类,然后我们看下整个目录的样子,
Android(Java)程式碼手動實現依賴注入--技術詳解

TestGenerator是一个我写的测试JavaPoet的测试类,可以忽略

现在我们开始写代码,首先在javapoet的build.gradle中加入libannotation  module的依赖,如下

implementation project(path: ':libannotation')

然后在javapoet module中新建HelloProcessor类,用AutoService标注它,然后继承AbstractProcessor方法,重写相应的方法,具体怎么写,原理里解释的比较详细,然后注释里我作了详细说明,如下

import com.aiiage.libannotation.HelloAnnotation;import com.google.auto.service.AutoService;import com.squareup.javapoet.JavaFile;import com.squareup.javapoet.MethodSpec;import com.squareup.javapoet.TypeSpec;import java.io.IOException;import java.util.Collections;import java.util.Set;import javax.annotation.processing.AbstractProcessor;import javax.annotation.processing.Filer;import javax.annotation.processing.ProcessingEnvironment;import javax.annotation.processing.Processor;import javax.annotation.processing.RoundEnvironment;import javax.lang.model.SourceVersion;import javax.lang.model.element.Modifier;import javax.lang.model.element.TypeElement;/**
 * Created By HuangQing on 2018/7/20 15:38
 **/@AutoService(Processor.class)public class HelloProcessor extends AbstractProcessor {
    private Filer filer;    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {        super.init(processingEnv);
        filer = processingEnv.getFiler(); // 获得filer对象,用来创建文件
    }    @Override
    public boolean process(Set annotations, RoundEnvironment roundEnv) {        for (TypeElement element : annotations) {//遍历扫描到的注解集合
            if (element.getQualifiedName().toString().equals(HelloAnnotation.class.getCanonicalName())) {                // 当前注解是我们自定义的注解,也就是HelloAnnotation时,执行下列代码
                TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")//声明类名为HelloWorld
                        .addModifiers(Modifier.PUBLIC, Modifier.FINAL)//声明类的修饰符为 public final
                        .addMethod(getMethodSpec("hello1", "Hello"))//为HelloWorld类添加名为hello1的方法,返回值为Hello
                        .addMethod(getMethodSpec("hello2", "Java"))//同上
                        .addMethod(getMethodSpec("hello3", "Poet!"))//同上
                        .build();                try {                    // 建立 com.aiiage.testjavapoet.HelloWorld.java 对象
                    JavaFile javaFile = JavaFile.builder("com.aiiage.testjavapoet", helloWorld)
                            .addFileComment(" This codes are generated automatically. Do not modify!")
                            .build();                    // 写入文件
                    javaFile.writeTo(filer);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }        return true;
    }    /**
     * @param methodStr  方法名
     * @param returnStr  返回值
     * @return
     */
    private static MethodSpec getMethodSpec(String methodStr, String returnStr) {        return MethodSpec.methodBuilder(methodStr)
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)//指定方法修饰符为 public static
                .returns(String.class) //指定返回值为String类型
                .addStatement("return $S", returnStr) //拼接返回值语句
                .build();
    }    @Override
    public Set getSupportedAnnotationTypes() {        return Collections.singleton(HelloAnnotation.class.getCanonicalName());
    }    @Override
    public SourceVersion getSupportedSourceVersion() {        return SourceVersion.latestSupported();
    }
}

上述代码中,一定不要忘了加AutoService的注解,通过这个注解,我们的HelloProcessor注解处理器相当于执行了一个注册的过程,这样才会被jvm在编译时加载,代码生成部分最终生成的文件代码长下面这个样子,可以结合注释对着体会下,还是很方便的,有了javapoet之后

import java.lang.String;public final class HelloWorld {
  public static String hello1() {    return "Hello";
  }  public static String hello2() {    return "Java";
  }  public static String hello3() {    return "Poet!";
  }
}

ok,代码方面准备完毕。
接下来我们为app module添加依赖,如下

//必须使用annotationProcessor,而不是implementation//annotationProcessor修饰的,最终不会打包到apk中,可理解为在编译时执行的annotationProcessor project(':javapoet')implementation project(':libannotation')

然后我们手动编译一下项目,build -> make project,然后我们来到熟悉的MainActivity,首先使用我们的自定义注解标注MainActivity,要标在class上面,因为我们自定义的注解作用范围是Type,然后用一个TextView简单显示一下注入的HelloWorld类的几个静态方法,如下

@HelloAnnotationpublic class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView tv=findViewById(R.id.tv);
        String str1="";
        str1+=HelloWorld.hello1()+" ";
        str1+=HelloWorld.hello2()+" ";
        str1+=HelloWorld.hello3();
        tv.setText(str1);
    }
}

编译运行,看到Hello Java Poet字样即表示成功!

然后我们上面提到,除了使用Google开源库auto实现注册注解处理器外,还可以使用手动配置的方式,手动配置的方式如下

在自定义注解处理器的module中,这里也就是javapoet的module中,在main目录下创建出resources目录,然后在resources目录下创建出META-INF目录,然后在META-INF目录下创建出services目录,然后在services目录下创建一个名为javax.annotation.processing.Processor 的文件,在该文件中声明我们的注解处理器:

com.aiiage.javapoet.HelloProcessor;

结语

这样我们就完成了一个最简单的依赖注入的实现,掌握其原理后,我们就不难明白类似ButterKnife、GreenDao是怎么实现的了,不过我们这个只是一个简单的功能,不得不感叹要生成那么多的代码,这得是一个多么心细和考验耐心的活啊!

相关推荐:

php生成Android客户端扫描可登录的二维码,android客户端

视频:Java视频教程_免费Java教程在线学习

以上是Android(Java)程式碼手動實現依賴注入--技術詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn