文档章节

说说Android应用的persistent属性

悠然红茶
 悠然红茶
发布于 2014/05/27 09:32
字数 1893
阅读 55876
收藏 27

说说Android应用的persistent属性

 

侯 亮

 

 

1 启动persistent应用

    在Android系统中,有一种永久性应用。它们对应的AndroidManifest.xml文件里,会将persistent属性设为true,比如:

<application android:name="PhoneApp" 
android:persistent="true" 
android:label="@string/dialerIconLabel" 
android:icon="@drawable/ic_launcher_phone">


    在系统启动之时,AMS的systemReady()会加载所有persistent为true的应用。

public void systemReady(final Runnable goingCallback) 
{
    . . . . . .
    . . . . . .
    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);
                }
            }
        }
    } 
    catch (RemoteException ex) {
        // pm is in same process, this will never happen.
    }

其中的STOCK_PM_FLAGS的定义如下:

// The flags that are set for all calls we make to the package manager.
static final int STOCK_PM_FLAGS = PackageManager.GET_SHARED_LIBRARY_FILES;

上面代码中的getPersistentApplications()函数的定义如下:

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 = UserId.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);
                finalList.add(PackageParser.generateApplicationInfo(p, flags,
                        ps != null ? ps.getStopped(userId) : false,
                        ps != null ? ps.getEnabled(userId) : COMPONENT_ENABLED_STATE_DEFAULT,
                        userId));
            }
        }
    }

    return finalList;
}


     在PKMS中,有一个记录所有的程序包信息的哈希表(mPackages),每个表项中含有ApplicationInfo信息,该信息的flags(int型)数据中有一个专门的bit用于表示persistent。getPersistentApplications()函数会遍历这张表,找出所有persistent包,并返回ArrayList<ApplicationInfo>。


     从代码里可以看出,带persistent标志的系统应用(即flags中设置了FLAG_SYSTEM)是一定会被选上的,但如果不是系统应用的话,则要进一步判断当前是否处于“安全模式”,一旦处于安全模式,那么就算应用设置了persistent属性,也不会被选中。

     随后systemReady()开始遍历选中的ApplicationInfo,并对包名不为“android”的结点执行addAppLocked()。addAppLocked()的代码如下:

final ProcessRecord addAppLocked(ApplicationInfo info, boolean isolated) 
{
    ProcessRecord app;
    if (!isolated) {
        app = getProcessRecordLocked(info.processName, info.uid);
    } else {
        app = null;
    }

    if (app == null) {
        app = newProcessRecordLocked(null, info, null, isolated);
        mProcessNames.put(info.processName, app.uid, app);
        if (isolated) {
            mIsolatedProcesses.put(app.uid, app);
        }
        updateLruProcessLocked(app, true, true);
    }

    // This package really, really can not be stopped.
    try {
        AppGlobals.getPackageManager().setPackageStoppedState(
                info.packageName, false, UserId.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);
    }

    return app;
}


    在AMS中,所谓的“add App”主要是指“添加一个与App进程对应的ProcessRecord节点”。当然,如果该节点已经添加过了,那么是不会重复添加的。在添加节点的动作完成以后,addAppLocked()还会检查App进程是否已经启动好了,如果尚未开始启动,此时就会调用startProcessLocked()启动这个进程。既然addAppLocked()试图确认App“正在正常运作”或者“将被正常启动”,那么其对应的package就不可能处于stopped状态,这就是上面代码调用setPackageStoppedState(...,false,...)的意思。


    现在,我们就清楚了,那些persistent属性为true的应用,基本上都是在系统启动伊始就启动起来的。

    因为启动进程的过程是异步的,所以我们需要一个缓冲列表(即上面代码中的mPersistentStartingProcesses列表)来记录那些“正处于启动状态,而又没有启动完毕的”ProcessRecord结点。一旦目标进程启动完毕后,目标进程会attach系统,于是走到AMS的attachApplicationLocked(),在这个函数里,会把目标进程对应的ProcessRecord结点从mPersistentStartingProcesses缓冲列表里删除。

