安卓编译时注解应用实战(二)

前一篇文章介绍了编译时注解的基本配置以及一个HelloWorld的应用,不知道理解了多少,本质上看过那篇应该基本上理解编译时注解是什么,有什么作用了。这篇文章将通过编写一个动态权限申请库,一步一步的带领大家了解编译时注解的实际用处。

结构还是安卓编译时注解的应用(一)那种结构,不清楚的可以去阅读一下。
我给这个动态权限申请库取名为MPermission,本文的重点不是介绍如何封装此库,下面直接放上MPermission类的代码。感兴趣的同学可以阅读一下。
github地址

public class MPermission {

    private WeakReference<AppCompatActivity> activityWeakReference;
    private WeakReference<Fragment> fragmentWeakReference;
    private Context context;
    private boolean isActivity;
    //权限的回调注入类
    private PermissionInject inject;

    private int requestCode;

    private MPermission(AppCompatActivity activity) {
        isActivity=true;
        activityWeakReference=new WeakReference<>(activity);
        context=activity.getApplicationContext();
        inject=new PermissionInject();
    }
    private MPermission(Fragment fragment){
        isActivity=false;
        fragmentWeakReference=new WeakReference<>(fragment);
        context=fragment.getActivity().getApplicationContext();
        inject=new PermissionInject();
    }

    public static MPermission with(AppCompatActivity activity) {

        return new MPermission(activity);
    }
    public static MPermission with(Fragment fragment) {

        return new MPermission(fragment);
    }

    public void apply(int requestCode,String ...permissions){
        noPermission.clear();
        this.requestCode=requestCode;
        int index=hasPermission(permissions);
        if (index!=-1) {
            String[] p=new String[noPermission.size()];
            noPermission.toArray(p);
            //当检查时发现系统不存在这个权限的时候,需要判断当前系统版本是否>=23
            if(Build.VERSION.SDK_INT>=23){
                requestPermissionApi23(p);
            }else{
                //此处模仿官方API中的方法 进行回调
                //API23一下的版本直接返回失败
                int[] grantResults = new int[permissions.length];
                for (int i = 0; i < grantResults.length; i++){
                    if(i<index){
                        grantResults[i] = PackageManager.PERMISSION_GRANTED;
                    }else {
                        grantResults[i]=PackageManager.PERMISSION_DENIED;
                    }
                }
                requestPermissionApi(grantResults,permissions);
            }
        } else {
            //权限被允许
            inject.callMethod(activityWeakReference.get(),requestCode,true);
        }
    }


    @TargetApi(Build.VERSION_CODES.M)
    private void requestPermissionApi23(String[] permissions){
        if(isActivity){
            if(activityWeakReference!=null&&activityWeakReference.get()!=null){
                activityWeakReference.get().requestPermissions(permissions,requestCode);
            }
        }else{
            if(fragmentWeakReference!=null&&fragmentWeakReference.get()!=null){
                fragmentWeakReference.get().requestPermissions(permissions,requestCode);
            }
        }
    }
    private void requestPermissionApi(int[] grantResults,String[] permissions){
        if(isActivity){
            if(activityWeakReference!=null&&activityWeakReference.get()!=null){
                activityWeakReference.get().onRequestPermissionsResult(requestCode,permissions,grantResults);
            }
        }else{
            if(fragmentWeakReference!=null&&fragmentWeakReference.get()!=null){
                fragmentWeakReference.get().onRequestPermissionsResult(requestCode,permissions,grantResults);
            }
        }
    }
    private List<String> noPermission=new ArrayList<>();
    public int hasPermission(String[] permissions) {
        int index=-1;
        for (int i=0,j=permissions.length;i<j;i++) {
            if (ActivityCompat.checkSelfPermission(context, permissions[i])
                    != PackageManager.PERMISSION_GRANTED) {
                noPermission.add(permissions[i]);
                index=i;
            }
        }
        return index;
    }

    public void onRequestPermissionsResult(Object o,int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == this.requestCode) {
            if (grantResults.length == permissions.length) {
                for (int grant : grantResults) {
                    if (grant != PackageManager.PERMISSION_GRANTED) {
                        inject.callMethod(o,requestCode,false);
                        return;
                    }
                }
                //权限都允许了,初始化相机
                inject.callMethod(o,requestCode,true);
            } else {
                inject.callMethod(o,requestCode,false);
            }
        }
    }

    public void recycle(){
        if(activityWeakReference!=null){
            activityWeakReference.clear();
            activityWeakReference=null;
        }
        if(fragmentWeakReference!=null){
            fragmentWeakReference.clear();
            fragmentWeakReference=null;
        }
        inject=null;
    }
}

现在关注这段代码块中注释的地方

    public void onRequestPermissionsResult(Object o,int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == this.requestCode) {
            if (grantResults.length == permissions.length) {
                for (int grant : grantResults) {
                    if (grant != PackageManager.PERMISSION_GRANTED) {
                        //回调权限申请失败方法
                        inject.callMethod(o,requestCode,false);
                        return;
                    }
                }
                //回调权限申请成功的方法
                inject.callMethod(o,requestCode,true);
            } else {
                //回调权限申请失败方法
                inject.callMethod(o,requestCode,false);
            }
        }
    }

代码inject.callMethod(o,requestCode,false|true)这个方法就是用来出发我们通过自动成功好的类来实现回调的功能,它是PermissionInject类中的方法。

public class PermissionInject {
    private MethodCallback callback;

    public  void callMethod(Object obj, int requestCode, boolean isSuccess){
        String fullName=obj.getClass().getName();
        if(callback==null){
            try {
                Class<?> clazz=Class.forName(fullName+"$$Authority");
                callback= (MethodCallback) clazz.newInstance();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        if(callback!=null){
            if(isSuccess)
                callback.invoke(obj,requestCode);
            else
                callback.invokeFail(obj,requestCode);
        }
    }
}

这个方法很简单,就通过反射创建了一个对象,并调用此对象的相应方法。从Class<?> clazz=Class.forName(fullName+"$$Authority");这段代码可以看出这个对象类的名字是有原则的,他是通过传递过来对象的全名加上$$Authority组成的并且该类实现了MethodCallback接口。我们来看看这个接口。

public interface MethodCallback<T> {
    //成功走这里
    void invoke(T source, int requestCode);
    //失败走这里
    void invokeFail(T source, int requesCode);
}

此接口定义两个方法,分别是用来执行成功后的回调 和失败后的回调,我们每个自动生成的类都需要实现这个接口。
以上的三个类都是放在ioc-api模块下的。

现在我们在ioc-annotation模块下创建两个注解。

//权限申请成功的注解
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface AuthorityOK {
    int value();
}
//权限申请失败的注解
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface AuthorityFail {
    int value();
}

这两个注解都是标注在方法上的,一个表示授权成功一个表示授权失败,他们都需要一个int值,这个值是用来和请求权限时候requestCode对应的,表示请求的某组权限成功或失败。

现在要看一下我们需要用代码创建出来的类的样本模板后面我们就会按照这个模板来写类。
假设有一个如下界面:

public class MainActivity extends AppCompatActivity {

    MPermission permission;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        permission=MPermission.with(this);
        //请求相机和读写的权限
        permission.apply(100, Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.CAMERA);

    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        permission.onRequestPermissionsResult(this,requestCode,permissions,grantResults);
    }

    @AuthorityOK(100)
    public void requestOK(){
           //授权成功执行这里
    }

    @AuthorityFail(100)
    public void requestFail(){
           //授权失败执行这里
    }

    @Override
    protected void onDestroy() {
        permission.recycle();
        super.onDestroy();
    }
}

那么我们会在每个使用@AuthorityOK@AuthorityFail注解类的同包自动生成我们的类,要生成类的模板结构如下:

public class MainActivity$$Authority implements MethodCallback<MainActivity>{

    @Override
    public void invoke(MainActivity source, int requestCode) {
        //更具requestCode来调用具体类的具体方法
        if(requestCode==100){
            source.requestOK();
        }else if(requestCode==101){
            source.requestOtherOK();
        }
    }

    @Override
    public void invokeFail(MainActivity source, int requesCode) {
        //更具requestCode来调用具体类的具体方法
        if(requesCode==100){
            source.requestFail();
        }else if(requesCode==101){
            source.requestOtherFail();
        }
    }
}

通过泛型,映射具体的类,通过requestCode调用具体类中的具体方法,每一个需要申请动态权限的类都是生成这样的一个类。
现在我们来实现这个生成过程。
在ioc-compilerm模块下创建一个注解处理器,我的名字叫做PermissionProcessor

@AutoService(Processor.class)
public class PermissionProcessor extends AbstractProcessor{

    private Filer filer;
    private Elements elements;
    private Map<String, PermissionClass> permissionClassMap;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        filer=processingEnvironment.getFiler();
        elements=processingEnvironment.getElementUtils();
        permissionClassMap=new HashMap<>();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> annotationTypes=new HashSet<>();
        annotationTypes.add(AuthorityOK.class.getCanonicalName());
        annotationTypes.add(AuthorityFail.class.getCanonicalName());
        return annotationTypes;
    }

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

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        permissionClassMap.clear();
        processAuthority(roundEnvironment);
        for(PermissionClass permissionClass:permissionClassMap.values()){
            try {
                permissionClass.toFile().writeTo(filer);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return true;
    }

    private void processAuthority(RoundEnvironment roundEnvironment){
        Set<? extends ExecutableElement> elements=ElementFilter.methodsIn(roundEnvironment.getElementsAnnotatedWith(AuthorityOK.class));
        for (ExecutableElement element:elements){
            PermissionClass clazz=getPermissionClass(element);
            AuthorityOKMethod authorityOKMethod=new AuthorityOKMethod(element);
            clazz.addOkMethod(authorityOKMethod);
        }
        Set<? extends ExecutableElement> elementFail=ElementFilter.methodsIn(roundEnvironment.getElementsAnnotatedWith(AuthorityFail.class));
        for (ExecutableElement element:elementFail){
            PermissionClass clazz=getPermissionClass(element);
            AuthorityFailMethod authorityFailMethod=new AuthorityFailMethod(element);
            clazz.addFailMethod(authorityFailMethod);
        }
    }

    private PermissionClass getPermissionClass(ExecutableElement element){
        TypeElement typeElement= (TypeElement) element.getEnclosingElement();
        String fullName=typeElement.getQualifiedName().toString();
        PermissionClass clazz=permissionClassMap.get(fullName);
        if(clazz==null){
            clazz=new PermissionClass(typeElement,elements);
            permissionClassMap.put(fullName,clazz);
        }
        return clazz;
    }
}

其他的都是写基本配置,我们重点看process方法中的处理逻辑。

//清楚之前的map集合确保每次都是新的。
 permissionClassMap.clear();
//这里分析创建对应的类
 processAuthority(roundEnvironment);
//这是最终写把类写成文件
for(PermissionClass permissionClass:permissionClassMap.values()){
    try {
           permissionClass.toFile().writeTo(filer);
     } catch (IOException e) {
            e.printStackTrace();
     }
}

可以看到,这里只是主要调用了processAuthority方法来分析注解和产生代码。最后的for循环只是把准备好的java文件真实的写出来而已。我们来看看processAuthority方法;

   private void processAuthority(RoundEnvironment roundEnvironment){
        Set<? extends ExecutableElement> elements=ElementFilter.methodsIn(roundEnvironment.getElementsAnnotatedWith(AuthorityOK.class));
        for (ExecutableElement element:elements){
            PermissionClass clazz=getPermissionClass(element);
            AuthorityOKMethod authorityOKMethod=new AuthorityOKMethod(element);
            clazz.addOkMethod(authorityOKMethod);
        }
        Set<? extends ExecutableElement> elementFail=ElementFilter.methodsIn(roundEnvironment.getElementsAnnotatedWith(AuthorityFail.class));
        for (ExecutableElement element:elementFail){
            PermissionClass clazz=getPermissionClass(element);
            AuthorityFailMethod authorityFailMethod=new AuthorityFailMethod(element);
            clazz.addFailMethod(authorityFailMethod);
        }
    }

    private PermissionClass getPermissionClass(ExecutableElement element){
        TypeElement typeElement= (TypeElement) element.getEnclosingElement();
        String fullName=typeElement.getQualifiedName().toString();
        PermissionClass clazz=permissionClassMap.get(fullName);
        if(clazz==null){
            clazz=new PermissionClass(typeElement,elements);
            permissionClassMap.put(fullName,clazz);
        }
        return clazz;
    }

此方法获取到所有标注了@AuthorityOK@AuthorityFail注解的可执行元素(ExecutableElement),在此有一个PermissionClass类,这个类是每个可执行元素原本所在类的定义包装,通过getPermissionClass方法获取每个PermissionClass,此类中会收集旗下所有的属于它的可执行元素并书写最终的代码。并将每个可执行元素构建包装成自定义的AuthorityOKMethodAuthorityFailMethod类,这些类只是方便生成代码用的。我们来看看它们。
AuthorityOKMethodAuthorityFailMethod是一样的只是类名有点区别。所以这里只贴出一个即可。

public class AuthorityOKMethod {

    private ExecutableElement executableElement;
    private int requestCode;

