最近有个客户需求:开机启动launcher前,先开启一个客户的service。
想想觉得挺简单的,无非就是在AMS里面启动home前startservice就得了。不过,这样做不但不符合架构的风格,更出现了bug。接着,尝试在init.rc文件里面添加开机自启动项,把客户的service启动起来,但这又失败了。最后,在大神的指导下,终于完成了这项功能。下面来说一下实现的关键点。
关键点:
当一个APP的AndroidManifest.xml中的application标签中声明了android:persistent="true"这个属性后,该APP会比launcher先加载。代码块如下:
<application
android:name="com.example.myapp.MyApplication"
android:persistent="true"
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
...
...
</application>
根据这个特点,我们可以在自己的APP里面声明android:persistent="true"这个属性,然后自己的APP就可以在launcher启动前,先加载了。而每个应用程序在第一次启动时,都会首先创建Application对象。所以,我们需要实现一个自己的Application,在其onCreate方法里面开启service,这样service就就会在launcher启动前开启了。
部分代码如下:
public class MyApplication extends Application {
@Override
public void onCreate() {
// TODO Auto-generated method stub
super.onCreate();
startService(new Intent("service_action"));
}
}
打印日志,查看进程ID,确认service是比launcher先启动了。可是,为什么加了android:persistent="true"这个属性后,就会这样呢?于是,网上找找资料,看看源码,发现这是在AMS里面定义好了的。
在Android启动的时候,会在SystemServer.java里面启动各种系统service,包括AccountManagerService、ContentService、WindowManagerService、PackageManagerService、ActivityManagerService等服务。其中,在启动ActivityManagerService的最后,SystemServer.java会调用ActivityManagerService的systemReady方法。我们来看看此处调用的注释:
// We now tell the activity manager it is okay to run third party
// code. It will call back into us once it has gotten to the state
// where third party code can really run (but before it has actually
// started launching the initial applications), for us to complete our
// initialization.
mActivityManagerService.systemReady(new Runnable() {
@Override
public void run() {
...
...
});
从注释中可以看到,执行到systemReady这个方法就可以去加载第三方的app了,包括launcher这些系统应用。我们接着看AMS里面的systemReady方法:
public void systemReady(final Runnable goingCallback) {
...
...
if (mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
try {
List apps = AppGlobals.getPackageManager().
getPersistentApplications(STOCK_PM_FLAGS);
if (apps != null) {
int N = apps.size();
int i;
for (i=0; i<N; i++) {
ApplicationInfo info = (ApplicationInfo)apps.get(i);
if (info != null && !info.packageName.equals("android")) {
addAppLocked(info, false, null /* ABI override */);
}
}
}
} catch (RemoteException ex) {
// pm is in same process, this will never happen.
}
}
// Start up initial activity.
mBooting = true;
startHomeActivityLocked(mCurrentUserId, "systemReady");//启动launcher
...
...
}
显然,getPersistentApplications这个方法名已经告诉我们了,这个方法是用来获取那些在AndroidManifest.xml中的application标签中声明了android:persistent="true"这个属性的app的信息。这个方法是在PackageManagerService(PKMS)中实现的,来看一下这个方法:
public List<ApplicationInfo> getPersistentApplications(int flags) {
final ArrayList<ApplicationInfo> finalList = new ArrayList<ApplicationInfo>();
// reader
synchronized (mPackages) {
final Iterator<PackageParser.Package> i = mPackages.values().iterator();
final int userId = UserHandle.getCallingUserId();
while (i.hasNext()) {
final PackageParser.Package p = i.next();
if (p.applicationInfo != null
&& (p.applicationInfo.flags&ApplicationInfo.FLAG_PERSISTENT) != 0
&& (!mSafeMode || isSystemApp(p))) {
PackageSetting ps = mSettings.mPackages.get(p.packageName);
if (ps != null) {
ApplicationInfo ai = PackageParser.generateApplicationInfo(p, flags,
ps.readUserState(userId), userId);
if (ai != null) {
finalList.add(ai);
}
}
}
}
}
return finalList;
}
在PKMS中,有一个记录所有的程序包信息的哈希表(mPackages),每个表项中含有ApplicationInfo信息,该信息的flags(int型)数据中有一个专门的bit用于表示persistent。getPersistentApplications()函数会遍历这张表,找出所有persistent包,并返回ArrayList。
这里需要注意的是:
从代码里可以看出,带persistent标志的系统应用(即flags中设置了FLAG_SYSTEM)是一定会被选上的,但如果不是系统应用的话,则要进一步判断当前是否处于“安全模式”,一旦处于安全模式,那么就算应用设置了persistent属性,也不会被选中。
随后systemReady()开始遍历选中的ApplicationInfo,并对包名不为“android”的结点执行addAppLocked()。addAppLocked()的代码如下:
final ProcessRecord addAppLocked(ApplicationInfo info, boolean isolated,
String abiOverride) {
ProcessRecord app;
if (!isolated) {
app = getProcessRecordLocked(info.processName, info.uid, true);
} else {
app = null;
}
if (app == null) {
app = newProcessRecordLocked(info, null, isolated, 0);
mProcessNames.put(info.processName, app.uid, app);
if (isolated) {
mIsolatedProcesses.put(app.uid, app);
}
updateLruProcessLocked(app, false, null);
updateOomAdjLocked();
}
// This package really, really can not be stopped.
try {
AppGlobals.getPackageManager().setPackageStoppedState(
info.packageName, false, UserHandle.getUserId(app.uid));
} catch (RemoteException e) {
} catch (IllegalArgumentException e) {
Slog.w(TAG, "Failed trying to unstop package "
+ info.packageName + ": " + e);
}
if ((info.flags&(ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PERSISTENT))
== (ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PERSISTENT)) {
app.persistent = true;
app.maxAdj = ProcessList.PERSISTENT_PROC_ADJ;
}
if (app.thread == null && mPersistentStartingProcesses.indexOf(app) < 0) {
mPersistentStartingProcesses.add(app);
startProcessLocked(app, "added application", app.processName, abiOverride,
null /* entryPoint */, null /* entryPointArgs */);
}
return app;
}
在AMS中,所谓的“add App”主要是指“添加一个与App进程对应的ProcessRecord节点”。当然,如果该节点已经添加过了,那么是不会重复添加的。在添加节点的动作完成以后,addAppLocked()还会检查App进程是否已经启动好了,如果尚未开始启动,此时就会调用startProcessLocked()启动这个进程。既然addAppLocked()试图确认App“正在正常运作”或者“将被正常启动”,那么其对应的package就不可能处于stopped状态,这就是上面代码调用setPackageStoppedState(…,false,…)的意思。
总的来说,带有android:persistent="true"这个属性的系统应用能在Android开机的时候开启。AMS里面的systemReady方法启动完这些应用后,接着执行startHomeActivityLocked方法来启动launcher。再把上面的代码贴一下:
public void systemReady(final Runnable goingCallback) {
...
...
if (mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
try {
List apps = AppGlobals.getPackageManager().
getPersistentApplications(STOCK_PM_FLAGS);
if (apps != null) {
int N = apps.size();
int i;
for (i=0; i<N; i++) {
ApplicationInfo info = (ApplicationInfo)apps.get(i);
if (info != null && !info.packageName.equals("android")) {
addAppLocked(info, false, null /* ABI override */);
}
}
}
} catch (RemoteException ex) {
// pm is in same process, this will never happen.
}
}
// Start up initial activity.
mBooting = true;
startHomeActivityLocked(mCurrentUserId, "systemReady");//启动launcher
...
...
}
至此,我们就可以解释了为什么加了android:persistent="true"这个属性就能在launcher启动前,启动service了。
后记:
前文提到,在init.rc文件里面添加开机自启动项,把service启动起来,失败了。但后来,我发现是我添加的代码有问题,现在把这个方法的实现写出来:
编写一个test.sh文件,让其启动service:
#! /system/bin/sh
echo "enter test.sh" > /Share/log.txt
date +%s >> /Share/log.txt
if [ ! -e /data/app/com.example.myapp-1.apk ] ; then
pm install /system/preinstall/myapp/MyApp.apk
am startservice -a service_action
fi
echo "end test.sh" >> /Share/log.txt
在某个mk文件(除Android.mk文件)中,把test.sh文件拷贝到Android系统路径下:
PRODUCT_COPY_FILES += \
device/common/test.sh:/system/bin/test.sh \
1
2
在init.rc文件里面声明启动test.sh脚本的代码:
service test /system/bin/sh /system/bin/test.sh
user root
group root
disabled
oneshot
on property:sys.system_ready=1
start test
其中,service test /system/bin/sh /system/bin/test.sh声明了一个名为test的服务,这个服务调用了/system/bin/sh这个可执行程序,后面的/system/bin/test.sh是这个可执行程序的参数。然后,需要在一个特定条件下开启这个服务。on property:sys.system_ready=1就是当sys.system_ready这个属性值等于1的时候,会执行下面的start test来启动这个test服务。关于init.rc的其他语法,我就不详述了,有兴趣可以找一下这方面的资料。
千万要记得要指定/system/bin/sh来启动/system/bin/test.sh,之前我就是没有添加/system/bin/sh这句代码,就导致了开启失败的。
本文介绍如何通过设置Android应用的persistent属性实现在系统启动时优先于Launcher启动Service的方法,并提供了使用init.rc文件实现开机自启Service的备选方案。
1893





