AndroidP 开机后台启动service
问题背景:客户要求开机能启动第三方应用service。
思路一:写个Receiver监听开机广播,在onReceive中startService。
按照这个思路,再解决startService中碰到的一些问题。
实现大致如下:
Receiver监听BOOT_COMPLETED这个就比较简单了,大致代码如下:
//AndroidManifest.xml中声明receiver及权限配置,service声明等
//BootReceiver的onReceive方法中启动service:
public void onReceive(Context context, Intent intent) {
context.startService(new Intent(context, BootService.class));
}
【AndroidP 开机后台启动service】碰到的问题就是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.46830603060 E AndroidRuntime: FATAL EXCEPTION: main
08-26 10:40:16.46830603060 E AndroidRuntime: Process: com.example.servicedemo, PID: 3060
08-26 10:40:16.46830603060 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.46830603060 E AndroidRuntime:at android.app.ActivityThread.handleCreateService(ActivityThread.java:3544)
08-26 10:40:16.46830603060 E AndroidRuntime:at android.app.ActivityThread.access$1300(ActivityThread.java:199)
08-26 10:40:16.46830603060 E AndroidRuntime:at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1666)
08-26 10:40:16.46830603060 E AndroidRuntime:at android.os.Handler.dispatchMessage(Handler.java:106)
08-26 10:40:16.46830603060 E AndroidRuntime:at android.os.Looper.loop(Looper.java:193)
08-26 10:40:16.46830603060 E AndroidRuntime:at android.app.ActivityThread.main(ActivityThread.java:6670)
08-26 10:40:16.46830603060 E AndroidRuntime:at java.lang.reflect.Method.invoke(Native Method)
08-26 10:40:16.46830603060 E AndroidRuntime:at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
08-26 10:40:16.46830603060 E AndroidRuntime:at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
08-26 10:40:16.46830603060 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.46830603060 E AndroidRuntime:at android.app.ContextImpl.getSharedPreferences(ContextImpl.java:419)
08-26 10:40:16.46830603060 E AndroidRuntime:at android.app.ContextImpl.getSharedPreferences(ContextImpl.java:404)
08-26 10:40:16.46830603060 E AndroidRuntime:at android.content.ContextWrapper.getSharedPreferences(ContextWrapper.java:174)
08-26 10:40:16.46830603060 E AndroidRuntime:at com.example.servicedemo.BootService.onCreate(BootService.java:27)
08-26 10:40:16.46830603060 E AndroidRuntime:at android.app.ActivityThread.handleCreateService(ActivityThread.java:3532)
08-26 10:40:16.46830603060 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.80225572572 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 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 list = new ArrayList(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.80125572572 V PackageManager: getServiceInfo ComponentInfo{com.example.servicedemo/com.example.servicedemo.BootService}: Service{58d3746 com.example.servicedemo/.BootService}
10003 09-01 13:21:46.80125572572 D PackageUserState: isMatch componentInfo = ServiceInfo{a1c0d07 com.example.servicedemo.BootService}
10004 09-01 13:21:46.80125572572 D PackageUserState: isMatch flags: 268960768 -:0 - false
10005 09-01 13:21:46.80125572572 D PackageUserState: isMatch matchesUnaware: false matchesAware:false
10006 09-01 13:21:46.80225572572 D PackageUserState: isMatch return false
10007 09-01 13:21:46.80225572572 D PackageSettings: isEnabledAndMatchLPr isMatch ? false
10008 09-01 13:21:46.80225572572 E PackageManager: s null
10009 09-01 13:21:46.80225572572 E PackageManager: last return null
10011 09-01 13:21:46.80225572572 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);
推荐阅读
- echart|echart 双轴图开发
- locate搜索
- 8、Flask构建弹幕微电影网站-搭建后台页面-密码修改、主页控制面板
- ubuntu开机默认进入命令行模式/用户图形界面
- 命令行上传小程序版本至微信后台
- echarts插件-从后台请求的数据在页面显示空白的问题
- Ubuntu|Ubuntu 下添加开机启动脚本
- 部署教程
- 后台|NATAPP内网穿透通过nginx实现一个端口访问多个不同端口服务
- vue|电商后台管理系统(vue+python|node.js)