Android Framework权限篇之RuntimePermission整体流程

概述

权限的作用是保护Android用戶的隐私。

如果设备搭载的是Android 6.0(API 级别 23)或更高版本,并且应用的targetSdkVersion 是23或更高版本,用戶在安装时不会收到任何应用权限的通知。您的应用必须在运行时请求用戶授予危险权限。当应用请求权限时,用戶会看到一个系统对话框,告知用戶应用正在尝试访问的权限组。该对话框包括拒绝和允许按钮。如果用戶选中不再询问复选框并点按拒绝,当您以后尝试请求相同权限时,系统不会再提示用戶。

如果设备搭载的是Android5.1.1(API级别22)或更低版本,或者应用在任何版本的Android上运行时其targetSdkVersion 是22或更低版本,系统将在安装时自动请求用戶向应用授予所有危险权限

在这里插入图片描述

权限保护级别

权限的分类总共有如下几种,先有个总览,本篇文章后面是展开讨论dangerous类型即运行时权限的整体流程。

级别描述
normal如果应用在清单中声明需要普通权限,系统会在安装时自动向应用授予该权限。系统不会提示用户授予普通权限,用户也无法撤消这些权限
dangerous为了使用危险权限,应用必须在运行时提示用户授予权限
signature在安装时授予这些应用权限,但仅会在尝试使用某权限的应用签名证书为定义该权限的同一证书时才会授予(即相同签名)
signatureOrSystem signatureprivileged

这里做Android Framework权限开发的经常会遇到系统app的同事过来问权限问题,我这个app是在/system/app目录下的,并且需要的这个权限是signature级别的;为什么在AndroidManifest声明了之后没有默认授予?可以结合下图总结看下;

在这里插入图片描述

运行时权限申请

