利用编译时注解来解决android权限请求问题

利用编译时注解来解决android权限请求问题

这几天在开大神写的Dagger2 和 butterknife 这俩货同时用到了java中的一个特性就是编译时注解。注解在项目中是经常在使用的,比如标志是继承方法的 @Override。这些注解只有在我们写代码的时候才会发挥作用并不会。

其实注释一共有3种,代码时,编译时,运行时。字面意思就是它的运行的范围。很多开源项目通过一局注释就能完成很多工做多是利用了编译时,和运行时这两种注释。其中运行时注解一般时通过在main方法种拿到注释的类再通过反射来进行一些特别的操作。一说到反射这个词我其实是很敏感的,反射的效率真的不是很高,所以能不用就不用,这也是上面提到的两个大神级项目不用运行时注解而是用编译时注解的原因。

如何只是通过一个简单的注解就实现后面各种复杂的逻辑呢?这里就用到java自身的机制了,每次我们打包生成apk就是需要把java变成通用的语言让android的虚拟机进行识别,这个时候时会对java中所有的注释进行检测的这个检测的过程需要我们继承AbstractProcessor类去实现。

当然还要有注释的定义了注释为我们提供了4个基本的注解来修饰我们的自定义注解分别是:

  1. @Target 用来说明对象的范围,这个能标记你自定义的注解修饰的内容的类型
  2. @retention 标记它是怎样的注解类型编译时是 Class
  3. @Documented javadoc 标识的注解
  4. @Inherited.阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。

首先新建一个项目要对它进行如下的构造

我在项目里建立了一个java的library(一定是java的呀!android的library是没有AbstractProcessor)和一个android的library(这个里面主要是对一些android方面的逻辑进行处理的位置)

在java library中建立一个注解类
~java
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface PAccept {
String value();
}
~

这个用来标志请求权限成功的回掉函数

再建立一个注解类

~java
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface PReject {
String value();
}
~

这个来标记失败的回调函数

然后写建立一个MyProcessor的类继承AbstractProcessor并且要建立如下的代码

这些代码是保证再编译阶段会运行我们自己写的AbstractProcessor。
下面是来让我们看看我们的MyProcessor里做了写什么吧

~~~java
@SupportedAnnotationTypes({“com.example.PAccept”,”com.example.PReject”})
public class MyProcessor extends AbstractProcessor {
public static final String SUFFIX = “$$PermissionAdapter”;

private Map mMaps = new HashMap<>();

private Elements elementUtils;

private Filer filer;

@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
    super.init(processingEnv);
    elementUtils = processingEnv.getElementUtils();
    filer = processingEnv.getFiler();
}

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

@Override
public boolean process(Set annotations, RoundEnvironment roundEnv) {
    for(Element element: roundEnv.getElementsAnnotatedWith(PAccept.class)){
        if(element.getKind() == ElementKind.METHOD){
            ExecutableElement  parameterElement = (ExecutableElement)element;
            AdapterClass  adapterClass;
            String packageName = getPackageName(parameterElement);
            String className = getClassName((TypeElement) (parameterElement.getEnclosingElement()),packageName);
            String methodName = parameterElement.getSimpleName().toString();
            String path = parameterElement.getAnnotation(PAccept.class).value();
            System.out.println(path);
            adapterClass = mMaps.get(className);
            if(adapterClass == null){
                adapterClass = new AdapterClass();
                mMaps.put(className,adapterClass);
            }
            adapterClass.element = element;
            adapterClass.ClassName = className;
            adapterClass.PackageName = packageName;
            adapterClass.AcceptMap.put(path,methodName);
        }
    }
    for(Element element: roundEnv.getElementsAnnotatedWith(PReject.class)){
        if(element.getKind() == ElementKind.METHOD){
            ExecutableElement  parameterElement = (ExecutableElement)element;
            AdapterClass  adapterClass;
            String packageName = getPackageName(parameterElement);
            String className = getClassName((TypeElement) (parameterElement.getEnclosingElement()),packageName);
            String methodName = parameterElement.getSimpleName().toString();
            String path = parameterElement.getAnnotation(PReject.class).value();
            System.out.println(path);
            adapterClass = mMaps.get(className);
            if(adapterClass == null){
                adapterClass = new AdapterClass();
                mMaps.put(className,adapterClass);
            }
            adapterClass.element = element;
            adapterClass.ClassName = className;
            adapterClass.PackageName = packageName;
            adapterClass.RejectMap.put(path,methodName);
        }
    }
    for (Map.Entry entry:mMaps.entrySet()){
        String key = (String)entry.getKey();
        AdapterClass adapterClass = (AdapterClass)entry.getValue();
        try {
            JavaFileObject jfo = filer.createSourceFile(adapterClass.ClassName + SUFFIX,adapterClass.element);
            Writer writer = jfo.openWriter();
            writer.write(adapterClass.getJavaToFile());
            writer.flush();
            writer.close();
        } catch (Exception e){
          System.out.println(e.toString());
        }
    }
    return true;
}

