更新日志
- 2020-04-09 首次发布
前言
由于目前大部分应用市场要求新上架应用应基于 Android 8.0 (API 等级 26,即 targetSdkVersion 大于等于 26)及以上开发。所以对现有的 APP 进行全面升级。
既然是要求最低 26,我们干脆就直接升级到 28,并迁移到 AndroidX。你可能会问,为什么选择 28,因为官方建议使用 28 进行迁移 AndroidX。
现状
- Android Studio 版本 3.5.3
- targetSdkVersion=23,compileSdkVersion=23
- Gradle 版本为 3.4
- Gradle 插件版本为 2.2.3
- android.support 为 28
目标
- Android Studio 保持该版本
- targetSdkVersion=28,compileSdkVersion=28
- Gradle 版本为 5.4.1
- Gradle 插件版本为 3.5.3
- 迁移到 AndroidX
这个升级的跨度有点大,隐约觉得到会有很多坑出现,既然我们目标已经很明确了,不用慌,一步步来。
实施
targetSdkVersion 和 compileSdkVersion 改为28
1. 出现 Add Google Maven repository and sync project
错误。
解决方案:在项目目录下的 build.gradle 两个地方添加上 google()
buildscript {
repositories {
google()
jcenter()
}
}
allprojects {
repositories {
google()
jcenter()
maven { url "https://jitpack.io" }
}
}
2. 出现 Gradle DSL method not found: 'google()'
,错误信息如下:
Gradle DSL method not found: 'google()'
Possible causes:
The project '你的项目名称' may be using a version of the Android Gradle plug-in that does not contain the method (e.g. 'testCompile' was added in 1.1.0).
Upgrade plugin to version 3.5.3 and sync project
The project '你的项目名称' may be using a version of Gradle that does not contain the method.
Open Gradle wrapper file
The build file may be missing a Gradle plugin.
Apply Gradle plugin
原因是当前 Gradle 插件版本太低,没有该方法。我们将 Gradle 修改为 5.4.1,Gradle 插件版本修改为 3.5.3。
Gradle 改为 5.4.1,Gradle 插件改为 3.5.3
1. 升级 Gradle 导致相关 api 失效,错误信息如下:
Could not get unknown property 'apkVariantData' for object of type com.android.build.gradle.internal.api.ApplicationVariantImpl.
原因是 Gradle 3.0 以后 apkVariantData 这个 api 已经被移除,所以出现找不到,网上大部分解决方案都是降低 Gradle 版本,我们就是为了升级,现在又说降低,明显是不符合我们的需求。继续查找问题所在。因为我们本身项目没有使用到这个 api,所以初步判断是第三方库使用到。通过二分法,最终确定了是我们引入的 AndResGuard 这个库导致的,我们当前使用的版本是 1.2.3,官方最新的已经是 1.2.17,我们尝试下升级为官方最新版本,问题解决。
2. 产品类型错误,错误信息如下:
ERROR: All flavors must now belong to a named flavor dimension. Learn more at https://d.android.com/r/tools/flavorDimensions-missing-error-message.html
Affected Modules: app
翻译过来的意思是:所有风味现在都必须属于命名的风味维度,并提供给我们一个地址,我们直接点击查看,文档里写的非常详细,最终解决方案在 app 下的 build.gradle,添加如下代码:
android {
// 省略...
flavorDimensions "version" // 增加这一行代码
productFlavors {
// 省略...
}
}
参考:
- 配置产品类型
3. android.useDeprecatedNdk 不再被支持使用,错误信息如下:
INFO: The following project options are deprecated and have been removed:
android.useDeprecatedNdk
NdkCompile is no longer supported
Affected Modules: app, library
由于当前项目没有使用到 NDK 相关,我们直接将 gradle.properties 里 android.useDeprecatedNdk 移除。
4. compile 已过时,并已被替换,错误信息如下:
INFO: Configuration 'compile' is obsolete and has been replaced with 'implementation' and 'api'.
It will be removed soon. For more information see: http://d.android.com/r/tools/update-dependency-configurations.html
Affected Modules: app
我们将项目里 compile 根据项目自身需求替换为 implementation 或 api 即可,两者有何不同,或其他用法可进入提供的网址查看。
到这里我们的项目又可以正常跑起来了,是不是觉得也没那么难。
参考:
- 依赖项配置
迁移到 AndroidX
终于到了这惊险又刺激的最后一步了,不成功便成仁。我们先看官网的前提条件。
前提条件
执行迁移之前,请先将应用更新到最新版本。我们建议您将项目更新为使用支持库的最终版本:版本 28.0.0。这是因为,1.0.0 版本的 AndroidX 工件是与支持库 28.0.0 工件等效的二进制文件。
前提条件我们已经满足了,直接通过 Android Studio,Refactor > Migrate to AndroidX。Android Studio 会帮我们进行支持库的替换。大部分的包导入也会自动替换,如有剩下没自动替换的,我们手动进行替换即可。类似的如下替换:
com.android.support:support-v4:28.0.0 -> androidx.legacy:legacy-support-v4:1.0.0
com.android.support:recyclerview-v7:28.0.0 -> androidx.recyclerview:recyclerview:1.1.0
...
在 gradle.properties 中,Android Studio 已经为我们自动添加了如下代码:
android.useAndroidX=true
android.enableJetifier=true
1. java 8 支持,错误信息如下:
Execution failed for task ':app:mergeExtDexDevDebug'.
> Could not resolve all files for configuration ':app:devDebugRuntimeClasspath'.
> Failed to transform artifact 'butterknife-runtime.aar (com.jakewharton:butterknife-runtime:10.2.1)' to match attributes {artifactType=android-dex, dexing-enable-desugaring=false, dexing-is-debuggable=true, dexing-min-sdk=25, org.gradle.usage=java-runtime}.
> Execution failed for DexingNoClasspathTransform: C:\Users\ry\.gradle\caches\transforms-2\files-2.1\7095ceecc071665593ef76235a41c81a\butterknife-runtime-10.2.1-runtime.jar.
> Error while dexing.
The dependency contains Java 8 bytecode. Please enable desugaring by adding the following to build.gradle
android {
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
}
See https://developer.android.com/studio/write/java8-support.html for details. Alternatively, increase the minSdkVersion to 26 or above.
这么详细的错误就不多解释了,按提示添加相关代码即可。
参考:
- 使用 Java 8 语言功能
成功迁移
经过以上步骤和踩坑,基本上算是完成了迁移工作,剩下的就是一些兼容和适配的问题了。这里我也列举一些遇到的兼容和适配问题。
兼容和适配
1. Android Studio 在 Android 9.0 机器上直接 Run 报错如下:
The application could not be installed: INSTALL_FAILED_TEST_ONLY
原因是 Android Studio 在 Run 的时候会自动添加 android:testOnly=“true”
导致安装失败。我们来看一下官方对 android:testOnly 这个属性的描述。
android:testOnly
指示此应用是否仅用于测试目的。例如,它可能会在自身之外公开功能或数据,这样会导致安全漏洞,但对测试很有用。此类 APK 只能通过 adb 安装,您不能将其发布到 Google Play。
当您点击 Run 图标时,Android Studio 会自动添加此属性。
有两种解决方案:
- 不使用 Android Studio 的 Run 按钮直接安装,而是依次点击 Build > Build Bundle(s)/APK(s) > Build APK(s) 方式进行安装
- 在 gradle.properties 添加
android.injected.testOnly=false
将该属性强制设置为 false。
参考:
- android:testOnly 属性说明
- Android Studio 构建项目说明
- INSTALL_FAILED_TEST_ONLY 原因
2. APP 运行出现 java.lang.NoClassDefFoundError:
// 第一种 Android 9.0 出现
java.lang.NoClassDefFoundError: Failed resolution of: Lorg/apache/http/conn/scheme/SchemeRegistry;
// 第二种 Android 10 出现
java.lang.NoClassDefFoundError: Failed resolution of: Lorg/apache/http/message/BasicNameValuePair;
出现这个错误的原因我们也可以在官网找到,更详细的描述可以通过点击参考的链接查看。
在 org.apache.http.* 中查找类时,这些应用需要委托给应用 ClassLoader。 如果它们委托给系统 ClassLoader,则应用在 Android 9 或更高版本上将失败并显示 NoClassDefFoundError。
解决方案官网也有给出,在 AndroidManifest.xml 的 application 标签下添加以下内容:
参考:
- NoClassDefFoundError 出现的原因
- NoClassDefFoundError 解决方法
3. Android 9.0 及以上网络请求时抛出异常,如下:
java.net.UnknownServiceException: CLEARTEXT communication not supported: [ConnectionSpec(cipherSuites=[TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_3DES_EDE_CBC_SHA], tlsVersions=[TLS_1_2, TLS_1_1, TLS_1_0], supportsTlsExtensions=true), ConnectionSpec(cipherSuites=[TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_3DES_EDE_CBC_SHA], tlsVersions=[TLS_1_0], supportsTlsExtensions=true)]
原因是:从 Android 9(API 级别 28)开始,系统默认情况下已停用明文支持。
解决方案,在 AndroidManifest.xml 的 application 标签内添加 android:usesCleartextTraffic="true"
。
参考:
- 网络安全配置
- stackoverflow 的回答
4. Android 8.0 及以上无法正常弹出通知。
根据官网的说明,从 Android 8.0(API 级别 26)开始,所有通知都必须分到一个渠道,否则通知将不会显示。这里我直接贴了官网的代码:
private void createNotificationChannel() {
// Create the NotificationChannel, but only on API 26+ because
// the NotificationChannel class is new and not in the support library
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = getString(R.string.channel_name);
String description = getString(R.string.channel_description);
int importance = NotificationManager.IMPORTANCE_DEFAULT;
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance);
channel.setDescription(description);
// Register the channel with the system; you can't change the importance
// or other notification behaviors after this
NotificationManager notificationManager = getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);
}
}
参考:
- 创建通知渠道并设置重要性
结语
本次升级并迁移 AndroidX 遇到的问题暂时就是这些,不要怕出问题,解决问题才能使我们更强大。通过 google、官网、二分法等都能很好的分析并解决问题。如有小伙伴升级过程遇到比较棘手的问题可以在评论区留言,本篇文章将不定期进行更新。
参考链接
官方-迁移到 AndroidX
Android Gradle 插件版本说明