Android14 Adaptation - What should I pay attention to when upgrading targetSdkVersion to 34?

According to the last article, another month has passed. Although there are gold, nine and silver, the atmosphere this year is really too deserted. It would be nice to have a job. I hope the U.S. dollar interest rate hike will end soon and the economy will get better soon~

The content mentioned in the previous article is the impact and content that need to be paid attention to when all apps are installed on Android14 devices. The next article will introduce what the app needs to pay attention to and modify when the targetSdkVersion is upgraded to 34.

1. Changes to core functions

1.1 Front desk service type

In the case of targetSdkVersion >= 34, at least one foreground service type must be specified for each foreground service (Foreground Service) in the application.

What is front desk service?
Foreground Service is a special type of service that is used to perform long-running tasks related to the user's current activity. These services will display notifications in the system status bar to Inform the user that the app is performing tasks in the foreground and using system resources. On Android12 (API level 31) and higher devices, the system has optimized short-running foreground services. The system will wait 10 seconds before displaying notifications associated with foreground services to improve user experience and reduce the interference of immediate notifications. You need to apply for android.permission.FOREGROUND_SERVICE permission in the Manifest file when using it.

The front-end service type was introduced in Android10. You can specify the service type of <service> through android:foregroundServiceType. The available front-end service types are:

  1. camera: You need to continue to access the camera when it is in the background, such as a video chat application that supports multitasking.
  2. connectedDevice: Interacts with external devices that require Bluetooth, NFC, IR, USB, or network connectivity.
  3. dataSync: Data transfer operations, such as: data upload or download, backup and recovery operations, import or export operations, obtaining data, local file processing, transferring data between devices and the cloud over the network. (This type may be deprecated in subsequent Android versions, it is recommended to use WorkManager or user-initiated data transfer jobs instead)
  4. health: Used for any use case that requires long-term operation to support fitness applications such as activity trackers.
  5. location: Long-running use cases that require location access, such as navigation and location sharing.
  6. mediaPlayback: Requires continuous playback of audio or video in the background, or support for digital video recording (DVR) on Android TV.
  7. mediaProjection: Use the MediaProjection API to project content to a non-primary monitor or external device. This content does not necessarily have to be exclusively media content.
  8. Microphone: Requires continuous microphone capture in the background (such as a voice recorder or communication application).
  9. phoneCall: Scenarios that require continuous use of ConnectionService API.
  10. remoteMessaging: Transfer text messages from one device to another. Help ensure continuity of user messaging tasks when users switch devices.
  11. shortService: Important work that cannot be interrupted or postponed needs to be completed quickly; has 5 characteristics: 1) can only run for a short time, about 3 minutes; 2) does not support sticky foreground services; 3) cannot start other Front-end service; 4) There is no need to apply for specific types of permissions, but the FOREGROUND_SERVICE permission is indispensable; 5) The running front-end service cannot be switched between shortService types. After timeout, Service.onTimeout() will be called. This API is new in Android14. In order to avoid ANR, it is recommended to implement onTimeout callback.
  12. specialUse: If not included in all the above types, use this type. In addition to declaringFOREGROUND_SERVICE_TYPE_SPECIAL_USE the foreground service type, use cases should also be declared in the Manifest. That is, the elements need to be specified in the <service> element as shown below. These values ​​and corresponding use cases will be reviewed when submitting your app in Google Play Console.
// code 1
<service android:name="fooService" android:foregroundServiceType="specialUse">
  <property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE" android:value="foo"/>
</service>
  1. systemExempted: Reserved for system applications and specific system integrations to continue using foreground services. Ordinary app developers don’t need to worry about it.

In addition, among the above 13 types,做有彩色标记 are newly added on Android14; others were existing before.

To give a common example of background playback, first declare permissions in the Manifest file and set them foregroundServiceType:

// code 2
<manifest ...>
 <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
 <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
 <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
   <application ...>
     <service
         android:name=".MusicPlayerService"
         android:foregroundServiceType="mediaPlayback"
         android:exported="false">
     </service>
   </application>
</manifest>

Then implement the MusicPlayerService class, mainly to open the notification and start playing music in the onStartCommand callback:

// code 3
class MusicPlayerService : Service() {
    
    
    private var mediaPlayer: MediaPlayer? = null

    override fun onBind(intent: Intent?): IBinder? {
    
    
        return null
    }

    @RequiresApi(Build.VERSION_CODES.O)
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    
    
        val NOTIFICATION_CHANNEL_ID = "com.example.foregroundservice"
        val notificationManager = NotificationManagerCompat.from(this)

        // If the notification supports a direct reply action, use
        // PendingIntent.FLAG_MUTABLE instead.
        val pendingIntent: PendingIntent =
            Intent(this, NotificationFullActivity::class.java).let {
    
     notificationIntent ->
                PendingIntent.getActivity(
                    this, 0, notificationIntent,
                    PendingIntent.FLAG_IMMUTABLE
                )
            }

        // 创建通知渠道
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    
    
            val name: CharSequence = "Notification Channel Name"
            val description = "Description of Notification Channel"
            val importance = NotificationManager.IMPORTANCE_DEFAULT
            val channel = NotificationChannel(NOTIFICATION_CHANNEL_ID, name, importance)
            channel.description = description
            notificationManager.createNotificationChannel(channel)
        }

        // 构建通知
        val builder: NotificationCompat.Builder =
            NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
                .setSmallIcon(android.R.drawable.ic_lock_idle_alarm)
                .setContentTitle("音乐播放中")
                .setContentText("艺术家 - 音乐")
                .setContentIntent(pendingIntent)
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)

        // 启动通知
        val notificationId = 1 // 每个通知的唯一标识符
        if (ActivityCompat.checkSelfPermission(
                this,
                Manifest.permission.POST_NOTIFICATIONS
            ) == PackageManager.PERMISSION_GRANTED
        ) {
    
    
            notificationManager.notify(notificationId, builder.build())
        }
        // Notification ID cannot be 0.
        startForeground(notificationId, builder.build())

        // 播放音乐
        mediaPlayer = MediaPlayer.create(this, R.raw.music1)
        mediaPlayer?.isLooping = true
        mediaPlayer?.start()

        return START_STICKY
    }
}

Finally, start the Service:

// code 4
requireActivity().startForegroundService(Intent(requireActivity(), MusicPlayerService::class.java))

If the type is not specified in the Manifest file, an exception will be thrown when the startForeground() method is called. MissingForegroundServiceTypeException

An additional note about the above sample code is that when a Notification notification pops up on Android13 and above mobile phones, you need to dynamically apply forandroid.permission.POST_NOTIFICATIONS permissions, and of course you also need to create a NotificationChannel channel. , which is already required in Android8 and above.

Let’s talk about front-end services. Each of the above-mentioned front-end service types requires different permissions, and these permissions are defined as ordinary permissions. They have been granted by default, and users cannot revoke these permissions. . For example, the Camera service type needs to declare FOREGROUND_SERVICE_CAMERA permissions in the Manifest file and apply for Camera permissions at runtime. This is true for other service types:

Foreground Service Type Manifest requirements Runtime requirements
Camera FOREGROUND_SERVICE_CAMERA CAMERA
Connected device FOREGROUND_SERVICE_CONNECTED_DEVICE Declare CHANGE_NETWORK_STATE or CHANGE_WIFI_STATE or CHANGE_WIFI_MULTICAST_STATE or NFC or TRANSMIT_IR in Manifest or request runtime permission BLUETOOTH_CONNECT or BLUETOOTH_ADVERTISE or BLUETOOTH_SCAN or UWB_RANGING or call UsbManager.requestPermission()
Data sync FOREGROUND_SERVICE_DATA_SYNC -
Health FOREGROUND_SERVICE_HEALTH Declare HIGH_SAMPLING_RATE_SENSORS in Manifest or apply for runtime permissions (BODY_SENSORS or ACTIVITY_RECOGNITION)
Location FOREGROUND_SERVICE_LOCATION ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION
MediaPlayback FOREGROUND_SERVICE_MEDIA_PLAYBACK -
Media projection FOREGROUND_SERVICE_MEDIA_PROJECTION CreateScreenCaptureIntent() needs to be called before
Microphone FOREGROUND_SERVICE_MICROPHONE RECORD_AUDIO
Phone call FOREGROUND_SERVICE_PHONE_CALL Declare MANAGE_OWN_CALLS in Manifest
Remote messaging FOREGROUND_SERVICE_REMOTE_MESSAGING -
Short service - -
Special use FOREGROUND_SERVICE_SPECIAL_USE -
System exempted FOREGROUND_SERVICE_SYSTEM_EXEMPTED -

