Android 14 Developer Preview一览

2023-10-18 17:26:57 浏览数 (2)

不久之前,Google公开了Android 14的首个Developer Preview版本。

按照Google正常的节奏,每年年初会发布两个Developer Preview版本,然后在每年中上旬发布两到三个Beta版本。之后会进入平台稳定期,将版本打磨精细,然后在秋季左右发布当年最新的Android系统版本。

其实之前我倒是基本没太写过这种新系统特性一览的文章。去年因为公司安排的原因,我去学习了一下Android 13 Developer Preview的新特性,并写了一篇比较全面的文章介绍,可以参考这里 Android 13 Developer Preview一览 。

然后我就发现,写这类文章还真是好处多多,每个版本具体到有哪些新特性和行为变更,只要再去翻一遍之前的文章就清清楚楚了,算是一个非常详实的笔记。

于是,我打算将这个Developer Preview做成一个系列吧,以后每年第一时间了解最新系统的最新特性,学习这些新特性的同时,也写成笔记分享给大家。

获取Android 14

如果你想要现在就对Android 14进行尝鲜,那么主要有两种办法,一是使用一台Pixel 4或更高版本的Pixel系统手机,二是使用Android Studio自带的模拟器。

使用手机的话需要进行刷机才可以,使用模拟器就很简单了,下载最新版的系统镜像即可。具体操作步骤我就不在这里演示了,详情请参考官方文档:

https://developer.android.google.cn/about/versions/14/get

完成以上步骤后,你就可以得到最新的Android 14系统了。

接着到设置里面去检查一下当前系统的版本号,如果你看到显示的是14或者UpsideDownCake(Android 14的内部代号),那么就代表你已经成功了。

接下来我们去研究一下Android 14具体带来了哪些新功能和变化,下图是我从官网截取的一张Developer Preview版本所有新功能与变化的截图:

从图中可以看出,Android 14的新功能与变化主要可以分为5个部分,无障碍、核心功能、国际化、非SDK接口限制、安全性。

其中无障碍这个群体过于小众(没有不尊重的意思),里面的内容我也看得不是很明白,这部分就跳过不讲了。

除此之外的部分,我会将图中列举出来的每一条新功能与变更,都展开进行讲解。

精准闹钟被默认禁止

Android中关于Alarm(闹钟)相关的内容其实我很少讲,甚至在《第一行代码 Android》整本书中,我都没有提及过Alarm相关的任何知识。

为什么?因为它不好用。或者说,Google在尽力让它变得不好用。

我相信使用Alarm的人都是希望自己程序的某段代码能够在某个特定的时间点去执行,但是这么想的人多了就会出问题。试想一下,如果所有程序都去注册了一个不同时间的闹钟,那么你的手机可能每时每秒都在被不同的应用所唤醒,手机的续航能力可想而知。

所以为了限制Alarm的功能,Android从4.4系统开始,将Alarm的触发时间由原来的精准变为不精准。Android系统不再保证你设置的Alarm将会精准地在某一时刻去执行,而是可能会将一些时间临近的Alarm拼凑在一起。大家设置的触发时间都相差不远,那么索性就凑个时间唤醒一次手机,将临近的这些Alarm一次性一起执行了,这样就可以大大降低手机被唤醒的次数。

这听上去好像很美好,但是如果你的业务需求就是要在某个非常精准的时间去触发一段逻辑呢?比如说一些抢购活动,晚一秒东西可能就没了。

所以,Google并没有把这个事情做绝。在AlarmManager当中,之前我们都是通过set()方法来设置闹钟的,从4.4系统开始多出了一个setExact()方法,如果你有非常明确的理由一定要使用精准闹钟,那么就可以调用这个方法。

不过故事到这里还远没有完,Alarm的功能还在继续一步步被限制。

Android 12系统中,Google引入了一个新的权限:SCHEDULE_EXACT_ALARM。

也就是说,以后再想要调用类似setExact()方法的这种精准定时的API,必须得在AndroidManifest.xml文件中声明SCHEDULE_EXACT_ALARM这个权限才行,如下所示:

代码语言:javascript复制
<manifest ...>

    <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />

</manifest>

不过至少在Android 12和Android 13系统中,我们只要声明一下就可以了,这个权限是默认允许的。

但是从Android 14开始,这个权限被默认禁止了。