private String getPackageName(ExecutableElement  type) {
    return elementUtils.getPackageOf(type).toString();
}

private String getClassName(TypeElement type, String packageName) {
    int packageLen = packageName.length() + 1;
    return type.toString().substring(packageLen).replace('.', '$');
}

}
~~~

@SupportedAnnotationTypes 注解用来标注这个processor能够解析的注解有那些
在实现自己的Processor要实现AbstractProcessor接口中process()方法。里面的逻辑其实也很简单,annotations会包含全部代码中包含你定义的那个注解的地方。我们可以通过annotations获取每个注解所在类所标识的方法名注解中的属性等等。然后通过这些信息动态的生成代码(这个就是关键,类似后台经常用的动态代理)。为了动态生成代码我写了一个AdapterClass的类来控制代码的生成和数据的保存。下面来看看AdapterClass做了些什么吧

~~~java
public class AdapterClass {

public String ClassName;

public String PackageName;

public HashMap AcceptMap;

public HashMap RejectMap;

public Element element;


public AdapterClass(){
    AcceptMap = new HashMap<>();
    RejectMap = new HashMap<>();
}

public String getJavaToFile(){
    StringBuilder builder = new StringBuilder();
    builder.append("import ").append("com.example.PermissionAdapter;").append("\n")
            .append("import ").append(PackageName).append(".").append(ClassName).append(";\n")
            .append("import android.app.Activity;").append("\n");
    builder.append("public class ").append(ClassName).append(MyProcessor.SUFFIX)
            .append(" implements PermissionAdapter {").append("\n")
            .append("public void onRequestSuccess(String[] strings,").append(ClassName).append(" activity){").append("\n")
            .append("for(String s:strings) {").append("\n");
    Iterator iterator = AcceptMap.entrySet().iterator();
    while (iterator.hasNext()){
        Map.Entry entry = (Map.Entry)iterator.next();
        String permission = (String)entry.getKey();
        String methname = (String)entry.getValue();
        builder.append("if(s.equals(\"").append(permission).append("\")){").append("\n")
                .append("activity.").append(methname).append("();").append("\n").append("}")
                .append("\n");
    }
    builder.append("}");
    builder.append("\n");
    builder.append("}").append("\n");
    builder.append("public  void onRequestReject(String[] strings,").append(ClassName).append(" activity){").append("\n")
            .append("for(String s:strings) {")
            .append("\n");

    Iterator iterator2 = RejectMap.entrySet().iterator();
    while (iterator2.hasNext()){
        Map.Entry entry = (Map.Entry)iterator2.next();
        String permission = (String)entry.getKey();
        String methname = (String)entry.getValue();
        builder.append("if(s.equals(\"").append(permission).append("\")){").append("\n")
                .append("activity.").append(methname).append("();").append("\n").append("}")
                .append("\n");
    }
    builder.append("}");
    builder.append("\n");
    builder.append("}");
    builder.append("\n")
            .append("}");

    return builder.toString();
}

}
~
其实这里可以用square公司大神写的javapoet来生成java代码,第一次写感觉还是手撕来的痛快,其实这里就是生成了一个适配器来适配activity中的接口和进行统一处理的标注接口。生成的代码如下
~~~java
import com.example.PermissionAdapter;
import com.hero.jhon.webtest.MainActivity;
import android.app.Activity;
public class MainActivity$$PermissionAdapter implements PermissionAdapter {
public void onRequestSuccess(String[] strings,MainActivity activity){
for(String s:strings) {
if(s.equals("android.permission.CAMERA")){
activity.onSuccess();
}
if(s.equals("android.permission.WRITE_EXTERNAL_STORAGE")){
activity.onSuccess2();
}
}
}
public void onRequestReject(String[] strings,MainActivity activity){
for(String s:strings) {
if(s.equals("android.permission.CAMERA")){
activity.onFature();
}
}
}
}
~