private final boolean attachApplicationLocked(IApplicationThread thread, int pid) {

        // Find the application record that is being attached...  either via
        // the pid if we are running in multiple processes, or just pull the
        // next app record if we are emulating process with anonymous threads.
        ProcessRecord app;
        
        . . . . . .
        thread.asBinder().linkToDeath(adr, 0);
        . . . . . .
        thread.bindApplication(processName, appInfo, providers,
                    app.instrumentationClass, profileFile, profileFd, profileAutoStop,
                    app.instrumentationArguments, app.instrumentationWatcher, testMode,
                    enableOpenGlTrace, isRestrictedBackupMode || !normalMode, 
                    app.persistent,
                    new Configuration(mConfiguration), app.compat, 
                    getCommonServicesLocked(),
                    mCoreSettingsObserver.getCoreSettingsLocked());
        . . . . . .
        . . . . . .
        // Remove this record from the list of starting applications.
        mPersistentStartingProcesses.remove(app);
        . . . . . .

2 如何保证应用的持久性(persistent)

    我们知道,persistent一词的意思是“持久”,那么persistent应用的意思又是什么呢?简单地说,这种应用会顽固地运行于系统之中,从系统一启动,一直到系统关机。

    为了保证这种持久性,persistent应用必须能够在异常出现时,自动重新启动。在Android里是这样实现的。每个ActivityThread中会有一个专门和AMS通信的binder实体——final ApplicationThread mAppThread。这个实体在AMS中对应的代理接口为IApplicationThread。

    当AMS执行到attachApplicationLocked()时,会针对目标用户进程的IApplicationThread接口,注册一个binder讣告监听器,一旦日后用户进程意外挂掉,AMS就能在第一时间感知到,并采取相应的措施。如果AMS发现意外挂掉的应用是persistent的,它会尝试重新启动这个应用。

    注册讣告监听器的代码如下:

AppDeathRecipient adr = new AppDeathRecipient(app, pid, thread);
thread.asBinder().linkToDeath(adr, 0);
app.deathRecipient = adr;

其中的thread就是IApplicationThread代理。

    AppDeathRecipient的定义如下:

private final class AppDeathRecipient implements IBinder.DeathRecipient 
{
    final ProcessRecord mApp;
    final int mPid;
    final IApplicationThread mAppThread;

    AppDeathRecipient(ProcessRecord app, int pid,
            IApplicationThread thread) 
    {
        if (localLOGV) 
            Slog.v(TAG, "New death recipient " + this
                   + " for thread " + thread.asBinder());
        mApp = app;
        mPid = pid;
        mAppThread = thread;
    }

    public void binderDied() 
    {
        if (localLOGV) 
            Slog.v(TAG, "Death received in " + this
                   + " for thread " + mAppThread.asBinder());
        synchronized(ActivityManagerService.this) 
        {
            appDiedLocked(mApp, mPid, mAppThread);
        }
    }
}

当其监听的binder实体死亡时,系统会回调AppDeathRecipient的binderDied()。这个回调函数会辗转重启persistent应用,调用关系如下:


    一般情况下,当一个应用进程挂掉后,AMS当然会清理掉其对应的ProcessRecord,这就是cleanUpApplicationRecordLocked()的主要工作。然而,对于persistent应用,cleanUpApplicationRecordLocked()会尝试再次启动对应的应用进程。代码截选如下:

private final void cleanUpApplicationRecordLocked(ProcessRecord app,
                                                  boolean restarting, 
                                                  boolean allowRestart, int index) 
{
    . . . . . .
    . . . . . .
    if (!app.persistent || app.isolated) 
    {
        . . . . . .
        mProcessNames.remove(app.processName, app.uid);
        mIsolatedProcesses.remove(app.uid);
        . . . . . . 
    } 
    else if (!app.removed) 
    {
        if (mPersistentStartingProcesses.indexOf(app) < 0) {
            mPersistentStartingProcesses.add(app);
            restart = true;
        }
    }
    . . . . . .
    . . . . . .
    if (restart && !app.isolated) 
    {
        mProcessNames.put(app.processName, app.uid, app);
        startProcessLocked(app, "restart", app.processName);
    } 
    else if (app.pid > 0 && app.pid != MY_PID) 
    {
        . . . . . .
    }
    . . . . . .
}

    现在我们可以画一张关于“启动persistent应用”的示意图:

 

3 补充知识点


3.1 persistent应用可以在系统未准备好时启动

    在AMS中,有一个isAllowedWhileBooting()函数,其代码如下:

boolean isAllowedWhileBooting(ApplicationInfo ai) 
{
    return (ai.flags & ApplicationInfo.FLAG_PERSISTENT) != 0;
}


    从这个函数可以看到,将persistent属性设为true的应用,是允许在boot的过程中启动的。我们可以查看前文提到的startProcessLocked()函数:

final ProcessRecord startProcessLocked(String processName,
                                       ApplicationInfo info, boolean knownToBeDead, 
                                       int intentFlags,
                                       String hostingType, ComponentName hostingName, 
                                       boolean allowWhileBooting,
                                       boolean isolated) 
{
    ProcessRecord app;
    
    if (!isolated) 
    {
        app = getProcessRecordLocked(processName, info.uid);
    } 
    else 
    {
        // If this is an isolated process, it can't re-use an existing process.
        app = null;
    }

    . . . . . .
    . . . . . .
    
    if (!mProcessesReady
        && !isAllowedWhileBooting(info)
        && !allowWhileBooting) {
        if (!mProcessesOnHold.contains(app)) {
            mProcessesOnHold.add(app);
        }
        if (DEBUG_PROCESSES) Slog.v(TAG, "System not ready, putting on hold: " + app);
        return app;
    }

    startProcessLocked(app, hostingType, hostingNameStr);
    return (app.pid != 0) ? app : null;
}

其中的最后几句可以改写为以下更易理解的形式:

if  (mProcessesReady || isAllowedWhileBooting(info) || allowWhileBooting) 
{
    startProcessLocked(app, hostingType, hostingNameStr);
    return (app.pid != 0) ? app : null;
}
else
{
    . . . . . .
    return app;
}


    也就是说,当系统已经处于以下几种情况时,多参数的startProcessLocked()会进一步调用另一个只有三个参数的startProcessLocked():
1)系统已经处于ready状态;
2)想要启动persistent应用;
3)参数中明确指定可以在boot过程中启动应用。

    补充说一下,一般情况下,当AMS调用startProcessLocked()时,传入的allowWhileBooting参数都为false。比如说,当系统需要启动“某个content provider或者某个service或者某个特定activity”时,此时传给startProcessLocked()的allowWhileBooting参数是写死为false的。只有一种特殊情况下会在该参数中传入true,那就是当系统发出的广播intent中携带有Intent.FLAG_RECEIVER_BOOT_UPGRADE标记时,此时允许在系统未ready时,启动接受广播的目标进程。

4 结束

    有关Android应用的persistent属性,我们就先说这么多。希望对大家有点儿帮助。

 

 

如需转载本文内容,请注明出处。

http://my.oschina.net/youranhongcha

 

© 著作权归作者所有

悠然红茶
粉丝 344
博文 20
码字总数 106144
作品 0
西安
高级程序员
私信 提问
加载中

评论(1)

wangtengoo7
wangtengoo7
牛 很好的知识。 NFC.apk就是这个例子,之前一直没搞懂为什么随系统启动了!
AndroidManifest.xml文件详解(application)

语法(SYNATX): . . . 被包含于(CONTAINED IN): 能够包含的元素(CAN CONTAIN): 说明(DESCRIPTION): 这个元素用于应用程序的声明。它包含了每个应用程序组件所声明的子元素,并且还...

长平狐
2012/10/16
1K
0
自己定义的preference,怎么应用系统默认的属性

自己定义的preference,怎么应用系统默认的属性,比如字体,边距,求大神,有做过的吗,我尝试了很多方法,都 不行 我在补充详细点,就是我继承一个PreferenceActivity, 之后set 一个prefere...

天外野草
2013/11/13
185
0
android:persistent="true"

@悠然红茶 你好,想跟你请教个问题:关于你说的android:persistent="true"这个属性 我想请问下 如果说非系统签名的应用push到system下边如果说一旦被disable 如何来复活...

Colin133
2016/08/05
304
0
Android xml application属性详解

例子: <application android:allowClearUserData=["true" | "false"] android:allowTaskReparenting=["true" | "false"] android:backupAgent="string" android:debuggable=["true" | "fals......

恰同学少年
2015/07/28
306
0
Android高版本联网失败报错:Cleartext HTTP traffic to xxx not permitted解决方法

前言:为保证用户数据和设备的安全,Google针对下一代 Android 系统(Android P) 的应用程序,将要求默认使用加密连接,这意味着 Android P 将禁止 App 使用所有未加密的连接,因此运行 Andr...

独钓渔
05/09
23
0

没有更多内容

加载失败,请刷新页面

加载更多

【2019个推开发者节】航母级APP都在用的SDK现在全部免费,35岁老程序员表示第一次见!

1024程序员节来了 双11近了 各路满减、折扣、领券、秒杀、集赞 营销玩法猛于虎,一看优惠两毛五 日常拼命赶“需求” 修“Bug”的开发者们 想找个好用又不贵的工具太难了 亲爱的开发者们,不要...

个推
34分钟前
9
0
Ceph对可用存储空间的校验与控制

Ceph一共使用了四个配置对可用存储空间进行校验并实施控制,如下: mon_osd_full_ratio:集群中的任一OSD空间使用率大于等于此数值时,集群将被标记为Full,此时集群将停止接受来自客户端的写...

浪里个浪浪
36分钟前
11
0
工厂方法模式

1.定义:创建一个接口,协助创建其它对象 2.优缺点 优: a.用户只需要知道这个工厂是创建哪种对象的,不需要知道创建的过程 b.满足开闭原则(开闭原则:对扩展开放,对修改关闭,即增加类可以...

wen123
37分钟前
6
0
Bootstrap Table -detailView和detailFilter的使用

查看表格 detailFilter 属性:data-detail-filter Type:Function Default:function(index,row){ return true} detailView 属性:data-detail-view Type:Boolean Default:false <table id="ta......

tianyawhl
42分钟前
4
0
场效应管的注意事项

  (1)为了安全使用场效应管,在线路的设计中不能超过管的耗散功率,最大漏源电压、最大栅源电压和最大电流等参数的极限值。   (2)各类型场效应管在使用时,都要严格按要求的偏置接入...

仙溪
45分钟前
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部