Android 各 API Level 权限变更和功能限制汇总

Android 各 API Level 权限变更和功能限制汇总

文章目录

  • 信息访问限制
    • 序列号
    • 不可重置的设备标识符
    • MAC 地址
    • 涉及位置信息的 telephony、WLAN、Bluetooth API
      • Android 8
        • WLAN
      • Android 9
        • Telephony
        • WLAN
      • Android Q
        • Telephony
        • WLAN
        • Bluetooth
    • 位置访问
      • Android 8.0
      • Android Q
    • 相机信息访问权限
    • 对屏幕内容的访问
    • 传感器的访问
    • 身体活动识别
    • 访问电话号码
    • 联系人亲密程度
    • 访问通话记录
    • 访问 /proc/net 文件系统
    • 访问剪贴板数据
    • 访问外部存储中的文件
    • 应用私有目录
  • 行为能力限制
    • 运行时权限
    • 前台 Service 启动条件
    • 后台 Service 限制
    • 绑定到服务
    • 广播限制
    • 允许 Activity 启动的条件
    • 提醒窗口
    • 全屏 Intent
    • WLAN 的限制
    • Http 与 Https
    • 音频管理器变更
    • getRecentTasks()
    • 非 SDK 接口限制
    • 应用主目录的执行权限
    • OAT 文件
    • APK 验证
    • NDK
    • 低电耗模式

参考:
https://developer.android.google.cn/preview/privacy/checklist
https://developer.android.google.cn/about/versions/pie/android-9.0-changes-all
https://developer.android.google.cn/about/versions/pie/android-9.0-changes-28
https://developer.android.google.cn/about/versions/oreo/android-8.0-changes
https://developer.android.google.cn/about/versions/oreo/background-location-limits
https://developer.android.google.cn/about/versions/nougat/android-7.0-changes
https://developer.android.google.cn/about/versions/marshmallow/android-6.0-changes
https://developer.android.google.cn/about/versions/android-5.0-changes

信息访问限制

序列号

如果以 Android 9 为目标平台,Build.SERIAL 始终设置为 "UNKNOWN" 以保护用户的隐私。

如果您的应用需要访问设备的硬件序列号,您应改为请求 READ_PHONE_STATE 权限,然后调用 getSerial()

不可重置的设备标识符

从 Android Q(API 级别 29)开始,应用必须具有 READ_PRIVILEGED_PHONE_STATE 特许权限才能访问设备的不可重置标识符(包含 IMEI序列号)。此权限为系统级别权限,第三方应用加入无效。
如果您的应用没有该权限,平台的响应会因目标 SDK 版本和应用类型而异:

  • 对于第三方普通应用:

  • 如果应用以 Android Q 为目标平台,则会发生 SecurityException

  • 如果应用以 Android 9(API 级别 28)或更低版本为目标平台,则相应方法会返回 null 或占位符数据(如果应用具有 READ_PHONE_STATE 权限)。否则,会发生 SecurityException

  • 对于设备所有者或配置文件所有者应用:
    即使您的应用以 Android Q 为目标平台,您也只需 READ_PHONE_STATE 权限即可访问不可重置的设备标识符。

  • 对于具有特殊运营商权限的应用:
    无需任何权限即可访问这些标识符。

如果您的应用将不可重置的设备标识符用于广告跟踪或用户分析目的,请为这些特定用例创建 Android 广告 ID。要了解详情,请参阅唯一标识符的最佳做法。

下面两种方法可以替代这些类型的标识符:

  1. 使用 com.google.android.gms.iid InstanceID API。getInstance(Context context).getID()将为您的应用实例返回一个唯一设备标识符。结果是一个应用实例作用域标识符,在存储有关应用的信息时,该标识符可用作键,如果用户重新安装应用,该标识符会重置。
  2. 使用 randomUUID() 之类的基本系统函数创建您自己的标识符,其作用域限定为应用的存储空间。

MAC 地址

从 Android 6.0(API 级别 23)版本开始,对于使用 WLAN API 和 Bluetooth API 的应用,Android 移除了对设备本地硬件标识符的编程访问权。WifiInfo.getMacAddress() 方法和 BluetoothAdapter.getAddress() 方法现在会返回常量值 02:00:00:00:00:00

要通过蓝牙和 WLAN 扫描访问附近外部设备的硬件标识符,您的应用必须拥有 ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION 权限。

默认情况下,搭载 Android Q 的设备会传输随机选择的 MAC 地址。如果您的应用处理企业用例,平台会提供几个新的 API:

  • 获取随机分配的 MAC 地址:设备所有者应用和配置文件所有者应用可以通过调用 getRandomizedMacAddress() 检索分配给特定网络的随机 MAC 地址。
// Added in API level 29
// 信息仅限于设备所有者、配置文件所有者和运营商应用程序,
// 且这些应用程序将仅获取其创建的配置的地址,
// 其他调用者将收到默认的“02:00:00:00:00:00”MAC地址。
public MacAddress getRandomizedMacAddress ()
  • 获取实际的出厂 MAC 地址:设备所有者应用可以通过调用 getWifiMacAddress() 检索设备的实际硬件 MAC 地址。此方法对于跟踪设备队列非常有用。
// Added in API level 24
// Throws SecurityException if admin is not a device owner
public String getWifiMacAddress (ComponentName admin)

涉及位置信息的 telephony、WLAN、Bluetooth API

Android 8

WLAN

由于 Android 8.0 会对后台应用检索用户当前位置的频率进行限制,startScan() 方法对后台应用执行完整扫描的频率仅为每小时数次。如果不久之后后台应用再次调用此方法, WifiManager 类将提供上次扫描所缓存的结果。

Android 9

Telephony

如果用户在运行 Android 9 的设备上停用设备定位,则以下函数不提供结果:

  • getAllCellInfo()
  • listen()
  • getCellLocation()
  • getNeighboringCellInfo()

WLAN

