Android targetSdkVersion28 适配

本文详细介绍了在将Android应用的targetSdkVersion升级到28时遇到的各种问题及解决方案,包括腾讯云语音识别错误、7.0以上设备的抓包问题、MTA崩溃、全屏活动请求方向限制、Android 9.0键盘不自动弹出、okhttp3上传文件名中文报错等。并提供了解决这些问题的具体步骤和代码示例。

平时csdn看资料的时候,都是游客模式,前两天要看个评论,不得不登一下,发现之前写的一个心得,有好几个朋友评论了。虽然是很小的问题,但能帮到别人,心里就很满足2333

正好近期把targetSdkVersion 升到了28,适配过程中踩到了各种坑,趁此机会写一篇个人总结与诸位一起分享。

 


1. 腾讯云-语音识别 code=-301 & 7.0无法抓包 & MTA崩溃

这几个其实本质是一个问题。详情参考官方文档网络安全配置。可以看到关键的一点是“从 Android 9(API 级别 28)开始,系统默认情况下已停用明文支持。”,即 cleartextTrafficPermitted="false"。

由此而知,腾讯云语音识别一直报错code=-301, message=server connect failed,其实是因为加了这个设置ClientConfiguration.setServerProtocolHttps(false); 导致接口都是http,自然无法使用了,去掉该配置就可以了,因为默认是用的https。因为demo中是这么设置的,所以之前同事就copy过来了,我升级target sdk后发现不能用也没多想就给腾讯云提了工单,那边一开始也接锅表示会修复的,结果升新版后还是不行,一来一回花了不少时间,以后用sdk还是得多看文档,但这语音识别的文档也太简陋了,连版本日志都没...

MTA崩溃也是同样的问题,一上报就崩了,CLEARTEXT communication to xxx not permitted by network security policy,看了下MTA已经是新版本了,再加上其他sdk中可能还有http的,所以索性修改networkSecurityConfig,允许明文。

7.0以上不能抓包则同样需要在networkSecurityConfig中配置信任的证书,我选择了debug调试时信任所有证书。

networkSecurityConfig配置如下:

在res/xml中新建 network_security_config.xml

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <debug-overrides cleartextTrafficPermitted="true">
        <trust-anchors>
            <certificates src="system" />
            <certificates src="user" />
        </trust-anchors>
    </debug-overrides>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>

在AndroidManifest里的<application>标签中,添加代码:

 android:networkSecurityConfig="@xml/network_security_config"

以上问题就一并解决啦,各位可以根据自己的需求来进行配置。 

 2. java.lang.IllegalStateException: Only fullscreen opaque activities can request orientation

这个问题发生在android 8.0 的机子上,app都没进就崩了,我的心也跟着一起崩了啊23333

这个问题中有较多讨论,问题来源是源码中这一段

  //Need to pay attention mActivityInfo.isFixedOrientation() and ActivityInfo.isTranslucentOrFloating(ta)
    if (getApplicationInfo().targetSdkVersion >= O_MR1 && mActivityInfo.isFixedOrientation()) {
        final TypedArray ta = obtainStyledAttributes(com.android.internal.R.styleable.Window);
        final boolean isTranslucentOrFloating = ActivityInfo.isTranslucentOrFloating(ta);
        ta.recycle();

        //Exception occurred
        if (isTranslucentOrFloating) {
            throw new IllegalStateException(
                    "Only fullscreen opaque activities can request orientation");
        }
    }

解决方法:项目中很多activity用的主题都是透明的

 <style name="CommonTheme" parent="AppTheme">
        <item name="android:windowIsTranslucent">true</item>
        <item name="android:windowBackground">@color/white</item>
 </style>

将用到透明主题的acitivity在manifest中的

android:screenOrientation="portrait"

这行都去掉就可以了,经测试,如果透明activity后面有不透明的固定方向的activity,透明的是不会转动的,所以主页activity不要用透明主题就好。

3. android 9.0 pie windowSoftInputMode不管怎么设置 进入activity键盘不会自动弹出

这个问题也蛮妖的,就是在9.0上弹不出键盘,android:windowSoftInputMode="adjustResize|stateVisible"不起作用。

依旧是在stackoverflow找到了解决办法,在9.0上,没有自动获取焦点,那就只能手动代码中requestFocus,或者xml加上</requestFocus>,例如:

 <EditText
        android:id="@+id/et_envelope_post_script"
        android:layout_width="match_parent"
        android:layout_height="98dp"
        android:background="@color/white"
        android:gravity="left|top"
        android:padding="16dp"
        android:textSize="16sp">
        <requestFocus />
 </EditText>

4. okhttp3 通过multipart上传文件名为中文报错Caused by: java.lang.IllegalArgumentException: Unexpected char

顺便升级了一下okhttp3,结果bugly中几个上报都是通过multipart/form-data上传书封时的,是因为用到的本地图片文件名含中文,报错如下:

 Caused by: java.lang.IllegalArgumentException: Unexpected char 0x6211 at 39 in Content-Disposition value: form-data; name="bookCover"; filename="我推荐吗.jpg"

