從Android Apt(Annotation-Processing-Tool)到手寫一個Android6.0以上的運行時權限申請框架

一.什麼是APT

apt是一種註解處理工具,對源碼文件進行檢測找出其中的註解,根據註解會自動生成代碼,如果想要自定義的註解處理器能夠正常運行,必須要通過apt工具來進行處理。相信用過EventBus、ButterKnife、Dagger2的同學都會有所瞭解,因爲它們都用的是APT的技術。如果對於註解都不瞭解的,可以去看下我之前寫過的註解反射一些總結

二.用APT寫一個自己的權限框架

我們寫個簡單的權限請求的Demo來熟悉下APT的玩法。
先看看效果

再次進入,選擇了拒絕,並選擇了不在詢問

第三次進入後提示進去設置界面打開權限

在看看代碼,首先我們新建兩個JavaLib,Annotations和Compiler,一個是存放自定義註解,一個是用來對註解進行處理,
然後我們在新建一個Android的Library,這個庫的作用是提供一些權限相關的處理類,還有用反射的方式,調用APT生成的代碼。
我們先看下效果

Annotation Module的配置:

apply plugin: 'java-library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
}

//處理亂碼問題
tasks.withType(JavaCompile) {
    options.encoding = "UTF-8"
}
sourceCompatibility = "1.8"
targetCompatibility = "1.8"

Compiler Module的配置:

apply plugin: 'java-library'

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')

    implementation 'com.google.auto.service:auto-service:1.0-rc4'
    //    implementation 'com.google.auto.service:auto-service:1.0-rc6'
    //        annotationProcessor "com.google.auto.service:auto-service:1.0-rc6" //3.3.2以上需要這個

    implementation project(':Annotations')
}

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

Library Module的配置:

apply plugin: 'com.android.library'

