如何写一个无侵入式的动态权限申请Android框架?

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 – 掘金 (juejin.cn)

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYzm3dg0' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片