从 Android 9 开始,有更严格的 Wi-Fi 扫描限制:
A successful call to WifiManager.startScan() requires all of the following conditions to be met:

  • Your app has the ACCESS_FINE_LOCATION or ACCESS_COARSE_LOCATION permission.
  • Your app has the CHANGE_WIFI_STATE permission.
  • Location services are enabled on the device (under Settings > Location).

To successfully call WifiManager.getScanResults() ensure all of the following conditions are met:

  • Your app has the ACCESS_FINE_LOCATION or ACCESS_COARSE_LOCATION permission.
  • Your app has the ACCESS_WIFI_STATE permission.
  • Location services are enabled on the device (under Settings > Location).

同样,如果要使用 getConnectionInfo() 函数返回的描述当前 Wi-Fi 连接的 WifiInfo对象来检索 SSID 和 BSSID 值,就也需要具备与 WifiManager.getScanResults() 相同的3个条件。

If the calling app doesn’t meet all of these requirements, the call fails with a SecurityException.

在 Android 9 中,下列事件和广播不接收用户位置或个人可识别数据方面的信息:

  • WifiManager 中的 getScanResults()getConnectionInfo() 函数。
  • WifiP2pManager 中的 discoverServices()addServiceRequest() 函数。
  • NETWORK_STATE_CHANGED_ACTION 广播。

Wi-Fi 的 NETWORK_STATE_CHANGED_ACTION系统广播不再包含 SSID(之前为 EXTRA_SSID)、BSSID(之前为 EXTRA_BSSID)或连接信息(之前为 EXTRA_NETWORK_INFO)。 如果应用需要此信息,请改为调用 getConnectionInfo()

Android Q

以 Android Q 为目标平台的应用,除非具有 ACCESS_FINE_LOCATION 权限,否则应用在 Android Q 上运行时无法使用 WLAN API、Wi-Fi Aware API 或 Bluetooth API 中的多种方法。下面列出了受影响的方法。

注意:如果您的应用在 Android Q 上运行但以 Android 9(API 级别 28)或更低版本为目标平台,则只要您的应用具有 ACCESS_COARSE_LOCATIONACCESS_FINE_LOCATION 权限,您就可以使用受影响的 API。

Telephony

  • TelephonyManager
    • getCellLocation()
    • getAllCellInfo()
    • requestNetworkScan()
    • requestCellInfoUpdate()
    • getAvailableNetworks()
    • getServiceStateForSubscriber
    • getServiceState()
  • TelephonyScanManager
    • requestNetworkScan()
  • TelephonyScanManager.NetworkScanCallback
    • onResults()
  • PhoneStateListener
    • onCellLocationChanged()
    • onCellInfoChanged()
    • onServiceStateChanged()

WLAN

  • WifiManager
    • startScan()
    • getScanResults()
    • getConnectionInfo()
    • getConfiguredNetworks()
  • WifiAwareManager
  • WifiP2pManager
  • WifiRttManager

Bluetooth

  • BluetoothAdapter
    • startDiscovery()
    • startLeScan()
    • LeScanCallback()

位置访问

Android 8.0

为降低功耗,无论应用的目标 SDK 版本为何,Android 8.0 都会对后台应用检索用户当前位置的频率进行限制。
我们只允许后台应用每小时接收几次位置更新。我们将在整个预览版阶段继续根据系统影响和开发者的反馈优化位置更新间隔。
如果应用在运行 Android 8.0 的设备上处于前台,其位置更新行为将与 Android 7.1.1(API 级别 25)及更低版本上相同。
应用满足以下任一条件即视为前台应用:

  • 它具有可见的 Activity,无论 Activity 处于启动还是暂停状态。
  • 它具有前台服务。
  • 另一个前台应用通过绑定到应用的其中一个服务或使用应用的其中一个内容提供程序与应用相连。

如果以上所有条件均不满足,应用即视为后台应用。

考虑在您的应用接收位置更新不频繁的情况下其后台运行用例是否根本无法成功。如果属于这种情况,您可以通过执行下列操作之一提高位置更新的检索频率:

  • 将您的应用转至前台。
  • 使用应用中的某个前台服务。激活此服务时,您的应用必须在通知区显示一个持续性的通知。
  • 使用 Geofencing API 的元素(例如 GeofencingApi 接口),这些元素针对最大限度减少耗电进行了专门优化。
  • 使用被动位置侦听器,它可以在后台应用加快位置请求频率时提高位置更新的接收频率。

Android Q

从 Android Q 开始,当应用请求位置信息访问权限时,用户会看到如下图所示的对话框。此对话框可让用户将位置信息访问权限授予到两个不同的范围:使用应用时(仅限前台)或始终(前台和后台)。

Android 各 API Level 权限变更和功能限制汇总_第1张图片

为了让用户更好地控制应用对位置信息的访问权限,Android Q 引入了新的位置权限 ACCESS_BACKGROUND_LOCATION。与现有的 ACCESS_FINE_LOCATIONACCESS_COARSE_LOCATION 权限不同,新权限仅会影响应用在后台运行时对位置信息的访问权。除非应用的某个 Activity 可见或应用正在运行前台服务,否则应用将被视为在后台运行。

  • 如果您的应用以 Android Q 为目标平台,并且在后台运行时需要访问设备的位置信息,则您必须在应用的清单文件中声明新权限:ACCESS_BACKGROUND_LOCATION

  • 如果您的应用在 Android Q 上运行但目标平台是 Android 9(API 级别 28)或更低版本,则以下系统行为会发生:

  • 如果您的应用为 ACCESS_FINE_LOCATIONACCESS_COARSE_LOCATION 声明了 元素,则系统会在安装期间自动为 ACCESS_BACKGROUND_LOCATION 添加 元素。

  • 如果您的应用请求了 ACCESS_FINE_LOCATIONACCESS_COARSE_LOCATION,系统会自动将 ACCESS_BACKGROUND_LOCATION 添加到请求中。

使用场景举例

  • 如果用户只允许您的应用在前台访问位置信息,那么当用户按下设备上的主屏幕按钮或关闭设备的显示屏时,为了延续用户发起的操作,例如导航或智能家居操作,在这种情况下,请在应用清单中声明具有前台服务类型"location" 的前台服务:
<service
		 android:name="MyNavigationService"
		 android:foregroundServiceType="location" ... >
	...
service>

在启动前台服务之前,请确保您的应用仍可访问设备位置信息:

val permissionAccessCoarseLocationApproved = ActivityCompat
	.checkSelfPermission(this, permission.ACCESS_COARSE_LOCATION) ==
	PackageManager.PERMISSION_GRANTED

	if (permissionAccessCoarseLocationApproved) {
	// App has permission to access location in the foreground. Start your
	// foreground service that has a foreground service type of "location".
} else {
	// Make a request for foreground-only location access.
	ActivityCompat.requestPermissions(this,
        arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION), 
		your-permission-request-code)
}
  • 如果您的应用需要定期查看设备位置信息,例如地理围栏或位置信息分享。在这种情况下,应用应向用户说明他们需要允许该应用始终都能访问设备位置信息,以确保正常运行。只要用户授权该应用始终访问设备位置信息,您就可以在后台直接访问位置而无需任何更改。

但用户也可以选择使应用只能在前台访问位置信息,或者完全撤消使用权。因此,每当您的应用启动服务时,都要查看用户是否仍允许该应用在后台访问位置信息。以下代码段中显示了此权限检查逻辑的示例:

    val permissionAccessCoarseLocationApproved = ActivityCompat
        .checkSelfPermission(this, permission.ACCESS_COARSE_LOCATION) ==
        PackageManager.PERMISSION_GRANTED

    if (permissionAccessCoarseLocationApproved) {
       val backgroundLocationPermissionApproved = ActivityCompat
           .checkSelfPermission(this, permission.ACCESS_BACKGROUND_LOCATION) ==
           PackageManager.PERMISSION_GRANTED

       if (backgroundLocationPermissionApproved) {
           // App can access location both in the foreground and in the background.
           // Start your service that doesn't have a foreground service type
           // defined.
       } else {
           // App can only access location in the foreground. Display a dialog
           // warning the user that your app must have all-the-time access to
           // location in order to function properly. Then, request background
           // location.
           ActivityCompat.requestPermissions(this,
               arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION),
               your-permission-request-code
           )
       }
    } else {
       // App doesn't have access to the device's location at all. Make full request
       // for permission.
       ActivityCompat.requestPermissions(this,
           arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION,
                   Manifest.permission.ACCESS_BACKGROUND_LOCATION),
           your-permission-request-code
       )
    }

相机信息访问权限

Android Q 更改了 getCameraCharacteristics() 方法默认返回的信息的广度。具体而言,您的应用必须具有 CAMERA 权限才能访问此方法的返回值中可能包含的设备特定元数据。

如果您的应用没有 CAMERA 权限,不管目标平台级别是多少,它都将无法访问以下字段:

  • ANDROID_LENS_POSE_ROTATION
  • ANDROID_LENS_POSE_TRANSLATION
  • ANDROID_LENS_INTRINSIC_CALIBRATION
  • ANDROID_LENS_RADIAL_DISTORTION
  • ANDROID_LENS_POSE_REFERENCE
  • ANDROID_LENS_DISTORTION
  • ANDROID_LENS_INFO_HYPERFOCAL_DISTANCE
  • ANDROID_LENS_INFO_MINIMUM_FOCUS_DISTANCE
  • ANDROID_SENSOR_REFERENCE_ILLUMINANT1
  • ANDROID_SENSOR_REFERENCE_ILLUMINANT2
  • ANDROID_SENSOR_CALIBRATION_TRANSFORM1
  • ANDROID_SENSOR_CALIBRATION_TRANSFORM2
  • ANDROID_SENSOR_COLOR_TRANSFORM1
  • ANDROID_SENSOR_COLOR_TRANSFORM2
  • ANDROID_SENSOR_FORWARD_MATRIX1
  • ANDROID_SENSOR_FORWARD_MATRIX2

对屏幕内容的访问

为了保护用户的屏幕内容,Android Q 更改了 READ_FRAME_BUFFERCAPTURE_VIDEO_OUTPUTCAPTURE_SECURE_VIDEO_OUTPUT 权限的作用域,使其只能通过签名访问,从而禁止以静默方式访问设备的屏幕内容,即使这些应用以 Android 9(API 级别 28)或更低版本为目标平台也是如此。

需要访问设备屏幕内容的应用应使用 MediaProjection API,此 API 会显示提示,要求用户同意声明。

传感器的访问

Android 9 限制后台应用访问用户输入和传感器数据的能力。 如果您的应用在运行 Android 9 设备的后台运行,系统将对您的应用采取以下限制:

  • 您的应用不能访问麦克风或摄像头。
  • 使用连续报告模式的传感器(例如加速度计和陀螺仪)不会接收事件。
  • 使用变化或一次性报告模式的传感器不会接收事件。

如果您的应用需要在运行 Android 9 的设备上检测传感器事件,请使用前台服务。

身体活动识别

Android Q 针对需要检测用户步数或对用户的身体活动(例如步行、骑车或坐车)进行分类的应用引入了一个新的 ACTIVITY_RECOGNITION 运行时权限。此项权限旨在让用户了解设备传感器数据在“设置”中的使用方式。

如果您的应用依赖于设备上其他内置传感器的数据,例如加速度计和陀螺仪,则无需在应用中声明此新权限。

访问电话号码

在未首先获得 READ_CALL_LOG 权限的情况下,除了应用的用例需要的其他权限之外,运行于 Android 9 上的应用无法读取电话号码或手机状态。

与来电和去电关联的电话号码可在手机状态广播(比如来电和去电的手机状态广播)中看到,并可通过 PhoneStateListener 类访问。 但是,如果没有 READ_CALL_LOG 权限,则 PHONE_STATE_CHANGED 广播和 PhoneStateListener 提供的电话号码字段为空。

要从手机状态中读取电话号码,请根据您的用例更新应用以请求必要的权限:

  • 要通过 PHONE_STATE Intent 操作读取电话号码,同时需要 READ_CALL_LOG 权限和 READ_PHONE_STATE 权限。
  • 要从 onCallStateChanged() 中读取电话号码,只需要 READ_CALL_LOG 权限。 不需要 READ_PHONE_STATE 权限。

