前言
正文大纲
正文
1.Demo地址
Demo地址:https://github.com/18598925736/GracefulPermissionFramework/tree/dev_aspectJ
2. 本文所涉技术盘点
以下适合有一定Android开发年限的开发阅读。并且对以下技术点至少有个基本了解,才能理解本文demo代码
3. 关于Android权限的梗
下面总结几点Android发展历史种,权限体系的重大变革。
Android 1.0 - Android 5.0/5.1 App开发者只需要在清单文件中声明权限,安装的时候就会自动授予。
-
Android 6.0 起 谷歌把所有的权限分为2类,普通权限,即 依然是只需要在清单文件中声明即可。另一类,是危险权限,涉及到用户隐私的权限,除了在清单文件种声明之外,还需要在
App
启动之时动态申请,并且,谷歌还提供了 权限组和权限的概念差别,把某一些功能类似的权限放在一个组别,当你去申请其中一个权限的时候,其实也是在默认申请该组的其他权限。虽然这种做法可以让你少写一个权限,但是谷歌依然建议把所需的权限写完整,因为保不齐哪天谷歌就变更了权限组,到时候代码出兼容问题,没必要,而且把所需权限写完整也是编程好习惯。下图是所有的危险权限以及权限组。
- Android Q 起 又有重大变革。从笔者适配Android Q的过程种,发现
- STORAGE 权限组的两个权限,READ_EXTERNAL_STORAGE / WRITE_ETERNAL_STORAGE 无需动态申请(但是依然要在清单文件中声明), 因为Q系统启用了沙盒机制,app访问自己app所属目录无需任何权限,而如果是要访问app所属目录之外的地方,就需要申请 READ_EXTERNAL_STORAGE / WRITE_ETERNAL_STORAGE这两个权限。
- 如果设备在后台运行时,需要使用 位置信息,需要动态申请权限,Q 引入了 ACCESS_BACKGROUND_LOCATION 这个新权限,目的是限制后台进程获取悄悄的获取用户位置信息。如果此权限运行在Q以下(不含)的系统时,就会默认授予,但是Q及以上,则必须申请。
- 其他一些改动,详见官网,https://developer.android.google.cn/about/versions/10/privacy/changes?hl=zh-tw.
- 另外说一个比较麻烦的问题,如果你运行我的Demo在多种机型上会发现效果可能会完全不同,举两个极端的例子:android6.0版本的mumu模拟器,很多权限会默认授予比如位置信息权限。另外,某些华为手机上,一些权限会默认拒绝,每一次你申请都是 用户已经永久拒绝。 这是因为 手机厂商或者模拟器厂家已经对 谷歌的原生安卓系统进行了深度定制,更改了权限的相关代码。所以,处理这种问题,要对多种机型进行适配处置。
4. 初级/中级/高级android开发的权限请求写法
权限的梗其实就那么一些,比较简单。上面这些梗,我们需要 特别关注的只有一个,那就是6.0以后的动态权限申请。它的处理方式为:
主要流程转化成代码展示出来:
AndroidManifest.xml
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
Java 代码
/**
* 申请权限
*/
protected void 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
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (PermissionUtil.verifyPermissions(grantResults)) {//检查是否都赋予了权限
granted(requestCode);
} else {
// 显示提示
if (PermissionUtil.shouldShowRequestPermissionRationale(this, permissions)) {
//shouldShowRequestPermissionRationale 这个方法就是检查,是不是权限被永久拒绝了。。。如果用就拒绝,这里就返回false,只是第一次拒绝,那就返回true
// 取消权限
denied(requestCode);
} else {
// 权限被拒绝
deniedForever(requestCode);
}
}
}
上面申请权限 ActivityCompat.requestPermissions
和处理回调onRequestPermissionsResult
是开发者需要手动编码的地方。
同样是上面的逻辑,初级/中级/高级
开发者的处理方式截然不同。
- 初级
同样一份回调方法,居然在项目中出现了25次之多. 而且是权限申请这种和业务并不直接搭边的代码 还嵌入到业务代码内部。OK,这里就不多说了。
(PS: 其实这个就是我自己公司的代码,我不知道为什么会这样....也许是公司人员更替太多,后人都懒得改架构)
public abstract class BaseActivity extends AppCompatActivity implements IPermissionCallback {
protected static final String TAG = "BaseActivity";
/**
* 申请权限
*/
protected void 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
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (PermissionUtil.verifyPermissions(grantResults)) {//检查是否都赋予了权限
granted(requestCode);
} else {
// 显示提示
if (PermissionUtil.shouldShowRequestPermissionRationale(this, permissions)) {
//shouldShowRequestPermissionRationale 这个方法就是检查,是不是权限被永久拒绝了。。。如果用就拒绝,这里就返回false,只是第一次拒绝,那就返回true
// 取消权限
denied(requestCode);
} else {
// 权限被拒绝
deniedForever(requestCode);
}
}
}
}
public abstract class BaseFragment extends Fragment implements IPermissionCallback {
protected static final String TAG = "BaseFragment";
/**
* 申请权限
*/
protected void 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
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (PermissionUtil.verifyPermissions(grantResults)) {//检查是否都赋予了权限
granted(requestCode);
} else {
// 显示提示
if (PermissionUtil.shouldShowRequestPermissionRationale(getActivity(), permissions)) {
//shouldShowRequestPermissionRationale 这个方法就是检查,是不是权限被永久拒绝了。。。如果用就拒绝,这里就返回false,只是第一次拒绝,那就返回true
// 取消权限
denied(requestCode);
} else {
// 权限被拒绝
deniedForever(requestCode);
}
}
}
}
然后使用同样一个IPermissionCallback
接口来处理申请权限的可能结果(用户同意,用户拒绝,用户永久拒绝)
/**
* 权限申请结果接口
*/
public interface IPermissionCallback {
/**
* 授予权限
*/
void granted(int requestCode);
/**
* 这次拒绝,但是并没有勾选"以后不再提示"
*/
void denied(int requestCode);
/**
* 勾选"以后不再提示",并且拒绝
*/
void deniedForever(int requestCode);
}
但是, 我们需要权限申请的地方只有Activity和Fragment么?
不,还可能有:
-
高级开发/架构师
详细的解析下一章节再写,先来看代码效果:
Activity:
Fragment:
普通Java类:
Service:
观察以上三张图中代码的相同点:
都利用了3个自定义注解: @PermissionNeed
,@PermissionDenied
,@PermissionCancel
@PermissionNeed
修饰修饰的是 用户授予权限之后的java
方法@PermissionDenied
注解修饰的是 用户拒绝权限之后的java
方法@PermissionCancel
注解修饰的是 用户永久拒绝之后的java
方法
5. AOP优雅权限框架详解
Demo地址:https://github.com/18598925736/GracefulPermissionFramework/tree/dev_aspectJ
gradle
配置
- 在project的
build.gradle
添加aspectJ
gradle
插件
- permission model 的
build.gradle
引入 aspect类库
- app module 的build.gradle中启用aspectJ插件,并且引入
permission module
Java
代码
-
app module
是使用框架的地方
普通类
public class LocationUtil {
private String TAG = "LocationUtil";
@PermissionNeed(
permissions = {Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION},
requestCode = PermissionRequestCodeConst.REQUEST_CODE_LOCATION)
public void getLocation() {
Log.e(TAG, "申请位置权限之后,我要获取经纬度");
}
/**
* 这里写的要特别注意,denied方法,必须是带有一个int参数的方法,下面的也一样
* @param requestCode
*/
@PermissionDenied
public void denied(int requestCode) {
Log.e(TAG, "用户不给啊");
}
@PermissionDeniedForever
public void deniedForever(int requestCode) {
Log.e(TAG, "用户永久拒绝");
}
}
Activity
public class MainActivity extends AppCompatActivity {
private static final String TAG = "PermissionAspectTag";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn_location).setOnClickListener(v -> getLocationPermission());
findViewById(R.id.btn_contact).setOnClickListener(v -> getContactPermission());
}
@PermissionNeed(
permissions = {Manifest.permission.READ_CONTACTS,Manifest.permission.WRITE_CONTACTS,Manifest.permission.GET_ACCOUNTS},
requestCode = PermissionRequestCodeConst.REQUEST_CODE_CONTACT)
private void getContactPermission() {
Log.d(TAG, "getContactPermission");
}
@PermissionNeed(
permissions = {Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION},
requestCode = PermissionRequestCodeConst.REQUEST_CODE_LOCATION)
private void getLocationPermission() {
Log.d(TAG, "getLocationPermission");
}
@PermissionDenied
private void permissionDenied(int requestCode) {
switch (requestCode) {
case PermissionRequestCodeConst.REQUEST_CODE_CONTACT:
Log.d(TAG, "联系人权限被拒绝");
break;
case PermissionRequestCodeConst.REQUEST_CODE_LOCATION:
Log.d(TAG, "位置权限被拒绝");
break;
default:
break;
}
}
@PermissionDeniedForever
private void permissionDeniedForever(int requestCode) {
switch (requestCode) {
case PermissionRequestCodeConst.REQUEST_CODE_CONTACT:
Log.d(TAG, "权限联系人被永久拒绝");
break;
case PermissionRequestCodeConst.REQUEST_CODE_LOCATION:
Log.d(TAG, "位置联系人被永久拒绝");
break;
default:
break;
}
}
}
Fragment
public class MyFragment extends Fragment {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
getLocation();
return super.onCreateView(inflater, container, savedInstanceState);
}
private String TAG = "LocationUtil";
@PermissionNeed(
permissions = {Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION},
requestCode = PermissionRequestCodeConst.REQUEST_CODE_LOCATION)
public void getLocation() {
Log.e(TAG, "申请位置权限之后,我要获取经纬度");
}
/**
* 这里写的要特别注意,denied方法,必须是带有一个int参数的方法,下面的也一样
*
* @param requestCode
*/
@PermissionDenied
public void denied(int requestCode) {
Log.e(TAG, "用户不给啊");
}
@PermissionDeniedForever
public void deniedForever(int requestCode) {
Log.e(TAG, "用户永久拒绝");
}
}
Service
public class MyService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
getLocation();
return super.onStartCommand(intent, flags, startId);
}
private String TAG = "LocationUtil";
@PermissionNeed(
permissions = {Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION},
requestCode = PermissionRequestCodeConst.REQUEST_CODE_LOCATION)
public void getLocation() {
Log.e(TAG, "申请位置权限之后,我要获取经纬度");
}
/**
* 这里写的要特别注意,denied方法,必须是带有一个int参数的方法,下面的也一样
* @param requestCode
*/
@PermissionDenied
public void denied(int requestCode) {
Log.e(TAG, "用户不给啊");
}
@PermissionDeniedForever
public void deniedForever(int requestCode) {
Log.e(TAG, "用户永久拒绝");
}
}
这里有个坑: 被@PermissionDenied
和 @PermissionDeniedForever
修饰的方法,必须有且仅有一个int类型参数, 返回值随意.
-
zpermission
module这里包含了框架的核心代码,现在一步一步讲解
类结构图
3个注解 @PermissionDenied @PermissionDeniedForever @PermissionNeed
/**
* 被此注解修饰的方法,会在方法执行之前去申请相应的权限,只有用户授予权限,被修饰的方法体才会执行
*/
@Target(ElementType.METHOD)//此注解用于修饰方法
@Retention(RetentionPolicy.RUNTIME)//注解保留到运行时,因为可能会需要反射执行方法(上面说了修饰的是方法!)
public @interface PermissionNeed {
String[] permissions();//需要申请的权限,支持多个,需要传入String数组
int requestCode() default 0;//此次申请权限之后的返回码
}
/**
* 被此注解修饰的方法,会在权限申请失败时被调用
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PermissionDenied {
}
/**
* 被此注解修饰的方法,会在用户永久禁止权限之后被调用
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PermissionDeniedForever {
}
处理权限回调结果的接口 IPermissionCallback
/**
* 权限申请结果接口
*/
public interface IPermissionCallback {
/**
* 授予权限
*/
void granted(int requestCode);
/**
* 这次拒绝,但是并没有勾选"以后不再提示"
*/
void denied(int requestCode);
/**
* 勾选"以后不再提示",并且拒绝
*/
void deniedForever(int requestCode);
}
PermissionAspect
类
@Aspect
public class PermissionAspect {
private static final String TAG = "PermissionAspectTag";
private final String pointcutExpression
= "execution(@com.zhou.zpermission.annotation.PermissionNeed * *(..)) && @annotation(permissionNeed)";
@Pointcut(value = pointcutExpression)
public void requestPermission(PermissionNeed permissionNeed) {
Log.d(TAG, "pointCut 定义切入点");
}
@Around("requestPermission(permissionNeed)")
public void doPermission(final ProceedingJoinPoint joinPoint, PermissionNeed permissionNeed) {
PermissionAspectActivity.startActivity(getContext(joinPoint), permissionNeed.permissions(), permissionNeed.requestCode(), new IPermissionCallback() {
@Override
public void granted(int requestCode) {
// 如果授予,那么执行joinPoint原方法体
try {
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
@Override
public void denied(int requestCode) {//这里的getThis也是要给梗
PermissionUtil.invokeAnnotation(joinPoint.getThis(), PermissionDenied.class, requestCode);
}
@Override
public void deniedForever(int requestCode) {
PermissionUtil.invokeAnnotation(joinPoint.getThis(), PermissionDeniedForever.class, requestCode);
}
});
}
private Context getContext(final ProceedingJoinPoint joinPoint) {
final Object obj = joinPoint.getThis();
if (obj instanceof Context) {// 如果切入点是一个类?那么这个类的对象是不是context?
return (Context) obj;
} else {// 如果切入点不是Context的子类呢? //jointPoint.getThis,其实是得到切入点所在类的对象
Object[] args = joinPoint.getArgs();
if (args.length > 0) {//
if (args[0] instanceof Context) {//看看第一个参数是不是context
return (Context) args[0];
} else {
return ApplicationUtil.getApplication();//如果不是,那么就只好hook反射了
}
} else {
return ApplicationUtil.getApplication();//如果不是,那么就只好hook反射了
}
}
}
}
此段代码解读如下:
使用
@Aspect
注解来修饰类 ,@Aspect
是来自AspectJ
框架的注解,被它修饰的类,在编译时会被认为是一个切面类-
使用
@Pointcut
注解来修饰方法requestPermission()
,被它修饰的方法会被认为是一个切入点.所谓切入点,就是 面向切面编程时,我们无侵入式地插入新的逻辑,总要找到一个确切的位置,我们要知道程序执行到哪一行的时候,轮到我们出场了!切入点,一定是方法, 不能是随意哪一段代码!
除了类型之外,这里还有一个重点,那就是 MethodSignature
的概念,这个类似于jni
里的方法签名,是为了标记一个或者一类方法,AspectJ
框架通过这个方法签名,来确定JVM
的所有class对象中,有哪些方法需要被插入 新的逻辑。
具体的签名的语法规则为:
看不懂? 看不懂就对了,举个例子:
execution(@com.zhou.zpermission.annotation.PermissionNeed * *(..))&&@annotation(permissionNeed)
这是Demo中我这么写的,现在逐步解析:
除此之外,还有后半截 &&@annotation(permission)
,它的含义为:
-
使用
@Around
注解来修饰 方法doPermission()
,被它修饰的方法会被认为是一个 切入策略。Around注解的参数 为:
"requestPermission(permissionNeed)"
, 也就是pointcut
修饰的方法名(形参名)在我们已经定义好切入点
requestPermission(PermissionNeed permissionNeed)
的前提下,如果程序已经执行到了切入点,那么我是选择怎么样的策略, 目前所选择的策略是 Around ,也就是,完全替代切入点的方法,但是依然保留了 执行原方法逻辑的可能性joinPoint.proceed();
除了
@Around
策略之外,还有以下:
PermissionAspect
类的作用是:定义切入点和切入策略,那么现在我们确定切入点是 被注解@PermissionNeed
修饰的方法,切入策略是@Around
,那么,切入之后我们做了哪些事呢?
接下往下看...
-
PermissionAspectActivity
类public class PermissionAspectActivity extends AppCompatActivity {
private final static String permissionsTag = "permissions";
private final static String requestCodeTag = "requestCode";
private static IPermissionCallback mCallback;
/**
* 启动当前这个Activity
*/
public static void startActivity(Context context, String[] permissions, int requestCode, IPermissionCallback callback) {
Log.d("PermissionAspectTag", "context is : " + context.getClass().getSimpleName());
if (context == null) return;
mCallback = callback;
//启动当前这个Activiyt并且取消切换动画
Intent intent = new Intent(context, PermissionAspectActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);//开启新的任务栈并且清除栈顶...为何要清除栈顶
intent.putExtra(permissionsTag, permissions);
intent.putExtra(requestCodeTag, requestCode);
context.startActivity(intent);//利用context启动activity
if (context instanceof Activity) {//并且,如果是activity启动的,那么还要屏蔽掉activity切换动画
((Activity) context).overridePendingTransition(0, 0);
}
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
String[] permissions = intent.getStringArrayExtra(permissionsTag);
int requestCode = intent.getIntExtra(requestCodeTag, 0);
if (PermissionUtil.hasSelfPermissions(this, permissions)) {
mCallback.granted(requestCode);
finish();
overridePendingTransition(0, 0);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(permissions, requestCode);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
//现在拿到了权限的申请结果,那么如何处理,我这个Activity只是为了申请,然后把结果告诉外界,所以结果的处理只能是外界传进来
boolean granted = PermissionUtil.verifyPermissions(grantResults);
if (granted) {//如果用户给了权限
mCallback.granted(requestCode);
} else {
if (PermissionUtil.shouldShowRequestPermissionRationale(this, permissions)) {
mCallback.denied(requestCode);
} else {
mCallback.deniedForever(requestCode);
}
}
finish();
overridePendingTransition(0, 0);
}
}
解读:
需要注意:
Gif
图效果演示:
6. AOP思想以及常用AOP框架
7. AspectJ
AOP框架的深入原理研究
...本来想写成一篇,但是发现篇幅太长,留个尾巴,下一篇,解析AspectJ是如何通过@注解的方式来插入逻辑的。