Android Process管理(进程管理) 详解

何松
2023-12-01
在Android中,进程只是一个运行组件的容器,当系统需要运行一个组件时,启动包含该组件的进程,当组件不在使用是,进程会被关闭;
在AMS中,还必须管理和调度进程;AMS对进程的管理,主要体现在两个方面:一个是动态调整进程在mLruProcesses列表的位置,而是调整进程的oom_adj的值,这两项调整和系统进程自动内存回收有关;当系统内存不足时,系统会关闭一些进程来释放内存;
系统主要根据进程的oom_adj值来挑选要杀死的进程,oom_adj值越大表示进程更可能被杀死;

1.创建进程:

执行的是AMS的addAppLocked方法:
final ProcessRecord addAppLocked(ApplicationInfo info, String customProcess, boolean isolated,
        boolean disableHiddenApiChecks, String abiOverride) {
    ProcessRecord app;
    if (!isolated) {
        app = getProcessRecordLocked(customProcess != null ? customProcess : info.processName,
                info.uid, true);
    } else {
        app = null;
    }

    if (app == null) {
        app = newProcessRecordLocked(info, customProcess, isolated, 0);
        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 & PERSISTENT_MASK) == PERSISTENT_MASK) {
        app.persistent = true;
        app.maxAdj = ProcessList.PERSISTENT_PROC_ADJ;
    }
    if (app.thread == null && mPersistentStartingProcesses.indexOf(app) < 0) {
        mPersistentStartingProcesses.add(app);
        startProcessLocked(app, "added application",
                customProcess != null ? customProcess : app.processName, disableHiddenApiChecks,
                abiOverride);
    }

    return app;
}
addAppLocked会根据isolated来决定是否重启一个新进程,如果isolated为TRUE,即使系统中可能已经有一个同名的进程存在,也会在创建一个进程;getProcessRecordLocked方法用于在当前运行的进程列表查询进程;newProcessRecordLocked方法用来创建一个ProcessRecord的数据结构,这两个函数很简单,updateLruProcessLocked方法用来更新运行中的进程状态,updateoomAdjLocked方法用来更新进程的优先级,这两个方法是Process管理的核心,startProcessLocked是启动进程的方法;

2.启动进程:

执行startProcessLocked方法:
private final boolean startProcessLocked(ProcessRecord app, String hostingType,
        String hostingNameStr, boolean disableHiddenApiChecks, String abiOverride) {
    if (app.pendingStart) {
        return true;
    }
    long startTime = SystemClock.elapsedRealtime();
    if (app.pid > 0 && app.pid != MY_PID) {
        checkTime(startTime, "startProcess: removing from pids map");
        synchronized (mPidsSelfLocked) {
            mPidsSelfLocked.remove(app.pid);
            mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
        }
        checkTime(startTime, "startProcess: done removing from pids map");
        app.setPid(0);
        app.startSeq = 0;
    }

    if (DEBUG_PROCESSES && mProcessesOnHold.contains(app)) Slog.v(TAG_PROCESSES,
            "startProcessLocked removing on hold: " + app);
    mProcessesOnHold.remove(app);

    checkTime(startTime, "startProcess: starting to update cpu stats");
    updateCpuStats();
    checkTime(startTime, "startProcess: done updating cpu stats");

    try {
        try {
            final int userId = UserHandle.getUserId(app.uid);
            AppGlobals.getPackageManager().checkPackageStartable(app.info.packageName, userId);
        } catch (RemoteException e) {
            throw e.rethrowAsRuntimeException();
        }

        int uid = app.uid;
        int[] gids = null;
        int mountExternal = Zygote.MOUNT_EXTERNAL_NONE;
        if (!app.isolated) {
            int[] permGids = null;
            try {
                checkTime(startTime, "startProcess: getting gids from package manager");
                final IPackageManager pm = AppGlobals.getPackageManager();
                permGids = pm.getPackageGids(app.info.packageName,
                        MATCH_DEBUG_TRIAGED_MISSING, app.userId);
                StorageManagerInternal storageManagerInternal = LocalServices.getService(
                        StorageManagerInternal.class);
                mountExternal = storageManagerInternal.getExternalStorageMountMode(uid,
                        app.info.packageName);
            } catch (RemoteException e) {
                throw e.rethrowAsRuntimeException();
            }

            /*
             * Add shared application and profile GIDs so applications can share some
             * resources like shared libraries and access user-wide resources
             */
            if (ArrayUtils.isEmpty(permGids)) {
                gids = new int[3];
            } else {
                gids = new int[permGids.length + 3];
                System.arraycopy(permGids, 0, gids, 3, permGids.length);
            }
            gids[0] = UserHandle.getSharedAppGid(UserHandle.getAppId(uid));
            gids[1] = UserHandle.getCacheAppGid(UserHandle.getAppId(uid));
            gids[2] = UserHandle.getUserGid(UserHandle.getUserId(uid));

            // Replace any invalid GIDs
            if (gids[0] == UserHandle.ERR_GID) gids[0] = gids[2];
            if (gids[1] == UserHandle.ERR_GID) gids[1] = gids[2];
        }
        checkTime(startTime, "startProcess: building args");
        if (mFactoryTest != FactoryTest.FACTORY_TEST_OFF) {
            if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL
                    && mTopComponent != null
                    && app.processName.equals(mTopComponent.getPackageName())) {
                uid = 0;
            }
            if (mFactoryTest == FactoryTest.FACTORY_TEST_HIGH_LEVEL
                    && (app.info.flags&ApplicationInfo.FLAG_FACTORY_TEST) != 0) {
                uid = 0;
            }
        }
        int runtimeFlags = 0;
        if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
            runtimeFlags |= Zygote.DEBUG_ENABLE_JDWP;
            runtimeFlags |= Zygote.DEBUG_JAVA_DEBUGGABLE;
            // Also turn on CheckJNI for debuggable apps. It's quite
            // awkward to turn on otherwise.
            runtimeFlags |= Zygote.DEBUG_ENABLE_CHECKJNI;
        }
        // Run the app in safe mode if its manifest requests so or the
        // system is booted in safe mode.
        if ((app.info.flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0 ||
            mSafeMode == true) {
            runtimeFlags |= Zygote.DEBUG_ENABLE_SAFEMODE;
        }
        if ("1".equals(SystemProperties.get("debug.checkjni"))) {
            runtimeFlags |= Zygote.DEBUG_ENABLE_CHECKJNI;
        }
        String genDebugInfoProperty = SystemProperties.get("debug.generate-debug-info");
        if ("1".equals(genDebugInfoProperty) || "true".equals(genDebugInfoProperty)) {
            runtimeFlags |= Zygote.DEBUG_GENERATE_DEBUG_INFO;
        }
        String genMiniDebugInfoProperty = SystemProperties.get("dalvik.vm.minidebuginfo");
        if ("1".equals(genMiniDebugInfoProperty) || "true".equals(genMiniDebugInfoProperty)) {
            runtimeFlags |= Zygote.DEBUG_GENERATE_MINI_DEBUG_INFO;
        }
        if ("1".equals(SystemProperties.get("debug.jni.logging"))) {
            runtimeFlags |= Zygote.DEBUG_ENABLE_JNI_LOGGING;
        }
        if ("1".equals(SystemProperties.get("debug.assert"))) {
            runtimeFlags |= Zygote.DEBUG_ENABLE_ASSERT;
        }
        if (mNativeDebuggingApp != null && mNativeDebuggingApp.equals(app.processName)) {
            // Enable all debug flags required by the native debugger.
            runtimeFlags |= Zygote.DEBUG_ALWAYS_JIT;          // Don't interpret anything
            runtimeFlags |= Zygote.DEBUG_GENERATE_DEBUG_INFO; // Generate debug info
            runtimeFlags |= Zygote.DEBUG_NATIVE_DEBUGGABLE;   // Disbale optimizations
            mNativeDebuggingApp = null;
        }

        if (app.info.isPrivilegedApp() &&
                DexManager.isPackageSelectedToRunOob(app.pkgList.keySet())) {
            runtimeFlags |= Zygote.ONLY_USE_SYSTEM_OAT_FILES;
        }

        if (!disableHiddenApiChecks && !mHiddenApiBlacklist.isDisabled()) {
            app.info.maybeUpdateHiddenApiEnforcementPolicy(
                    mHiddenApiBlacklist.getPolicyForPrePApps(),
                    mHiddenApiBlacklist.getPolicyForPApps());
            @HiddenApiEnforcementPolicy int policy =
                    app.info.getHiddenApiEnforcementPolicy();
            int policyBits = (policy << Zygote.API_ENFORCEMENT_POLICY_SHIFT);
            if ((policyBits & Zygote.API_ENFORCEMENT_POLICY_MASK) != policyBits) {
                throw new IllegalStateException("Invalid API policy: " + policy);
            }
            runtimeFlags |= policyBits;
        }

        String invokeWith = null;
        if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
            // Debuggable apps may include a wrapper script with their library directory.
            String wrapperFileName = app.info.nativeLibraryDir + "/wrap.sh";
            StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
            try {
                if (new File(wrapperFileName).exists()) {
                    invokeWith = "/system/bin/logwrapper " + wrapperFileName;
                }
            } finally {
                StrictMode.setThreadPolicy(oldPolicy);
            }
        }

        String requiredAbi = (abiOverride != null) ? abiOverride : app.info.primaryCpuAbi;
        if (requiredAbi == null) {
            requiredAbi = Build.SUPPORTED_ABIS[0];
        }

        String instructionSet = null;
        if (app.info.primaryCpuAbi != null) {
            instructionSet = VMRuntime.getInstructionSet(app.info.primaryCpuAbi);
        }

        app.gids = gids;
        app.requiredAbi = requiredAbi;
        app.instructionSet = instructionSet;

        // the per-user SELinux context must be set
        if (TextUtils.isEmpty(app.info.seInfoUser)) {
            Slog.wtf(TAG, "SELinux tag not defined",
                    new IllegalStateException("SELinux tag not defined for "
                    + app.info.packageName + " (uid " + app.uid + ")"));
        }
        final String seInfo = app.info.seInfo
                + (TextUtils.isEmpty(app.info.seInfoUser) ? "" : app.info.seInfoUser);
        // Start the process.  It will either succeed and return a result containing
        // the PID of the new process, or else throw a RuntimeException.
        final String entryPoint = "android.app.ActivityThread";

        return startProcessLocked(hostingType, hostingNameStr, entryPoint, app, uid, gids,
                runtimeFlags, mountExternal, seInfo, requiredAbi, instructionSet, invokeWith,
                startTime);
    } catch (RuntimeException e) {
        Slog.e(TAG, "Failure starting process " + app.processName, e);

        // Something went very wrong while trying to start this process; one
        // common case is when the package is frozen due to an active
        // upgrade. To recover, clean up any active bookkeeping related to
        // starting this process. (We already invoked this method once when
        // the package was initially frozen through KILL_APPLICATION_MSG, so
        // it doesn't hurt to use it again.)
        forceStopPackageLocked(app.info.packageName, UserHandle.getAppId(app.uid), false,
                false, true, false, false, UserHandle.getUserId(app.userId), "start failure");
        return false;
    }
}
startProcessLocked方法的流程是:准备启动应用的参数后,调用Process类的start方法启动进程;启动进程后AMS给自己发一个PROC_START_TIMEOUT_MSG消息,这个消息是用来防止进程启动时间超时;如果时间到了,但是进程还没有启动完成;AMS将弹出发生ANR的对话框;