联系人亲密程度

从 Android Q 开始,平台不再跟踪联系人亲密程度信息。因此,如果您的应用对用户的联系人进行搜索,则系统将不再按互动频率对搜索结果排序。

访问通话记录

Android 9 引入 CALL_LOG 权限组并将 READ_CALL_LOGWRITE_CALL_LOGPROCESS_OUTGOING_CALLS 权限移入该组。 在之前的 Android 版本中,这些权限位于 PHONE 权限组。

对于需要访问通话敏感信息(如读取通话记录和识别电话号码)的应用,该 CALL_LOG 权限组为用户提供了更好的控制和可见性。

如果您的应用需要访问通话记录或者需要处理去电,则您必须向 CALL_LOG 权限组明确请求这些权限。 否则会发生 SecurityException

访问 /proc/net 文件系统

  • 从 Android 9 开始,不再允许应用直接读取 /proc/net/xt_qtaguid 文件夹中的文件。 这样做是为了确保与某些根本不提供这些文件的设备保持一致。

依赖这些文件的公开 API [TrafficStats](https://developer.android.google.cn/reference/android/net/TrafficStats.html)[NetworkStatsManager](https://developer.android.google.cn/reference/android/app/usage/NetworkStatsManager.html) 继续按照预期方式运行。 然而,不受支持的 cutils 函数(例如 qtaguid_tagSocket())在不同设备上可能不会按照预期方式运行 — 甚至根本不运行。

  • Android Q 撤消了 /proc/net 访问权限,其中包含有关设备网络状态的信息。需要访问此信息的应用(如 VPN)应引用 NetworkStatsManagerConnectivityManager 类。

访问剪贴板数据

除非您的应用是默认输入法 (IME) 或是目前处于焦点的应用,否则应用无法访问剪贴板数据。

访问外部存储中的文件

如果应用以 Android Q 为目标平台,则在访问外部存储设备中的文件时会进入过滤视图。应用可以使用 Context.getExternalFilesDir() 将专用于自己的文件存储在特定于自己的目录中。

只有在满足以下两个条件时,您的应用才能访问其他应用创建的文件:

  1. 您的应用已获得 READ_EXTERNAL_STORAGE 权限。

  2. 这些文件位于以下其中一个明确定义的媒体集合中:

    • 照片:存储在 MediaStore.Images 中。
    • 视频:存储在 MediaStore.Video 中。
    • 音乐文件:存储在 MediaStore.Audio 中。

为了访问另一应用创建的任何其他文件(包括“downloads”目录下的文件),您的应用必须使用存储访问框架,用户可以通过该框架选择特定文件。

注意:访问外部存储设备中的文件时会进入过滤视图的应用不具有对 /sdcard/DCIM/IMG1024.JPG 等路径的直接内核访问权限。要访问此类文件,应用必须使用 MediaStore,并调用 openFile() 等方法。

过滤视图还施加了以下媒体相关数据限制:

  • 除非您的应用已获得 ACCESS_MEDIA_LOCATION 权限,否则图片文件中的 Exif 元数据会被遮盖。如需了解详情,请参阅关于如何访问照片中的位置信息的部分。
  • 媒体存储器中每个文件的 DATA 列都会被遮盖。
  • MediaStore.Files 表格会自行过滤,仅显示照片、视频和音频文件。例如,该表格不会再显示 PDF 文件。

下表总结了以 Android Q 为目标平台在访问外部存储设备中的文件时会进入过滤视图的应用访问文件的方式:

文件位置 所需权限 访问方法 (*) 卸载应用时是否移除文件?
特定于应用的目录 getExternalFilesDir()
媒体集合(照片、视频、音频) READ_EXTERNAL_STORAGE_(仅当_访问其他应用的文件时) MediaStore
下载内容(文档和电子书籍) 存储访问框架(加载系统的文件选择器)

不要使用已弃用的 DATA 列加载媒体文件。请改为从 ContentResolver 调用以下其中一种方法:

  • 对于单个媒体文件的缩略图,请使用 loadThumbnail(),并传递要加载的缩略图的大小。
  • 对于单个媒体文件,请使用 openFileDescriptor()
  • 对于媒体文件的集合,请使用 query()

注意:您可以通过调用 MediaStore.setIncludePending() 查看待处理媒体文件集。

以下代码段展示了如何访问媒体文件:

// Load thumbnail of a specific media item.
    val mediaThumbnail = resolver.loadThumbnail(item, Size(640, 480), null)

    // Open a specific media item.
    resolver.openFileDescriptor(item, mode).use { pfd ->
        // ...
    }

    // Find all videos on a given storage device, including pending files.
    val collection = MediaStore.Video.Media.getContentUri(volumeName)
    val collectionWithPending = MediaStore.setIncludePending(collection)
    resolver.query(collectionWithPending, null, null, null).use { c ->
        // ...
    }

应用私有目录

面向 Android 7.0 或更高版本的应用私有目录被限制访问 (0700)。此设置可防止私有文件的元数据泄漏,如它们的大小或存在性。此权限更改有多重副作用:

  • 私有文件的文件权限不应再由所有者放宽,为使用 MODE_WORLD_READABLE 和/或 MODE_WORLD_WRITEABLE 而进行的此类尝试将触发 SecurityException。

    :迄今为止,这种限制尚不能完全执行。应用仍可能使用原生 API 或 File API 来修改它们的私有目录权限。但是,我们强烈反对放宽私有目录的权限。

  • 传递软件包网域外的 file:// URI 可能给接收器留下无法访问的路径。因此,尝试传递 file:// URI 会触发 FileUriExposedException。分享私有文件内容的推荐方法是使用 FileProvider。

  • DownloadManager 不再按文件名分享私人存储的文件。旧版应用在访问 COLUMN_LOCAL_FILENAME 时可能出现无法访问的路径。面向 Android 7.0 或更高版本的应用在尝试访问 COLUMN_LOCAL_FILENAME 时会触发 SecurityException。通过使用 DownloadManager.Request.setDestinationInExternalFilesDir() 或 DownloadManager.Request.setDestinationInExternalPublicDir() 将下载位置设置为公共位置的旧版应用仍可以访问 COLUMN_LOCAL_FILENAME 中的路径,但是我们强烈反对使用这种方法。对于由 DownloadManager 公开的文件,首选的访问方式是使用 ContentResolver.openFileDescriptor()。

行为能力限制

运行时权限

对于以 Android 6.0(API 级别 23)或更高版本为目标平台的应用,请务必在运行时检查和请求权限。要确定您的应用是否已被授予权限,请调用新增的 checkSelfPermission() 方法。要请求权限,请调用新增的 requestPermissions() 方法。

如需了解有关在您的应用中支持新权限模式的详情,请参阅使用系统权限。如需了解有关如何评估新模式对应用的影响的提示,请参阅权限最佳做法。

前台 Service 启动条件

以 Android 9 或更高版本为目标平台的应用,使用前台服务时必须请求 FOREGROUND_SERVICE 权限。 这是普通权限,因此,系统会自动为请求权限的应用授予此权限。

后台 Service 限制

以 Android 8.0(API 级别 26)或更高版本为目标平台的应用,会受到如下限制:

  • 应用处于前台时,可以自由创建和运行前台与后台 Service。应用进入后台时,上述行为会受到限制。如果满足以下任意条件,应用将被视为处于前台:
  • 具有可见 Activity(不管该 Activity 已启动还是已暂停)。
  • 具有前台 Service。
  • 另一个前台应用已关联到该应用(不管是通过绑定到其中一个 Service,还是通过使用其中一个内容提供程序)。 例如,如果另一个应用绑定到该应用的 Service,那么该应用处于前台:
    • IME
    • 壁纸 Service
    • 通知侦听器
    • 语音或文本 Service

如果以上条件均不满足,应用将被视为处于后台。

  • 进入后台时,在一个持续数分钟(模拟器测试在1分钟左右后被kill掉)的时间窗内,应用仍可以创建和使用 Service。
    在该时间窗结束后,应用将被视为处于_空闲_状态。 此时,系统将停止应用的后台 Service,就像应用已经调用 Service 的 Service.stopSelf() 方法一样。此时不允许后台应用创建后台 Service,而应该按如下步骤启动前台服务:

    • Android 8.0 中首先调用一种全新的方法,即 startForegroundService(),以在前台启动新 Service。
    • 在系统创建 Service 后,应用有五秒的时间来调用该 Service 的 startForeground() 方法以显示新 Service 的用户可见通知。
    • 如果应用在此时间限制内_未_调用 startForeground(),则系统将停止此 Service 并声明此应用为 ANR。
  • 在很多情况下,您的应用都可以使用 JobScheduler 作业替换后台 Service。
    请注意: IntentService 是一项 Service,因此其遵守针对后台 Service 的新限制。 因此,许多依赖IntentService 的应用在适配 Android 8.0 或更高版本时无法正常工作。 出于这一原因,Android 支持库 26.0.0 引入了一个新的JobIntentService类,该类提供与 IntentService 相同的功能,但在 Android 8.0 或更高版本上运行时使用作业而非 Service。

绑定到服务

Android 5.0 开始 Context.bindService() 方法现在需要显式 Intent,如果提供隐式 intent,将引发异常。为确保应用的安全性,请使用显式 intent 启动或绑定 Service,且不要为服务声明 intent 过滤器。

广播限制

Android 7.0 应用了以下优化措施:

  • 面向 Android 7.0 开发的应用不会收到 CONNECTIVITY_ACTION 广播,即使它们已有清单条目来请求接受这些事件的通知。在前台运行的应用如果使用 BroadcastReceiver 请求接收通知,则仍可以在主线程中侦听 CONNECTIVITY_CHANGE
  • 应用无法发送或接收 ACTION_NEW_PICTURE 或 ACTION_NEW_VIDEO 广播。此项优化会影响所有应用,而不仅仅是面向 Android 7.0 的应用。

以 Android 8.0(API 级别 26)或更高版本为目标平台的应用,会受到如下限制:

  • 应用无法继续在其清单AndroidManifest.xml中为隐式广播注册广播接收器。 隐式广播是一种不专门针对该应用的广播。 例如,ACTION_PACKAGE_REPLACED 就是一种隐式广播,因为该广播将被发送给所有已注册侦听器,让后者知道设备上的某些软件包已被替换。 不过,ACTION_MY_PACKAGE_REPLACED 不是隐式广播,因为不管已为该广播注册侦听器的其他应用有多少,它都会只被发送给软件包已被替换的应用。
  • 应用可以继续在它们的清单中注册显式广播。
  • 应用可以在运行时使用 Context.registerReceiver() 为任意广播(不管是隐式还是显式)注册接收器。
  • 需要签名权限的广播不受此限制所限,因为这些广播只会发送到使用相同证书签名的应用,而不是发送到设备上的所有应用。

**请注意:**很多隐式广播当前已不受此限制所限。 应用可以继续在其清单中为这些广播注册接收器,不管应用适配哪个 API 级别。 有关已豁免广播的列表,请参阅隐式广播例外。

允许 Activity 启动的条件

在 Android Q 上运行的应用只有在满足以下一个或多个条件时才能启动 Activity:

  • 该应用具有可见窗口,例如在前台运行的 Activity。

  • 该应用在前台任务的返回栈中具有一项 Activity。

  • 该应用具有最近启动的 Activity。

  • 该应用对最近的一项 Activity 调用了 finish()。这仅适用于在调用 finish() 时,应用在前台中具有一项 Activity,或在前台任务的返回栈中具有一项 Activity 的情况。

  • 该应用的一项服务被系统绑定。该条件仅适用于以下服务(可能需要启动界面):AccessibilityServiceAutofillServiceCallRedirectionServiceHostApduServiceInCallServiceTileServiceVoiceInteractionService 以及 VrListenerService

  • 该应用的某一项服务被其他可见应用绑定。请注意,绑定到该服务的应用必须在后台对该应用保持可见,才能成功启动 Activity。

  • 该应用会从系统收到通知 PendingIntent。如果存在针对服务和广播接收器的待定 intent,则该应用可以在待定 intent 发送后启动 Activity 几秒钟时间。

  • 该应用会收到从其他可见应用发送的 PendingIntent

  • 该应用会收到系统广播,其中要求应用启动界面。示例包括 ACTION_NEW_OUTGOING_CALLSECRET_CODE_ACTION。该应用可以在广播发送后启动 Activity 几秒钟时间。

  • 该应用已通过 CompanionDeviceManager API 与配套硬件设备相关联。借助此 API,该应用可以启动 Activity 以响应用户在配对设备上执行的操作。

  • 该应用是在设备所有者模式下运行的设备政策控制器。示例用例包括完全托管的企业设备,以及数字标识牌和自助服务终端等专属设备。

  • 该应用已获得用户授予的 SYSTEM_ALERT_WINDOW 权限。

如果您的应用在 Android Q 的最新测试版上运行并尝试从后台启动 Activity,则平台会向 logcat 发送警告消息并显示以下警告提示消息:

Background activity start from package-name blocked.

几乎在所有情况下,后台应用都应创建通知以便向用户提供信息,而不是直接启动 Activity。

在特定情况下,您的应用可能需要立即引起用户的注意,例如闹钟正在响铃或有来电时。此时您可以创建高优先级通知,请务必添加描述性标题和消息。您还可以选择提供全屏 intent。
以下代码段中显示了示例通知:

    val fullScreenIntent = Intent(this, CallActivity::class.java)
    val fullScreenPendingIntent = PendingIntent.getActivity(this, 0,
        fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT)

    val notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID)
        .setSmallIcon(R.drawable.notification_icon)
        .setContentTitle("Incoming call")
        .setContentText("(919) 555-1234")
        .setPriority(NotificationCompat.PRIORITY_HIGH)
        .setCategory(NotificationCompat.CATEGORY_CALL)

        // Use a full-screen intent only for the highest-priority alerts where you
        // have an associated activity that you would like to launch after the user
        // interacts with the notification. Also, if your app targets Android Q, you
        // need to request the USE_FULL_SCREEN_INTENT permission in order for the
        // platform to invoke this notification.
        .setFullScreenIntent(fullScreenPendingIntent, true)

    val incomingCallNotification = notificationBuilder.build()

