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

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

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

public class MPermission {

    private WeakReference activityWeakReference;
    private WeakReference 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 noPermission=new ArrayList<>();
    public int hasPermission(String[] permissions) {
        int index=-1;
        for (int i=0,j=permissions.length;i

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

    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 {
    //成功走这里
    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{

    @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 permissionClassMap;

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

    @Override
    public Set getSupportedAnnotationTypes() {
        Set 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 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 elements=ElementFilter.methodsIn(roundEnvironment.getElementsAnnotatedWith(AuthorityOK.class));
        for (ExecutableElement element:elements){
            PermissionClass clazz=getPermissionClass(element);
            AuthorityOKMethod authorityOKMethod=new AuthorityOKMethod(element);
            clazz.addOkMethod(authorityOKMethod);
        }
        Set 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 elements=ElementFilter.methodsIn(roundEnvironment.getElementsAnnotatedWith(AuthorityOK.class));
        for (ExecutableElement element:elements){
            PermissionClass clazz=getPermissionClass(element);
            AuthorityOKMethod authorityOKMethod=new AuthorityOKMethod(element);
            clazz.addOkMethod(authorityOKMethod);
        }
        Set 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 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 okMethods;
    private ArrayList 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

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

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

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

你可能感兴趣的:(安卓编译时注解应用实战(二))