Android (Java) コードで依存関係注入を手動で実装する - 詳細な技術説明

php是最好的语言
リリース: 2018-07-25 10:56:52
オリジナル
2235 人が閲覧しました

Android (Java) コード生成テクノロジにおける依存関係の挿入。たとえば、Android を開発している人は、 コードの即時生成と呼ばれるテクノロジを使用します。コンパイル中に、コンパイル済みのクラスを使用して、作業負荷を軽減できます。これは、コンパイル中にも実行できるということを聞いたことがないようです。コードを生成します。この魔法のテクノロジーを見てみましょう。

コンパイル時コード生成の原理

まず、その前に、

JavaPoet というテクノロジーについてある程度知っておく必要があります。まず、そのソース コードについて説明します。高度な処理を行うには、クラスは合計で 12 個しかありません。それらのほとんどは、いくつかのクラスをカプセル化し、簡単に使用できるようにいくつかのインターフェイスを提供し、次に示すように、使用するためのツール クラスも提供します。図では、そのソースコードは次のとおりです

この時点で私は混乱していますが、このライブラリはコンパイル中にコードを生成する役割を担っているのではないでしょうか? Android (Java) コードで依存関係注入を手動で実装する - 詳細な技術説明正確に言うと、そうではありません。では、その機能は何ですか?実際には、私たちが話している
コード生成の機能の半分しか完了しません。カプセル化されており、基本的にいくつかの一般的なコード生成要件に対応するメソッドを提供するため、シンプルで理解しやすいコード生成です。コードの複雑さが大幅に軽減されました。必要なものと比較すると、コンパイル中に生成される が 1 つ少なくなります。 実際、コンパイル中に何かを行う必要があります。最初に、クラス AbstractProcessor を見てみましょう。しかし、それは問題ではありません。これを簡単に理解するには、まず、javax.annotation.processing.AbstractProcessor; の下にあり、

コンパイル時のスキャンと処理に使用される抽象的な

アノテーション プロセッサ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() として理解することができます。クラス

では、ButterKnife のような関数を実装したいと考えています。次に示すように、最初に独自のアノテーション プロセッサを定義してから、それを継承します 🎜
implementation &#39;com.squareup:javapoet:1.11.1&#39;implementation &#39;com.google.auto.service:auto-service:1.0-rc4&#39;
ログイン後にコピー
ログイン後にコピー
🎜 このメソッドでは、process という抽象メソッドを実装する必要があることがわかります。内容は次のようになります。コンパイル中に実行されます。 🎜それでは、コンパイル中に作成したアノテーション プロセッサを jvm に呼び出すにはどうすればよいでしょうか? 簡単な方法は、Google のオープンソース ライブラリ auto を使用し、それが提供する AutoService アノテーションを使用してそれを実装することです。もう 1 つの方法は、自分で手動で実行することです。指定したフォルダーを作成し、注釈プロセッサへのパスを構成します。 🎜🎜上記の作業を完了した後、コンパイル中に、jvm は AbstractProcessor (ここでは MyProcessor) のすべての実装クラスをスキャンし、実装クラス Method を実装するプロセスを呼び出します。対応する操作を実行すると、コードを生成する作業をプロセスのここに記述することができ、JavaPoet ツールを使用してコードを生成する具体的な方法を簡素化できます。 🎜コード生成に使用するツールはコンパイル中に🎜実行されるため、最終的に生成されたJavaコードは通常のJavaコードと一緒にコンパイルされ、通常のクラスとして使用されるため、最終的なプログラムのパフォーマンスには影響しません。追加のクラスとコードを生成する必要があるため、コンパイル速度が少し遅くなるだけです。 🎜🎜これで、コンパイル中にコードを生成する方法がわかり、基本的な実装アイデアが得られましたが、まだ問題があります。 🎜🎜注釈プロセッサを使用して注釈を処理し、@BindView と同様の効果を実現するにはどうすればよいですか?もちろん、最初に、アノテーションをカスタマイズする必要があります。特定のカスタム アノテーションが分からない場合は、それについて学ぶことができます。これは、この章の焦点では​​ないためです。もちろん、これについては説明しませんが、記事の最後にあるデモ リンクに置くこともできます。注釈を定義した後、最初に annotations</code を確認します。 > <code>MyProcessor アノテーション プロセッサの process() メソッドのパラメータ。このパラメータは、コンパイル中にスキャンされたすべての非メタ アノテーションを保存するために使用されます (非メタ アノテーションもカスタム アノテーションです)。 、メタ アノテーションを除く合計 4 つのメタ アノテーションがあり、残りはカスタム アノテーションです)、そしてこのコレクションを走査し、カスタム アノテーションを取得したら、対応するロジックを実行します。具体的なコードは次のとおりです。XXXAnnotation はカスタム アノテーションの名前です 🎜
@Retention(RetentionPolicy.CLASS)@Target(ElementType.TYPE)public @interface HelloAnnotation {}
ログイン後にコピー
ログイン後にコピー
🎜 次に、getSupportedAnnotationTypes() メソッドと AbstractProcessor</ の <code>getSupportedSourceVersion も書き直す必要があります。 code> class () メソッド、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 &#39;com.squareup:javapoet:1.11.1&#39;implementation &#39;com.google.auto.service:auto-service:1.0-rc4&#39;
ログイン後にコピー
ログイン後にコピー

第一个依赖是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: &#39;:libannotation&#39;)
ログイン後にコピー

然后在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(&#39;:javapoet&#39;)implementation project(&#39;:libannotation&#39;)
ログイン後にコピー

然后我们手动编译一下项目,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 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート