在launcher启动前,开启一个service

本文介绍如何通过设置Android应用的persistent属性实现在系统启动时优先于Launcher启动Service的方法,并提供了使用init.rc文件实现开机自启Service的备选方案。

最近有个客户需求:开机启动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这句代码,就导致了开启失败的。
 

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值