targetSdkVersion升级到28一些修改的地方(持续更新)

前言

Google Play应用市场对于应用的targetSdkVersion有了更为严格的要求。从 2018 年 8 月 1 日起,所有向 Google Play 首次提交的新应用都必须针对 Android 8.0 (API 等级 26) 开发; 2018 年 11 月 1 日起,所有 Google Play 的现有应用更新同样必须针对 Android 8.0。转载请注明来源「Bug总柴」

以下记录了我们升级targetSdkVersion的坑以及解决办法,希望对各位开发者有帮助。

错误1. java.lang.IllegalStateException: Not allowed to start service Intent {}: app is in background uid UidRecord{}

原因分析

从Android8.0开始,系统会对后台执行进行限制。初步判断由于我们应用在Application的onCreate过程中使用了IntentService来后台初始化一些任务,这个时候被系统认为是应用还处于后台,从而报出了java.lang.IllegalStateException错误。

解决办法

解决后台服务的限制,首先想到的办法是将服务变成前台服,随即我们又遇到了另一个问题,见错误2

错误2. android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{}

原因分析

见Android8.0行为变更。新的 Context.startForegroundService() 函数将启动一个前台服务。现在,即使应用在后台运行,系统也允许其调用 Context.startForegroundService()。不过,应用必须在创建服务后的五秒内调用该服务的 startForeground() 函数。

解决办法

在后台服务启动执行执行之后,通过Service.startForeground()方法传入notification变成前台服务。需要注意的是从Android8.0开始,Notification必须制定Channel才可以正常弹出通知,如果创建Notification Channels详见这里

由于我们的初衷是在启动程序的过程中后台进行一些初始化,这种前台给用户带来感知的效果并不是我们所希望的,因此我们考虑可以采用另一个后台执行任务的方法。这里官方推荐使用JobScheduler。由于我们引入了ktx以及WorkManager,这里我们采用了OneTimeWorkRequest来实现。具体实现如下:

class InitWorker : Worker(){
    override fun doWork(): Result {
        // 把耗时的启动任务放在这里

        return Result.SUCCESS
    }
}

然后在Applicaiton的onCreate中调用

val initWork = OneTimeWorkRequestBuilder<InitWorker>().build()
WorkManager.getInstance().enqueue(initWork)

来执行后台初始化工作

错误3. java.lang.NoClassDefFoundError: Failed resolution of: Lorg/apache/http/ProtocolVersion; Caused by: java.lang.ClassNotFoundException: Didn't find class "org.apache.http.ProtocolVersion"

原因分析

Android P Developer Preview的bug

解决办法

在AndroidManifest.xml文件中<Application>标签里面加入

<uses-library android:name="org.apache.http.legacy" android:required="false"/>

错误4. java.io.IOException: Cleartext HTTP traffic to dict.youdao.com not permitted

原因分析

从Android 6.0开始引入了对Https的推荐支持,与以往不同,Android P的系统上面默认所有Http的请求都被阻止了。

<application android:usesCleartextTraffic=["true" | "false"]>

原本这个属性的默认值从true改变为false

解决办法

解决的办法简单来说可以通过在AnroidManifest.xml中的application显示设置

<application android:usesCleartextTraffic="true">

更为根本的解决办法是修改应用程序中Http的请求为Https,当然这也需要服务端的支持。

错误5. android.os.FileUriExposedException file exposed beyond app through Intent.getData()

原因分析

主要原因是7.0系统对file uri的暴露做了限制,加强了安全机制。详见:官方文档
代码里出现问题的原因是,在需要安装应用的时候将下载下来的安装包地址传给了application/vnd.android.package-archive的intent

解决办法

使用FileProvider
具体代码可参考这篇文章
简单说明就是要在AndroidManifest里面声明FileProvider,并且在xml中声明需要使用的uri路径

<provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="${applicationId}.fileProvider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths" />
</provider>

对应的xml/file_paths中指定需要使用的目录

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path
        name="download"
        path="yddownload"/>
</paths>