代码使用:

MultipartBody.Builder builder = new MultipartBody.Builder()
                .setType(MultipartBody.FORM)
                .addFormDataPart(param, targetFile.getName(), RequestBody.create(MEDIA_TYPE, targetFile));

源码部分:

 /** Add a form data part to the body. */
    public Builder addFormDataPart(String name, @Nullable String filename, RequestBody body) {
      return addPart(Part.createFormData(name, filename, body));
    }
   public static Part createFormData(String name, @Nullable String filename, RequestBody body) {
      if (name == null) {
        throw new NullPointerException("name == null");
      }
      StringBuilder disposition = new StringBuilder("form-data; name=");
      appendQuotedString(disposition, name);

      if (filename != null) {
        disposition.append("; filename=");
        appendQuotedString(disposition, filename);
      }

      return create(Headers.of("Content-Disposition", disposition.toString()), body);
    }
  public static Headers of(String... namesAndValues) {
    if (namesAndValues == null) throw new NullPointerException("namesAndValues == null");
    if (namesAndValues.length % 2 != 0) {
      throw new IllegalArgumentException("Expected alternating header names and values");
    }

    // Make a defensive copy and clean it up.
    namesAndValues = namesAndValues.clone();
    for (int i = 0; i < namesAndValues.length; i++) {
      if (namesAndValues[i] == null) throw new IllegalArgumentException("Headers cannot be null");
      namesAndValues[i] = namesAndValues[i].trim();
    }

    // Check for malformed headers.
    for (int i = 0; i < namesAndValues.length; i += 2) {
      String name = namesAndValues[i];
      String value = namesAndValues[i + 1];
      checkName(name);
      checkValue(value, name);
    }

    return new Headers(namesAndValues);
  }
 static void checkName(String name) {
    if (name == null) throw new NullPointerException("name == null");
    if (name.isEmpty()) throw new IllegalArgumentException("name is empty");
    for (int i = 0, length = name.length(); i < length; i++) {
      char c = name.charAt(i);
      if (c <= '\u0020' || c >= '\u007f') {
        throw new IllegalArgumentException(Util.format(
            "Unexpected char %#04x at %d in header name: %s", (int) c, i, name));
      }
    }
  }

  static void checkValue(String value, String name) {
    if (value == null) throw new NullPointerException("value for name " + name + " == null");
    for (int i = 0, length = value.length(); i < length; i++) {
      char c = value.charAt(i);
      if ((c <= '\u001f' && c != '\t') || c >= '\u007f') {
        throw new IllegalArgumentException(Util.format(
            "Unexpected char %#04x at %d in %s value: %s", (int) c, i, name, value));
      }
    }
  }

由此可知,文件名中含中文时,checkValue会抛出异常,解决办法就是上传前重命名文件名。同时注意,header头中也是不能带中文的。

5. 其他

有些项目中已经适配,或者没涉及的点,也一起提一下。

一个是7.0开始App对外无法暴露file://类型的URI了,在类似调用系统相机或是通过代码安装apk等需要暴露uri给其他app的情况下,会抛出FileUriExposedException异常,需要使用FileProvider来定义对外暴露的文件夹路径,再调用:

FileProvider.getUriForFile(this.mContext.getApplicationContext(), authority, file);

来获取content://类型的Uri。其中authority指在manifest的<provider>中配置的provider名,例如:

<provider
            android:name="me.iwf.photopicker.utils.PhotoFileProvider"
            android:authorities="${applicationId}.provider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/__picker_provider_paths"/>
</provider>

其中__picker_provider_paths是在res/xml中定义的对外暴露的文件夹路径,如下:

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path name="external_storage_root" path="."/>
    <files-path name="files" path="."/>
</paths>

关于paths配置比较详细的讲解可参考Android 7.0适配-应用之间共享文件(FileProvider)

二是android 8.0以上未知来源的应用是不可以通过代码来执行安装的,会提示“解析包时出现问题”。需要申明权限

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

并在代码中动态申请。 这么一想之前一些app更新时是碰到过这个问题,当时只当是apk有问题,现在看来是没有进行适配。我们的项目因为是直接跳到网页进行下载的,所以没有碰到这个问题。

目前为止碰到的问题大概就这些,如果之后还有的话,会在此更新的,也希望各位能留言自己在升级sdk中碰到的坑,我也会一并整理进来的。

 

Android 应用的 `targetSdkVersion` 从 34 升级到 36 时,不仅涉及 SDK 版本的提升,还需要同步调整多个相关配置和代码逻辑,以确保应用在新版本系统上的兼容性、稳定性与安全性。以下是需要同步修改和注意的内容: ### 3.1 编译 SDK 版本和构建工具版本 升级 `targetSdkVersion` 时,通常也应同步更新 `compileSdkVersion` 和 `buildToolsVersion` 至对应版本,以确保使用最新的 API 和构建工具特性。例如: ```groovy android { compileSdkVersion 36 buildToolsVersion "36.0.0" // 或更高版本 defaultConfig { minSdkVersion 21 targetSdkVersion 36 versionCode 1 versionName "1.0" } } ``` 此做法有助于减少编译时的兼容性问题,并利用新版本 SDK 提供的优化功能[^2]。 ### 3.2 权限模型更新 Android 13(API 33)引入了更细粒度的运行时权限模型,例如 `READ_MEDIA_IMAGES`、`READ_MEDIA_VIDEO`、`READ_MEDIA_AUDIO` 等。如果应用需要访问媒体文件,必须根据具体类型申请权限,而不能继续使用 `READ_EXTERNAL_STORAGE`。此外,从 Android 14(API 34)开始,部分权限的使用场景也进一步受限,例如后台位置权限的获取需明确声明用途并获得用户确认[^2]。 ### 3.3 PendingIntent 的不可变性标志 从 Android 12(API 31)开始,`PendingIntent` 必须明确指定是否可变。在 `targetSdkVersion` 36 的应用中,所有创建 `PendingIntent` 的地方都应使用 `FLAG_IMMUTABLE` 或 `FLAG_MUTABLE` 标志,否则系统会抛出异常。例如: ```java PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE); ``` 该变更提高了应用的安全性,防止 PendingIntent 被恶意篡改。 ### 3.4 通知权限的强制请求 Android 13 引入了通知权限(`POST_NOTIFICATIONS`),即使应用之前默认允许发送通知,现在也必须在运行时请求用户授权。否则,通知将不会显示。例如: ```java if (ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.POST_NOTIFICATIONS}, REQUEST_CODE); } ``` 此变更增强了用户对通知的控制能力,开发者需在适当时机主动请求权限。 ### 3.5 前台服务类型声明 Android 10(API 29)引入了前台服务类型的细分,如 `FOREGROUND_SERVICE_TYPE_LOCATION`、`FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK` 等。在 `targetSdkVersion` 36 中,必须为每个前台服务指定具体类型,否则系统将抛出异常。例如: ```java startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION); ``` 这有助于系统更有效地管理资源并提升用户体验。 ### 3.6 后台启动 Activity 的限制 从 Android 10 开始,系统限制了应用在后台启动 Activity 的行为。在 `targetSdkVersion` 36 中,应用必须通过 `PendingIntent` 或 `ActivityService` 启动前台界面,并且只能在用户明确交互后执行。否则系统将阻止操作并抛出异常。这一限制旨在提升用户体验并防止恶意行为[^2]。 ### 3.7 存储访问适配 尽管作用域存储(Scoped Storage)从 Android 10(API 29)开始引入,但在 `targetSdkVersion` 36 中仍需确保应用完全适配。应使用 `MediaStore`、`Storage Access Framework` 或 `app-specific directories` 来访问文件,避免直接操作共享存储路径。对于访问其他应用的文件,必须通过 `Intent` 或 `ContentResolver` 获取授权访问。 ### 3.8 Gradle 配置统一管理 为了统一管理多个模块的 `targetSdkVersion` 和依赖库版本,可以在项目根目录下创建 `config.gradle` 文件,并在各模块的 `build.gradle` 中引用该配置。例如: ```groovy // config.gradle ext { android = [ compileSdkVersion: 36, buildToolsVersion: "36.0.0", minSdkVersion : 21, targetSdkVersion : 36, versionCode : 1, versionName : "1.0" ] } ``` 然后在模块的 `build.gradle` 文件中应用该配置: ```groovy apply from: "../config.gradle" android { compileSdkVersion rootProject.ext.android.compileSdkVersion buildToolsVersion rootProject.ext.android.buildToolsVersion defaultConfig { minSdkVersion rootProject.ext.android.minSdkVersion targetSdkVersion rootProject.ext.android.targetSdkVersion versionCode rootProject.ext.android.versionCode versionName rootProject.ext.android.versionName } } ``` 此方式可集中管理配置,减少版本不一致带来的问题[^1]。 ### 3.9 依赖库版本兼容性检查 升级 `targetSdkVersion` 后,需检查项目中使用的第三方库是否兼容 API 36。部分库可能仍依赖旧版本的 SDK 或未适配新的权限模型、通知机制等。建议更新到最新版本的依赖库,并测试其行为是否符合预期。例如: ```groovy dependencies { implementation 'androidx.core:core-ktx:1.10.0' implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'com.google.android.material:material:1.9.0' } ``` ### 3.10 用户交互行为变更 Android 12(API 31)对用户交互行为进行了限制,例如在没有用户直接触发的情况下,禁止启动前台 Activity。在 `targetSdkVersion` 36 中,所有启动 Activity 的操作都应确保是用户主动行为,否则将被系统阻止。开发者应检查应用中所有启动 Activity 的逻辑,确保符合新规则。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值