Of course, there must be special circumstances. If your front-end services are not related to the 13 mentioned above, then the official recommendation is to migrate these services to WorkManager or user-initiated data transfer jobs.

User-initiated data transfer jobs are data transfer tasks initiated by users. This API is new in Android 14 and is suitable for long-duration data transfers that need to be initiated by the user, such as downloading files from a remote server. These tasks require a notification to be displayed in the notification bar, start immediately, and may run for as long as system conditions permit. We can run multiple user-initiated data transfer jobs simultaneously.

Summary: If a front-end service is currently used in the application and the targetSdkVersion wants to be raised to 34, then you must add the type of the front-end service; otherwise, leave it alone.

1.2 Permission changes for Bluetooth connection

On Android14, you must apply for permission when calling BluetoothAdapter’s getProfileConnectionState() API. It was not necessary before, but now This permission must be declared in the manifest file and applied to the user at runtime. BLUETOOTH_CONNECT

It is obvious that Android has gradually reclaimed some system permissions in recent years, which is more troublesome for developers, but beneficial to the majority of users.

1.3 OpenJDK 17 Update

Android14 continues to update Android's core libraries to align them with the features and functionality of the latest OpenJDK LTS version, including updates to libraries and Java17 language support for application and platform developers. Some of the following changes may affect application compatibility:

  1. Regular expression changes: Some regular expressions have been changed. Please check where regular expressions are used in the application to see if there are any errors. You can turn off compatibility mode in the developer options to easily find problematic areas. The specific compatibility mode switch is inSystem> Advanced> Developer Options> Application Compatibility Changes< /span>Enabled for targetSdkVersion >= 34 and find the DISALLOW_INVALID_GROUP_REFERENCE option switch; here (the native system is here, it’s hard to say for other manufacturers), and select your own App in the list to close or open it. Here you need to slide to the bottom of the list
  2. UUID handling: java.util.UUID.fromString() methods perform stricter checks when validating input parameters, so may throw IllegalArgumentException during deserialization abnormal. The self-test method is the same as above, but you need to switch the ENABLE_STRICT_VALIDATION option under Application Compatibility Changes;
  3. ProGuard problems: In some cases, when using ProGuard to compress, obfuscate, and optimize code, problems will occur after adding java.lang.ClassValue. This issue is caused by a Kotlin library changing the runtime behavior of whether a class will be returned when executing Class.forName("java.lang.ClassValue") if the application is targeting a class without java.lang.ClassValue Developed in older versions, these optimizations will remove methods from java.lang.ClassValue derived classes. computeValue

Summary: Although JDK17 will be backward compatible, it is better to upgrade when you have time. After all, there are many new writing methods and optimizations.

2. Security

Android14 also has higher requirements for security, which is also the direction Google has been focusing on in recent years.

2.1 Restrictions on Implicit Intent and PendingIntent

Implicit Intent (Implicit Intent) is a mechanism for communication between Android application components. It does not explicitly specify which component to start, but declares the operation to be performed. The system looks for components that can handle this operation and starts them. Implicit Intents are mainly used to trigger various operations within an application or with other applications, such as starting activities, starting services, sending broadcasts, etc. A common example is to first set the action of the intent-filter in an Activity in the Manifest file, and then set the action in the startup Intent to match the Activity to start it.

  1. Implicit Intent can only be passed to the component of android:exported="true" (four major components: Activity, Service···). Therefore, when using Intent to pass data in the App, you must either use explicit Intent to pass to the component of android:exported="false"; or use implicit Intent to pass to the component of android:exported="true". Of course, it is definitely possible to pass explicit Intent to exported="true".
  2. A mutable PendingIntent must set packageName, otherwise an exception will be thrown.

Give a chestnut:

// code 5
<!-- android:exported 设置为false,隐式 Intent 无法启动 -->
<activity
    android:name=".AppActivity"
    android:exported="false">
    <intent-filter>
        <action android:name="com.example.action.SEND" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>
// code 6
// Throws an exception when targeting Android 14.  直接用隐式的 Intent 调用不管用
context.startActivity(Intent("com.example.action.SEND"))

