前言

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文件中标签里边参加

<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