1、核心逻辑

在Activity或许fragment中,写在几个办法写一些注释,用来表示权限恳求成功恳求失利多次回绝。以上便是运用者需求做的。

简略吧,简略就对了,不用传任何上下文。只需求写注解。给大家看下。

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    public void permissionRequestTest(View view) {
        testRequest();
    }
    // 恳求权限  函数名可以随意些
    @Permission(value = Manifest.permission.READ_EXTERNAL_STORAGE, requestCode = 200)
    public void testRequest() {
        Toast.makeText(this, "权限恳求成功...", Toast.LENGTH_SHORT).show();
    }
    // 权限被撤销  函数名可以随意些
    @PermissionCancel
    public void testCancel() {
        Toast.makeText(this, "权限被回绝", Toast.LENGTH_SHORT).show();
    }
    // 多次回绝,还勾选了“不再提示”
    @PermissionDenied
    public void testDenied() {
        Toast.makeText(this, "权限被回绝(用户勾选了 不再提示),留意:你有必要要去设置中翻开此权限,不然功能无法运用", Toast.LENGTH_SHORT).show();
    }

2、完成

需求用到的技术有Aspect、注解、反射

2.1、Aspect

它的作用便是绑架被注释的办法的履行。比如上方testRequest()是用来恳求权限的,可是我在ASPECT中装备阻拦@permission注释的办法。先做判别。

假如没有听过Aspect的话,AOP面向切面编程,大家应该听说过,它可以用来装备事务、做日志、权限验证、在用户恳求时做一些处理等等。而用@Aspect做一个切面,就可以直接完成。

2.2、PermissionAspect

咱们会创建一个PermissionAspect类,整一个切点,让@Permission被绑架。

// AOP 思想 切面的思想
// 切点 --- 是注解 @
// * * 任何函数 都可以运用 此注解
//(..) 我要带参数  带的参数便是后面那个 @annotation(permission)意思便是  参数里是permission。这样我就拿到了Permission注解它里边的参数
//这样经过切点就拿到了下面这个注解
//@Permission(value = Manifest.permission.READ_EXTERNAL_STORAGE, requestCode = 200) 这便是  Permission permission
@Pointcut
("execution(@com.derry.premissionstudy.permission.annotation.Permission * *(..)) && @annotation(permission)")
//那么这个
//  @Permission == permission
public void pointActionMethod(Permission permission) {
} // 切点函数
//切面
@Around("pointActionMethod(permission)")
public void aProceedingJoinPoint(final ProceedingJoinPoint point,Permission permission) throws Throwable{
    //我需求拿到 MainActivity this

这样@Permission就被切点绑架了,然后办法就会跑到切面aProceedingJoinPoint。然后获取上下文Context,把权限恳求交给一个通明的Activity来做。做完之后判别成果,用户是赞同了还是回绝了还是曲线了。赞同了直接履行point.proceed(),其他方法则经过Activity或许fragment获取带注解的办法,反射履行即可。

//切面
@Around("pointActionMethod(permission)")
public void aProceedingJoinPoint(final ProceedingJoinPoint point,Permission permission) throws Throwable{
    //我需求拿到 MainActivity this
    Context context = null;
    // MainActivity this == thisObject
    final Object thisobject = point.getThis();
    // context初始化
    if(thisobject instanceof Context){
        context = (Context) thisobject;
    } else if(thisobject instanceof Fragment){
        context = ((Fragment) thisobject).getActivity();
    }
    // 判别是否为null
    if (null == context || permission == null) {
        throw new IllegalAccessException("null == context || permission == null is null");
    }
    //trestRequest 次函数被控制了  不会执
    //
    //动态恳求  风险权限  通明的空白的Ativity
    //这儿一定要得知接口三个状况    现已授权  撤销授权  回绝授权
    //调用 空白的Actiivty  开端授权
    MyPermissionActivity.requestPermissionAction(context, permission.value(), permission.requestCode(), new IPermission() {
        @Override
        public void ganted() {
            try {
                point.proceed();
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
        }
        @Override
        public void cancel() {
            PermissionUtils.invokeAnnotion(thisobject, PermissionCancel.class);
        }
        @Override
        public void denied() {
            PermissionUtils.invokeAnnotion(thisobject, PermissionDenied.class);
        }
    });
}

2.3、空白履行权限的Activity

履行恳求权限的Activity的的相应办法会流到PermissionAspect,然后到空白Activity恳求。恳求完之后的成果,再经过回调传回去就好了。


public class MyPermissionActivity extends AppCompatActivity {
    // 定义权限处理的标记, ---- 接收用户传递进来的
    private final static String PARAM_PERMSSION = "param_permission";
    private final static String PARAM_PERMSSION_CODE = "param_permission_code";
    public final static int PARAM_PERMSSION_CODE_DEFAULT = -1;
    private String[] permissions;
    private int requestCode;
    // 方便回调的监听  告知外交 已授权,被回绝,被撤销
    private static IPermission iPermissionListener;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my_permission);
        permissions = getIntent().getStringArrayExtra(PARAM_PERMSSION);
        requestCode = getIntent().getIntExtra(PARAM_PERMSSION_CODE, PARAM_PERMSSION_CODE_DEFAULT);
        if(permissions == null){
            this.finish();
            return;
        }
        // 可以走到这儿,就开端去查看,是否现已授权了
        boolean permissionRequest = PermissionUtils.hasPermissionRequest(this,permissions);
        if (permissionRequest) {
            // 经过监听接口,告知外交,现已授权了
            iPermissionListener.ganted();
            this.finish();
            return;
        }
        ActivityCompat.requestPermissions(this,permissions,requestCode);
    }
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if(PermissionUtils.requestPermissionSuccess(grantResults)){
            iPermissionListener.ganted();//现已授权成功了  告知AspectJ
            this.finish();
        }
        // 没有成功,可能是用户 不听话
        // 假如用户点击了,回绝(勾选了”不再提示“) 等操作
        if(!PermissionUtils.shouldShowRequestPermissionRationable(this,permissions)){
            iPermissionListener.denied();
            this.finish();
            return;
        }
        // 撤销
        iPermissionListener.cancel(); // 接口告知 AspectJ
        this.finish();
        return;
    }
    // 让此Activity不要有任何动画
    @Override
    public void finish() {
        super.finish();
        overridePendingTransition(0, 0);
    }
    public static void requestPermissionAction(Context context, String[] permissions, int requestCode, IPermission iPermissionLIstener){
        MyPermissionActivity.iPermissionListener = iPermissionLIstener;
        Intent intent = new Intent(context,MyPermissionActivity.class);
        //作用
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
        Bundle bundle = new Bundle();
        Log.d("TAG", "requestPermissionAction: "+requestCode);
        bundle.putInt(PARAM_PERMSSION_CODE,requestCode);
        bundle.putStringArray(PARAM_PERMSSION,permissions);
        intent.putExtras(bundle);
        context.startActivity(intent);
    }
}

2.4、其它

app gradle中


apply plugin: 'com.android.application'
buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'org.aspectj:aspectjtools:1.8.9'
        classpath 'org.aspectj:aspectjweaver:1.8.9'
    }
}
android {
    compileSdkVersion 29
    buildToolsVersion "29.0.2"
    defaultConfig {
        applicationId "com.netease.premissionstudy"
        minSdkVersion 19
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
    implementation 'org.aspectj:aspectjrt:1.8.13'
}
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
final def log = project.logger
final def variants = project.android.applicationVariants
variants.all { variant ->
    if (!variant.buildType.isDebuggable()) {
        log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
        return;
    }
    JavaCompile javaCompile = variant.javaCompile
    javaCompile.doLast {
        String[] args = ["-showWeaveInfo",
                         "-1.8",
                         "-inpath", javaCompile.destinationDir.toString(),
                         "-aspectpath", javaCompile.classpath.asPath,
                         "-d", javaCompile.destinationDir.toString(),
                         "-classpath", javaCompile.classpath.asPath,
                         "-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)]
        log.debug "ajc args: " + Arrays.toString(args)
        MessageHandler handler = new MessageHandler(true);
        new Main().run(args, handler);
        for (IMessage message : handler.getMessages(null, true)) {
            switch (message.getKind()) {
                case IMessage.ABORT:
                case IMessage.ERROR:
                case IMessage.FAIL:
                    log.error message.message, message.thrown
                    break;
                case IMessage.WARNING:
                    log.warn message.message, message.thrown
                    break;
                case IMessage.INFO:
                    log.info message.message, message.thrown
                    break;
                case IMessage.DEBUG:
                    log.debug message.message, message.thrown
                    break;
            }
        }
    }
} 

项目 gradle中


buildscript {
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.1'
        classpath 'org.aspectj:aspectjtools:1.8.9'
        classpath 'org.aspectj:aspectjweaver:1.8.9'
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}
allprojects {
    repositories {
        google()
        jcenter()
    }
}
task clean(type: Delete) {
    delete rootProject.buildDir
}

3、总结

其实核心便是,用aspect去绑架注解,然后让一个公共的Activity来处理这个工作,然后回调,再反射其它办法履行。
等有时间了给他打成一个包。让未被注册的空白Activity也能运用。就简略多了。至于未被注册的空白Activity请看我的文章假如启动一个未注册的Activity – ()