アンドロイドの適切な例

1. はじめに

APT(アノテーション処理ツール)は、アノテーション処理ツールです。これはコンパイル時の注釈を処理し、コンパイル中に .java ファイルを生成するために使用されます。これにより、繰り返しのコード記述を軽減できます。ButterKnife、Dagger、EventBus、AndroidAnnotation などの一般的なフレームワークはすべて apt を使用します。以下では、ButterKnife と同様の、bindView 関数と bindingOnClick 関数を実装する簡単な例を使用します。

2. 導入プロセス

まずプロジェクトの全体構造を見てください。

ここに画像の説明を挿入
プロジェクトには、appmyannotationprocessor の3 つのモジュールが含まれています。このうち、myannotation はカスタム アノテーションであり、processor はアノテーション プロセッサです。なぜ 2 つのモジュールを作成するのでしょうか? apt は AbstractProcessor クラスを継承する必要があるため、このクラスは新しいモジュール -> Java ライブラリで取得する必要があり、コンパイル時アノテーション プロセッサはコンパイル時にのみ動作し、apk パッケージ化には参加しないため、プロセッサは独立したモジュールになります。 。また、カスタム アノテーションはアプリとプロセッサに同時に提供する必要があるため、myannotation も独立したモジュールです。

1. カスタム注釈

新しい Java ライブラリ myannotation を作成し、2 つのカスタム アノテーション クラスを作成します。

/***
 * 自定义BindView注解
 */
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
    
    
    int value();
}