这里开始看下运行时权限的整体流程;正常应用的权限申请过程如下,先看下应用是如何申请运行时权限,再从应用侧到Framework侧看下去; 更多应用权限请求细节可以参考这里

    private fun requestPermmission() {
        // 判断是否需要运行时申请权限
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && ContextCompat.checkSelfPermission(context, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
            // 判断是否需要对用户进行提醒,用户点击过拒绝&&没有勾选不再提醒时,即拒绝一次时进行提示
            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CALL_PHONE)) {
                // 给用户予以权限解释, 对于已经拒绝过的情况,先提示申请理由,再通过点击弹出的Dialog进行申请
                showDialog("需要打开电话权限直接进行拨打电话,方便您的操作")
            } else {
                // 无需说明理由的情况下,直接进行申请。如:1.第一次使用该功能(第一次申请权限),2.用户拒绝权限并勾选了不再提醒,3.已授权 这3种情况shouldShowRequestPermissionRationale()返回false
                ActivityCompat.requestPermissions(
        this, new String[]{Manifest.permission. CALL_PHONE,}, 1);
            }
        } else {
            // 拥有权限直接进行功能调用
            callPhone();
        }
    }

    /**
     * 权限申请回调
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        // 根据requestCode判断是那个权限请求的回调
        if (requestCode == REQUEST_PERMISSION_CODE_CALL_PHONE) {
            // 判断用户是否同意了请求
            if (grantResults.size > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                callPhone()
            } else {
                // 未同意的情况
                if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CALL_PHONE)) {
                    // 判断是拒绝一次的情况,则同样先是弹Dialog提示用户,再重新申请权限
                    showDialog("需要打开电话权限直接进行拨打电话,方便您的操作")
                } else {
                   // 用户勾选了不再提醒,引导用户进入设置界面进行开启权限
                   // 如果是用户点击了拒绝(拒绝并且设置不再询问)则requestPermissions完会直接回调onRequestPermissionsResult返回结果PERMISSION_DENIED, 走到这里的逻辑去提示用户去设置打开权限
                   showDialog("需要跳转去到设置打开权限");
                }
            }
        } else {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        }
    }

}

检查权限流程

从上面的ContextCompat.checkSelfPermission(context, Manifest.permission.CALL_PHONE) 看下源码的检查流程

在这里插入图片描述

这里在源码环境下加断点的形式可以看到调用堆栈,如上图最终是调用到PermissionManagerService.checkUidPermission() 一起看下这个方法里的具体逻辑,这里先对PersmissionsState数据类有个印象,权限授权持久化是通过此类进行操作,先过下流程,后面再展开。

...
if (pkg != null) {
   ...
    //1.这里获取pkg对应的PermissionsState
    final PermissionsState permissionsState =
            ((PackageSetting) pkg.mExtras).getPermissionsState();
    //2.再判断该权限是否授权
    if (permissionsState.hasPermission(permName, userId)) {
        if (isUidInstantApp) {
            if (mSettings.isPermissionInstant(permName)) {
                return PackageManager.PERMISSION_GRANTED;
            }
        } else {
            //正常流程如果权限授权了是走到这里进行返回
            return PackageManager.PERMISSION_GRANTED;
        }
    }
    if (isImpliedPermissionGranted(permissionsState, permName, userId)) {
        return PackageManager.PERMISSION_GRANTED;
    }
...
//3.最后以上条件都没满足则返回拒绝
return PackageManager.PERMISSION_DENIED;


请求权限过程

接着从之前的ActivityCompat.requestPermissions(this, new String[]{Manifest.permission. CALL_PHONE,}, 1);看下源码的请求过程,这里实际是启动一个权限请求弹窗页面。

public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
    if (requestCode < 0) {
        throw new IllegalArgumentException("requestCode should be >= 0");
    }
    if (mHasCurrentPermissionsRequest) {
        Log.w(TAG, "Can request only one set of permissions at a time");
        // Dispatch the callback with empty arrays which means a cancellation.
        onRequestPermissionsResult(requestCode, new String[0], new int[0]);
        return;
    }
    //1. 生成指定action的intent,该intent会启动权限管理的权限弹窗界面
    Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
    //2. 通过startActivityForResult启动,结果会回调到onRequestPermissionsResult()
    startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
    mHasCurrentPermissionsRequest = true;
}


权限授权流程

应用请求权限弹窗后,如果点击允许或者拒绝,此时底下源码会发生什么,一起往下看

在这里插入图片描述

如果权限弹窗点击允许,最终会走到PermissionManagerService.grantRuntimePermission(),这里我们看下核心代码,可以看到这里还是通过PermissionsState.grantRuntimePermission()进行授权,具体关于权限内存状态和持久化存储在后续文章讲解,这里先有个印象。

private void grantRuntimePermission(String permName, String packageName, boolean overridePolicy,int callingUid, final int userId, PermissionCallback callback) {
...
final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
...
//1. 以上省略了很多判断条件,这里会先获取对应包名的权限状态PermissionsState和其flag
final PackageSetting ps = (PackageSetting) pkg.mExtras;
final PermissionsState permissionsState = ps.getPermissionsState();
final int flags = permissionsState.getPermissionFlags(permName, userId);
...
//2. 这里对权限进行授权并返回结果
final int result = permissionsState.grantRuntimePermission(bp, userId);
...
//3. 权限授权成功后,除了更新内存中的状态值,还会持久化到本地xml
if (callback != null) {
    callback.onPermissionGranted(uid, userId);
}
...
}


同样看下撤销权限流程,对应权限弹窗上的拒绝按钮,这里可以看到是通过PermissionsState.revokeRuntimePermission()进行撤销授权

private void revokeRuntimePermission(String permName, String packageName,boolean overridePolicy, int userId, PermissionCallback callback) {
final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
...
//1. 以上省略了很多判断条件,这里会先获取对应包名的权限状态PermissionsState和其flag
final PackageSetting ps = (PackageSetting) pkg.mExtras;
final PermissionsState permissionsState = ps.getPermissionsState();
final int flags = permissionsState.getPermissionFlags(permName, userId);
...
//2. 撤销权限并判断返回结果
if (permissionsState.revokeRuntimePermission(bp, userId) ==
        PERMISSION_OPERATION_FAILURE) {
    return;
}
...
//3. 撤销成功后,除了更新内存中的状态值,还会持久化到本地xml
if (callback != null) {
    callback.onPermissionRevoked(pkg.applicationInfo.uid, userId);
}
...
}


flag更新

上述权限授权流程的123执行完之后,从4开始会更新flag,这个flag的作用是什么?这里先说下结论,这个flag对应用户选择权限弹窗的允许、拒绝并勾选了不再提醒、拒绝并未勾选不再提醒。下一次权限弹窗时会通过此标志位判断是否不再弹窗。

//允许的标志位为0
packageManager.updatePermissionFlags(permission,
                            packageName,
                            PackageManager.FLAG_PERMISSION_USER_FIXED
                                    | PackageManager.FLAG_PERMISSION_USER_SET,
                            0, userHandle);
//拒绝并未勾选不再提醒的标志位为PackageManager.FLAG_PERMISSION_USER_SET
packageManager.updatePermissionFlags(permission,
                            packageName,
                            PackageManager.FLAG_PERMISSION_USER_SET
                                    | PackageManager.FLAG_PERMISSION_USER_FIXED,
                            PackageManager.FLAG_PERMISSION_USER_SET,
                            userHandle);
//拒绝并勾选了不再提醒的标志位为PackageManager.FLAG_PERMISSION_USER_FIXED
packageManager.updatePermissionFlags(permission,
                            packageName,
                            PackageManager.FLAG_PERMISSION_USER_SET
                                    | PackageManager.FLAG_PERMISSION_USER_FIXED,
                            PackageManager. FLAG_PERMISSION_USER_FIXED,
                            userHandle);


接下来看下具体方法调用下去是如何作用的,这里关于flag更新的地方单独画了个图看下比较好理解,flagMask参数对应前面传的PackageManager.FLAG_PERMISSION_USER_SET | PackageManager.FLAG_PERMISSION_USER_FIXED,而PackageManager.FLAG_PERMISSION_USER_SET对应的值是1, PackageManager.FLAG_PERMISSION_USER_FIXED对应的值是2;两个值二进制相或是11。

在这里插入图片描述

private void updatePermissionFlags(String permName, String packageName, int flagMask,int flagValues, int callingUid, int userId, boolean overridePolicy,
PermissionCallback callback) {
...
//1.获取对应包名的权限状态PermissionsState
final PackageSetting ps = (PackageSetting) pkg.mExtras;
final PermissionsState permissionsState = ps.getPermissionsState();
final boolean hadState =
        permissionsState.getRuntimePermissionState(permName, userId) != null;
//2.更新flag
final boolean permissionUpdated =
        permissionsState.updatePermissionFlags(bp, userId, flagMask, flagValues);
...
}

PermissionsState.java下的PermissionData内部类
//真正更新flag的地方
public boolean updateFlags(int userId, int flagMask, int flagValues) {
    //flagMask与flagValues相与后为新的flag
    final int newFlags = flagValues & flagMask;
    PermissionState userState = mUserStates.get(userId);
    if (userState != null) {
        final int oldFlags = userState.mFlags;
        //userState.mFlags & ~flagMask: 将旧的falg上关于flagMask的标志位清0
        //| newFlags再与上新的flag,即在关于flagMask上的标志位上赋值
        userState.mFlags = (userState.mFlags & ~flagMask) | newFlags;
        if (userState.isDefault()) {
            mUserStates.remove(userId);
        }
        return userState.mFlags != oldFlags;
    } else if (newFlags != 0) {
        userState = new PermissionState(mPerm.getName());
        //如果是新的状态则直接将newFlags赋值即可
        userState.mFlags = newFlags;
        mUserStates.put(userId, userState);
        return true;
    }
    return false;
}


如何学习Framework?

由于许多Android开发者日常工作主要集中在业务层面,大量时间用于编写基础代码、应用现成框架,导致对底层技术如Framework、Handler源码、Binder机制等了解不足,仅停留在表面认知阶段。

为此,为了帮助广大开发者弥补这一短板,特此准备了一份详尽的Android Framework内核源码知识体系图解,以及配套的《Android Framework源码开发解析》学习笔记,旨在引导大家系统性地攻克Android Framework领域的核心技术,从而提升自身的竞争力,从容应对金三银四的求职挑战。

【有需要的朋友,扫描下方二维码即可领取!!】👇👇

在这里插入图片描述

《Android Framework源码开发揭秘》

第一章 系统启动流程分析
  • 第一节 Android启动概括
  • 第二节 init.rc解析
  • 第三节 Zygote
  • 第四节 面试题
    在这里插入图片描述
第二章 跨进程通信IPC解析
  • 第一节 Service还可以这么理解
  • 第二节 Binder基础
  • 第三节 Binder应用
  • 第四节 AIDL应用(上)
  • 第五节 AIDL应用(下)
  • 第六节 Messenger原理及应用
  • 第七节 服务端回调
  • 第八节 获取服务(IBinder)
  • 第九节 Binder面试题全解析
    在这里插入图片描述
第三章 Handler源码解析
  • 第一节 源码分析
  • 第二节 难点问题
  • 第三节 Handler常问面试题在这里插入图片描述
第四章 AMS源码解析
  • 第一节 引言
  • 第二节 Android架构
  • 第三节 通信方式
  • 第四节 系统启动系列
  • 第五节 AMS
  • 第六节 AMS面试题解析在这里插入图片描述
第五章 WMS源码解析
  • 第一节 WMS与activity启动流程
  • 第二节 WMS绘制原理
  • 第三节 WMS角色与实例化过程
  • 第四节 WMS工作原理在这里插入图片描述
第六章 Surface源码解析
  • 第一节 创建流程及软硬件绘制
  • 第二节 双缓冲及Surface View解析
  • 第三节 Android图形系统综述在这里插入图片描述
第七章 基于Android12.0的SurfaceFlinger源码解析
  • 第一节 应用建立和SurfaceFlinger的沟通桥梁
  • 第二节 SurfaceFlinger的启动和消息队列处理机制
  • 第三节 SurfaceFlinger之VSyns(上)
  • 第四节 SurfaceFlinger之VSyns(中)
  • 第五节 SurfaceFlinger之VSyns(下)在这里插入图片描述
第八章 PKMS源码解析
  • 第一节 PKMS调用方式
  • 第二节 PKMS启动过程分析
  • 第三节 APK的扫描
  • 第四节 APK的安装
  • 第五节 PKMS之权限扫描
  • 第六节 静默安装
  • 第七节 requestPermissions源码流程解析
  • 第八节 PKMS面试题在这里插入图片描述
第九章 InputManagerService源码解析
  • 第一节 Android Input输入事件处理流程(1)
  • 第二节 Android Input输入事件处理流程(2)
  • 第三节 Android Input输入事件处理流程(3)在这里插入图片描述
第十章 DisplayManagerService源码解析
  • 第一节 DisplayManagerService启动
  • 第二节 DisplayAdepter和DisplayDevice的创建
  • 第三节 DMS部分亮灭屏流程
  • 第四节 亮度调节
  • 第五节 Proximity Sensor灭屏原理
  • 第六节 Logical Display和Physical Display配置的更新在这里插入图片描述
【有需要的朋友,扫描下方二维码即可领取!!】👇👇

最后 在Android开发市场饱和的大背景下,初级开发者只有通过对Android Framework进行全面而深入的学习与实践,才能有效提高自身的专业素养和技术能力,从而在职场竞争中找准定位,实现从量到质的飞跃,成为行业内的佼佼者。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值