android {
    compileSdkVersion 28
    compileOptions {
        sourceCompatibility 1.8
        targetCompatibility 1.8
    }


    defaultConfig {
        minSdkVersion 19
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        javaCompileOptions { annotationProcessorOptions { includeCompileClasspath = true } }
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'com.android.support:appcompat-v7:28.0.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    implementation project(':Annotations')
}

項目中的配置:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    compileOptions {
        sourceCompatibility 1.8
        targetCompatibility 1.8
    }
    defaultConfig {
        applicationId "com.seven.pemissions"
        minSdkVersion 19
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
//        javaCompileOptions { annotationProcessorOptions { includeCompileClasspath = true } }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    implementation project(':Annotations')
    implementation project(':Library')
    annotationProcessor project(':Compiler')

}

我們在來看看Compiler Module中的代碼:

package com.seven.permissions.compiler;

import com.google.auto.service.AutoService;
import com.seven.annotations.NeedPermissions;
import com.seven.annotations.OnNeverAskPermissions;
import com.seven.annotations.OnPermissionsDenied;


import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
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.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;

/**
 * Time:2019/12/5
 * <p>
 * Author:wangzhou
 * <p>
 * Description:
 */
@AutoService(Processor.class)
public class PermissionProcessor extends AbstractProcessor {

    private Messager messager;
    private Elements elementUtils;
    private Filer filer;
    private Types typeUtils;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        typeUtils = processingEnvironment.getTypeUtils();
        elementUtils = processingEnvironment.getElementUtils();
        filer = processingEnvironment.getFiler();
        messager = processingEnvironment.getMessager();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new LinkedHashSet<>();
        types.add(NeedPermissions.class.getCanonicalName());
        types.add(OnPermissionsDenied.class.getCanonicalName());
        types.add(OnNeverAskPermissions.class.getCanonicalName());
        return types;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latest();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        messager.printMessage(Diagnostic.Kind.NOTE, "processing...");
        System.out.println("start process");
        Set<? extends Element> needPermissionsSet = roundEnvironment.getElementsAnnotatedWith(NeedPermissions.class);
        Map<String, List<ExecutableElement>> needPermissionMap = new HashMap<>();
        for (Element element : needPermissionsSet) {
            ExecutableElement executableElement = (ExecutableElement) element;
            String activityName = getActivityName(executableElement);  //獲取類
            List<ExecutableElement> list = needPermissionMap.get(activityName);
            if (list == null) {
                list = new ArrayList<>();
                needPermissionMap.put(activityName, list);
            }
            list.add(executableElement);
            System.out.println("NeedPermission executableElement" + element.getSimpleName().toString());
        }

        Set<? extends Element> onNeverAskAgainSet = roundEnvironment.getElementsAnnotatedWith(OnNeverAskPermissions.class);
        Map<String, List<ExecutableElement>> onNeverAskAgainMap = new HashMap<>();
        for (Element element : onNeverAskAgainSet) {
            ExecutableElement executableElement = (ExecutableElement) element;
            String activityName = getActivityName(executableElement);
            List<ExecutableElement> list = onNeverAskAgainMap.get(activityName);
            if (list == null) {
                list = new ArrayList<>();
                onNeverAskAgainMap.put(activityName, list);
            }
            list.add(executableElement);
            System.out.println("NeedPermission executableElement" + element.getSimpleName().toString());
        }


        Set<? extends Element> onPermissionDeniedSet = roundEnvironment.getElementsAnnotatedWith(OnPermissionsDenied.class);
        Map<String, List<ExecutableElement>> onPermissionDeniedMap = new HashMap<>();
        for (Element element : onPermissionDeniedSet) {
            ExecutableElement executableElement = (ExecutableElement) element;
            String activityName = getActivityName(executableElement);
            List<ExecutableElement> list = onPermissionDeniedMap.get(activityName);
            if (list == null) {
                list = new ArrayList<>();
                onPermissionDeniedMap.put(activityName, list);
            }
            list.add(executableElement);
            System.out.println("NeedPermission executableElement" + element.getSimpleName().toString());
        }


        for (String activityName : needPermissionMap.keySet()) {


            List<ExecutableElement> needPermissionElements = needPermissionMap.get(activityName);
            List<ExecutableElement> onNeverAskAgainElements = onNeverAskAgainMap.get(activityName);
            List<ExecutableElement> onPermissionDeniedElements = onPermissionDeniedMap.get(activityName);

            final String CLASS_SUFFIX = "$Permissions";
//            Filer filer = processingEnv.getFiler();
            try {
                JavaFileObject javaFileObject = filer.createSourceFile(activityName + CLASS_SUFFIX);
                String packageName = getPackageName(needPermissionElements.get(0)); //獲取註解地方的包名
                Writer writer = javaFileObject.openWriter();
                String activitySimpleName = needPermissionElements.get(0).getEnclosingElement()
                        .getSimpleName()
                        .toString() + CLASS_SUFFIX;


                writer.write("package " + packageName + ";\n");
                writer.write("import com.seven.permissions.library.listener.RequestPermission;\n");
                writer.write("import com.seven.permissions.library.utils.PermissionUtils;\n");
                writer.write("import android.support.v4.app.ActivityCompat;\n");
                writer.write("import java.lang.ref.WeakReference;\n");
                writer.write("import com.seven.permissions.library.listener.PermissionRequest;\n");
                writer.write("public class " + activitySimpleName + " implements RequestPermission<" + activityName + ">{\n");
                writer.write("private static final int REQUEST_CODE = 666;\n");
                writer.write("private static String[] PERMISSION;\n");
                writer.write("public void requestPermission(" + activityName + " target,String[] permissions) {\n");
                writer.write(" PERMISSION = permissions ;\n");
                writer.write(" if (PermissionUtils.hasSelfPermissions(target, permissions)) {\n");


                if (needPermissionElements != null) {
                    for (ExecutableElement executableElement : needPermissionElements) {
                        String methodName = executableElement.getSimpleName().toString();
                        writer.write("target." + methodName + "();\n");
                    }
                }

                writer.write("}else{  ActivityCompat.requestPermissions(target, PERMISSION, REQUEST_CODE);}}\n");


                writer.write("public void onRequestPermissionsResult(\n" + activityName + " target, int requestCode, int[] grantResults){");
                writer.write("     switch (requestCode) {\n" + "case REQUEST_CODE:\n" + "if (PermissionUtils.verifyPermissions(grantResults)) {\n");

                if (needPermissionElements != null) {
                    for (ExecutableElement executableElement : needPermissionElements) {
                        String methodName = executableElement.getSimpleName().toString();
                        writer.write("target." + methodName + "();\n");
                    }
                }
                writer.write("} else if (!PermissionUtils.shouldShowRequestPermissionRationale(target, PERMISSION)) {\n");
                if (onNeverAskAgainElements != null) {
                    for (ExecutableElement executableElement : onNeverAskAgainElements) {
                        String methodName = executableElement.getSimpleName().toString();
                        writer.write("target." + methodName + "();\n");
                    }
                }
                writer.write("}else{\n");
                if (onPermissionDeniedElements != null) {
                    for (ExecutableElement executableElement : onPermissionDeniedElements) {
                        String methodName = executableElement.getSimpleName().toString();
                        writer.write("target." + methodName + "();\n");
                    }
                }
                writer.write(" } break;\n default:\nbreak;\n}\n }\n}");


                writer.flush();
                writer.close();

            } catch (Exception e) {
                e.printStackTrace();

            }


        }


        return true;
    }

    private String getPackageName(ExecutableElement executableElement) {
        TypeElement typeElement = (TypeElement) executableElement.getEnclosingElement();
        String packageName = elementUtils.getPackageOf(typeElement).getQualifiedName().toString();
        return packageName;
    }

    private String getActivityName(ExecutableElement executableElement) {
        String packageName = getPackageName(executableElement);
        TypeElement typeElement = (TypeElement) executableElement.getEnclosingElement();
        return packageName + "." + typeElement.getSimpleName().toString();
    }
}

這個就是非常笨的方式,去掃描到註解,然後拼接代碼。最終生成的代碼,會在我們的主項目的這個目錄下
在這裏插入圖片描述

代碼生成完了,我們在Library中提供兩個方法將APT生成的代碼和需要使用的地方做個綁定就可以了。代碼我就不貼了,有興趣可以去我的Git上看一看。地址:玩一玩APT

上面的純手工寫代碼是不是顯得有點不智能,不過作爲入門級玩一玩APT還是可以的,我們在來玩玩JavaPoet,Git地址是:JakeWharton大神的JavaPoet,JavaPoet是提供一些API,幫我們去生成代碼。有興趣的同學可以去看看。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章