Android 各 API Level 权限变更和功能限制汇总
如果以 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。要了解详情,请参阅唯一标识符的最佳做法。
下面两种方法可以替代这些类型的标识符:
com.google.android.gms.iid
InstanceID API。getInstance(Context context).getID()
将为您的应用实例返回一个唯一设备标识符。结果是一个应用实例作用域标识符,在存储有关应用的信息时,该标识符可用作键,如果用户重新安装应用,该标识符会重置。randomUUID()
之类的基本系统函数创建您自己的标识符,其作用域限定为应用的存储空间。从 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:
getRandomizedMacAddress()
检索分配给特定网络的随机 MAC 地址。// Added in API level 29
// 信息仅限于设备所有者、配置文件所有者和运营商应用程序,
// 且这些应用程序将仅获取其创建的配置的地址,
// 其他调用者将收到默认的“02:00:00:00:00:00”MAC地址。
public MacAddress getRandomizedMacAddress ()
getWifiMacAddress()
检索设备的实际硬件 MAC 地址。此方法对于跟踪设备队列非常有用。// Added in API level 24
// Throws SecurityException if admin is not a device owner
public String getWifiMacAddress (ComponentName admin)
由于 Android 8.0 会对后台应用检索用户当前位置的频率进行限制,startScan() 方法对后台应用执行完整扫描的频率仅为每小时数次。如果不久之后后台应用再次调用此方法, WifiManager 类将提供上次扫描所缓存的结果。
如果用户在运行 Android 9 的设备上停用设备定位,则以下函数不提供结果:
getAllCellInfo()
listen()
getCellLocation()
getNeighboringCellInfo()
从 Android 9 开始,有更严格的 Wi-Fi 扫描限制:
A successful call to WifiManager.startScan() requires all of the following conditions to be met:
To successfully call WifiManager.getScanResults() ensure all of the following conditions are met:
同样,如果要使用 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 为目标平台的应用,除非具有 ACCESS_FINE_LOCATION
权限,否则应用在 Android Q 上运行时无法使用 WLAN API、Wi-Fi Aware API 或 Bluetooth API 中的多种方法。下面列出了受影响的方法。
注意:如果您的应用在 Android Q 上运行但以 Android 9(API 级别 28)或更低版本为目标平台,则只要您的应用具有
ACCESS_COARSE_LOCATION
或ACCESS_FINE_LOCATION
权限,您就可以使用受影响的 API。
TelephonyManager
getCellLocation()
getAllCellInfo()
requestNetworkScan()
requestCellInfoUpdate()
getAvailableNetworks()
getServiceStateForSubscriber
getServiceState()
TelephonyScanManager
requestNetworkScan()
TelephonyScanManager.NetworkScanCallback
onResults()
PhoneStateListener
onCellLocationChanged()
onCellInfoChanged()
onServiceStateChanged()
WifiManager
startScan()
getScanResults()
getConnectionInfo()
getConfiguredNetworks()
WifiAwareManager
WifiP2pManager
WifiRttManager
BluetoothAdapter
startDiscovery()
startLeScan()
LeScanCallback()
为降低功耗,无论应用的目标 SDK 版本为何,Android 8.0 都会对后台应用检索用户当前位置的频率进行限制。
我们只允许后台应用每小时接收几次位置更新。我们将在整个预览版阶段继续根据系统影响和开发者的反馈优化位置更新间隔。
如果应用在运行 Android 8.0 的设备上处于前台,其位置更新行为将与 Android 7.1.1(API 级别 25)及更低版本上相同。
应用满足以下任一条件即视为前台应用:
如果以上所有条件均不满足,应用即视为后台应用。
考虑在您的应用接收位置更新不频繁的情况下其后台运行用例是否根本无法成功。如果属于这种情况,您可以通过执行下列操作之一提高位置更新的检索频率:
GeofencingApi
接口),这些元素针对最大限度减少耗电进行了专门优化。从 Android Q 开始,当应用请求位置信息访问权限时,用户会看到如下图所示的对话框。此对话框可让用户将位置信息访问权限授予到两个不同的范围:使用应用时(仅限前台)或始终(前台和后台)。
为了让用户更好地控制应用对位置信息的访问权限,Android Q 引入了新的位置权限 ACCESS_BACKGROUND_LOCATION
。与现有的 ACCESS_FINE_LOCATION
和 ACCESS_COARSE_LOCATION
权限不同,新权限仅会影响应用在后台运行时对位置信息的访问权。除非应用的某个 Activity 可见或应用正在运行前台服务,否则应用将被视为在后台运行。
如果您的应用以 Android Q 为目标平台,并且在后台运行时需要访问设备的位置信息,则您必须在应用的清单文件中声明新权限:ACCESS_BACKGROUND_LOCATION
如果您的应用在 Android Q 上运行但目标平台是 Android 9(API 级别 28)或更低版本,则以下系统行为会发生:
如果您的应用为 ACCESS_FINE_LOCATION
或 ACCESS_COARSE_LOCATION
声明了
元素,则系统会在安装期间自动为 ACCESS_BACKGROUND_LOCATION
添加
元素。
如果您的应用请求了 ACCESS_FINE_LOCATION
或 ACCESS_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_BUFFER
、CAPTURE_VIDEO_OUTPUT
和 CAPTURE_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_LOG
、WRITE_CALL_LOG
和 PROCESS_OUTGOING_CALLS
权限移入该组。 在之前的 Android 版本中,这些权限位于 PHONE
权限组。
对于需要访问通话敏感信息(如读取通话记录和识别电话号码)的应用,该 CALL_LOG
权限组为用户提供了更好的控制和可见性。
如果您的应用需要访问通话记录或者需要处理去电,则您必须向 CALL_LOG
权限组明确请求这些权限。 否则会发生 SecurityException
。
/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()
)在不同设备上可能不会按照预期方式运行 — 甚至根本不运行。
/proc/net
访问权限,其中包含有关设备网络状态的信息。需要访问此信息的应用(如 VPN)应引用 NetworkStatsManager
和 ConnectivityManager
类。除非您的应用是默认输入法 (IME) 或是目前处于焦点的应用,否则应用无法访问剪贴板数据。
如果应用以 Android Q 为目标平台,则在访问外部存储设备中的文件时会进入过滤视图。应用可以使用 Context.getExternalFilesDir()
将专用于自己的文件存储在特定于自己的目录中。
只有在满足以下两个条件时,您的应用才能访问其他应用创建的文件:
您的应用已获得 READ_EXTERNAL_STORAGE
权限。
这些文件位于以下其中一个明确定义的媒体集合中:
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() 方法。
如需了解有关在您的应用中支持新权限模式的详情,请参阅使用系统权限。如需了解有关如何评估新模式对应用的影响的提示,请参阅权限最佳做法。
以 Android 9 或更高版本为目标平台的应用,使用前台服务时必须请求 FOREGROUND_SERVICE
权限。 这是普通权限,因此,系统会自动为请求权限的应用授予此权限。
以 Android 8.0(API 级别 26)或更高版本为目标平台的应用,会受到如下限制:
如果以上条件均不满足,应用将被视为处于后台。
进入后台时,在一个持续数分钟(模拟器测试在1分钟左右后被kill掉)的时间窗内,应用仍可以创建和使用 Service。
在该时间窗结束后,应用将被视为处于_空闲_状态。 此时,系统将停止应用的后台 Service,就像应用已经调用 Service 的 Service.stopSelf() 方法一样。此时不允许后台应用创建后台 Service,而应该按如下步骤启动前台服务:
在很多情况下,您的应用都可以使用 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 应用了以下优化措施:
CONNECTIVITY_CHANGE
。以 Android 8.0(API 级别 26)或更高版本为目标平台的应用,会受到如下限制:
AndroidManifest.xml
中为隐式广播
注册广播接收器。 隐式广播
是一种不专门针对该应用的广播。 例如,ACTION_PACKAGE_REPLACED 就是一种隐式广播,因为该广播将被发送给所有已注册侦听器,让后者知道设备上的某些软件包已被替换。 不过,ACTION_MY_PACKAGE_REPLACED 不是隐式广播,因为不管已为该广播注册侦听器的其他应用有多少,它都会只被发送给软件包已被替换的应用。**请注意:**很多隐式广播当前已不受此限制所限。 应用可以继续在其清单中为这些广播注册接收器,不管应用适配哪个 API 级别。 有关已豁免广播的列表,请参阅隐式广播例外。
在 Android Q 上运行的应用只有在满足以下一个或多个条件时才能启动 Activity:
该应用具有可见窗口,例如在前台运行的 Activity。
该应用在前台任务的返回栈中具有一项 Activity。
该应用具有最近启动的 Activity。
该应用对最近的一项 Activity 调用了 finish()
。这仅适用于在调用 finish()
时,应用在前台中具有一项 Activity,或在前台任务的返回栈中具有一项 Activity 的情况。
该应用的一项服务被系统绑定。该条件仅适用于以下服务(可能需要启动界面):AccessibilityService
、AutofillService
、CallRedirectionService
、HostApduService
、InCallService
、TileService
、VoiceInteractionService
以及 VrListenerService
。
该应用的某一项服务被其他可见应用绑定。请注意,绑定到该服务的应用必须在后台对该应用保持可见,才能成功启动 Activity。
该应用会从系统收到通知 PendingIntent
。如果存在针对服务和广播接收器的待定 intent,则该应用可以在待定 intent 发送后启动 Activity 几秒钟时间。
该应用会收到从其他可见应用发送的 PendingIntent
。
该应用会收到系统广播,其中要求应用启动界面。示例包括 ACTION_NEW_OUTGOING_CALL
和 SECRET_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_APPLICATION_OVERLAY 窗口类型的窗口下方。
如果应用针对的是 Android 8.0,使用 SYSTEM_ALERT_WINDOW 权限的应用无法再使用上述窗口类型来在其他应用和系统窗口上方显示提醒窗口,相反,应用必须使用名为 TYPE_APPLICATION_OVERLAY 的新窗口类型。
使用 TYPE_APPLICATION_OVERLAY 窗口类型显示应用的提醒窗口时,请记住新窗口类型的以下特性:
如果应用以 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 的限制
以 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()
添加和移除在自动连接网络选择对话框中显示的网络。这些方法不需要任何位置权限。
[HttpURLConnection](https://developer.android.google.cn/reference/java/net/HttpURLConnection.html)
类。此 API 效率更高,因为它可以通过透明压缩和响应缓存减少网络使用,并可最大限度降低耗电量。要继续使用 Apache HTTP API,您必须先在 build.gradle
文件中声明以下编译时依赖项:android {
useLibrary 'org.apache.http.legacy'
}
[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。
Android 5.0 中引入新的“并发文档和 Activity 任务”功能后(请参阅下文最近使用的应用屏幕中的并发文档和 Activity),为提升用户隐私的安全性,现已弃用 ActivityManager.getRecentTasks() 方法。对于向后兼容性,此方法仍会返回它的一小部分数据,包括调用应用自己的任务和可能的一些其他非敏感任务(如首页)。
如果您的应用使用此方法检索它自己的任务,则改用 getAppTasks() 检索该信息。
为了帮助确保应用稳定性和兼容性,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
) 文件。
以 Android Q 为目标平台时,Android 运行时 (ART) 不再从应用进程调用 dex2oat
。这项变更意味着 ART 将仅接受系统生成的 OAT 文件。
从 Android 6.0 开始,执行的 APK 验证更为严格。如果在清单中声明的文件在 APK 中并不存在,该 APK 将被视为已损坏。移除任何内容后必须重新签署 APK。
.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 文件的有效路径。
您可以通过检查 /data/tombstones/
中的相关 tombstone 文件来确定崩溃是否由变更改所导致。与只执行相关的崩溃包含以下中止消息:
Cause: execute-only (no-read) memory access error; likely due to data in .text.
要解决此问题,开发者可以通过调用 mprotect()
将只执行内存段标记为“读取+执行”,例如用于执行内存检查。不过,我们强烈建议您事后将其重新设为只执行,因为这样可以更好地保护您的应用和用户。
对 ptrace
的调用不会受到影响,因此 ptrace
调试也不会受到影响。
共享内存
在 Android Q 中,Ashmem 更改了 /proc/
中的 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 和网络限制,进一步增强了低电耗模式。
当设备处于充电状态且屏幕已关闭一定时间后,设备会进入低电耗模式并应用第一部分限制:关闭应用网络访问、推迟作业和同步。如果进入低电耗模式后设备处于静止状态达到一定时间,系统则会对 PowerManager.WakeLock、AlarmManager 闹铃、GPS 和 WLAN 扫描应用余下的低电耗模式限制。
无论是应用部分还是全部低电耗模式限制,系统都会唤醒设备以提供简短的维护时间窗口,在此窗口期间,应用程序可以访问网络并执行任何被推迟的作业/同步。
请注意,激活屏幕或插接设备电源时,系统将退出低电耗模式并移除这些处理限制。