所以,如果你还需要依赖这种精准闹钟的API,那么在使用之前,应该先调用canScheduleExactAlarms()方法来判断我们是否有权限设置精准闹钟。如果没有的话,可以发起ACTION_REQUEST_SCHEDULE_EXACT_ALARM这个Intent来跳转到设置界面,提醒用户手动打开授权。

示例代码如下:

代码语言:javascript复制
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
    if (alarmManager.canScheduleExactAlarms()) {
        Toast.makeText(this, "Can schedule exact alarm", Toast.LENGTH_SHORT).show()
    } else {
        Toast.makeText(this, "Can not schedule exact alarm", Toast.LENGTH_SHORT).show()
        val intent = Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM)
        startActivity(intent)
    }
}

上述代码的运行效果如下图所示:

虽然Alarm的功能在继续一步步被限制,但这都还不是最主要的。

最主要的是,你通过Alarm设置的定时任务可能压根就不会执行。

是的,你以为你设置的某个定时任务在未来的某个时间节点会执行,但前提是,你的App还没有被回收掉。只要App进入后台被回收了,那么设置的定时任务也就永远不会执行了。

回收的原因有很多种,最常见的就是用户把App强杀了。这个很简单,手指划一划就可以把App杀掉,相信大家都用过。另外如果App长期进入后台,Android系统也会根据一定的电池优化策略将这种不活跃的App进行回收。

所以,如果想要确保你设置的定时任务一定会执行,还需要额外做一步非常关键的操作,那就是让手机的电池优化策略忽略我们的应用。可以通过下来代码来实现这个功能:

代码语言:javascript复制
val intent = Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS)
val powerManager = getSystemService(POWER_SERVICE) as PowerManager
if (powerManager.isIgnoringBatteryOptimizations(packageName)) {
    Toast.makeText(this, "Ignoring battery optimizations", Toast.LENGTH_SHORT).show()
} else {
    intent.action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
    intent.data = Uri.parse("package:$packageName")
    startActivity(intent)
}

另外还得在AndroidManifest.xml中声明如下权限:

代码语言:javascript复制
<manifest>

    <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />

</manifest>

运行一下程序,效果如下图所示:

只要忽略了电池优化选项,我们的App就基本不太可能被回收了,通过Alarm设置的定时任务也就能够确保得到执行了。

但是请注意,忽略电池优化选项是可能被应用商店所审查的,如果没有非常合理的理由,还可能会被商店强制下架。

面对如此繁琐的步骤,还需要用户反复同意各种权限,你还愿意使用Alarm吗?

动态注册广播在App进入缓存状态时将会延迟送达

这其实是一个挺细的知识点,因为在Android 14做出这项调整之前,我甚至不知道Android 13及以前的版本原先在这方面的行为是什么样的。

为了解释清楚这项行为变更,我需要先将一个App可能所处于的状态类型解释一下。

简单来说,一个App可能会处于4种状态类型:1. 前台状态。2. 可见状态。3. 服务状态。4. 缓存状态。

前台状态最好理解,你每天使用的App,打开使用它的时候,那就是处于前台状态的。

可见状态指的是,你的App虽然没有位于最前台,但对于用户来说仍然是可见的。比如有其他半透明的App盖在你的上面,或者你启动了一个带通知的前台Service,再或者你的App给用户提供了一些通用类的功能,如输入法、壁纸等。

服务状态指的是,你的App已经完全不可见的,但是因为启动了后台Service,所以短时间内,你的App还在继续运行。但是这种状态是不可持续的,因为Google在每个Android系统版本中都在持续削弱和限制后台Service的能力,所以运行不了多久,你的App就会进入缓存状态。

缓存状态指的是,用户已经完全不在使用你的App了,系统随时可以杀死和回收你的App以释放内存。至于Android系统的回收策略,这又是另外一个话题了,不在我们本篇文章的讨论范围。

了解完了这些内容,接下来我们再来看Android 14的这项行为变更。

首先我们都知道,在Android中注册广播接收器来监听广播发送有动态注册和静态注册两种方式。

动态注册就是在代码中进行注册,静态注册就是在AndroidManifest.xml中进行注册。

那么从Android 14开始,所有使用动态方式注册的广播接收器,只要你的App进入了缓存状态,那么就无法再接收到广播了。所有原本能接收到的广播会暂时进入到一个系统的广播队列当中,当你的App重新回到前台之后,这些队列中的广播会一次性送达。