3.调整进程的位置:

AMS的代码经常调用updateLruProcessLocked方法来调整某个进程在mLruProcesses列表中的位置;每当进程中的Activity或Service发生变化时,意味着进程发生了活动,因此调用这个方法将该进程调整到尽可能高的位置,同时还拥要更新关联进程的位置;在mLruProcesses列表中,最近活动过得进程总是位于最高位置,同时拥有Activity的进程位置,总是高于只有Service的进程的位置;
AMS的成员变量mLruProcessActivityStart和mLruProcessServiceStart分别指向列表中位置最高的,带有Activity进程和没有Activity的进程:
final void updateLruProcessLocked(ProcessRecord app, boolean activityChange,
        ProcessRecord client) {
    final boolean hasActivity = app.activities.size() > 0 || app.hasClientActivities
            || app.treatLikeActivity || app.recentTasks.size() > 0;
    final boolean hasService = false; // not impl yet. app.services.size() > 0;
    if (!activityChange && hasActivity) {
        // The process has activities, so we are only allowing activity-based adjustments
        // to move it.  It should be kept in the front of the list with other
        // processes that have activities, and we don't want those to change their
        // order except due to activity operations.
        return;
    }

    mLruSeq++;
    final long now = SystemClock.uptimeMillis();
    app.lastActivityTime = now;

    // First a quick reject: if the app is already at the position we will
    // put it, then there is nothing to do.
    if (hasActivity) {
        final int N = mLruProcesses.size();
        if (N > 0 && mLruProcesses.get(N-1) == app) {
            if (DEBUG_LRU) Slog.d(TAG_LRU, "Not moving, already top activity: " + app);
            return;
        }
    } else {
        if (mLruProcessServiceStart > 0
                && mLruProcesses.get(mLruProcessServiceStart-1) == app) {
            if (DEBUG_LRU) Slog.d(TAG_LRU, "Not moving, already top other: " + app);
            return;
        }
    }

    int lrui = mLruProcesses.lastIndexOf(app);

    if (app.persistent && lrui >= 0) {
        // We don't care about the position of persistent processes, as long as
        // they are in the list.
        if (DEBUG_LRU) Slog.d(TAG_LRU, "Not moving, persistent: " + app);
        return;
    }

    /* In progress: compute new position first, so we can avoid doing work
       if the process is not actually going to move.  Not yet working.
    int addIndex;
    int nextIndex;
    boolean inActivity = false, inService = false;
    if (hasActivity) {
        // Process has activities, put it at the very tipsy-top.
        addIndex = mLruProcesses.size();
        nextIndex = mLruProcessServiceStart;
        inActivity = true;
    } else if (hasService) {
        // Process has services, put it at the top of the service list.
        addIndex = mLruProcessActivityStart;
        nextIndex = mLruProcessServiceStart;
        inActivity = true;
        inService = true;
    } else  {
        // Process not otherwise of interest, it goes to the top of the non-service area.
        addIndex = mLruProcessServiceStart;
        if (client != null) {
            int clientIndex = mLruProcesses.lastIndexOf(client);
            if (clientIndex < 0) Slog.d(TAG, "Unknown client " + client + " when updating "
                    + app);
            if (clientIndex >= 0 && addIndex > clientIndex) {
                addIndex = clientIndex;
            }
        }
        nextIndex = addIndex > 0 ? addIndex-1 : addIndex;
    }

    Slog.d(TAG, "Update LRU at " + lrui + " to " + addIndex + " (act="
            + mLruProcessActivityStart + "): " + app);
    */

    if (lrui >= 0) {
        if (lrui < mLruProcessActivityStart) {
            mLruProcessActivityStart--;
        }
        if (lrui < mLruProcessServiceStart) {
            mLruProcessServiceStart--;
        }
        /*
        if (addIndex > lrui) {
            addIndex--;
        }
        if (nextIndex > lrui) {
            nextIndex--;
        }
        */
        mLruProcesses.remove(lrui);
    }

    /*
    mLruProcesses.add(addIndex, app);
    if (inActivity) {
        mLruProcessActivityStart++;
    }
    if (inService) {
        mLruProcessActivityStart++;
    }
    */

    int nextIndex;
    if (hasActivity) {
        final int N = mLruProcesses.size();
        if ((app.activities.size() == 0 || app.recentTasks.size() > 0)
                && mLruProcessActivityStart < (N - 1)) {
            // Process doesn't have activities, but has clients with
            // activities...  move it up, but one below the top (the top
            // should always have a real activity).
            if (DEBUG_LRU) Slog.d(TAG_LRU,
                    "Adding to second-top of LRU activity list: " + app);
            mLruProcesses.add(N - 1, app);
            // To keep it from spamming the LRU list (by making a bunch of clients),
            // we will push down any other entries owned by the app.
            final int uid = app.info.uid;
            for (int i = N - 2; i > mLruProcessActivityStart; i--) {
                ProcessRecord subProc = mLruProcesses.get(i);
                if (subProc.info.uid == uid) {
                    // We want to push this one down the list.  If the process after
                    // it is for the same uid, however, don't do so, because we don't
                    // want them internally to be re-ordered.
                    if (mLruProcesses.get(i - 1).info.uid != uid) {
                        if (DEBUG_LRU) Slog.d(TAG_LRU,
                                "Pushing uid " + uid + " swapping at " + i + ": "
                                + mLruProcesses.get(i) + " : " + mLruProcesses.get(i - 1));
                        ProcessRecord tmp = mLruProcesses.get(i);
                        mLruProcesses.set(i, mLruProcesses.get(i - 1));
                        mLruProcesses.set(i - 1, tmp);
                        i--;
                    }
                } else {
                    // A gap, we can stop here.
                    break;
                }
            }
        } else {
            // Process has activities, put it at the very tipsy-top.
            if (DEBUG_LRU) Slog.d(TAG_LRU, "Adding to top of LRU activity list: " + app);
            mLruProcesses.add(app);
        }
        nextIndex = mLruProcessServiceStart;
    } else if (hasService) {
        // Process has services, put it at the top of the service list.
        if (DEBUG_LRU) Slog.d(TAG_LRU, "Adding to top of LRU service list: " + app);
        mLruProcesses.add(mLruProcessActivityStart, app);
        nextIndex = mLruProcessServiceStart;
        mLruProcessActivityStart++;
    } else  {
        // Process not otherwise of interest, it goes to the top of the non-service area.
        int index = mLruProcessServiceStart;
        if (client != null) {
            // If there is a client, don't allow the process to be moved up higher
            // in the list than that client.
            int clientIndex = mLruProcesses.lastIndexOf(client);
            if (DEBUG_LRU && clientIndex < 0) Slog.d(TAG_LRU, "Unknown client " + client
                    + " when updating " + app);
            if (clientIndex <= lrui) {
                // Don't allow the client index restriction to push it down farther in the
                // list than it already is.
                clientIndex = lrui;
            }
            if (clientIndex >= 0 && index > clientIndex) {
                index = clientIndex;
            }
        }
        if (DEBUG_LRU) Slog.d(TAG_LRU, "Adding at " + index + " of LRU list: " + app);
        mLruProcesses.add(index, app);
        nextIndex = index-1;
        mLruProcessActivityStart++;
        mLruProcessServiceStart++;
    }

    // If the app is currently using a content provider or service,
    // bump those processes as well.
    for (int j=app.connections.size()-1; j>=0; j--) {
        ConnectionRecord cr = app.connections.valueAt(j);
        if (cr.binding != null && !cr.serviceDead && cr.binding.service != null
                && cr.binding.service.app != null
                && cr.binding.service.app.lruSeq != mLruSeq
                && !cr.binding.service.app.persistent) {
            nextIndex = updateLruProcessInternalLocked(cr.binding.service.app, now, nextIndex,
                    "service connection", cr, app);
        }
    }
    for (int j=app.conProviders.size()-1; j>=0; j--) {
        ContentProviderRecord cpr = app.conProviders.get(j).provider;
        if (cpr.proc != null && cpr.proc.lruSeq != mLruSeq && !cpr.proc.persistent) {
            nextIndex = updateLruProcessInternalLocked(cpr.proc, now, nextIndex,
                    "provider reference", cpr, app);
        }
    }
}
updateLruProcessLocked方法中调整进程很重要的一个依据是进程中有没有Activity,除了进程本身存在Activity对象外,如果和进程中运行的Service相关联的进程中有Activity,该进程也算是用于Activity进程;这个调整目的是为了将来杀死进程释放内存做准备,如果一个进程的关联进程有Activity对象存在,那么它的重要性也和真正拥有Activity对象的进程相当,如果杀死它,将导致另一个进程出现严重错误;Activity用来显示UI,关系着用户得体验,因此Android尽量不关闭运行Activity组件的进程;
如果一个进程拥有Activity,那么通常把它插入到队列的最高位置,否则,只会把它放到所有没有Activity的进程前面,这个位置正是变量mLruProcessServiceStart所指向的;
调整某个进程的位置之后,还是调整合该进程的关联进程的位置,进程的关联进程有两种类型:一种是绑定了本进程服务的进程,另一种是连接了本进程的ContentProvider的进程;如果这些进程本身有Activity是不会调整的,需要调整的是那些没有Activity的进程,在updateLruProcessInternalLocked方法中会执行这种调整,但是能够调整到最高位置也就是mLruProcessServiceStart指向的位置;

4.调整进程的oom_adj值:

AMS中调整进程oom_adj值的方法是updateOomAdjLocked方法:
private final boolean updateOomAdjLocked(ProcessRecord app, int cachedAdj,
        ProcessRecord TOP_APP, boolean doingAll, long now) {
    if (app.thread == null) {
        return false;
    }

    computeOomAdjLocked(app, cachedAdj, TOP_APP, doingAll, now);

    return applyOomAdjLocked(app, doingAll, now, SystemClock.elapsedRealtime());
}
updateOomAdjLocked方法中通过调用computeoomAdjlocked方法来计算进程的oom_adj值,则表明该进程属于“cached”进程或空进程,updateOomAdjLocked方法将会为该进程分配oom_adj的值,如果用来表示进程状态的变量curProcState的值为PROCESS_STATE_CACHED_ACTIVITY或者PROCESS_STATE_CACHED_ACTIVITY_CLIENT,说明进程是cached进程,否则是空进程;

参考文档:
《深入理解Android5.0系统》

 类似资料: