android 动态申请权限_AOP编程_Android优雅权限框架(1)概念基础

ccdec598-f219-eb11-8da9-e4434bdf6706.gif

前言

上一个大的系列文章叫 "手把手讲解", 历时10个月,出产博文二十余篇,讲解细致,几乎每一篇都提供了详实的原理讲解,提供了可运行 githubDemo,并且针对Demo中的关键地地方进行了重点拆解。相信每一位详细阅读文章的同行都会有所收获。但是,讲解虽详细,但是缺乏对于技术的深度的挖掘。

从今天开始开辟新的专题: 移动架构师专业技能深入浅出,以一步步成为架构师为目标,详述一项架构师技能的最直接使用价值横向周边知识以及纵深专业技术.

最直接使用价值: 网上最怕看到一种文章,全文开篇高大上,让人觉得遥不可及,通篇看下来却没有展示技术如何落地,落地之后是何种效果。文章写出来,就要以最容易让人接受的方式带读者进入作者的世界,而不是装作一副高高在上的样子俯视众生。所以,文章开篇,一定是最直接的展示技术的落地效果。提供可运行Demo可以让读者亲自尝试。

横向周边知识: 一项核心技术,必然不是独立存在,技术是一个体系,但是一篇文章能够详述的技术有限,必然是以一项技术为中心,其他技术作为辅助。核心技术需要详述,但是周边技术,也需要交代,参天大树拔地而起也少不得土壤作为依附。用简明的语言交代周边知识,并提供这些知识正确的研究方向。也是一个负责任的博文作者不可忽视的一步。

纵深专业技术: 做技术,最忌讳的就是浅尝辄止。稍微深入一点就退出去,一来不利于理解底层实现,长此以往永远只是一个技术小白,成不了大师;二来不利于长久记忆, 记忆力再强的人时间长了,技术细节必然会记忆模糊。但是如果深入内核,理解了原理,在技术的大方向上绝对不会偏差。作为要成为架构师的男人,即使记不了那么多细节,但是对于大方向的把握绝对不能错。所以,技术纵深很有必要。

正文大纲

1、Demo地址

2、本文所涉技术盘点

3、关于Android权限的梗

4、初级/中级/高级android开发的权限请求写法

5、AOP优雅权限框架详解

     gradle配置

     Java代码

6、AOP思想以及常用AOP框架

7、AspectJ AOP框架的深入原理研究

正文

1.Demo地址

Demo地址:https://github.com/18598925736/GracefulPermissionFramework/tree/dev_aspectJ

2. 本文所涉技术盘点

以下适合有一定Android开发年限的开发阅读。并且对以下技术点至少有个基本了解,才能理解本文demo代码

  • java 注解基础

java代码中大量使用@符号作为注解标志,注解用途多种多样,但是基本都是做标记,用于源码期,编译期,或者运行期的特别处理。注解有自己的特定语法以及API。

  • Gradle插件基础

Gradle是androidStudio中的项目构建框架,用于将android源码工程整合编译打包成apk。其中可以自定义gradle插件,也可以引用他人发布的gradle插件来给项目构建过程中加入自己想要的逻辑。

  • Java 反射基础

java反射,某些不方便直接使用的类或者方法,可以通过反射的方式使用。反射通常用在框架设计,hook技术中。

  • Android 权限基础知识

本文的重点是优雅地写出权限申请的代码,要读懂本文自然不能对权限一无所知。

  • AOP面向切面编程思想

面向切面编程是代码解耦的重要手段之一。更多信息且看下文。

3. 关于Android权限的梗

权限问题,自android问世以来就是一个梗,最早做android的那一批人,当时可以随便获取用户信息,包括联系人,包括短信内容,包括通话记录,可以说Android被人诟病的安全问题,源自于此。代码层面,开发者只需要对照android官网权限说明,在manifest文件中声明所需的权限,即可在代码种访问所需的数据。各类权限十分繁多,超过上百种权限,适用于各种不同场景,记不下来,一般也不用记。

需要的时候到官网https://developer.android.com/reference/android/Manifest.permission.html查找即可.

下面总结几点Android发展历史种,权限体系的重大变革。

  • Android 1.0 - Android 5.0/5.1 App开发者只需要在清单文件中声明权限,安装的时候就会自动授予。

  • Android 6.0 起 谷歌把所有的权限分为2类,普通权限,即 依然是只需要在清单文件中声明即可。另一类,是危险权限,涉及到用户隐私的权限,除了在清单文件种声明之外,还需要在 App启动之时动态申请,并且,谷歌还提供了 权限组权限的概念差别,把某一些功能类似的权限放在一个组别,当你去申请其中一个权限的时候,其实也是在默认申请该组的其他权限。虽然这种做法可以让你少写一个权限,但是谷歌依然建议把所需的权限写完整,因为保不齐哪天谷歌就变更了权限组,到时候代码出兼容问题,没必要,而且把所需权限写完整也是编程好习惯。

  • 下图是所有的危险权限以及权限组。

cedec598-f219-eb11-8da9-e4434bdf6706.png

  • Android Q 起 又有重大变革。从笔者适配Android Q的过程种,发现

   1. STORAGE 权限组的两个权限,READEXTERNALSTORAGE / WRITEETERNALSTORAGE 无需动态申请(但是依然要在清单文件中声明), 因为Q系统启用了沙盒机制,app访问自己app所属目录无需任何权限,而如果是要访问app所属目录之外的地方,就需要申请 READEXTERNALSTORAGE / WRITEETERNALSTORAGE这两个权限。

   2. 如果设备在后台运行时,需要使用 位置信息,需要动态申请权限,Q 引入了 ACCESSBACKGROUNDLOCATION 这个新权限,目的是限制后台进程获取悄悄的获取用户位置信息。如果此权限运行在Q以下(不含)的系统时,就会默认授予,但是Q及以上,则必须申请。

   3. 其他一些改动,详见官网,https://developer.android.google.cn/about/versions/10/privacy/changes?hl=zh-tw.

  • 另外说一个比较麻烦的问题,如果你运行我的Demo在多种机型上会发现效果可能会完全不同,举两个极端的例子:android6.0版本的mumu模拟器,很多权限会默认授予比如位置信息权限。另外,某些华为手机上,一些权限会默认拒绝,每一次你申请都是 用户已经永久拒绝。这是因为 手机厂商或者模拟器厂家已经对 谷歌的原生安卓系统进行了深度定制,更改了权限的相关代码。所以,处理这种问题,要对多种机型进行适配处置。

4. 初级/中级/高级android开发的权限请求写法

权限的梗其实就那么一些,比较简单。上面这些梗,我们需要 特别关注的只有一个,那就是6.0以后的动态权限申请。它的处理方式为:

d0dec598-f219-eb11-8da9-e4434bdf6706.png

主要流程转化成代码展示出来:

AndroidManifest.xml

android:name="android.permission.ACCESS_FINE_LOCATION"/>

android:name="android.permission.ACCESS_COARSE_LOCATION"/>

Java 代码

/**

* 申请权限

*/

protectedvoid requestPermission(String[] permissions, int requestCode) {

// 检查已经有了这些权限

if(PermissionUtil.hasSelfPermissions(this, permissions)) {

Log.e(TAG, "Activity,requestPermission: 所有权限都已经有了,无需申请");

} else{

// 开始请求权限

ActivityCompat.requestPermissions(this, permissions, requestCode);

}

}

/**

* 处理回调

*

* @param requestCode

* @param permissions

* @param grantResults

*/

@Override

publicvoid onRequestPermissionsResult(int requestCode, @NonNullString[] permissions, @NonNullint[] grantResults) {

if(PermissionUtil.verifyPermissions(grantResults)) {//检查是否都赋予了权限

granted(requestCode);

} else{

// 显示提示

if(PermissionUtil.shouldShowRequestPermissionRationale(this, permissions)) {

//shouldShowRequestPermissionRationale 这个方法就是检查,是不是权限被永久拒绝了。。。如果用就拒绝,这里就返回false,只是第一次拒绝,那就返回true

// 取消权限

denied(requestCode);

} else{

// 权限被拒绝

deniedForever(requestCode);

}

}

}

上面申请权限 ActivityCompat.requestPermissions处理回调 onRequestPermissionsResult是开发者需要手动编码的地方。

同样是上面的逻辑, 初级/中级/高级开发者的处理方式截然不同。

  • 初级

一个完整的商业项目,势必会涉及到非常多的 ActivityFragment,以及 普通Java类等等 ,诸多地方需要使用到特定的权限,如果我们 ctrl+H全文搜索一下 onRequestPermissionsResult,发现如下场景:

d3dec598-f219-eb11-8da9-e4434bdf6706.png

同样一份回调方法,居然在项目中出现了25次之多. 而且是权限申请这种和业务并不直接搭边的代码 还嵌入到业务代码内部。OK,这里就不多说了。

(PS: 其实这个就是我自己公司的代码,我不知道为什么会这样....也许是公司人员更替太多,后人都懒得改架构)

  • 中级

PS:参考 https://github.com/18598925736/GracefulPermissionFramework/tree/dev

中级开发,作为有一定工作经验的程序员,知道如何优化代码,减少维护成本,那么他很可能会发现,需要用到权限申请的地方,基本上是以Activity和Fragment,只要解决了这里的代码冗余,他会这么写

publicabstractclassBaseActivityextendsAppCompatActivityimplementsIPermissionCallback{

protectedstaticfinalString TAG = "BaseActivity";

/**

* 申请权限

*/

protectedvoid requestPermission(String[] permissions, int requestCode) {

// 检查已经有了这些权限

if(PermissionUtil.hasSelfPermissions(this, permissions)) {

Log.e(TAG, "Activity,requestPermission: 所有权限都已经有了,无需申请");

} else{

// 开始请求权限

ActivityCompat.requestPermissions(this, permissions, requestCode);

}

}

/**

* 请求回馈

*

* @param requestCode

* @param permissions

* @param grantResults

*/

@Override

publicvoid onRequestPermissionsResult(int requestCode, @NonNullString[] permissions, @NonNullint[] grantResults) {

if(PermissionUtil.verifyPermissions(grantResults)) {//检查是否都赋予了权限

granted(requestCode);

} else{

// 显示提示

if(PermissionUtil.shouldShowRequestPermissionRationale(this, permissions)) {

//shouldShowRequestPermissionRationale 这个方法就是检查,是不是权限被永久拒绝了。。。如果用就拒绝,这里就返回false,只是第一次拒绝,那就返回true

// 取消权限

denied(requestCode);

} else{

// 权限被拒绝

deniedForever(requestCode);

}

}

}

}

publicabstractclassBaseFragmentextendsFragmentimplementsIPermissionCallback{

protectedstaticfinalString TAG = "BaseFragment";

/**

* 申请权限

*/

protectedvoid requestPermission(String[] permissions, int requestCode) {

// 是否已经有了这些权限

if(PermissionUtil.hasSelfPermissions(getActivity(), permissions)) {

Log.e(TAG, "Activity,requestPermission: 所有权限都已经有了,无需申请");

} else{

// 开始请求权限

ActivityCompat.requestPermissions(getActivity(), permissions, requestCode);

}

}

/**

* 请求回馈

*

* @param requestCode

* @param permissions

* @param grantResults

*/

@Override

publicvoid onRequestPermissionsResult(int requestCode, @NonNullString[] permissions, @NonNullint[] grantResults) {

if(PermissionUtil.verifyPermissions(grantResults)) {//检查是否都赋予了权限

granted(requestCode);

} else{

// 显示提示

if(PermissionUtil.shouldShowRequestPermissionRationale(getActivity(), permissions)) {

//shouldShowRequestPermissionRationale 这个方法就是检查,是不是权限被永久拒绝了。。。如果用就拒绝,这里就返回false,只是第一次拒绝,那就返回true

// 取消权限

denied(requestCode);

} else{

// 权限被拒绝

deniedForever(requestCode);

}

}

}

}

然后使用同样一个 IPermissionCallback接口来处理申请权限的可能结果(用户同意,用户拒绝,用户永久拒绝)

/**

* 权限申请结果接口

*/

publicinterfaceIPermissionCallback{

/**

* 授予权限

*/

void granted(int requestCode);

/**

* 这次拒绝,但是并没有勾选"以后不再提示"

*/

void denied(int requestCode);

/**

* 勾选"以后不再提示",并且拒绝

*/

void deniedForever(int requestCode);

}

但是, 我们需要权限申请的地方只有Activity和Fragment么?

,还可能有:

Service : 比如启动一个Service播放本地音乐,可能需要本地存储权限,如果此时才来申请,那么service应该如何申请权限?经过实验,我发现Service没有办法去申请权限,因为 ActivityCompat.requestPermissions()方法的第一个参数是 Activity, 而在一个Service中,无法直接去获得一个Activity对象。

普通Java类: 一个普通的Java工具类,他的作用是从手机内部存储中读写文件,那么他需要本地存储权限, 它该如何申请?获取你可以想出一点偏方来解决这个问题,但是如果停留在中级开发的层次,永远无法给出一个优雅的解决方案。

  • 高级开发/架构师

详细的解析下一章节再写,先来看代码效果:

Activity:

d5dec598-f219-eb11-8da9-e4434bdf6706.png

Fragment:

d7dec598-f219-eb11-8da9-e4434bdf6706.png

普通Java类:

d8dec598-f219-eb11-8da9-e4434bdf6706.png

**Service**:

d9dec598-f219-eb11-8da9-e4434bdf6706.png

观察以上三张图中代码的相同点:

都利用了3个自定义注解: @PermissionNeed , @PermissionDenied, @PermissionCancel

  • @PermissionNeed 修饰修饰的是 用户授予权限之后的 java方法

  • @PermissionDenied 注解修饰的是 用户拒绝权限之后的 java方法

  • @PermissionCancel 注解修饰的是 用户永久拒绝之后的 java方法

3个注解,在Activity,Fragment,Service 以及 普通Java类的使用方式完全相同,也可以说,高级开发/架构师的处理方式,把 Activity,Fragment,Service以及普通Java类 的差异化消除了,达成了 代码调用的通用性, 从根本上解决了 动态权限申请在 业务代码中的冗余问题。

使用这种做法,再也不用担心自己的业务代码会和 权限相关的代码发生交叉,让业务代码更加清晰。

下一篇开始讲解Demo.

dadec598-f219-eb11-8da9-e4434bdf6706.png

万水千山总是情,点个在看行不行dbdec598-f219-eb11-8da9-e4434bdf6706.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值