应用保活

141 阅读4分钟

前言:笔者前段原因因为职业规划所以在试用期离职,现在工作面试受阻(主要是没有面试机会),哈哈哈。所以趁着这段时间不如整理下自己从事Android客户端开发三年的一些经历或者经验,仅作记录,也供大家参考。
背景:研究应用是怎么推送通知

原生保活策略:

Android系统APP保活

主要有两个因素决定:

  • APP预置到system分区下的app、priv-app或Vendor-app下,预置到/data/app目录下APP的的不能被保活。
  • APP的AndroidManifest.xml添加android:persistent="true"属性。
该属性的代码位置 : frameworks/base/core/res/res/values/attrs_manifest.xml

<!-- Flag to control special persistent mode of an application.  This should
     not normally be used by applications; it requires that the system keep
     your application running at all times. -->
<attr name="persistent" format="boolean" />

通过官方注释 该属性用于是否让你的应用一直处于运行状态(通常说的常驻内存)。

设置该属性为true的app具有如下特点:

1、在系统启动的时候会被系统启动起来

2、在该app被强制杀掉后系统会重新启动该app,这种情况只针对系统内置app,第三方安装app不会被重启

Persistent属性的解析

1、persistent属性的解析该属性的解析主要在app被安装或者系统启动的时候发生

解析代码:

frameworks/base/core/java/android/content/pm/PackageParser.java