另外,这项改动只针对使用动态方式注册的广播接收器,静态方式注册的广播接收器和之前的行为保持不变。

强制指定前台Service类型

3年前,我在介绍Android 11新特性的时候,首次提到了Google引入了前台Service类型这个概念,具体可以参考这篇文章 Android 11权限变更讲解 。

但当时的前台Service类型还比较简单,只有location、camera、microphone这3种。如果想要在前台Service中使用某项权限,那么就必须得在AndroidManifest.xml中声明对应权限的前台Service类型。

比如说,你想要在前台Service中获取用户的位置信息,那么就得声明location这种前台Service类型:

代码语言:javascript复制
<manifest>
    ...
    <service ... 
		android:foregroundServiceType="location" />
</manifest>

如果你想要同时使用多个权限,也可以一次性声明多种前台Service类型:

代码语言:javascript复制
<manifest>
    ...
    <service ...
        android:foregroundServiceType="location|camera|microphone" />
</manifest>

之后,Android 12和Android 13系统又陆陆续续添加了一些新的类型。

直到Android 14,声明前台Service类型变成强制性的了。不过目前还只对targetSdkVersion指定到Android 14及以上的App才生效。

也就是说,以后前台Service不是说开就能开的了,你必须得有一个开的理由才行。通过声明前台Service类型,来告诉系统你开前台Service的目的是什么。

目前为止,Android 14 Developer Preview版本一共定义了13种前台Service类型,并且每种类型还对应了一个前台Service权限,需要一起在AndroidManifest.xml文件中进行声明才行。

类型和权限的关系如下表所示:

前台Service类型

前台Service权限

camera

FOREGROUND_SERVICE_CAMERA

connectedDevice

FOREGROUND_SERVICE_CONNECTED_DEVICES

dataSync

FOREGROUND_SERVICE_DATA_SYNC

health

FOREGROUND_SERVICE_HEALTH

location

FOREGROUND_SERVICE_LOCATION

mediaPlayback

FOREGROUND_SERVICE_MEDIA

mediaProjection

FOREGROUND_SERVICE_MEDIA_PROJECTION

microphone

FOREGROUND_SERVICE_MICROPHONE

phoneCall

FOREGROUND_SERVICE_PHONE_CALL

remoteMessaging

FOREGROUND_SERVICE_REMOTE_MESSAGING

shortService

No need

specialUse

FOREGROUND_SERVICE_SPECIAL_USE

systemExempted

FOREGROUND_SERVICE_SYSTEM_EXEMPTED

然后,根据你要在前台Service当中执行的业务逻辑,选择一个或多个关系最为贴合的前台Service类型就可以了。

如果实在没有任何合适的类型,那么就选specialUse这个类型好了,这也算是一个大保底的类型。

示例代码如下:

代码语言:javascript复制
<manifest ...>
  <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
    <application ...>
      <service
          android:name=".MyForegroundService"
          android:foregroundServiceType="specialUse"
          android:permission="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"
          android:exported="false">
      </service>
    </application>
</manifest>

还有一点需要注意的是,specialUse这个类型在提交Play Store商店的时候会被Google审查,可能还需要进行一些额外的解释才行。

如果不上架Play Store的话就不必有这个顾虑了。

应用单独语言设置

应用单独语言设置是Android 13中引入的一个新特性,它允许我们为每一个App单独设置语言,从而可以无视系统全局的语言设定。详情可以参考这篇文章 Android 13 Developer Preview一览 。

但是,在Android 13 Developer Preview版本当中,这个功能是所有App自动就会拥有的,不需要做任何适配。而到了Android 13正式版当中,想要让App支持这个功能,还得在AndroidManifest.xml文件中配置一个android:localeConfig属性才行。

其实我到现在都没能理解配置这个android:localeConfig属性的意义是什么。但是系统既然这么要求了,我们就只好照做。

首先在res/xml目录下创建一个locales_config.xml文件,并在里面配置我们的App所支持的语言列表:

代码语言:javascript复制
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
    <locale android:name="en"/>
    <locale android:name="fr"/>
    <locale android:name="ja"/>
    <locale android:name="zh"/>
</locale-config>

接下来在AndroidManifest.xml中引用这个locales_config.xml文件,如下所示:

代码语言:javascript复制
<manifest>
    <application
        ...
        android:localeConfig="@xml/locales_config">
    </application>