自动生成的代码缩进都没有。。。。我也懒得写空格了太麻烦了,主要做了点啥呢继承了一个PermissionAdapter的接口这个接口有两个方法onRequestSuccess 和 onRequestReject
好这个全是关于注解的代码那这个生成的适配器在那里使用的呢?
我们来看看另一个android的 library做了些什么呢?
这里其实只有一个PermissionUnit的类,关于权限管理的代码全在这里了。
~~~
public class PermissionUnit {

public static void instance(Activity activity,int requestCode,String[] permissions,int[] grantResults){
    try {
        PermissionAdapter adapter = findPermissionAdapterForClass(activity.getClass());
        if(adapter != null){
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                adapter.onRequestSuccess(permissions,activity);
            } else {
                if(permissions!=null && permissions.length>0){
                  isShowNotifationDialog(activity,permissions,adapter);
                }
            }
        }

    } catch (Exception e){
        System.out.println(e.toString());
    }
}

public static void isShowNotifationDialog(final Activity activity,String[] permissions,PermissionAdapter adapter){
    final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
    builder.setTitle(R.string.title);
    builder.setMessage(R.string.content);
    builder.setPositiveButton("取消", new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialogInterface, int i) {
            dialogInterface.dismiss();
        }
    });
    builder.setNegativeButton("设置", new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialogInterface, int i) {
            Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
            Uri uri = Uri.fromParts("package", "com.babytree.apps.lama", null);
            intent.setData(uri);
            activity.startActivity(intent);
            dialogInterface.dismiss();
        }
    });
    builder.show();
    adapter.onRequestReject(permissions,activity);
}

private static PermissionAdapter findPermissionAdapterForClass(Class cls){
    PermissionAdapter mAdapter;
    try {
        String clsName = cls.getSimpleName();
        Class  adapterClass = Class.forName(clsName + MyProcessor.SUFFIX);
        mAdapter = (PermissionAdapter)adapterClass.newInstance();
    } catch (Exception e){
        mAdapter = findPermissionAdapterForClass(cls.getSuperclass());
    }
    return mAdapter;
}

public static void RequestPermission(Activity activity,int requestid,String... permission){
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        boolean result = false;
        for(String temppermission:permission) {

            if(ActivityCompat.checkSelfPermission(activity,temppermission) != PackageManager.PERMISSION_GRANTED){
                result = true;
            }

        }
        if(result){
            ActivityCompat.requestPermissions(activity,permission , requestid);
        } else {
            for(String temppermission:permission) {
                instance(activity, requestid,new String[] {temppermission},new int[] {PackageManager.PERMISSION_GRANTED});
            }
        }
    }

}

~~~
instance 这个方法一般是在activity的onRequestPermissionsResult中调用的,可以看到其实他主要是通过PermissionAdapter的方法来处理成功和失败的逻辑那我们有是如何获取到我们生成的PermissionAdapter的呢?
主要是 String clsName = cls.getSimpleName();
Class

你可能感兴趣的:(利用编译时注解来解决android权限请求问题)