@UnsupportedAppUsage
private boolean parseBaseApplication(Package owner, Resources res,
        XmlResourceParser parser, int flags, String[] outError)
    throws XmlPullParserException, IOException {
    final ApplicationInfo ai = owner.applicationInfo;
    final String pkgName = owner.applicationInfo.packageName;

    TypedArray sa = res.obtainAttributes(parser,
            com.android.internal.R.styleable.AndroidManifestApplication);
            ...........
            ...........
            
if (sa.getBoolean(
        com.android.internal.R.styleable.AndroidManifestApplication_persistent,
        false)) {
    // Check if persistence is based on a feature being present
    final String requiredFeature = sa.getNonResourceString(com.android.internal.R.styleable
            .AndroidManifestApplication_persistentWhenFeatureAvailable);
    if (requiredFeature == null || mCallback.hasFeature(requiredFeature)) {
        ai.flags |= ApplicationInfo.FLAG_PERSISTENT;
    }
}

在解析完包信息之后系统会将解析好的所有包信息存放到PKMS中的mPackages的map中,

而ApplicationInfo的flag中有一个bit位用于保存该app是否是persistent的。这里只是把保存persistent的flag设置为FLAG_PERSISTENT。

在AndroidManifest设置了persistent为true的app是否能够在被异常杀死后能够得到重启的权力需要取决于该app对应的ProcessRecord的persistent属性,该属性只有在你的app既在AndroidManifest中配置了persistent=“true”,又是系统内置app时才会被设置为true。

2、app被异常结束后在frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java , cleanUpApplicationRecordLocked中对persistent为true的app进行重启.

@GuardedBy("this")
final boolean cleanUpApplicationRecordLocked(ProcessRecord app, int pid,
        boolean restarting, boolean allowRestart, int index, boolean replacingPid,
        boolean fromBinderDied) {
    boolean restart;
    synchronized (mProcLock) {
        if (index >= 0) {
            removeLruProcessLocked(app);
            ProcessList.remove(pid);
        }

        // We don't want to unlinkDeathRecipient immediately, if it's not called from binder
        // and it's not isolated, as we'd need the signal to bookkeeping the dying process list.
        restart = app.onCleanupApplicationRecordLSP(mProcessStats, allowRestart,
                fromBinderDied || app.isolated /* unlinkDeath */);

        // Cancel pending frozen task and clean up frozen record if there is any.
        mOomAdjuster.mCachedAppOptimizer.onCleanupApplicationRecordLocked(app);
    }
    mAppProfiler.onCleanupApplicationRecordLocked(app);
    skipCurrentReceiverLocked(app);
    updateProcessForegroundLocked(app, false, 0, false);
    mServices.killServicesLocked(app, allowRestart);
    mPhantomProcessList.onAppDied(pid);

    // If the app is undergoing backup, tell the backup manager about it
    final BackupRecord backupTarget = mBackupTargets.get(app.userId);
    if (backupTarget != null && pid == backupTarget.app.getPid()) {
        if (DEBUG_BACKUP || DEBUG_CLEANUP) Slog.d(TAG_CLEANUP, "App "
                + backupTarget.appInfo + " died during backup");
        mHandler.post(new Runnable() {
            @Override
            public void run(){
                try {
                    IBackupManager bm = IBackupManager.Stub.asInterface(
                            ServiceManager.getService(Context.BACKUP_SERVICE));
                    bm.agentDisconnectedForUser(app.userId, app.info.packageName);
                } catch (RemoteException e) {
                    // can't happen; backup manager is local
                }
            }
        });
    }

    mProcessList.scheduleDispatchProcessDiedLocked(pid, app.info.uid);

    // If this is a preceding instance of another process instance
    allowRestart = mProcessList.handlePrecedingAppDiedLocked(app);

    // If somehow this process was still waiting for the death of its predecessor,
    // (probably it's "killed" before starting for real), reset the bookkeeping.
    final ProcessRecord predecessor = app.mPredecessor;
    if (predecessor != null) {
        predecessor.mSuccessor = null;
        predecessor.mSuccessorStartRunnable = null;
        app.mPredecessor = null;
    }

    // If the caller is restarting this app, then leave it in its
    // current lists and let the caller take care of it.
    if (restarting) {
        return false;
    }

    if (!app.isPersistent() || app.isolated) {
        if (DEBUG_PROCESSES || DEBUG_CLEANUP) Slog.v(TAG_CLEANUP,
                "Removing non-persistent process during cleanup: " + app);
        if (!replacingPid) {
            mProcessList.removeProcessNameLocked(app.processName, app.uid, app);
        }
        mAtmInternal.clearHeavyWeightProcessIfEquals(app.getWindowProcessController());
    } else if (!app.isRemoved()) {
        // This app is persistent, so we need to keep its record around.
        // If it is not already on the pending app list, add it there
        // and start a new process for it.
        if (mPersistentStartingProcesses.indexOf(app) < 0) {
            mPersistentStartingProcesses.add(app);
            restart = true; //!!!!!!!!!!!!!!!!!
        }
    }
    if ((DEBUG_PROCESSES || DEBUG_CLEANUP) && mProcessesOnHold.contains(app)) Slog.v(
            TAG_CLEANUP, "Clean-up removing on hold: " + app);
    mProcessesOnHold.remove(app);

    mAtmInternal.onCleanUpApplicationRecord(app.getWindowProcessController());
    mProcessList.noteProcessDiedLocked(app);

    if (restart && allowRestart && !app.isolated) {
        // We have components that still need to be running in the
        // process, so re-launch it.
        if (index < 0) {
            ProcessList.remove(pid);
        }

        // Remove provider publish timeout because we will start a new timeout when the
        // restarted process is attaching (if the process contains launching providers).
        mHandler.removeMessages(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG, app);

        mProcessList.addProcessNameLocked(app);
        app.setPendingStart(false);
        mProcessList.startProcessLocked(app, new HostingRecord(
                HostingRecord.HOSTING_TYPE_RESTART, app.processName),
                ZYGOTE_POLICY_FLAG_EMPTY);
        return true;
    } else if (pid > 0 && pid != MY_PID) {
        // Goodbye!
        removePidLocked(pid, app);
        mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
        mBatteryStatsService.noteProcessFinish(app.processName, app.info.uid);
        if (app.isolated) {
            mBatteryStatsService.removeIsolatedUid(app.uid, app.info.uid);
        }
        app.setPid(0);
    }
    return false;
}

常见的第三方应用保活

以信鸽推送举例的流程原理介绍
简单说明Android客户端实现推送流程的步骤。
1.客户端App启动的时候会启动一个信鸽主Service,信鸽主Service全局唯一,一台设备共享一个信鸽主service
2.信鸽主Service在接入信鸽的应用中随机启动一个备份的Service,2个Service互相拉活,互相备份
3.信鸽主Service建立一个信鸽服务器的Socket长连接,并通过心跳等机制维持长连接一直存在
4.客户端主Service通过Socket长连接请求向信鸽服务器请求Token
5.信鸽服务器通过Socket长连接推送消息到客户端主Service
6.主Service把Push消息转发到对应的客户端App上

简单说明Android客户端实现厂商通道推送流程的步骤

  1. 发送注册请求到厂商服务请求Token
  2. 获取厂商的token
  3. 保存厂商返回的 Token 并同步到服务器
  4. 服务器将厂商生成的 Token 和账号等信息建立映射关系并保存
  5. 将需要推送的内容等信息告知给服务器
  6. 服务器根据 Token 关系调用厂商推送的 API ,推送消息到厂商服务器
  7. 厂商服务器再推送消息到客户端 APP