乐趣区

关于android:AndroidP-开机后台启动service

问题背景 :客户要求开机能启动第三方利用 service。

思路一: 写个 Receiver 监听开机播送,在 onReceive 中 startService。

依照这个思路,再解决 startService 中碰到的一些问题。
实现大抵如下:
Receiver 监听 BOOT_COMPLETED 这个就比较简单了,大抵代码如下:

//AndroidManifest.xml 中申明 receiver 及权限配置,service 申明等
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

        <receiver android:name=".BootReceiver">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
            </intent-filter>
        </receiver>
        <service  android:exported="true" 
            android:name="com.example.servicedemo.BootService">
            <intent-filter>
                <action android:name="com.example.servicedemo.boot_service"/>
            </intent-filter>
        </service>

//BootReceiver 的 onReceive 办法中启动 service:public void onReceive(Context context, Intent intent) {context.startService(new Intent(context, BootService.class));
    }

碰到的问题就是 P 上不再容许后盾启动 service 了。所以在 framework 中加了一个白名单过滤:

diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index ca715b5..9df5245 100644
— a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -389,7 +389,7 @@ public final class ActiveServices {
ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
int callingPid, int callingUid, boolean fgRequired, String callingPackage, final int userId)
throws TransactionTooLargeException {if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "startService:" + service
+ if (true) Slog.v(TAG_SERVICE, "startService:" + service
+ "type=" + resolvedType + "args=" + service.getExtras());

final boolean callerFg;
@@ -473,7 +473,11 @@ public final class ActiveServices {
// background, then at this point we aren't going to let it period.
final int allowed = mAm.getAppStartModeLocked(r.appInfo.uid, r.packageName,
r.appInfo.targetSdkVersion, callingPid, false, false, forcedStandby);

if (allowed != ActivityManager.APP_START_MODE_NORMAL) { + //add Background start service white list

+ boolean isBackgroundStartServiceWhiteList="com.example.servicedemo".equals(callingPackage);
+ Slog.d(TAG, "callingPackage =" + callingPackage + "isBackgroundStartServiceWhiteList =" + isBackgroundStartServiceWhiteList);
+ //add Background start service white list
+ if (allowed != ActivityManager.APP_START_MODE_NORMAL && !isBackgroundStartServiceWhiteList)

{Slog.w(TAG, "Background start not allowed: service" + service + "to" + r.name.flattenToShortString() + "from pid=" + callingPid + "uid=" + callingUid

后果客户说监听开机播送再启动 service 可能会慢,须要尽早启动,同时在启动 service 的时候还要写 SharedPreference,这里就还存在另外一个问题:SharedPreferences in credential encrypted storage are not available until after user is unlocked,具体 log 如下:

--------- beginning of crash
08-26 10:40:16.468  3060  3060 E AndroidRuntime: FATAL EXCEPTION: main
08-26 10:40:16.468  3060  3060 E AndroidRuntime: Process: com.example.servicedemo, PID: 3060
08-26 10:40:16.468  3060  3060 E AndroidRuntime: java.lang.RuntimeException: Unable to create service com.example.servicedemo.BootService: java.lang.IllegalStateException: SharedPreferences in credential encrypted storage are not available until after user is unlocked
08-26 10:40:16.468  3060  3060 E AndroidRuntime:     at android.app.ActivityThread.handleCreateService(ActivityThread.java:3544)
08-26 10:40:16.468  3060  3060 E AndroidRuntime:     at android.app.ActivityThread.access$1300(ActivityThread.java:199)
08-26 10:40:16.468  3060  3060 E AndroidRuntime:     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1666)
08-26 10:40:16.468  3060  3060 E AndroidRuntime:     at android.os.Handler.dispatchMessage(Handler.java:106)
08-26 10:40:16.468  3060  3060 E AndroidRuntime:     at android.os.Looper.loop(Looper.java:193)
08-26 10:40:16.468  3060  3060 E AndroidRuntime:     at android.app.ActivityThread.main(ActivityThread.java:6670)
08-26 10:40:16.468  3060  3060 E AndroidRuntime:     at java.lang.reflect.Method.invoke(Native Method)
08-26 10:40:16.468  3060  3060 E AndroidRuntime:     at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
08-26 10:40:16.468  3060  3060 E AndroidRuntime:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
08-26 10:40:16.468  3060  3060 E AndroidRuntime: Caused by: java.lang.IllegalStateException: SharedPreferences in credential encrypted storage are not available until after user is unlocked
08-26 10:40:16.468  3060  3060 E AndroidRuntime:     at android.app.ContextImpl.getSharedPreferences(ContextImpl.java:419)
08-26 10:40:16.468  3060  3060 E AndroidRuntime:     at android.app.ContextImpl.getSharedPreferences(ContextImpl.java:404)
08-26 10:40:16.468  3060  3060 E AndroidRuntime:     at android.content.ContextWrapper.getSharedPreferences(ContextWrapper.java:174)
08-26 10:40:16.468  3060  3060 E AndroidRuntime:     at com.example.servicedemo.BootService.onCreate(BootService.java:27)
08-26 10:40:16.468  3060  3060 E AndroidRuntime:     at android.app.ActivityThread.handleCreateService(ActivityThread.java:3532)
08-26 10:40:16.468  3060  3060 E AndroidRuntime:     ... 8 more

这个问题本来也比拟好解决,一来 receiver 中减少 android:directBootAware=”true” 属性配置,二来 Context 应用 Context directBootContext = appContext.createDeviceProtectedStorageContext() 获取。不过客户说 receiver 代码不能改变,此路不通……
官网:https://developer.android.com…
csdn:https://blog.csdn.net/wuweika…

思路二:framework 中想方法间接启动 service

在 SystemServer.java 中有如下代码,能够参考这种写法写一个 servicedemo 的启动:

            traceBeginAndSlog("StartSystemUI");
            try {startSystemUi(context, windowManagerF);
            } catch (Throwable e) {reportWtf("starting System UI", e);
            }
            traceEnd();

    static final void startSystemUi(Context context, WindowManagerService windowManager) {Intent intent = new Intent();
        intent.setComponent(new ComponentName("com.android.systemui",
                    "com.android.systemui.SystemUIService"));
        intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
        context.startServiceAsUser(intent, UserHandle.SYSTEM);
        windowManager.onSystemUiStarted();}

或者在 ActivityManagerService.java 的 finishBooting 办法结尾处 startService:

            try {Slog.i(TAG, "start service");
                Intent ii = new Intent();
                ii.setClassName("com.example.servicedemo", "com.example.servicedemo.BootService");
                mContext.startServiceAsUser(ii, UserHandle.SYSTEM);
            } catch (Exception exx) {Slog.e(TAG, "start error");
                exx.printStackTrace();}

两者成果差不多。
这样启动会报一个错:

10011 09-01 13:21:46.802  2557  2572 W ActivityManager: Unable to start service Intent {cmp=com.example.servicedemo/.BootService} U=0: not found

这个 log 是在 ActiveServices.java 的 retrieveServiceLocked 办法中输入的:

                ResolveInfo rInfo = mAm.getPackageManagerInternalLocked().resolveService(service,
                        resolvedType, flags, userId, callingUid);
                ServiceInfo sInfo =
                    rInfo != null ? rInfo.serviceInfo : null;
                if (sInfo == null) {
                    Slog.w(TAG_SERVICE, "Unable to start service" + service + "U=" + userId +
                          ": not found");
                    return null;
                }

跟踪 startServiceAsUser 办法,同时联合报错点 resolveService 这个办法,梳理出流程:

//[ContextImpl.java][startServiceAsUser]
    @Override
    public ComponentName startServiceAsUser(Intent service, UserHandle user) {return startServiceCommon(service, false, user);
    }

//[ContextImpl.java][startServiceCommon]

    private ComponentName startServiceCommon(Intent service, boolean requireForeground,
            UserHandle user) {
        try {validateServiceIntent(service);
            service.prepareToLeaveProcess(this);
            ComponentName cn = ActivityManager.getService().startService(mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(getContentResolver()), requireForeground,
                            getOpPackageName(), user.getIdentifier());
            if (cn != null) {if (cn.getPackageName().equals("!")) {
                    throw new SecurityException(
                            "Not allowed to start service" + service
                            + "without permission" + cn.getClassName());
                } else if (cn.getPackageName().equals("!!")) {
                    throw new SecurityException(
                            "Unable to start service" + service
                            + ":" + cn.getClassName());
                } else if (cn.getPackageName().equals("?")) {
                    throw new IllegalStateException("Not allowed to start service" + service + ":" + cn.getClassName());
                }
            }
            return cn;
        } catch (RemoteException e) {throw e.rethrowFromSystemServer();
        }
    }

//[ActivityManagerService.java][startService]
    @Override
    public ComponentName startService(IApplicationThread caller, Intent service,
            String resolvedType, boolean requireForeground, String callingPackage, int userId)
            throws TransactionTooLargeException {enforceNotIsolatedCaller("startService");
        // Refuse possible leaked file descriptors
        if (service != null && service.hasFileDescriptors() == true) {throw new IllegalArgumentException("File descriptors passed in Intent");
        }

        if (callingPackage == null) {throw new IllegalArgumentException("callingPackage cannot be null");
        }

        if (DEBUG_SERVICE) Slog.v(TAG_SERVICE,
                "*** startService:" + service + "type=" + resolvedType + "fg=" + requireForeground);
        synchronized(this) {final int callingPid = Binder.getCallingPid();
            final int callingUid = Binder.getCallingUid();
            final long origId = Binder.clearCallingIdentity();
            ComponentName res;
            try {
                res = mServices.startServiceLocked(caller, service,
                        resolvedType, callingPid, callingUid,
                        requireForeground, callingPackage, userId);
            } finally {Binder.restoreCallingIdentity(origId);
            }
            return res;
        }
    }

//[ActiveServices.java][startServiceLocked]

        ServiceLookupResult res =
            retrieveServiceLocked(service, resolvedType, callingPackage,
                    callingPid, callingUid, userId, true, callerFg, false, false);

//[ActiveServices.java][retrieveServiceLocked]
                ResolveInfo rInfo = mAm.getPackageManagerInternalLocked().resolveService(service,
                        resolvedType, flags, userId, callingUid);
                ServiceInfo sInfo =
                    rInfo != null ? rInfo.serviceInfo : null;
                if (sInfo == null) {
                    Slog.w(TAG_SERVICE, "Unable to start service" + service + "U=" + userId +
                          ": not found");// 这里就是报错的点
                    return null;
                }

//[PackageManagerService.java][resolveService]
    @Override
    public ResolveInfo resolveService(Intent intent, String resolvedType, int flags, int userId) {Slog.d(TAG, "resolveService userId =" + userId + "intent =" + intent);
        final int callingUid = Binder.getCallingUid();
        return resolveServiceInternal(intent, resolvedType, flags, userId, callingUid);
    }

//[PackageManagerService.java][resolveServiceInternal]
    private ResolveInfo resolveServiceInternal(Intent intent, String resolvedType, int flags,
            int userId, int callingUid) {Slog.d(TAG, "resolveServiceInternal userId =" + userId + "intent =" + intent);
        if (!sUserManager.exists(userId)) {Slog.e(TAG, "resolveServiceInternal userId NOT exist");
            return null;
        }
        Slog.d(TAG, "resolveServiceInternal updateFlagsForResolve");
        flags = updateFlagsForResolve(flags, userId, intent, callingUid, false /*includeInstantApps*/);
        List<ResolveInfo> query = queryIntentServicesInternal(intent, resolvedType, flags, userId, callingUid, false /*includeInstantApps*/);// 要害函数
        if (query != null) {Slog.d(TAG, "resolveServiceInternal query size =" + query.size());
            if (query.size() >= 1) {
                // If there is more than one service with the same priority,
                // just arbitrarily pick the first one.
                return query.get(0);
            }
        } else {Slog.d(TAG, "resolveServiceInternal query is null");
        }
        return null;
    }

//[PackageManagerService.java][queryIntentServicesInternal]
        Slog.d(TAG, "queryIntentServicesInternal comp =" + comp);
        if (comp != null) {final List<ResolveInfo> list = new ArrayList<ResolveInfo>(1);
            final ServiceInfo si = getServiceInfo(comp, flags, userId);// 这里
            if (si != null) {........}
            ........
        }

    @Override
    public ServiceInfo getServiceInfo(ComponentName component, int flags, int userId) {Slog.d(TAG, "getServiceInfo flag =" + flags + "userId ="+ userId);
        if (!sUserManager.exists(userId)) {Slog.d(TAG, "getServiceInfo userId invalid, return");
            return null;
        }
        final int callingUid = Binder.getCallingUid();
        flags = updateFlagsForComponent(flags, userId, component);
        Slog.d(TAG, "getServiceInfo flag =" + flags + "callingUid ="+ callingUid);
        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                false /* requireFullPermission */, false /* checkShell */, "get service info");
        synchronized (mPackages) {PackageParser.Service s = mServices.mServices.get(component);
            if (true) Log.v(TAG, "getServiceInfo" + component + ":" + s);
            if (s != null && mSettings.isEnabledAndMatchLPr(s.info, flags, userId)) {// 这里
                PackageSetting ps = mSettings.mPackages.get(component.getPackageName());
                if (ps == null) {Slog.e(TAG, "ps null, return");
                    return null;
                }
                if (filterAppAccessLPr(ps, callingUid, component, TYPE_SERVICE, userId)) {Slog.e(TAG, "filterAppAccessLPr return");
                    return null;
                }
                Slog.d(TAG, "filterAppAccessLPr return generateServiceInfo");
                return PackageParser.generateServiceInfo(s, flags, ps.readUserState(userId), userId);
            } else {Slog.e(TAG, "s null");
            }
        }

        Slog.e(TAG, "last return null");
        return null;
    }
//[services/core/java/com/android/server/pm/Settings.java][isEnabledAndMatchLPr]
    boolean isEnabledAndMatchLPr(ComponentInfo componentInfo, int flags, int userId) {final PackageSetting ps = mPackages.get(componentInfo.packageName);
        if (ps == null) {Slog.d(TAG, "isEnabledAndMatchLPr ps null, return");
            return false;
        }

        final PackageUserState userState = ps.readUserState(userId);
        boolean isMatch = userState.isMatch(componentInfo, flags);// 这里
        Slog.d(TAG, "isEnabledAndMatchLPr isMatch ?" + isMatch);
        return isMatch;
    }

///[core/java/android/content/pm/PackageUserState.java][isMatch]
    public boolean isMatch(ComponentInfo componentInfo, int flags) {Slog.d(TAG, "isMatch componentInfo =" + componentInfo);
//        if ("com.example.servicedemo".equals(componentInfo.packageName)) {//            Slog.d(TAG, "white list package, return directly");
//            return true;
//        }

        final boolean isSystemApp = componentInfo.applicationInfo.isSystemApp();
        final boolean matchUninstalled = (flags & PackageManager.MATCH_KNOWN_PACKAGES) != 0;
        if (!isAvailable(flags)
                && !(isSystemApp && matchUninstalled)) return false;
        if (!isEnabled(componentInfo, flags)) return false;

        if ((flags & MATCH_SYSTEM_ONLY) != 0) {if (!isSystemApp) {return false;}
        }

        final boolean matchesUnaware = ((flags & MATCH_DIRECT_BOOT_UNAWARE) != 0)
                && !componentInfo.directBootAware;
        final boolean matchesAware = ((flags & MATCH_DIRECT_BOOT_AWARE) != 0)
                && componentInfo.directBootAware;
        Slog.d(TAG, "isMatch flags:" + flags + "-:" + (flags & MATCH_DIRECT_BOOT_UNAWARE) + "-" + componentInfo.directBootAware);

        Slog.d(TAG, "isMatch matchesUnaware:" + matchesUnaware + "matchesAware:" + matchesAware);
        Slog.d(TAG, "isMatch return" + (matchesUnaware || matchesAware));
        return matchesUnaware || matchesAware;
    }

整个流程追踪下来,能看到是 PackageUserState.java 这个办法返回了 false 所致。
log 如下:

10002 09-01 13:21:46.801  2557  2572 V PackageManager: getServiceInfo ComponentInfo{com.example.servicedemo/com.example.servicedemo.BootService}: Service{58d3746 com.example.servicedemo/.BootService}
10003 09-01 13:21:46.801  2557  2572 D PackageUserState: isMatch componentInfo = ServiceInfo{a1c0d07 com.example.servicedemo.BootService}
10004 09-01 13:21:46.801  2557  2572 D PackageUserState: isMatch flags: 268960768 -:0 - false
10005 09-01 13:21:46.801  2557  2572 D PackageUserState: isMatch matchesUnaware: false matchesAware:false
10006 09-01 13:21:46.802  2557  2572 D PackageUserState: isMatch return false
10007 09-01 13:21:46.802  2557  2572 D PackageSettings: isEnabledAndMatchLPr isMatch ? false
10008 09-01 13:21:46.802  2557  2572 E PackageManager: s null
10009 09-01 13:21:46.802  2557  2572 E PackageManager: last return null
10011 09-01 13:21:46.802  2557  2572 W ActivityManager: Unable to start service Intent {cmp=com.example.servicedemo/.BootService} U=0: not found

解决方案一个是在这里加上一个过滤白名单,如下面正文掉的代码,另外一个就是在 ActivityManagerService 的 finishBooting 中 startService 时,做个延时解决,如下:


        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {Slog.i(TAG, "finishBooting start service");
            try {Slog.i(TAG, "start service");
                Intent ii = new Intent();
                ii.setClassName("com.example.servicedemo", "com.example.servicedemo.BootService");
                mContext.startServiceAsUser(ii, UserHandle.SYSTEM);
            } catch (Exception exx) {Slog.e(TAG, "start error");
                exx.printStackTrace();}
            }
        }, 5*1000);
退出移动版