错误6. java.lang.SecurityException: Failed to find provider ** for user 0; expected to find a valid ContentProvider for this authority

原因分析

target到android8.0之后对ContentResolver.notifyChange() 以及 registerContentObserver(Uri, boolean, ContentObserver)做了限制,官方解释在这里

解决办法

参考文章
简单来说解决的办法就是创建一个contentprovider,并在AndroidManifest里面注册的provider的authority声明为registerContentObserver中uri的authority就可以了。

<provider
        android:name=".download.DownloadUriProvider"
        android:authorities="${applicationId}"
        android:enabled="true"
        android:exported="false"/>
public class DownloadUriProvider extends ContentProvider {
    public DownloadUriProvider() {
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        return 0;
    }

    @Override
    public String getType(Uri uri) {
        return null;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        return null;
    }

    @Override
    public boolean onCreate() {
        return true;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        return null;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        return 0;
    }
}

错误7. notification没有显示

原因分析

如果targetsdkversion设定为26或以上,开始要求notification必须知道channel,具体查阅这里

解决办法

在notify之前先创建notificationChannel

private void createNotificationChannel() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        CharSequence name = "下载提醒";
        String description = "显示下载过程及进度";
        int importance = NotificationManager.IMPORTANCE_DEFAULT;
        NotificationChannel channel = new NotificationChannel(DOWNLOAD_CHANNEL_ID, name, importance);
        channel.setDescription(description);
        mNotificationManager.createNotificationChannel(channel);
    }
}

错误8. 在AndroidManifest中注册的receiver不能收到广播

原因分析

针对targetsdkversion为26的应用,加强对匿名receiver的控制,以至于在manifest中注册的隐式receiver都失效。具体见官方原文

解决办法

将广播从在AndroidManifest中注册移到在Activity中使用registerReceiver注册

错误9. 无法通过“application/vnd.android.package-archive” action安装应用

原因分析

targetsdkversion大于25必须声明REQUEST_INSTALL_PACKAGES权限,见官方说明:
REQUEST_INSTALL_PACKAGES

解决办法

在AndroidManifest中加入

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

错误10. java.lang.RuntimeException: Unable to start activity ComponentInfo{xxxActivity}: java.lang.IllegalStateException: Only fullscreen opaque activities can request orientation

原因分析

targetsdk26以上,对于透明主题的activity不能够通过manifest设定android:screenOrientation。
具体分析见这里

解决办法

检查报错的Activity是否在AndroidManifest中声明了
,若有需要将其去除。

所有影响checklist

Andriod版本适配 check list

推荐阅读更多精彩内容

  • 之前整理过一些关于常见的错误日志,基于生产的bug日志系统,我这边会不间断的更新错误日志及相应的解决方案,抛砖引玉...
    大荣言午阅读 11,734评论 1 8
  • 随着android 版本不得不断发布 也随着应用的发展吧由谷歌率先要求,所有 Google Play 的现有...
    卡卡_4ae1阅读 739评论 0 1
  • 0.Android手机操作系统的四层架构? Applications , Application Framewor...
    lucas777阅读 5,145评论 0 16
  • Android.util.Log 常用的方法有以下5个: Log.v() Log.d() Log.i() Log....
    Michael_荣阅读 2,540评论 0 3
  • 【新的一年,用努力迎接更好的自己】 今天,大年初三,也是立春后的第三天。 “一年之计在于春”,今天起,请开启一整年...
    格局星星阅读 96评论 0 1
  • “我这一生都排在第二。高中时我是全美第二好的球员,选秀的时候我是二号秀,MVP投票中三次我都是第二名,总决赛我还是...
    飞城阅读 107评论 0 1
  • 前天,我们测试了卷子测试完交给杨老师了然后我们等待成绩今天 ,成绩发了下来了。哎!我的分数又很低分数是32.5我心...
    李博一西部大镖客阅读 128评论 0 0
  • 这是第二次陪孩子玩密室逃脱,说真心话,真的不喜欢这个游戏,因为我害怕,每次孩子都告诉我多恐怖,多害怕,吓...
    晋春77阅读 977评论 2 5