// This makes the intent explicit. 当设置了 intent 中的 package 参数时就可以了
val explicitIntent =
        Intent("com.example.action.SEND")
explicitIntent.apply {
    
    
    `package` = context.packageName
}
context.startActivity(explicitIntent)

Summary: Pay attention to this change, especially components with android:exported set to false. These components may crash when started and need to be modified. This update is also for security, as these changes prevent malicious apps from intercepting implicit intents used by components inside the app.

2.2 Dynamic broadcast receivers must specify exported behavior

Dynamically registered broadcast receivers must set a flag indicating whether the receiver is exported to all apps on the device. The flag bits are RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED. Introduced as early as Android 13, this feature allows applications to specify whether a registered broadcast receiver should be exported and visible to other applications on the device.

It just became a "required setting" on Android14. In previous versions of Android, any app on the device could send unprotected broadcasts to a dynamically registered broadcast receiver unless the receiver had signed permission.

For example, register AlarmReceiver in application A and send a broadcast:

// code 7
val filter = IntentFilter("alarmReceiver_custom_action")
val listenToBroadcastsFromOtherApps = true
val receiverFlags = if (listenToBroadcastsFromOtherApps) {
    
    
    ContextCompat.RECEIVER_EXPORTED    // 该接收器对其他应用开放
} else {
    
    
    ContextCompat.RECEIVER_NOT_EXPORTED    // 该接收器不对其他应用开放
}
// 这里的 registerReceiver 方法必须设置 receiverFlags 参数
registerReceiver(requireContext(), AlarmReceiver(), filter, receiverFlags)

// 发送广播
val intent = Intent("alarmReceiver_custom_action")    // 方式1
//val intent = Intent(requireActivity(), AlarmReceiver::class.java)    // 方式2
requireActivity().sendBroadcast(intent)

In other applications, broadcasts can only be sent through method 1 in code7. If A application'slistenToBroadcastsFromOtherApps is set to true, then application A can receive broadcasts through other applications. 1 has sent the broadcast information, otherwise it cannot be received.

In practice, we also found that if application A also sends its own internal broadcast through method 1 and sets ContextCompat.RECEIVER_NOT_EXPORTED, then this broadcast cannot be received. Interested students can try it.

If the application only registers a receiver for the system broadcast through the Context#registerReceiver method (such as Context#registerReceiver() ), it may not specify this flag when registering the receiver. .

Summary: The registration method of dynamic broadcast has been changed. You need to set whether it is visible to other applications. This is the same as the setting of android:exported. In fact, the functions of local broadcast and global broadcast are the same, but more emphasis is placed on targetSdkVersion >= 34.

2.3 Safer dynamic code loading

All dynamically loaded files must be marked read-only. Otherwise, the system will throw an exception. Officials recommend that applications avoid dynamically loading code as much as possible, because doing so will greatly increase the risk of the application being damaged by code injection or code tampering.

If code must be loaded dynamically, the dynamically loaded file (such as a DEX, JAR, or APK file) needs to be set to read-only before the file is opened and anything is written:

// code 8
val jar = File("DYNAMICALLY_LOADED_FILE.jar")
val os = FileOutputStream(jar)
os.use {
    
    
    // Set the file to read-only first to prevent race conditions
    jar.setReadOnly()
    // Then write the actual file content
}
val cl = PathClassLoader(jar.absolutePath, parentClassLoader)

In addition, in order to prevent the system from throwing exceptions for existing dynamically loaded files, officials recommend deleting and recreating the files before trying to dynamically load these files again in the application. When recreating the file, follow the guidelines above to mark the file as read-only while writing. Alternatively, existing files can be remarked as read-only, but in this case we recommend verifying the file's integrity first (for example, checking the file's signature against a trusted value) to protect the app from malicious actions.

2.4 Zip path traversal

For Android14 applications, the Android system prevents Zip path traversal vulnerabilities in the following ways: If the zip file entry name contains "..." or starts with "/", thenZipFile(String) and < /span> exception. ZipInputStream.getNextEntry() will throw an ZipException

If you do not want to throw an exception and the file name cannot be changed, you can choose to exit verification by calling dalvik.system.ZipPathValidator.clearCallback(). Of course this is not recommended.

