遇到的一些问题
1.WebChromeClient的 API onReachedMaxAppCacheSize 没了
'onReachedMaxAppCacheSize' overrides nothing
// 扩充缓存的容量
override fun onReachedMaxAppCacheSize(
spaceNeeded: Long, totalUsedQuota: Long, quotaUpdater: QuotaUpdater
) {
quotaUpdater.updateQuota(spaceNeeded * 2)
}
2.WebSettings的setAppCacheEnabled(true) 没了
WebView调整:废弃setAppCacheEnabled与setForceDark方法;
(若设置 targetSdk>=33 则此项必需适配!)
修改为
mWebSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
IntentFilter
在之前版本的Android系统中,只需将android:exported设为true就可以跨应用显式启动Activity和Service,即使intent-filter中的action或者type不匹配,也能够启动。
为避免上述漏洞,Android 13增强了intent-filter的匹配过滤逻辑。在接收方的targetSdk == 33的情况下,如果intent-filter匹配命中,无论发送方的targetSdk版本如何,intent都将生效。
温馨提示:
以下几种情况不需要遵循intent-filter的匹配过滤逻辑:
-
组件没有声明
-
同一个App里的intent
-
系统或Root进程发出的intent
BroadcastReceiver
以往的Android系统下,应用动态注册的BroadcastReceiver广播接收器会接收到任何应用发送的广播(除非该接收器使用了应用签名权限保护),这会使动态注册的广播接收器存在安全风险。
Android13要求,应用动态注册的广播接收器必须以显著的方式指出是否允许其他应用访问,即其他应用是否可以向其发送广播。否则,在动态注册时系统将抛出安全异常(SecurityException)。
目前该增强措施并非默认生效,开发者需启用 DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED兼容性框架,并在动态注册广播时指定是否接受其他应用的广播:
context.registerReceiver(receiver, intentFilter, RECEIVER_EXPORTED)
context.registerReceiver(receiver, intentFilter, RECEIVER_NOT_EXPORTED)
权限
细分媒体权限
Google在Android 13上对本地数据访问权限做了更进一步的细化。
将 READ_EXTERNAL_STORAGE 细分为IAMGES、VIDEO、AUDIO权限, targetSdk>=33 此项必需适配。
从Android 13开始,系统新增运行时权限READ_MEDIA_IAMGES、READ_MEDIA_VIDEO、READ_MEDIA_AUDIO 替代原有的READ_EXTERNAL_STORAGE权限。
权限 | 权限说明 |
READ_MEDIA_IAMGES | 图片权限 |
READ_MEDIA_VIDEO | 视频权限 |
READ_MEDIA_AUDIO | 音频权限 |
当应用升级到targetSdk>=33时:
已授权READ_EXTERNAL_STORAGE权限的应用:系统将自动赋予对应的细化权限。
未授权仍请求READ_EXTERNAL_STORAGE权限:亲测系统将不会授予任何权限。
因此,API 32也就是Android 12及以下系统,我们仍然声明的是READ_EXTERNAL_STORAGE权限。从Android 13开始,我们就会使用READ_MEDIA_IMAGES、READ_MEDIA_VIDEO、READ_MEDIA_AUDIO来替代了。
静态广播注册
从Android 13开始,注册静态广播时,需设置对其他应用的可见性:
若对其他应用可见,广播注册时设置:Context.RECEIVER_EXPORTED
若仅应用内使用,广播注册时设置:Context.RECEIVER_NOT_EXPORTED
// This broadcast receiver should be able to receive broadcasts from other apps.
// This option causes the same behavior as setting the broadcast receiver's
// "exported" attribute to true in your app's manifest.
context.registerReceiver(sharedBroadcastReceiver, intentFilter,
RECEIVER_EXPORTED)
// For app safety reasons, this private broadcast receiver should **NOT**
// be able to receive broadcasts from other apps.
context.registerReceiver(privateBroadcastReceiver, intentFilter,
RECEIVER_NOT_EXPORTED)
通知权限
Android 13 引入了一种新的运行时通知权限:POST_NOTIFICATIONS。
POST_NOTIFICATIONS 权限级别被定义为dangerous 开发者使用该权限时需动态申请。
对于以Android13,在显示Android通知栏时,一方面需要在AndroidManifest中声明 android.permission.POST_NOTIFICATION,另一方面代码中需动态申请该通知栏权限。
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="xxx.xxxx.xxxx">
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
EasyPermissions.requestPermissions(this, this.getString(R.string.ask_again),
PermissionActivity.RC_PERMISSION_NOTIFICATIONS, Manifest.permission.POST_NOTIFICATIONS);
判断应用是否有能力发出通知让用户看到(各个系统版本上都是能正常工作):
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
if (notificationManager.areNotificationsEnabled()) {
// Permission granted
} else {
// Permission not granted
}
Android 13上判断发送通知权限 :
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
== PackageManager.PERMISSION_GRANTED) {
// Permission granted
} else {
// Permission not granted
}
注意:一旦被用户拒绝授权,下次系统将不会再出现权限申请的弹窗。
引导用户前往设置界面打开通知权限。代码如下:
private void goSetting() {
final ApplicationInfo applicationInfo = getApplicationInfo();
try {
Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction("android.settings.APP_NOTIFICATION_SETTINGS");
intent.putExtra("app_package", applicationInfo.packageName);
intent.putExtra("android.provider.extra.APP_PACKAGE", applicationInfo.packageName);
intent.putExtra("app_uid", applicationInfo.uid);
startActivity(intent);
} catch (Throwable t) {
t.printStackTrace();
Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
intent.setData(Uri.fromParts("package", applicationInfo.packageName, null));
startActivity(intent);
}
}
class TestNavigationActivity : AppCompatActivity() {
companion object {
const val TAG = "TestNavigationActivityTAG"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test_navigation)
checkAndShow()
}
private fun checkAndShow() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val manager = getSystemService(NotificationManager::class.java)
val flag = manager.areNotificationsEnabled()
if (!flag) {
requestPermission(PermissionWrapper(POST_NOTIFICATIONS, 1001))
}
}
}
private fun checkPermission(permission: String): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return true
}
return (ContextCompat.checkSelfPermission(
this, permission
) == PackageManager.PERMISSION_GRANTED)
}
private fun requestPermission(permission: PermissionWrapper) {
if (checkPermission(permission.name)) {
Toast.makeText(this, "已授予权限:" + permission.name, Toast.LENGTH_LONG).show()
Log.v(TAG, "已授予权限:" + permission.name)
} else {
Toast.makeText(this, "未授予权限:" + permission.name + ",尝试获取权限...", Toast.LENGTH_LONG)
.show()
Log.v(TAG, "未授予权限:" + permission.name + ",尝试获取权限...")
ActivityCompat.requestPermissions(this, arrayOf(permission.name), permission.code)
}
}
data class PermissionWrapper(val name: String, val code: Int)
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<out String>, grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
val result = StringBuilder().apply {
append("请求码:$requestCode")
append("-权限:")
permissions.forEach {
append(it)
}
append("-授权结果:")
grantResults.forEach {
append(it)
}
}.toString()
Toast.makeText(this, result, Toast.LENGTH_LONG).show()
Log.v(TAG, result)
}
}
V/TestNavigationActivityTAG: 未授予权限:android.permission.POST_NOTIFICATIONS,尝试获取权限...
V/TestNavigationActivityTAG: 请求码:1001-权限:android.permission.POST_NOTIFICATIONS-授权结果:0
拿到权限后才能发送通知,代码如下:
class TestNavigationActivity : AppCompatActivity() {
companion object {
private const val TAG = "TestNavigationActivityTAG"
private const val CHANNEL_ID = "myApplication_channel_1"
private const val CHANNEL_NAME = "myApplication_channel_name"
private const val NOTIFICATION_ID_MSG = 100
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test_navigation)
checkAndShow()
}
private fun checkAndShow() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val manager = getSystemService(NotificationManager::class.java)
val flag = manager.areNotificationsEnabled()
if (!flag) {
requestPermission(PermissionWrapper(POST_NOTIFICATIONS, 1001))
} else {
showNotification()
}
}
}
private fun checkPermission(permission: String): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return true
}
return (ContextCompat.checkSelfPermission(
this, permission
) == PackageManager.PERMISSION_GRANTED)
}
private fun requestPermission(permission: PermissionWrapper) {
if (checkPermission(permission.name)) {
Toast.makeText(this, "已授予权限:" + permission.name, Toast.LENGTH_LONG).show()
Log.v(TAG, "已授予权限:" + permission.name)
} else {
Toast.makeText(this, "未授予权限:" + permission.name + ",尝试获取权限...", Toast.LENGTH_LONG)
.show()
Log.v(TAG, "未授予权限:" + permission.name + ",尝试获取权限...")
ActivityCompat.requestPermissions(this, arrayOf(permission.name), permission.code)
}
}
data class PermissionWrapper(val name: String, val code: Int)
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<out String>, grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
val result = StringBuilder().apply {
append("请求码:$requestCode")
append("-权限:")
permissions.forEach {
append(it)
}
append("-授权结果:")
grantResults.forEach {
append(it)
}
}.toString()
Toast.makeText(this, result, Toast.LENGTH_LONG).show()
Log.v(TAG, result)
}
private fun showNotification() {
val intent = Intent(this, NotificationResultMainActivity::class.java)
val manager = getSystemService(NotificationManager::class.java)
val pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel =
NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH)
channel.setShowBadge(true)
manager.createNotificationChannel(channel)
}
val builder = NotificationCompat.Builder(this@TestNavigationActivity, CHANNEL_ID)
.setContentTitle("重要通知")
.setContentText("重要通知")
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher))
.setAutoCancel(true)
.setNumber(999)
.addAction(R.mipmap.ic_launcher, "去看看", pendingIntent)
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
.setVisibility(NotificationCompat.VISIBILITY_PRIVATE)
manager.notify(NOTIFICATION_ID_MSG, builder.build())
}
}