</manifest>

经过这些配置之后,我们的App就会出现在系统的应用单独语言设置列表当中了,如下图所示。

点击Android14Test,可以查看到刚才所配置的那些语言类型。

这样,当用户在这里选择了中文,即使手机系统的语言是英文的,我们的App里显示的文字仍然都是中文的。

这就是Android 13的应用单独语言设置功能。

而Android 14对这项功能又进行了扩展,目前Developer Preview版本增加了两项能力。

第一,现在可以调用LocaleManager的setOverrideLocaleConfig()方法来动态设置当前应用所支持的语言列表,而不是像Android 13那样只能在AndroidManifest.xml中配置了。

拥有了动态设置语言列表的能力,就拥有了更高的灵活性,比如可以通过服务器来云控所支持的语言列表。

第二,输入法软件可以调用LocaleManager的getApplicationLocales()方法来获取当前App正在显示的语言。这样就可以针对性地调整键盘,以满足用户的输入需求。

总体来说,Android 14在应用单独语言设置方面的变化不大,只是有一些小幅的功能增强,了解一下就好。

Grammatical Inflection API

Grammatical Inflection API我不确定该如何翻译,如果是直译的话,那就是语法变化API。但是我感觉这种直译好像体现不出来这个API的实际用途,所以干脆这里我就不翻译了,等待后续官方的翻译吧。

要说这个Grammatical Inflection API的作用,如果仅是对国内用户而言,那真的可以说是毫无用处。

这是为了更加满足海外的多样性和包容性而设计的一套API。可千万别小瞧这一点,在海外,多样性和包容性是非常受重视的,不尊重这个文化的话容易被社死。

那么Grammatical Inflection API到底是干什么的呢?我这里可能实在找不出国内应景的例子,所以就只能照搬Android官网的例子了。

在中文当中,一句话的表达方式对于不同性别的人基本也都是相同的。

但有些语言并不是,同样的一句话,对于不同性别的人来说,表达方式是不同的。

比如用户订阅了什么服务,我们可以提醒用户:“你订阅了。。。”

在中文当中,不论男女你都可以用同样的说法。但在法语当中,这句“你订阅了。。。”对于男性、女性、中性的人来说,表达方式都是不同的:

男性应该说: “Vous êtes abonné à…”

女性应该说:“Vous êtes abonnée à…”

中性应该说:“Abonnement à…activé”

所以对于这种场景,你打算要怎样进行适配呢?

倒也不是完全没有办法,就是会比较麻烦,需要写很多额外的代码才行。

而Android 14的Grammatical Inflection API就是用来解决这个问题的。

首先Android 14引入了几个新的限定符,masculine表示男性,feminine表示女性、neuter表示中性。

然后在创建字符串资源的时候,可以将这些性别限定符也一同进行指定:

限定符

字符串资源文件

feminine

res/values-fr-feminine/strings.xml

masculine

res/values-fr-masculine/strings.xml

neuter

res/values-fr-neuter/strings.xml

这样,我们就在法语环境下,为不同性别的人群创建了独立的字符串资源文件。

之后,调用GrammaticalInflectionManager的setRequestedApplicationGrammaticalGender()来设置用户的性别偏好,系统就会显示相应性别的语言表达方式了。

注意,调用了setRequestedApplicationGrammaticalGender()方法之后会触发一次configuration change,以更新当前界面的语言显示。

另外,如果是一些没有性别偏好的语言表达方式,仍然将这些字符串配置到默认的res/values-fr/strings.xml文件当中即可。

更新非SDK接口的限制

这块内容其实我也不太想讲,因为我对于一些所谓的“技术黑活”其实一直不是很感兴趣。

大概是从2016年的时候开始,国内Android圈兴起了一阵热修复的浪潮,后来紧接着热修复,又兴起了一阵插件化的浪潮。

这些技术都不是通过常规的技术手段实现的,而是绕过了Android官方的API接口,通过反射的方式去Hook那些没有开放的私有API,然后在里面注入自己的逻辑,从而实现动态替换可执行代码的功能。

Google对于这种调用私有API的行为一直是反对的,有严重违规行为的App会被禁止上架Play Store商店。但是正如前面所说,这项技术主要是在国内流行,Play Store商店也管不着,所以热修复和插件化着实流行了几年。