/**
 * 自定义onclick注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface OnClick {
    
    
    int value();
}

上記は、BindView と OnClick の 2 つのアノテーションを定義しています。BindView のオブジェクトはメンバー変数 (FIELD)、OnClick のオブジェクトはメソッド (METHOD) であり、両方のライフサイクルはコンパイル時 (CLASS) です。

注: これら 2 つのクラスには中国語の文字は含まれないはずですが、便宜上、ここに中国語の注記を追加しました。コンパイル時にGBK マップ不可能な文字のエンコードでエラーが発生するか、次のコードを build.gradle ファイルに追加して問題を解決します。

tasks.withType(JavaCompile) {
    
    
    options.encoding = "UTF-8"
}

アノテーションの詳細な分析については、ここを参照してください。

2. アノテーションプロセッサを実装する

新しい Java ライブラリ: プロセッサを作成します。myannotation に頼ってください。

2.1 AbstractProcessor を継承する MyProcessor クラスを追加します。

public class MyProcessor extends AbstractProcessor {
    
    
    private Messager messager;
    private Elements elementUtils;
    private Map<String, ClassCreatorProxy> mProxyMap = new HashMap<>();
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    
    
        return false;
    }
    
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
    
    
        super.init(processingEnv);
        //获取message、elements
        messager = processingEnv.getMessager();
        elementUtils = processingEnv.getElementUtils();
    }
    @Override
    public Set<String> getSupportedAnnotationTypes() {
    
    
        //定义支持的注解类型为 自定义的BindView、OnClick
        HashSet<String> supportedType = new LinkedHashSet<>();
        supportedType.add(BindView.class.getCanonicalName());
        supportedType.add(OnClick.class.getCanonicalName());
        return supportedType;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
    
    
        //指定使用的Java版本,通常这里返回最新支持版本
        return SourceVersion.latestSupported();
    }
}

通常、次の 4 つのメソッドを実装する必要があります。

  • init() : 初期化、ElementUtils と Messager を取得します。ElementUtils はコード作成時にパッケージ名やその他の情報を取得するために使用され、Messager はコンパイル時に関連情報を出力するために使用されます。
  • getSupportedAnnotationTypes() : サポートされている注釈のリストを返します。ここには、定義した注釈 BindView と OnClick が追加されています。
  • getSupportedSourceVersion() : 使用する Java バージョンを指定します。通常は、サポートされている最新のバージョンがここで返されます。
  • process() : アノテーションを処理し、関連する Java コードを生成するために使用されるキー メソッド。

process() メソッドの完全なコードを見てみましょう。

  @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    
    
        //输出打印信息
        messager.printMessage(Diagnostic.Kind.NOTE, "processing...");
        mProxyMap.clear();
        //获取所有被BindView注解修饰的元素
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);
        for (Element element :
                elements) {
    
    
            VariableElement variableElement = (VariableElement) element;
            TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();
            String fullClassName = classElement.getQualifiedName().toString();
            ClassCreatorProxy proxy = mProxyMap.get(fullClassName);
            if (proxy == null) {
    
    
                proxy = new ClassCreatorProxy(elementUtils, classElement);
                mProxyMap.put(fullClassName, proxy);
            }
            BindView bindAnnotation = variableElement.getAnnotation(BindView.class);
            int id = bindAnnotation.value();
            proxy.putElement(id, variableElement);
        }
        //获取所有被OnClick注解修饰的元素
        Set<? extends Element> onClickElement = roundEnv.getElementsAnnotatedWith(OnClick.class);
        for (Element element :
                onClickElement) {
    
    

            TypeElement classElement = (TypeElement) element.getEnclosingElement();
            ExecutableElement executableElement = (ExecutableElement) element;
            String fullClassName = classElement.getQualifiedName().toString();
            ClassCreatorProxy proxy = mProxyMap.get(fullClassName);
            if (proxy == null) {
    
    
                proxy = new ClassCreatorProxy(elementUtils, classElement);
                mProxyMap.put(fullClassName, proxy);
            }
            OnClick bindAnnotation = element.getAnnotation(OnClick.class);
            int id = bindAnnotation.value();
            proxy.putOnclickElement(id, executableElement);
        }
//        //通过遍历mProxyMap,创建java文件
        for (String key : mProxyMap.keySet()) {
    
    
            ClassCreatorProxy proxyInfo = mProxyMap.get(key);
            try {
    
    
                messager.printMessage(Diagnostic.Kind.NOTE, " --> create " + proxyInfo.getProxyClassFullName());
                JavaFileObject jfo = processingEnv.getFiler().createSourceFile(proxyInfo.getProxyClassFullName(), proxyInfo.getTypeElement());
                Writer writer = jfo.openWriter();
                writer.write(proxyInfo.generateJavaCode());
                writer.flush();
                writer.close();
            } catch (IOException e) {
    
    
                messager.printMessage(Diagnostic.Kind.NOTE, " --> create " + proxyInfo.getProxyClassFullName() + "error");
            }
        }
        messager.printMessage(Diagnostic.Kind.NOTE, "process finish ...");
        return true;
    }

Map<String, ClassCreatorProxy> mProxyMap はカスタム アノテーションを含むクラスのコレクションです。String パラメーターはクラスの完全なクラス名によって識別され、ClassCreatorProxy はコードの生成に使用されるプロキシ クラスです。まずアノテーションによって変更された要素を取得し、次に要素から完全なクラス名を取得します。クラスが既に mProxyMap に存在する場合は、ClassCreatorProxy を取り出します。それ以外の場合は、完全なクラス名と新しく作成した ClassCreatorProxy を mProxyMap に保存します。最後に、アノテーション情報を ClassCreatorProxy クラスに保存します。

 这里有两段逻辑一样的代码分别处理了BindView和OnClick注解。

mProxyMap を走査した直後に、createSourceFile メソッドによって Java ファイルが作成され、ClassCreatorProxy のgenerateJavaCode メソッドによってそのファイルにコードが書き込まれます。

上記は MyProcessor の基本的な動作です。次に、コードを生成するクラス ClassCreatorProxy を見てください。

2.2コード生成: ClassCreatorProxy

文字列のスプライシングを実現する方法は次のとおりです。ロジックは比較的単純で、コードを直接貼り付けます。

public class ClassCreatorProxy {
    
    

    private Elements elements;
    private TypeElement typeElement;
    private String mPakageName;
    private String mBindingClassName;
    private Map<Integer, VariableElement> mVariableElementMap = new HashMap();
    private Map<Integer, ExecutableElement> onClickMap = new HashMap();

    public ClassCreatorProxy(Elements elementUtils, TypeElement classElement) {
    
    
        this.elements = elementUtils;
        this.typeElement = classElement;
        PackageElement packageElement = elements.getPackageOf(typeElement);
        mPakageName = packageElement.getQualifiedName().toString();
        String className = classElement.getSimpleName().toString();
        mBindingClassName = className + "_ViewBinding";
    }

    public void putElement(int id, VariableElement element) {
    
    
        mVariableElementMap.put(id, element);
    }

    public void putOnclickElement(int id, ExecutableElement element) {
    
    
        onClickMap.put(id, element);
    }

    //创建java代码
    public String generateJavaCode() {
    
    
        //StringBuilder 速度大于StringBuffer ,线程不安全
        StringBuilder sb = new StringBuilder();
        sb.append("package ").append(mPakageName).append(";\n\n");
        //sb.append("import com.jia.reflectionandannotation.*;\n");
        sb.append("\n");
        sb.append("public class ").append(mBindingClassName).append(" {\n");
        generateBindViewMethod(sb);
        generateBindOnclickMethod(sb);
       // generateOnclickMethod2(sb);
        sb.append(" \n}");
        return sb.toString();
    }

    //添加bindView方法
    public void generateBindViewMethod(StringBuilder sb) {
    
    

        sb.append("\tpublic void bindView(" + typeElement.getQualifiedName() + " activity){\n");
        for (int id :
                mVariableElementMap.keySet()) {
    
    
            VariableElement variableElement = mVariableElementMap.get(id);
             //获取view的类型:TextView、Button。。。
            String viewType = variableElement.asType().toString();
            //获取view的名字
            String viewName = variableElement.getSimpleName().toString();
            //生成 activity.textView=(TextView)activity.findViewById(id);
            sb.append("\t\tactivity." + viewName + " = (" + viewType + ")activity.findViewById(" + id + ");\n");
        }
        sb.append("\t}");
    }

    private void generateBindOnclickMethod(StringBuilder sb) {
    
    
        sb.append("\n\tpublic void bindOnClick(final " + typeElement.getQualifiedName() + " activity){\n");
        for (int id :
                onClickMap.keySet()) {
    
    
            ExecutableElement executableElement = onClickMap.get(id);
            String methodName = executableElement.getSimpleName().toString();//方法的名字
            sb.append("\t\tactivity.findViewById(" + id + ").setOnClickListener(new android.view.View.OnClickListener() {\n" +
                    "\t\t\t@Override\n" +
                    "\t\t\tpublic void onClick(android.view.View view) {\n" +
                    "\t\t\t\tactivity." + methodName + "();\n" +
                    "\t\t\t}\n" +
                    "\t\t});\n");
        }
        sb.append("\t}");
    }
    public String getProxyClassFullName() {
    
    
        return mPakageName + "." + mBindingClassName;
    }

    public TypeElement getTypeElement() {
    
    
        return typeElement;
    }

}

mVariableElementMap と onClickMap を使用して、BindView 注釈と OnClick 注釈をそれぞれ保存します。2 つの要素タイプは VariableElement と ExecutableElement であり、それぞれ FIELD と METHOD を変更するためのアノテーションに対応していることに注意してください。特定の要素分析については、 こちら を参照してください
putElement() は BindView 注釈の保存に使用され、
putOnclickElement() は OnClick 注釈の保存に使用され、
generateBindViewMethod() メソッドを使用して BindView 注釈を走査し、注釈値 (id) と注釈付き変数名、およびスプライシングを取得します。 findviewbyid コードを生成します。OnClick アノテーションは同じであり、bindView() メソッドと bindingOnClick() メソッドは最終的に xxx_ViewBinding クラスで生成されます。

*或者用javapoet来生成代码,这里就不作介绍了。

2.3 自動サービスの依存関係を追加する

 implementation 'com.google.auto.service:auto-service:1.0-rc2'
    //gradle3.4.1以上必须多加这一句,否则不会执行apt注解器
 annotationProcessor 'com.google.auto.service:auto-service:1.0-rc2'

最後に、MyProcessor の前に注釈を追加して、このクラスが注釈の処理に使用されることを示します。

@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {
    
    
....
注意!别忘了加annotationProcessor这一句!gradle3.1.2以下不需要加,高版本必须加,否则就算加了AutoService
也不会处理注解!我这里用的是3.4.1,具体看自己版本。

3. コードを生成する

app build.gradle の下に依存関係を追加します。

   annotationProcessor project(path: ':processor')
   implementation project(path: ':myannotation')
注意:processor module使用的是annotationProcessor,否则编译时会报错。

MainActivity でのアノテーションの使用はバターナイフに似ています (笑):

public class MainActivity extends AppCompatActivity {
    
    
    @BindView(R.id.text1)
    TextView text1;
    
    @OnClick(R.id.button)
    void onButtonClick() {
    
    
        Toast.makeText(this, "onButtonClick", Toast.LENGTH_SHORT).show();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        BindViewUtils.bindView(this);
        text1.setText("this is apt sample");
    }
}

注釈を追加した後、rebuildProject は、対応する MainActivity_ViewBinding クラスが generatedJava->com.example.xxx の下に生成されていることを確認できます。
ここに画像の説明を挿入

4 番目に、生成されたコードを呼び出します

上記に BindViewUtils.bindView(this) メソッドがあることがわかりますが、これは実際には単純なリフレクション呼び出しです。

public class BindViewUtils {
    
    
    public static void bindView(Activity activity) {
    
    
        Class clazz = activity.getClass();
        try {
    
    
            Class<?> bindViewClass = Class.forName(clazz.getName() + "_ViewBinding");
            Method bindView = bindViewClass.getMethod("bindView", activity.getClass());
            bindView.invoke(bindViewClass.newInstance(),activity);
            Method bindOnClick = bindViewClass.getMethod("bindOnClick", activity.getClass());
            bindOnClick.invoke(bindViewClass.newInstance(),activity);
        } catch (ClassNotFoundException e) {
    
    
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
    
    
            e.printStackTrace();
        } catch (IllegalAccessException e) {
    
    
            e.printStackTrace();
        } catch (InstantiationException e) {
    
    
            e.printStackTrace();
        } catch (InvocationTargetException e) {
    
    
            e.printStackTrace();
        }
    }
}

リフレクションは MainActivity_ViewBinding クラスを取得し、内部で bindingView() メソッドと bindingOnClick() メソッドを呼び出します。

5. 結果

ここに画像の説明を挿入
オリジナルの helloworld はこれに適したサンプルとなり、ボタンをクリックすると Toast onButtonClick もポップアップします。また、Butterknife の @OnCheckedChanged、@OnPageChange、@OnItemClick などの原則も同様であるため、1 つずつ実装するわけではありません。

おすすめ

転載: blog.csdn.net/weixin_40652755/article/details/109141298