如果您的通知正在进行(例如来电),请将该通知与前台服务相关联。以下代码段展示了如何显示与前台服务关联的通知:

    // Provide a unique integer for the "notificationId" of each notification.
    startForeground(notificationId, notification)

提醒窗口

在 Android 8.0 上,如果应用使用 SYSTEM_ALERT_WINDOW 权限并且尝试使用以下窗口类型之一来在其他应用和系统窗口上方显示提醒窗口:

  • TYPE_PHONE
  • TYPE_PRIORITY_PHONE
  • TYPE_SYSTEM_ALERT
  • TYPE_SYSTEM_OVERLAY
  • TYPE_SYSTEM_ERROR

那么,这些窗口将始终显示在使用 TYPE_APPLICATION_OVERLAY 窗口类型的窗口下方。

如果应用针对的是 Android 8.0,使用 SYSTEM_ALERT_WINDOW 权限的应用无法再使用上述窗口类型来在其他应用和系统窗口上方显示提醒窗口,相反,应用必须使用名为 TYPE_APPLICATION_OVERLAY 的新窗口类型。

使用 TYPE_APPLICATION_OVERLAY 窗口类型显示应用的提醒窗口时,请记住新窗口类型的以下特性:

  • 应用的提醒窗口始终显示在状态栏和输入法等关键系统窗口的下面。
  • 系统可以移动使用 TYPE_APPLICATION_OVERLAY 窗口类型的窗口或调整其大小,以改善屏幕显示效果。
  • 通过打开通知栏,用户可以访问设置来阻止应用显示使用 TYPE_APPLICATION_OVERLAY 窗口类型显示的提醒窗口。