    public AuthorityOKMethod(Element element) {
        if(element.getKind()!= ElementKind.METHOD){
            throw new RuntimeException("Only methods can be annotation!");
        }
        this.executableElement= (ExecutableElement) element;
        requestCode=executableElement.getAnnotation(AuthorityOK.class).value();
    }

    /**
     * 获取成功回调方法的名字
     */
    public Name getName(){
        return executableElement.getSimpleName();
    }

    /**
     * 获取成功回调方法的参数列表
     */
    public List<? extends VariableElement> getParameters(){
        return executableElement.getParameters();
    }

    /**
     * 获取成功回调方法的返回类型
     */
    public TypeMirror getReturnType(){
        return executableElement.getReturnType();
    }

    /**
     * 获取方法的requestCode
     */
    public int getRequestCode(){
        return requestCode;
    }
}

在来看看PermissionClass类

public class PermissionClass {


    //注解所在的类元素
    private TypeElement typeElement;

    private Elements elements;
    private ArrayList<AuthorityOKMethod> okMethods;
    private ArrayList<AuthorityFailMethod> failMethods;

    public PermissionClass(TypeElement typeElement, Elements elements) {
        this.typeElement = typeElement;
        this.elements = elements;
        okMethods=new ArrayList<>();
        failMethods=new ArrayList<>();
    }

    public void addOkMethod(AuthorityOKMethod executableElement){
        okMethods.add(executableElement);
    }

    public void addFailMethod(AuthorityFailMethod failMethod){
        failMethods.add(failMethod);
    }

    public JavaFile toFile(){
        //创建调用成功的绑定方法
        MethodSpec.Builder invokeOK=MethodSpec.methodBuilder("invoke")
                .addModifiers(Modifier.PUBLIC)
                .addAnnotation(Override.class)
                //添加方法中的参数类型为注解所在类的类型
                .addParameter(TypeName.get(typeElement.asType()),"source")
                .addParameter(TypeName.INT,"requestCode");
        for (int i=0,j=okMethods.size();i<j;i++){
            AuthorityOKMethod method=okMethods.get(i);
            if(i==0){
                invokeOK.beginControlFlow("if(requestCode==$L)",method.getRequestCode());
            }else {
                invokeOK.nextControlFlow("else if(requestCode==$L)",method.getRequestCode());
            }
            if(method.getParameters().isEmpty()){
                invokeOK.addStatement("source.$N()",method.getName());
            }else {

            }
            if(i==j-1){
                invokeOK.endControlFlow();
            }
        }
        //创建调用失败的绑定方法
        MethodSpec.Builder invokeFail=MethodSpec.methodBuilder("invokeFail")
                .addModifiers(Modifier.PUBLIC)
                .addAnnotation(Override.class)
                .addParameter(TypeName.get(typeElement.asType()),"source")
                .addParameter(TypeName.INT,"requestCode");
        for (int i=0,j=failMethods.size();i<j;i++) {
            AuthorityFailMethod method = failMethods.get(i);
            if (i == 0) {
                invokeFail.beginControlFlow("if(requestCode==$L)", method.getRequestCode());
            } else {
                invokeFail.nextControlFlow("else if(requestCode==$L)", method.getRequestCode());
            }
            if (method.getParameters().isEmpty()) {
                invokeFail.addStatement("source.$N()", method.getName());
            } else {

            }
            if(i==j-1){
                invokeFail.endControlFlow();
            }
        }
        TypeSpec injectClass=TypeSpec.classBuilder(typeElement.getSimpleName()+"$$Authority")
                .addModifiers(Modifier.PUBLIC)
                .addSuperinterface(ParameterizedTypeName.get(ClassName.get("ggx.com.ioc_api","MethodCallback"),ClassName.get(typeElement.asType())))
                .addMethod(invokeOK.build())
                .addMethod(invokeFail.build()).build();
        String packageName=elements.getPackageOf(typeElement).getQualifiedName().toString();
        return JavaFile.builder(packageName,injectClass).build();
    }
}

可以看出这个类中最重点的就是toFile方法,没错这里面就是使用JavaPoet工具所写的类,在此安利一下这个工具真是非常的方便。这代码创建的过程其实没什么好介绍的,按照之前的代码模板,写就行然后就是如何使用JavaPoet这个工具的事了,不懂的人可以去github上看它的readme,介绍的真的非常详细。

最后编译代码,你就会在build/generated/source/apt下看到你的类了。文章是在太长,有点写不下去的感觉,如有没能理解的可以加群,也可以在下方评论留言,我会一一解答疑惑。

欢迎共同探讨更多安卓,java,c/c++相关技术QQ群:392154157

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容