但是从Android 9开始,Google终于在禁用私有API上面从严处理了。之前是不准上架Play Store商店,从Android 9开始,调用私有API直接会导致App崩溃。

不过由于私有API太过于海量,Google并没有直接一棒子打死,而是将所有API分成了白名单、灰名单、黑名单来过渡性进行管理。

白名单自然就是所有Android官方的API接口,这些都可以放心地调用。

黑名单包括了那些被Google认为极度危险的私有API,可能会造成严重的安全隐患,是完全禁止调用的,尝试调用则会直接导致App崩溃。

灰名单则是属于一种灰色地带了,Google不建议你调用,但是考虑到有些私有API已经在被大量App使用,直接禁用的话影响面可能过大,所以就暂时将它们放到了灰名单当中。

显然,这些名单是不可能恒定不变的,每个Android系统版本名单都会有大规模的变化。但总体的变化趋势就是,灰名单的内容在逐渐变少,黑名单的内容在逐渐变多。

因为限制越来越多了,渐渐地,热修复和插件化技术在国内也就随之逐渐降温。谁也不希望自己的App随着系统一更新就全面坏掉了,还是老老实实用官方稳定的API更加放心。

Android 14这次对私有API的名单又进行了更新,想要查看完整版本的朋友可以通过这个链接下载:

https://dl.google.com/developers/android/udc/non-sdk/hiddenapi-flags.csv

限制最小targetSdkVersion

在我们开发Android项目的时候通常必须要指定两个sdk版本号,一个是compileSdkVersion,一个是targetSdkVersion。

compileSdkVersion比较好理解,就是用于编译当前项目的Android系统版本。你指定到了哪个版本,就可以使用哪个版本的API,指定版本过低的话,一些新版系统中推出的API就使用不了了。

targetSdkVersion则是另外一回事,它指的是,你的App已经为哪个Android系统版本做好了适配,系统会根据你指定的版本号来决定是否启动某项功能。

设置targetSdkVersion的目的是,Google每发布一个新的Android系统版本,都会推出一些新的功能,以及对一些老功能进行修改。但是这些变动可能会导致原有的App出现不兼容的情况。为了避免这种问题,Google可以选择只对那些targetSdkVersion指定到最新版本的App才启动这些变动,这样那些还未升级适配的App暂时就不会受到影响了。

举一个具体点的例子,Android在6.0系统之前,权限的管理是相当宽松的,App想要使用什么权限,只需要在AndroidManifest.xml里声明一下就行了,用户只要安装了你的App就代表接受了你申请的所有权限。

而从6.0系统开始,Android引入了运行时权限机制。App想要使用一些敏感权限时,必须先弹窗询问用户,用户点击允许之后才能使用相应的权限。

不过这项变动可能会导致许多老App出现不兼容的情况,因此Android只会对targetSdkVersion指定到23(6.0)及以上的App才会启用运行时权限这项特性。

然而,现在都已经2023年了,Android都出到14了,竟然还有一些App的targetSdkVersion是低于23的。

只要一直低于23,运行时权限就会一直不启用,这对于一些恶意软件来说是一个非常低成本的绕过方式。

因此,这次Android 14系统对最小targetSdkVersion做出了限制,低于23版本的App将被禁止安装,以此进一步提升Android设备的安全性。

但如果是之前就已经安装上的App,然后升级到了Android 14系统,即使它的targetSdkVersion小于23,也仍然会被保留。毕竟系统不能代表你去自动删除某个已安装的App。

隐式Intent的限制

Android 14对Intent的使用又增加了更多限制,虽然我也感觉变得越来越麻烦了,但不得不承认,每次的改动确实都是在提升安全性。

首先这项改动只针对targetSdkVersion指定到Android 14及以上的App才生效,关于targetSdkVersion的作用我们刚刚才讲过。

为了能够清楚地讲解这项改动,我们先来看一下如下代码:

代码语言:javascript复制
<activity
    android:name=".PickPhotoActivity"
    android:exported="false">
    <intent-filter>
        <action android:name="com.example.action.PICK_PHOTO" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

这里定义了一个PickPhotoActivity,用于选择用户需要的照片。注意这个Activity的exported属性是false,说明它是仅供内部使用的。

那么如果我们想要调用这个Activity来选择照片,可以怎么写呢?由于它能够响应com.example.action.PICK_PHOTO这个自定义action,很容易就能写出如下代码:

代码语言:javascript复制
val intent = Intent("com.example.action.PICK_PHOTO")
context.startActivity(intent)

这段代码确实可以正常工作,但是大家有没有想过一个问题,假如现在你的手机上有另外一个App,它的AndroidManifest.xml里是这么写的:

代码语言:javascript复制
<activity
    android:name=".HookPickPhotoActivity"
    android:exported="true">
    <intent-filter>
        <action android:name="com.example.action.PICK_PHOTO" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

可以看到,它也能够响应com.example.action.PICK_PHOTO这个action,并且它的exported属性是true,说明它是允许外部调用的。

那么此时你还使用上述的代码来选择照片,启动的到底是谁的Activity?

这种情况下,系统也不知道你到底想要启动谁,所以就只能弹出一个对话框,让你自己去选择。

由此我们可以看出,恶意软件在这种场景下是有空子可以钻的,因为必然会有用户选择错误。

那么为了解决这方面的安全隐患,Android 14对Intent的使用又做出了更多的限制。

当你的targetSdkVersion指定到了Android 14及以上,再使用刚才的代码去启动Activity,系统就会抛出异常。

这是因为,PickPhotoActivity的exported属性是false,说明它是仅供内部使用的。仅供内部使用的组件,在调用它的时候一定要显示地指定当前App的包名才行,如下所示:

代码语言:javascript复制
val intent = Intent("com.example.action.PICK_PHOTO")
intent.package = context.packageName
context.startActivity(intent)

这样的话,对于系统来说就不会存在二义性,它会非常明确地知道要去启动哪一个Activity,从而进一步提升了安全性。

动态注册广播必须声明exported属性

前面刚说过,在Android中注册广播接收器有静态注册和动态注册两种方式。

静态注册的话,我们可以通过在AndroidManifest.xml中配置exported属性来决定是否接收外部程序发送的广播。

而动态注册则没长期没有这项功能。在Android 13之前,动态注册的广播接收器可以接收到手机上任意程序发送出来的广播。

这方面仔细想一想,确实可能会存在一些安全隐患。因此从Android 13开始,Google为动态注册的广播接收器增加了这项能力,使用如下代码即可配置动态注册的广播接收器是否允许接收外部程序发送的广播:

代码语言:javascript复制
val br: BroadcastReceiver = MyBroadcastReceiver()
val filter = IntentFilter("com.example.myintentfilter")
val listenToBroadcastsFromOtherApps = false
val receiverFlags = if (listenToBroadcastsFromOtherApps) {
    ContextCompat.RECEIVER_EXPORTED
} else {
    ContextCompat.RECEIVER_NOT_EXPORTED
}
ContextCompat.registerReceiver(context, br, filter, receiverFlags)

这项配置在Android 13上是可选的,而到了Android 14变成了强制性配置。

只要你的targetSdkVersion指定到了Android 14及以上,就必须得要为动态注册的广播接收器声明是否接收外部广播,否则系统就会抛出异常。

更安全的动态代码加载

动态代码加载本身就是官方极力不推荐的事情,果不其然,Google在新系统中又要增加更多的限制了。

如果你的targetSdkVersion指定到了Android 14及以上,所有需要动态加载的文件必须标记为只读类型,否则系统就会抛出异常。

首先Google是推荐我们尽量避免使用动态代码加载的技术,因为这会导致App的安全性问题受到很大的挑战,比如我们动态加载的文件可能会被注入和篡改内容。

而如果你一定要使用动态代码加载的技术,那么现在需要将动态加载的文件(如dex、jar、apk)在被写入任何内容之前就标记为只读,以防止后续被别人纂改。

可以参考官方如下的代码示例来完成此项功能:

代码语言:javascript复制
val jar = File("dynamic_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, parentClassLoader)

而对于那些之前已经存在的动态加载文件,官方给出的建议是先将这些文件删除掉,然后再按上述代码的方式重新创建这些文件。

当然你也可以将这些已经存在的动态加载文件直接标记成只读,但由于它们存在已经被篡改过了的风险,所以最好再进行一些诸如文件完整性之类的校验。

结语

以上就是关于Android 14 Developer Preview的所有内容了。

当然随着后续Beta版本的发布,一定还会有更多的新特性和行为变更出现。重要的内容我会再另行写文章进行讲解,其他一些小的变化就等待你自己去探索了。

0 人点赞