全屏 Intent

如果应用以 Android Q 或更高版本为目标平台并使用涉及全屏 intent 的通知,则必须在清单文件中请求 USE_FULL_SCREEN_INTENT 权限。这是普通权限,因此,系统会自动为请求权限的应用授予此权限。

如果以 Android Q 或更高版本为目标平台的应用试图创建使用全屏 intent 的通知,而不请求 USE_FULL_SCREEN_INTENT 权限,则系统会忽略此全屏 intent 并输出以下日志消息:

Package [pkg]: Use of fullScreenIntent requires the USE_FULL_SCREEN_INTENT permission

WLAN 的限制

  • 对启用和停用 WLAN 的限制
    以 Android Q 为目标平台的应用无法启用或停用 WLAN。WifiManager.setWifiEnabled() 方法始终返回 false
    如果需要,请使用设置面板提示用户启用和停用 WLAN。

  • WLAN 网络配置限制
    为了保护用户隐私,现在只有系统应用和设备政策控制器 (DPC) 支持手动配置系统的 WLAN 网络列表。给定 DPC 可以是设备所有者或配置文件所有者。
    如果您的应用不属于以上其中一个类别,而且以 Android Q 为目标平台,则下列方法将不再返回有用数据:

  • getConfiguredNetworks() 方法始终返回空列表。

  • 每个返回整数值的网络操作方法(addNetwork()updateNetwork())始终返回 -1。

  • 每个返回布尔值的网络操作(removeNetwork()reassociate()enableNetwork()disableNetwork()reconnect()disconnect())始终返回 false

  • 注意:如果运营商应用调用 getConfiguredNetworks(),系统便会返回仅包含运营商配置的网络的列表。

  • 如果您的应用需要连接到 WLAN 网络,请使用以下备用方法:

  • 要触发与 WLAN 网络的即时本地连接,请在标准 NetworkRequest 对象中使用 WifiNetworkSpecifier

  • 要添加 WLAN 网络以便考虑为用户提供互联网访问权限,请使用 WifiNetworkSuggestion 对象。您可以通过分别调用 addNetworkSuggestions()removeNetworkSuggestions() 添加和移除在自动连接网络选择对话框中显示的网络。这些方法不需要任何位置权限。

