Android(Java)程式碼產生技術中的依賴注入,相信大家在平常的開發中這個詞沒少聽說過吧,比如做安卓開發的,使用的Butterknife、Greendao等等第三方庫,都是使用的一種叫做編譯期程式碼即時產生的技術,然後我們可以利用編譯產生的類別來輔助我們的開發,減少我們的工作量,這個技術聽上去感覺挺高大上的,編譯期間程式碼生成,這該怎麼做到啊,好像從來沒有從哪裡聽說編譯還能生成程式碼的,下面讓我們來看看這門神奇的技術!
首先在這之前,我們可能或多或少了解到一個叫做JavaPoet的技術,首先我們從它開始,我們來到它的源碼,你會發現它好像並沒有做什麼高深的事情,總共才十幾個類,而且大多數只是做了一些類的封裝,提供一些接口方便使用,然後還提供了一個工具類供我們使用,如圖,它的原始碼僅僅只有下面幾個類別而已
這時候就迷了,難道編譯期產生程式碼不是這個函式庫幫我們完成的嗎?
準確的說確實不是的,那它的功能是什麼呢?它其實只是完成了我們所說的一半的功能,就是代碼生成,而且是簡單易懂的代碼生成,因為它做了封裝,對一些常用的代碼生成需求基本上都提供了相應的方法,大大減少了程式碼複雜度。那和我們想要的需求來比,少了一個編譯期間產生,那這個怎麼做呢?
其實要實作編譯期間做一些事情,這個工作Java已經幫我們做好了,我們先來看一個類別AbstractProcessor
,這個類別可能平常不會用到,但是也沒關係,我們簡單了解一下它,首先你可以把它理解為一個抽象的註解處理器,它位於javax.annotation.processing.AbstractProcessor;
下面,是用於在編譯時掃描和處理註解的類別,我們要實作類似ButterKnife這樣的功能,先定義一個自己的註解處理器,然後繼承它即可,如下
public class MyProcessor extends AbstractProcessor{ @Override public boolean process(Set<? extends TypeElement> 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
然后在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)
表示我们的这个注解作用对象是类,然后我们看下整个目录的样子,
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<? extends TypeElement> 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<String> 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客户端
以上是Android(Java)程式碼手動實現依賴注入--技術詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!