Android Framework权限篇之后台定位权限源码分析

AppOps介绍

首先,先介绍下AppOps机制,在Android中,已经有一套Android Runtime运行时权限机制,对应Framework中的PermissionManager和PermissionService服务;其实还有一套AppOpsManager和AppOpsService服务,关于这个服务官方的介绍是如下:App-ops提供两个用途:一个是访问控制,这个具体是和runtime运行时权限配合;这个在下篇文章介绍;一个是跟踪,这里翻译是电量耗电跟踪,我自己个人的理解是和电量耗电相关的权限,比如定位权限,本篇文章介绍的就是应用退到后台以后,如何通过Appops机制跟踪限制后台使用定位。

    /**
     * App-ops are used for two purposes: Access control and tracking.
     *
     * <p>App-ops cover a wide variety of functionality from helping with runtime permissions access
     * control and tracking to battery consumption tracking.

后台位置权限

在这里插入图片描述

在ard10上,权限新增了后台位置权限,需要额外申请ACCESS_BACKGROUND_LOCATION权限,此时权限弹窗会展示始终允许和使用期间这两个选项;选择相应选项相应权限会授权;这里除了运行时权限会授权外,AppOps权限机制也会授权,这里定位权限在AppOps机制上对应的授权结果为:

  • 始终允许:MODE_ALLOWED
  • 使用期间:MODE_FOREGROUND
  • 拒绝:MODE_IGNORED

这几个值在源码定义处


/frameworks/base/core/java/android/app/AppOpsManager.java

public static final int MODE_ALLOWED = 0;
public static final int MODE_IGNORED = 1;
public static final int MODE_ERRORED = 2;
public static final int MODE_DEFAULT = 3;
public static final int MODE_FOREGROUND = 4;

获取位置

在Android设备上,获取设备位置,具体可以看下这片博客:Android获取位置 ,接下来从获取设备位置API去推怎么限制应用退到后台访问位置

private LocationManager locationManager;
locationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
//获取Location
Location location = locationManager.getLastKnownLocation(locationProvider);

可以看到这里通过LocationManager调用下来,最后会调用到LocationManagerService.getLastLocation() 在这里面会去检查权限是否授权,否则返回为空

/frameworks/base/services/core/java/com/android/server/LocationManagerService.java

@Override
public Location getLastLocation(LocationRequest r, String packageName) {

      ...
      // Don't report location access if there is no last location to deliver.
            if (lastLocation != null) {
                //这里会检查权限,如果不通过则拿到的location是null的
                if (!reportLocationAccessNoThrow(
                        pid, uid, packageName, allowedResolutionLevel)) {
                    if (D) {
                        Log.d(TAG, "not returning last loc for no op app: " + packageName);
                    }
                    lastLocation =  null;
                }
            }
            return lastLocation;
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }
}


这里看下是怎么检查权限的

    private boolean reportLocationAccessNoThrow(
            int pid, int uid, String packageName, int allowedResolutionLevel) {
        //这里先做个转换得到FINE/CORSE对应的op值
        int op = resolutionLevelToOp(allowedResolutionLevel);
        if (op >= 0) {
            //这里判断如果值不等于MODE_ALLOWED则会返回false
            if (mAppOps.noteOpNoThrow(op, uid, packageName) != AppOpsManager.MODE_ALLOWED) {
                return false;
            }
        }

        return getAllowedResolutionLevel(pid, uid) >= allowedResolutionLevel;
    }

这里(AppOpsManager)mAppOps.noteOpNoThrow() -> AppOpsService.noteOperationUnchecked(),如下代码的第一点和第二点是关键,展开讨论之前,我们先看下应用在退到前后台的时候怎么更新状态;

private int noteOperationUnchecked(int code, int uid, String packageName,
          int proxyUid, String proxyPackageName, @OpFlags int flags) {
      synchronized (this) {
          //1.getOpsRawLocked()这里会去查询并更新前后台状态
          final Ops ops = getOpsRawLocked(uid, packageName, true /* edit */,
                  false /* uidMismatchExpected */);
          ...
          final Op op = getOpLocked(ops, code, true);
          ...
          final UidState uidState = ops.uidState;
          ...
          final int switchCode = AppOpsManager.opToSwitch(code);
          // If there is a non-default per UID policy (we set UID op mode only if
          // non-default) it takes over, otherwise use the per package policy.
          if (uidState.opModes != null && uidState.opModes.indexOfKey(switchCode) >= 0) {
              //2.UidState.evalMode()这里会得到结果MODE_ALLOWED或者MODE_IGNORED; 
              final int uidMode = uidState.evalMode(code, uidState.opModes.get(switchCode));
              ...

应用前后台状态更新

  1. 切换应用前后台时会走ActivityManagerService.noteUidProcessState()-> mAppOpsService.updateUidProcState(uid, state);
  2. 这里通过AppOpsService更新对应uid的应用进程前后台状态,
  3. 如从前台切到后台,前台状态是200,后台状态是700
  4. 这里是关于状态码的定义,主要关注下200-对应应用在前台

    /frameworks/base/core/java/android/app/AppOpsManager.java
     
      * Uid state: The UID is top foreground app. The lower the UID
      * state the more important the UID is for the user.
      * @hide
      */
     @TestApi
     @SystemApi
     public static final int UID_STATE_TOP = 200;

     /**
      * Uid state: The UID is running a foreground service of location type.
      * The lower the UID state the more important the UID is for the user.
      * @hide
      */
     @TestApi
     @SystemApi
     public static final int UID_STATE_FOREGROUND_SERVICE_LOCATION = 300;

     /**
      * Uid state: The UID is running a foreground service. The lower the UID
      * state the more important the UID is for the user.
      * @hide
      */
     @TestApi
     @SystemApi
     public static final int UID_STATE_FOREGROUND_SERVICE = 400;

     /**
      * The max, which is min priority, UID state for which any app op
      * would be considered as performed in the foreground.
      * @hide
      */
     public static final int UID_STATE_MAX_LAST_NON_RESTRICTED = UID_STATE_FOREGROUND_SERVICE;

     /**
      * Uid state: The UID is a foreground app. The lower the UID
      * state the more important the UID is for the user.
      * @hide
      */
     @TestApi
     @SystemApi
     public static final int UID_STATE_FOREGROUND = 500;

     /**
      * Uid state: The UID is a background app. The lower the UID
      * state the more important the UID is for the user.
      * @hide
      */
     @TestApi
     @SystemApi
     public static final int UID_STATE_BACKGROUND = 600;

     /**
      * Uid state: The UID is a cached app. The lower the UID
      * state the more important the UID is for the user.
      * @hide
      */
     @TestApi
     @SystemApi
     public static final int UID_STATE_CACHED = 700;

public void updateUidProcState(int uid, int procState) {
    synchronized (this) {
        final UidState uidState = getUidStateLocked(uid, true);
        int newState = PROCESS_STATE_TO_UID_STATE[procState];
        if (uidState != null && uidState.pendingState != newState) {
            final int oldPendingState = uidState.pendingState;
            //设置对应uid的进程uidState的pendingState,state为当前状态,pendingState为即将变成的状态
            uidState.pendingState = newState;
            //如果从后台切到前台,是200<700,则直接通过commitUidPendingStateLocked()更新进程前后台状态信息并且pendingStateCommitTime = 0;
            if (newState < uidState.state
                    || (newState <= UID_STATE_MAX_LAST_NON_RESTRICTED
                            && uidState.state > UID_STATE_MAX_LAST_NON_RESTRICTED)) {
                // We are moving to a more important state, or the new state may be in the
                // foreground and the old state is in the background, then always do it
                // immediately.
                commitUidPendingStateLocked(uidState);
            } else if (uidState.pendingStateCommitTime == 0) {
                // We are moving to a less important state for the first time,
                // delay the application for a bit.
                final long settleTime;
                //如果是从前台切到后台,则这里断点看了下设置settleTime为30000即30s,这里会延迟30s获取到的进程状态才会更新成后台,主要是通过pendingStateCommitTime来判断
                if (uidState.state <= UID_STATE_TOP) {
                    settleTime = mConstants.TOP_STATE_SETTLE_TIME;
                } else if (uidState.state <= UID_STATE_FOREGROUND_SERVICE) {
                    settleTime = mConstants.FG_SERVICE_STATE_SETTLE_TIME;
                } else {
                    settleTime = mConstants.BG_STATE_SETTLE_TIME;
                }
                uidState.pendingStateCommitTime = SystemClock.elapsedRealtime() + settleTime;
                BinderCallCacheAgent.removeCheckPackageBinderCache(uid);
            }
...


1. 如上后台切前台,则进入if (newState < uidState.state...
会调用commitUidPendingStateLocked();
private void commitUidPendingStateLocked(UidState uidState) {

    ...
    //即将变成的状态pendingState变成了当前的状态
    uidState.state = uidState.pendingState;
    uidState.pendingStateCommitTime = 0;
}
2. 此时如果是从前台切后台,则700<200不满足,是进入else if (uidState.pendingStateCommitTime == 0) {
此时设置uidState.pendingStateCommitTime为当前时间加30s延时


getOpsRawLocked()

这里继上面的获取位置的两个关键点展开讲,第一个://1.getOpsRawLocked()这里会去查询并更新前后台状态;看下这个方法

在这里插入图片描述

private @Nullable UidState getUidStateLocked(int uid, boolean edit) {

    UidState uidState = mUidStates.get(uid);
    if (uidState == null) {
        if (!edit) {
            return null;
        }
        uidState = new UidState(uid);
        mUidStates.put(uid, uidState);
    } else {
        //这里获取uidState状态前会判断是否更新state,如果当前时间是超过了之前设置的pendingStateCommitTime,则会更新,这里断点看是30s
        if (uidState.pendingStateCommitTime != 0) {
            if (uidState.pendingStateCommitTime < mLastRealtime) {
                commitUidPendingStateLocked(uidState);
            } else {
                mLastRealtime = SystemClock.elapsedRealtime();
                if (uidState.pendingStateCommitTime < mLastRealtime) {
                    commitUidPendingStateLocked(uidState);
                }
            }
        }
    }
    return uidState;
}


这里和上面的uidState.pendingStateCommitTime的值串起来了,前面从前台切到后台,设置的pendingStateCommitTime是30s,也就是说之前从前台切到后台,则需要30s之后状态才会更新为后台状态;如果是后台切到前台,则是马上调用commitUidPendingStateLocked()即时更新,看官方解释原因如下:

        if (newState < uidState.state
                || (newState <= UID_STATE_MAX_LAST_NON_RESTRICTED
                        && uidState.state > UID_STATE_MAX_LAST_NON_RESTRICTED)) {
            //后台->前台,认为是变化到一个更重要的状态,所以需要即时更新
            // We are moving to a more important state, or the new state may be in the
            // foreground and the old state is in the background, then always do it
            // immediately.
            commitUidPendingStateLocked(uidState);
        } else if (uidState.pendingStateCommitTime == 0) {
            //前台->后台,认为是变化到不那么重要的状态,所以可以延迟更新
            // We are moving to a less important state for the first time,
            // delay the application for a bit.
            final long settleTime;
            if (uidState.state <= UID_STATE_TOP) {
                settleTime = mConstants.TOP_STATE_SETTLE_TIME;

UidState.evalMode()

继上面的第二个关键点讲,evalMode()

    //2.UidState.evalMode()这里会得到结果MODE_ALLOWED或者MODE_IGNORED; 
    final int uidMode = uidState.evalMode(code, uidState.opModes.get(switchCode));

这个方法加断点可以看到,evalMode()第一个参数是对应的位置权限值,第二个参数是之前权限弹窗设置的值;

  1. 如果之前设置的是使用期间对应AppOpsManager.MODE_FOREGROUND,则需要判断state状态,这个state指的是当前的前后台状态,如果是前台200<=300,则返回MODE_ALLOWED,允许访问位置;如果是后台700<=300不成立,则返回MODE_IGNORED,不允许访问位置;
  2. 如果之前设置的是始终允许对应,则不需要检查state前台状态,直接返回允许或拒绝;

在这里插入图片描述
在这里插入图片描述

这里也做个引申解释下为什么下面这种方式能做到,原因是使用后台定位服务则当前应用进程状态是300,如上判断300<=300,所以返回MODE_ALLOWED,允许访问位置;

在这里插入图片描述

这里也做个引申解释下为什么下面这种方式能做到,原因是使用后台定位服务则当前应用进程状态是300,如上判断300<=300,所以返回MODE_ALLOWED,允许访问位置;

最后

如何学习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进行全面而深入的学习与实践,才能有效提高自身的专业素养和技术能力,从而在职场竞争中找准定位,实现从量到质的飞跃,成为行业内的佼佼者。

  • 16
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android音频驱动中,snd_soc_dai_ops是一个重要的结构体,用于描述数字音频接口(Digital Audio Interface,DAI)的操作,它包含了一组回调函数指针,用于实现数字音频接口的初始化、启动、停止、参数设置等操作。 该结构体的定义如下: ``` struct snd_soc_dai_ops { int (*probe)(struct snd_soc_dai *dai); void (*remove)(struct snd_soc_dai *dai); int (*startup)(struct snd_pcm_substream *substream, struct snd_soc_dai *dai); void (*shutdown)(struct snd_pcm_substream *substream, struct snd_soc_dai *dai); int (*set_fmt)(struct snd_soc_dai *dai, unsigned int fmt); int (*set_clkdiv)(struct snd_soc_dai *dai, int div_id, int div); int (*set_sysclk)(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir); int (*set_pll)(struct snd_soc_dai *dai, int pll_id, int source, unsigned int freq_in, unsigned int freq_out); int (*set_tdm_slot)(struct snd_soc_dai *dai, unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width); int (*set_tristate)(struct snd_soc_dai *dai, int tristate); int (*set_bias_level)(struct snd_soc_dai *dai, enum snd_soc_bias_level level); int (*hw_params)(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai); int (*hw_free)(struct snd_pcm_substream *substream, struct snd_soc_dai *dai); int (*digital_mute)(struct snd_soc_dai *dai, int mute); int (*set_dai_sysclk)(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir); int (*set_dai_pll)(struct snd_soc_dai *dai, int pll_id, int source, unsigned int freq_in, unsigned int freq_out); }; ``` 下面是各个回调函数的作用: - probe:初始化DAI接口,检查接口是否可用。 - remove:卸载DAI接口,释放资源。 - startup:启动DAI接口,打开物理接口并开始传输数据。 - shutdown:停止DAI接口,关闭物理接口并结束数据传输。 - set_fmt:设置DAI接口的数据格式,如采样位宽、通道数、采样率等。 - set_clkdiv:设置时钟分频,用于调整数据传输速率。 - set_sysclk:设置DAI接口的主时钟源和时钟频率。 - set_pll:设置DAI接口的PLL时钟源和时钟频率。 - set_tdm_slot:设置TDM(Time Division Multiplexing)时隙,用于多路数据复用传输。 - set_tristate:设置DAI接口的三态输出,用于控制外设的使能和失能。 - set_bias_level:设置DAI接口的偏置电平,用于控制电源管理。 - hw_params:设置硬件参数,如DMA缓冲区大小、DMA通道等。 - hw_free:释放硬件资源,如DMA缓冲区、DMA通道等。 - digital_mute:数字静音,用于在不影响数据传输的情况下静音。 - set_dai_sysclk:设置DAI接口的系统时钟源和时钟频率。 - set_dai_pll:设置DAI接口的PLL时钟源和时钟频率。 这些回调函数可以根据具体的硬件平实现,以实现数字音频接口的操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值