Android Fk:【JavaCrash】Android 26以后限制使用startService启动后台服务

Android Fk:【JavaCrash】Android 26以后限制使用startService启动后台服务

一. 问题概述

1.出错调用栈

E AndroidRuntime: java.lang.IllegalStateException: Not allowed to start service Intent { act=com.unionpay.uppay.action.HCE pkg=com.sankuai.meituan }: app is in background uid null

07-23 19:06:29.734 15328 15377 E AndroidRuntime: FATAL EXCEPTION: Thread-9
07-23 19:06:29.734 15328 15377 E AndroidRuntime: Process: com.unionpay.uppay, PID: 15328
07-23 19:06:29.734 15328 15377 E AndroidRuntime: java.lang.IllegalStateException: Not allowed to start service Intent { act=com.unionpay.uppay.action.HCE pkg=com.sankuai.meituan }: app is in background uid null
07-23 19:06:29.734 15328 15377 E AndroidRuntime: at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1515)
07-23 19:06:29.734 15328 15377 E AndroidRuntime: at android.app.ContextImpl.startService(ContextImpl.java:1471)
07-23 19:06:29.734 15328 15377 E AndroidRuntime: at android.content.ContextWrapper.startService(ContextWrapper.java:654)
07-23 19:06:29.734 15328 15377 E AndroidRuntime: at com.unionpay.mobile.android.hce.f.a(Unknown Source:33)
07-23 19:06:29.734 15328 15377 E AndroidRuntime: at com.unionpay.mobile.android.hce.h.run(Unknown Source:6)

2.主要原因:

Android O 8.0(API 26之后) 之后不再允许后台service直接通过startService方式去启动, 具体行为变更
如果针对 Android 8.0 的应用尝试在不允许其创建后台服务的情况下使用 startService() 函数,则该函数将引发一个 IllegalStateException。新的 Context.startForegroundService() 函数将启动一个前台服务。
现在,即使应用在后台运行, 系统也允许其调用 Context.startForegroundService()。不过,应用必须在创建服务后的五秒内调用该服务的 startForeground() 函数。

3.解决办法:

1. 修改启动方式

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    context.startForegroundService(intent);
} else {
    context.startService(intent);
}
//并且在service里再调用startForeground方法,不然就会出现ANR
context.startForeground(SERVICE_ID, builder.getNotification());

该种方式启动用户会有感知啊,有个通知额,==

2. 调用者作好保护,防止被炸

try {
      Intent cameraIntent = new Intent("XXX.XXX");
      cameraIntent.setPackage("com.XXX.XXX");
      mContext.startServiceAsUser(cameraIntent, UserHandle.CURRENT);
 } catch (Exception e) {
      Slog.e(TAG, "IllegalAccessException", e);
}

3. 放弃使用起后台服务唤醒进程

采用jobJobScheduler替换需要起后台服务的唤醒操作方式。

二.原因分析

1. 先搜所log打印的地方

搜索“Not allowed to start service”

    //frameworks/base/core/java/android/app/ContextImpl.java
    private ComponentName startServiceCommon(Intent service, boolean requireForeground,
            UserHandle user) {
        try {

            ComponentName cn = ActivityManager.getService().startService(
                mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(
                            getContentResolver()), requireForeground,
                            getOpPackageName(), user.getIdentifier());
            if (cn != null) {
                ...
                } else if (cn.getPackageName().equals("?")) {
                //看出是在这里抛的异常
                    throw new IllegalStateException(
                            "Not allowed to start service " + service + ": " + cn.getClassName());
                }
            }
            return cn;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

搜索“app is in background”

    //frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
    ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
            int callingPid, int callingUid, boolean fgRequired, String callingPackage, final int userId)
            throws TransactionTooLargeException {
        // If this isn't a direct-to-foreground start, check our ability to kick off an
        // arbitrary service
        if (!r.startRequested && !fgRequired) {
             ...
             return new ComponentName("?", "app is in background uid " + uidRec);
             ...
        }
    }

下面就是找这两者之间的联系了,概括在图中:
这里写图片描述

三.总结

  1. 从抛异常的地方可以看出,最终将是调用者会crash(如果不保护处理的话)
    即 A应用进程以startservice的形式调用B应用进程的service,如果满足以下条件:
    a.O及O以上的手机平台上
    b.B应用进程的AndroidManifest里声明了targetSdk大于与等于26
    c.B应用进程不是persistent应用
    d.B应用进程当前进入后台且处于idle状态
    e.B应用不在电源管理的白名单中
    f.B应用进程不再运行后台运行的白名单中
    此时A应用进程就会crash(如果不做相关保护的话)
  2. A应用进程可以是system_server
  3. O及O以上的app尽量不要通过起后台service进行操作,需要用到后台service的话可以通过JobScheduler进行处理,即如果你是个简单的三方应用,不要再使用
    调用后台service的形式唤醒应用了,调用者会很危险!!!
    4.具体流程图如下:
    (提供上面简图的draw.io的xml文件,可以使用draw.io导入修改
    提供如下时序图的uml文件,可使用带plantuml插件的inteliJ AS打开编辑
    https://pan.baidu.com/s/17MO9CXdcSBLI_kuywvCuZA)
    这里写图片描述

猜你喜欢

转载自blog.csdn.net/TaylorPotter/article/details/82466706