Zip path traversal vulnerability: By constructing a file path containing ".../" or starting with "/", a malicious attacker can access any files or directories on the file system other than the Zip file when decompressing the Zip file, thereby damaging the Zip file. Vulnerabilities in applications that pose security risks.

2.5 New restrictions on starting Activity in the background

On Android14, the system further restricts the situation in which apps can start activities from the background:

  1. When the App usesPendingIntent#send() or similar methods to sendPendingIntent, you must choose whether to grant permission to start its own background Activity to send PendingIntent . If you choose authorization, you need to return and pass a object through the setPendingIntentBackgroundActivityStartMode(MODE_BACKGROUND_ACTIVITY_START_ALLOWED) method. ActivityOptions
  2. When a visible application in the foreground uses the bindService() method to bind the Service of another background application, the visible application must now choose whether to grant its own background Activity startup permission to the bound one Serve. If authorization is selected, the application needs to set the flag when calling the bindService() method. BIND_ALLOW_ACTIVITY_STARTS

These changes extendthe existing set of restrictions to protect users by preventing malicious applications from abusing the API to launch destructive activity from the background.

Summary: The control over background startup has become stricter. If there is relevant logic in the project, it is recommended to run it to see if it can be started in the background. If there are any problems, make modifications according to the above content. I really don’t know what it means. . .

3. Updates on restrictions on non-SDK interfaces

Android14 updates the list of restricted non-SDK interfaces (based on collaboration with Android developers and the latest API list used by internal testing). Before restricting the use of non-SDK interfaces, we will try our best to ensure that there are public alternatives available.

Some of these changes may not have an immediate impact on your app if it's not targeting Android 14. But whenever an app uses any non-SDK methods or fields, there is always a significant risk of causing problems with the app.

Generally speaking, the public SDK interface is in the Android frameworkPackage Index (https://developer.android.google.cn/reference/packages)< a Those interfaces recorded in i=2>. The handling of non-SDK interfaces is an implementation detail abstracted away by the API, so these interfaces may change at any time without notice.

If you are not sure whether your app uses non-SDK interfaces, you can run the test app in Debug mode and the system will output a log message if the app accesses some non-SDK interfaces. You can examine your app's log messages for the following details:
1) The declared class, name, and type (in the format used by the Android runtime);
2) Access method: link, reflection or JNI;
3) Which list does the accessed non-SDK interface belong to;
You can also use adb logcat to view these logs Messages that appear under the PID of the running application. For example, the log might contain entries such as:

Accessing hidden field Landroid/os/Message;->flags:I (light greylist, JNI)

If your app relies on non-SDK interfaces, you should start planning for migration to SDK alternatives. If you cannot find an alternative using a non-SDK interface for a feature in your app, you should request a new public API.

For a complete list of all non-SDK interfaces in Android14, you can download and view the following file:hiddenapi-flags.csv (https://dl.google.com/developers /android/udc/non-sdk/hiddenapi-flags.csv?hl=zh-cn), this table file has a lot of content and can be used for query.

Summary: Ordinary application developers generally do not use non-SDK interfaces, so this can be ignored.

The above is all the content of this article. It can be seen that if the existing App directly upgrades the targetSdkVersion to 34 (Android14), there are still some places that need to be paid attention to and modified and tested. If you still want to know what new features are added in Android 14, please follow me. See you in the next article!

For more content, please follow the official account: Xiuzhizhu
or view Xiuzhizhu’s Android album

Roses that praise people leave lingering fragrance in their hands! Welcome to like and repost~ Please indicate the source when reposting~

references

  1. Android 14 official documentation https://developer.android.com/about/versions/14
  2. https://developer.android.google.cn/about/versions/14/behavior-changes-14?hl=zh-cn
  3. Android 14 quick adaptation points; Lianmao de Xiaoguo; https://juejin.cn/post/7231835495557890106?searchId=202307240025039D8229C74EA62159077B
  4. https://developer.android.google.cn/guide/components/foreground-services
  5. https://developer.android.com/about/versions/14/changes/user-initiated-data-transfers?hl=zh-cn
  6. https://developer.android.google.cn/about/versions/14/changes/fgs-types-required
  7. https://developer.android.google.cn/about/versions/13/features#runtime-receivers
  8. https://developer.android.google.cn/about/versions/14/changes/non-sdk-14?hl=zh-cn

Guess you like

Origin blog.csdn.net/lbs458499563/article/details/132928217