Http 与 Https

  • Android 6.0 版移除了对 Apache HTTP 客户端的支持。如果您的应用使用该客户端,并以 Android 2.3(API 级别 9)或更高版本为目标平台,请改用 [HttpURLConnection](https://developer.android.google.cn/reference/java/net/HttpURLConnection.html) 类。此 API 效率更高,因为它可以通过透明压缩和响应缓存减少网络使用,并可最大限度降低耗电量。要继续使用 Apache HTTP API,您必须先在 build.gradle 文件中声明以下编译时依赖项:
    android {
        useLibrary 'org.apache.http.legacy'
    }
    
  • Android 8.0 平台不再支持 SSLv3。
  • Android 8.0 平台在与未正确实现 TLS 协议版本协商的服务器建立 HTTPS 连接时,[HttpsURLConnection](https://developer.android.google.cn/reference/javax/net/ssl/HttpsURLConnection.html) 不再尝试回退到之前的 TLS 协议版本并重试的权宜方法。

音频管理器变更

Android 6.0 版不再支持通过 AudioManager 类直接设置音量或将特定音频流静音。setStreamSolo() 方法已弃用,您应该改为调用 requestAudioFocus() 方法。类似地,setStreamMute() 方法也已弃用,请改为调用 adjustStreamVolume() 方法并传入方向值 ADJUST_MUTE 或 ADJUST_UNMUTE。

getRecentTasks()

Android 5.0 中引入新的“并发文档和 Activity 任务”功能后(请参阅下文最近使用的应用屏幕中的并发文档和 Activity),为提升用户隐私的安全性,现已弃用 ActivityManager.getRecentTasks() 方法。对于向后兼容性,此方法仍会返回它的一小部分数据,包括调用应用自己的任务和可能的一些其他非敏感任务(如首页)。

如果您的应用使用此方法检索它自己的任务,则改用 getAppTasks() 检索该信息。

非 SDK 接口限制

为了帮助确保应用稳定性和兼容性,Android 平台开始限制您的应用可在 Android 9(API 级别 28)中使用哪些非 SDK 接口。Android Q 包含更新后的受限非 SDK 接口列表(基于与 Android 开发者之间的协作以及最新的内部测试)。我们的目标是在限制使用非 SDK 接口之前确保有可用的公开替代方案。

一般而言,公共 SDK 接口是在 Android 框架软件包索引中记录的那些接口。非 SDK 接口的处理是 API 抽象出来的实现细节,因此这些接口可能会在不另行通知的情况下随时发生更改。

为了避免发生崩溃和意外行为,应用应仅使用 SDK 中经过正式记录的类。这也意味着当您的应用通过反射等机制与类互动时,不应访问 SDK 中未列出的方法或字段。

如果您不打算以 Android Q 为目标平台,那么其中一些变更可能不会立即对您产生影响。虽然您目前可以使用灰名单中的一些非 SDK 接口(取决于您应用的目标 API 级别),但如果您使用任何非 SDK 方法或字段,则应用无法运行的风险终归较高。

如果您不确定自己的应用是否使用了非 SDK 接口,则可以测试该应用进行确认。如果您的应用依赖于非 SDK 接口,则应该开始计划迁移到 SDK 替代方案。不过,我们知道某些应用具有使用非 SDK 接口的有效用例。如果您无法为应用中的某项功能找到使用非 SDK 接口的替代方案,则应该请求新的公共 API。

要了解详情,请参阅非 SDK 接口在 Android Q 中的受限情况出现变化以及对非 SDK 接口的限制。

应用主目录的执行权限

以 Android Q 为目标平台的不受信任的应用无法再针对应用主目录中的文件调用 exec()。这种从可写应用的主目录执行文件的行为违反了 W^X。应用应该仅加载嵌入到应用的 APK 文件中的二进制代码。

此外,以 Android Q 为目标平台的应用无法针对已执行 dlopen() 的文件中的可执行代码进行内存中修改。这包括含有文本重定位的所有共享对象 (.so) 文件。

OAT 文件

以 Android Q 为目标平台时,Android 运行时 (ART) 不再从应用进程调用 dex2oat。这项变更意味着 ART 将仅接受系统生成的 OAT 文件。

APK 验证

从 Android 6.0 开始,执行的 APK 验证更为严格。如果在清单中声明的文件在 APK 中并不存在,该 APK 将被视为已损坏。移除任何内容后必须重新签署 APK。

NDK

  • 非公开 NDK 库
    从 Android 7.0 开始,系统将阻止应用动态链接非公开 NDK 库,这种库可能会导致您的应用崩溃。如果您的应用使用原生代码,则只能使用公开 NDK API。
    您的应用可通过以下几种方式尝试访问私有平台 API:
    • 您的应用直接访问私有平台库。您应更新您的应用以添加该应用的库副本,或使用公开 NDK API。
    • 您的应用使用一个可访问私有平台库的第三方库。即使您确定您的应用不会直接访问私有库,您仍应针对此情景测试您的应用。利用 Android 7.0DK 中的 readelf 工具,您可以通过运行以下命令生成给定 .so 文件的所有动态链接的共享库列表:
    aarch64-linux-android-readelf -dW libMyLibrary.so
    
    为帮助您识别加载私有库的问题,logcat 可能会生成一个警告或运行时错误。例如,如果您的应用面向 API 级别 23 或更低级别,并在运行 Android 7.0 的设备上尝试访问私有库,您可能会看到一个类似于下面所示的警告:
    W linker  : library "libandroid_runtime.so"
    

("/system/lib/libandroid_runtime.so") needed or dlopened by
“/data/app/com.popular-app.android-2/lib/arm/libapplib.so” is not accessible
for the namespace “classloader-namespace” - the access is temporarily granted
as a workaround for http://b/26394120
如果应用面向 API 级别 24 或更高级别,logcat 会生成以下运行时错误,您的应用可能会崩溃:java
java.lang.UnsatisfiedLinkError: dlopen failed: library “libcutils.so”
("/system/lib/libcutils.so") needed or dlopened by
“/system/lib/libnativeloader.so” is not accessible for the namespace
“classloader-namespace”
at java.lang.Runtime.loadLibrary0(Runtime.java:977)
at java.lang.System.loadLibrary(System.java:1602)
```

通过下面的一些步骤,您可以修复上述类型的错误并确保您的应用不会在将来的更新版平台上崩溃:

*   如果您的应用使用私有平台库,您应更新它,以添加该应用自己的库副本或使用[公开 NDK API](https://developer.android.google.cn/ndk/guides/stable_apis.html)。
*   如果您的应用使用访问私有符号的第三方库,则联系库作者以更新库。
*   请确保将您的所有非 NDK 库与您的 APK 打包在一起。
*   使用标准 JNI 函数而非来自 `libandroid_runtime.so` 的 `getJavaVM` 和 `getJNIEnv`
*   使用 `__system_property_get` 而非来自 `libcutils.so` 的私有 `property_get` 符号。为此,请使用 `__system_property_get` 及 include 函数:`#include `
*   使用来自 libcrypto.so 的 SSL_ctrl 符号的本地版本。例如,您应在您的 .so 文件中静态链接 libcyrpto.a,或从 BoringSSL/OpenSSL 添加一个动态链接的 libcrypto.so 版本,并将其打包到您的 APK 中。
  • 原生库
    在针对 Android 8.0 的应用中,如果原生库包含任何可写且可执行的加载代码段,则不会再加载原生库。
    如需了解详细信息,请参阅可写且可执行的代码段。

  • 共享对象不得包含文本重定位
    Android 6.0(API 级别 23)已禁止在共享对象中使用文本重定位。代码必须按原样加载,且不得修改。此变更可以缩短应用的加载时间并提高安全性。要详细了解如何处理文本重定位,请参阅此指南。

  • Bionic 库和动态链接器路径变更
    从 Android Q 开始,多个路径不再采用常规文件形式,而是采用符号链接形式。如果应用一直以来依赖的都是采用常规文件形式的路径,则可能会出现故障:

  • /system/lib/libc.so -> /apex/com.android.runtime/lib/bionic/libc.so

  • /system/lib/libm.so -> /apex/com.android.runtime/lib/bionic/libm.so

  • /system/lib/libdl.so -> /apex/com.android.runtime/lib/bionic/libdl.so

  • /system/bin/linker -> /apex/com.android.runtime/bin/linker

这些变更也会影响文件的 64 位版本,对于这些版本,会将 lib/ 替换为 lib64/

为了确保兼容性,新符号链接会基于旧路径提供,例如 /system/lib/libc.so 现在是指向 /apex/com.android.runtime/lib/bionic/libc.so 的符号链接,等等。因此,dlopen(“/system/lib/libc.so”) 会继续工作,但当应用尝试通过读取 /proc/self/maps 或类似项来检测已加载的库时,将会发现不同之处。这并不常见,但我们发现一些应用会将这种做法作为对抗黑客攻击的一项举措。如果是这样,则应该将新的 /apex/… 路径添加为 Bionic 文件的有效路径。

  • 系统二进制文件/库会映射到只执行内存
    从 Android Q 开始,系统二进制文件和库会映射到只执行(不可读取)内存,作为应对代码重用攻击的安全强化技术。有意或意外读入已标记为只执行的内存段会抛出 SIGSEGV,无论此读入行为是来自错误、漏洞还是有意的内存自省都不例外。

您可以通过检查 /data/tombstones/ 中的相关 tombstone 文件来确定崩溃是否由变更改所导致。与只执行相关的崩溃包含以下中止消息:

Cause: execute-only (no-read) memory access error; likely due to data in .text.

要解决此问题,开发者可以通过调用 mprotect() 将只执行内存段标记为“读取+执行”,例如用于执行内存检查。不过,我们强烈建议您事后将其重新设为只执行,因为这样可以更好地保护您的应用和用户。

ptrace 的调用不会受到影响,因此 ptrace 调试也不会受到影响。

  • 共享内存
    在 Android Q 中,Ashmem 更改了 /proc//maps 中的 dalvik 映射的格式,这会影响那些直接解析映射文件的应用。

    以 Android Q 为目标平台的应用无法再直接使用 ashmem (/dev/ashmem),而必须通过 NDK 的 ASharedMemory 类访问共享内存。此外,应用无法直接对现有 ashmem 文件描述符进行 IOCTL,而必须改为使用 NDK 的 ASharedMemory 类或 Android Java API 创建共享内存区域。这项变更可以提高使用共享内存时的安全性和稳健性,从而提高 Android 的整体性能和安全性。

低电耗模式

Android 6.0(API 级别 23)引入了低电耗模式,当用户设备未插接电源、处于静止状态且屏幕关闭时,该模式会推迟 CPU 和网络活动,从而延长电池寿命。

而 Android 7.0 则通过在设备未插接电源且屏幕关闭状态下、但不一定要处于静止状态(例如用户外出时把手持式设备装在口袋里)时应用部分 CPU 和网络限制,进一步增强了低电耗模式。

Android 各 API Level 权限变更和功能限制汇总_第2张图片

当设备处于充电状态且屏幕已关闭一定时间后,设备会进入低电耗模式并应用第一部分限制:关闭应用网络访问、推迟作业和同步。如果进入低电耗模式后设备处于静止状态达到一定时间,系统则会对 PowerManager.WakeLock、AlarmManager 闹铃、GPS 和 WLAN 扫描应用余下的低电耗模式限制。

无论是应用部分还是全部低电耗模式限制,系统都会唤醒设备以提供简短的维护时间窗口,在此窗口期间,应用程序可以访问网络并执行任何被推迟的作业/同步。

请注意,激活屏幕或插接设备电源时,系统将退出低电耗模式并移除这些处理限制。

你可能感兴趣的:(Android)