force-stop流程
(1).onForceStopPackage():强制停止该package,主要是没有绑定进程的activities,绑定进程的activity会随着进程消亡而消亡,第一步执行这个方法主要是为了防止这个package中中没有绑定进程的activity重新启动该package。一般地force-stop会指定包名,该方法会遍历当前所有运行中的进程mProcessNames,以下条件同时都不满足的进程
基于Android 12的force-stop流程分析
force-stop可以强制结束一个package进程及其相关的信息。可以通过adb命令和设置中的入口来使用。如果想结束进程,主要的adb命令有以下几种:
(1)adb shell kill -9 <进程号>
这种方式只会结束一个进程
(2)adb shell am force-stop <包名>
这种方式会结束package进程及其相关的进程,也就是说可能不止结束一个进程。
(3)adb shell pm clear <包名>
这种方法不仅会停止APP进程,而且会清除这个APP进程产生的所有数据。
这里,只分析force-stop的流程
从adb shell am命令来开始分析,force-stop是一个am的命令,所以可以从ActivityManagerShellCommand.java来开始分析,这个文件里定义了各种am命令会执行的原生方法,我们可以通过这个类来很快的判断am命令最终调用的方法。
frameworks/base/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
case "force-stop":
return runForceStop(pw);
int runForceStop(PrintWriter pw) throws RemoteException {
int userId = UserHandle.USER_ALL;//当不指定userId时,则默认为UserHandle.USER_ALL
String opt;
while ((opt = getNextOption()) != null) {
if (opt.equals("--user")) {//是否需要指定userId
userId = UserHandle.parseUserArg(getNextArgRequired());
} else {
getErrPrintWriter().println("Error: Unknown option: " + opt);
return -1;
}
}
mInterface.forceStopPackage(getNextArgRequired(), userId);//binder访问AMS中的方法
return 0;
}
adb shell force-stop命令有两种使用方法:
(1)adb shell force-stop <包名>
(2)adb shell force-stop --user <userid> <包名> :停止某个userId下某个包名的进程信息,指定userId
无论哪种方法,都是通过binder调用了AMS的forceStopPackage()方法
AMS
@Override
public void forceStopPackage(final String packageName, int userId) {
if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
!= PackageManager.PERMISSION_GRANTED) {//force-stop需要FORCE_STOP_PACKAGES权限
String msg = "Permission Denial: forceStopPackage() from pid="
+ Binder.getCallingPid()
+ ", uid=" + Binder.getCallingUid()
+ " requires " + android.Manifest.permission.FORCE_STOP_PACKAGES;
Slog.w(TAG, msg);
throw new SecurityException(msg);//缺少权限的情况下需要抛出异常
}
final int callingPid = Binder.getCallingPid();
userId = mUserController.handleIncomingUser(callingPid, Binder.getCallingUid(),
userId, true, ALLOW_FULL_ONLY, "forceStopPackage", null);
final long callingId = Binder.clearCallingIdentity();
try {
IPackageManager pm = AppGlobals.getPackageManager();
synchronized(this) {
int[] users = userId == UserHandle.USER_ALL
? mUserController.getUsers() : new int[] { userId };
for (int user : users) {//遍历所有用户,可能涉及到多用户
if (getPackageManagerInternal().isPackageStateProtected(
packageName, user)) {//如果package的状态是受保护的,那么不能被force-stop
Slog.w(TAG, "Ignoring request to force stop protected package "
+ packageName + " u" + user);
return;
}
int pkgUid = -1;
try {
pkgUid = pm.getPackageUid(packageName, MATCH_DEBUG_TRIAGED_MISSING,
user);
} catch (RemoteException e) {
}
if (pkgUid == -1) {
Slog.w(TAG, "Invalid packageName: " + packageName);
continue;
}
try {
pm.setPackageStoppedState(packageName, true, user);//将包的状态设置为stopped
} catch (RemoteException e) {
} catch (IllegalArgumentException e) {
Slog.w(TAG, "Failed trying to unstop package "
+ packageName + ": " + e);
}
if (mUserController.isUserRunning(user, 0)) {
forceStopPackageLocked(packageName, pkgUid, "from pid " + callingPid);//主要实现方法
finishForceStopPackageLocked(packageName, pkgUid);//结束force-stop之后的广播发送
}
}
}
} finally {
Binder.restoreCallingIdentity(callingId);
}
}
setPackageStoppedState()方法会将包的状态设置为stopped(被force-stop或者没有被启动过的三方应用都是stopped状态),这个状态下所有广播都无法被接收,除非带有标记FLAG_INCLUDE_STOPPED_PACKAGES的广播。系统默认的广播几乎都是不带有该标志,也就意味着一般情况下被force-stop的应用是无法通过广播来拉起进程的,但是我们可以通过给广播添加FLAG_INCLUDE_STOPPED_PACKAGES标识来让stopped状态下的应用接收到此广播。
finishForceStopPackageLocked()方法,主要是发送ACTION_PACKAGE_RESTARTED广播,这个广播的作用是用户重新开始一个包,包的所有进程将被杀死,所有与其联系的运行时间状态应该被移除,包括包名(重新开始包程序不能接收到这个广播)
主要来看下forceStopPackageLocked()方法
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
@GuardedBy("this")
private void forceStopPackageLocked(final String packageName, int uid, String reason) {
forceStopPackageLocked(packageName, UserHandle.getAppId(uid), false,
false, true, false, false, UserHandle.getUserId(uid), reason);
}
@GuardedBy("this")
final boolean forceStopPackageLocked(String packageName, int appId,
boolean callerWillRestart, boolean purgeCache, boolean doit,
boolean evenPersistent, boolean uninstalling, int userId, String reason) {
int i;
if (userId == UserHandle.USER_ALL && packageName == null) {
Slog.w(TAG, "Can't force stop all processes of all users, that is insane!");//不允许stop 所有用户的所有进程
}
if (appId < 0 && packageName != null) {
try {
appId = UserHandle.getAppId(AppGlobals.getPackageManager()
.getPackageUid(packageName, MATCH_DEBUG_TRIAGED_MISSING, 0));
} catch (RemoteException e) {
}
}
boolean didSomething;//当方法中有所行为,则返回true。只要杀过一个进程则代表didSomething为true.
if (doit) {
if (packageName != null) {
Slog.i(TAG, "Force stopping " + packageName + " appid=" + appId
+ " user=" + userId + ": " + reason);//log打印,会打印出stop的reason
} else {
Slog.i(TAG, "Force stopping u" + userId + ": " + reason);
}
mAppErrors.resetProcessCrashTime(packageName == null, appId, userId);
}
synchronized (mProcLock) {
// Notify first that the package is stopped, so its process won't be restarted
// unexpectedly if there is an activity of the package without attached process
// becomes visible when killing its other processes with visible activities.
didSomething = mAtmInternal.onForceStopPackage(
packageName, doit, evenPersistent, userId);//主要方法:强制停止该package
didSomething |= mProcessList.killPackageProcessesLSP(packageName, appId, userId,
ProcessList.INVALID_ADJ, callerWillRestart, false /* allowRestart */, doit,
evenPersistent, true /* setRemoved */,
packageName == null ? ApplicationExitInfo.REASON_USER_STOPPED
: ApplicationExitInfo.REASON_USER_REQUESTED,
ApplicationExitInfo.SUBREASON_UNKNOWN,
(packageName == null ? ("stop user " + userId) : ("stop " + packageName))
+ " due to " + reason);//主要方法:停止该pakcage所涉及的进程
}
if (mServices.bringDownDisabledPackageServicesLocked(
packageName, null /* filterByClasses */, userId, evenPersistent, doit)) {//主要方法:清理该package所涉及的Service
if (!doit) {
return true;
}
didSomething = true;
}
if (packageName == null) {
// Remove all sticky broadcasts from this user.
mStickyBroadcasts.remove(userId);//删除粘性广播
}
ArrayList<ContentProviderRecord> providers = new ArrayList<>();
if (mCpHelper.getProviderMap().collectPackageProvidersLocked(packageName, null, doit,
evenPersistent, userId, providers)) {//收集该package相关的provider
if (!doit) {
return true;
}
didSomething = true;
}
for (i = providers.size() - 1; i >= 0; i--) {
mCpHelper.removeDyingProviderLocked(null, providers.get(i), true);//主要方法:清理该package所涉及的Provider
}
// Remove transient permissions granted from/to this package/user
mUgmInternal.removeUriPermissionsForPackage(packageName, userId, false, false);//主要方法:删除授予/授予此包/用户的临时权限
if (doit) {
for (i = mBroadcastQueues.length - 1; i >= 0; i--) {
didSomething |= mBroadcastQueues[i].cleanupDisabledPackageReceiversLocked(
packageName, null, userId, doit);//主要方法:清理该package所涉及的广播
}
}
if (packageName == null || uninstallbringDownDisabledPackageServicesLockeding) {
didSomething |= mPendingIntentController.removePendingIntentsForPackage(
packageName, userId, appId, doit);//主要方法:移除所涉及到的intent
}
if (doit) {
if (purgeCache && packageName != null) {
AttributeCache ac = AttributeCache.instance();
if (ac != null) {
ac.removePackage(packageName);
}
}
if (mBooted) {
mAtmInternal.resumeTopActivities(true /* scheduleIdle */);
}
}
return didSomething;
}
forceStopPackageLocked()方法是force-stop的主要方法,这个方法会清理跟该包名相关的进程和四大组件。可以看到,主要流程可以分为以下几个部分:
(1).onForceStopPackage():强制停止该package,主要是没有绑定进程的activities,绑定进程的activity会随着进程消亡而消亡,第一步执行这个方法主要是为了防止这个package中中没有绑定进程的activity重新启动该package
(2).killPackageProcessesLSP():停止该pakcage所涉及的进程
(3).bringDownDisabledPackageServicesLocked():停止该pakcage所涉及的服务
(4).removeDyingProviderLocked():停止该pakcage所涉及的provider
(5).cleanupDisabledPackageReceiversLocked():停止该package所涉及的广播
(6).removePendingIntentsForPackage():停止该package所涉及的pending intent
下面依次分析这几个流程
1.onForceStopPackage;强制停止该package,主要是没有绑定进程的activities
frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@Override
public boolean onForceStopPackage(String packageName, boolean doit, boolean evenPersistent,
int userId) {//doit传过来的值为true,evenPersistent值为false
synchronized (mGlobalLock) {
return mRootWindowContainer.finishDisabledPackageActivities(packageName,
null /* filterByClasses */, doit, evenPersistent, userId,
// Only remove the activities without process because the activities with
// attached process will be removed when handling process died with
// WindowProcessController#isRemoved == true.
true /* onlyRemoveNoProcess */);
}
}
调用了RootWindowContainer.java里的finishDisabledPackageActivities()方法,主要是强制停止所涉及的无进程activities.。在这里,只移除没有进程的activity,因为那些有进程的activity,后面将会通过kill process的方法随着进程一起被kill
frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java
boolean finishDisabledPackageActivities(String packageName, Set<String> filterByClasses,
boolean doit, boolean evenPersistent, int userId, boolean onlyRemoveNoProcess) {
return mFinishDisabledPackageActivitiesHelper.process(packageName, filterByClasses, doit,
evenPersistent, userId, onlyRemoveNoProcess);
}
交给了内部类FinishDisabledPackageActivitiesHelper去处理finsh的操作
frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java
boolean process(String packageName, Set<String> filterByClasses,
boolean doit, boolean evenPersistent, int userId, boolean onlyRemoveNoProcess) {
reset(packageName, filterByClasses, doit, evenPersistent, userId, onlyRemoveNoProcess);
final PooledFunction f = PooledLambda.obtainFunction(
FinishDisabledPackageActivitiesHelper::collectActivity, this,
PooledLambda.__(ActivityRecord.class));//collectActivity获取所涉及的activities,可以从这个方法中看出哪些activity符合被finsh的条件
forAllActivities(f);
f.recycle();
boolean didSomething = false;
final int size = mCollectedActivities.size();//被finsh的activity的数量
// Keep the finishing order from top to bottom.
for (int i = 0; i < size; i++) {
final ActivityRecord r = mCollectedActivities.get(i);
if (mOnlyRemoveNoProcess) {//是否只移除没有进程的activity,传过来的值为true
if (!r.hasProcess()) {
didSomething = true;
Slog.i(TAG, " Force removing " + r);
r.cleanUp(false /* cleanServices */, false /* setState */);
r.removeFromHistory("force-stop");//清除activity
}
} else {
didSomething = true;
Slog.i(TAG, " Force finishing " + r);
r.finishIfPossible("force-stop", true /* oomAdj */);//清除activity
}
}
mCollectedActivities.clear();
return didSomething;
}
首先通过collectActivity()方法获取所涉及的activities,可以从这个方法中看出哪些activity符合被finsh的条件,看一下这个方法。
private boolean collectActivity(ActivityRecord r) {
final boolean sameComponent =
(r.packageName.equals(mPackageName) && (mFilterByClasses == null
|| mFilterByClasses.contains(r.mActivityComponent.getClassName())))
|| (mPackageName == null && r.mUserId == mUserId);//具有相同包名的activity需要被finsh
final boolean noProcess = !r.hasProcess();//没有进程绑定的activity
if ((mUserId == UserHandle.USER_ALL || r.mUserId == mUserId)
&& (sameComponent || r.getTask() == mLastTask)
&& (noProcess || mEvenPersistent || !r.app.isPersistent())) {
if (!mDoit) {//mDoit表示是否有操作
if (r.finishing) {//如果次activity正在finsh,不做重复操作
// If this activity is just finishing, then it is not
// interesting as far as something to stop.
return false;
}
return true;
}
mCollectedActivities.add(r);//将符合条件的activity添加到mCollectedActivities中
mLastTask = r.getTask();
}
return false;
}
}
2.killPackageProcessesLSP:停止该pakcage所涉及的进程
frameworks/base/services/core/java/com/android/server/am/ProcessList.java
boolean killPackageProcessesLSP(String packageName, int appId,
int userId, int minOomAdj, boolean callerWillRestart, boolean allowRestart,
boolean doit, boolean evenPersistent, boolean setRemoved, int reasonCode,
int subReason, String reason) {
ArrayList<ProcessRecord> procs = new ArrayList<>();
// Remove all processes this package may have touched: all with the
// same UID (except for the system or root user), and all whose name
// matches the package name.
final int NP = mProcessNames.getMap().size();//移除这个package可能接触的所有进程:具有相同uid(除非系统或者root用户)、所有名称的package名相同的进程
for (int ip = 0; ip < NP; ip++) {
SparseArray<ProcessRecord> apps = mProcessNames.getMap().valueAt(ip);
final int NA = apps.size();
for (int ia = 0; ia < NA; ia++) {
ProcessRecord app = apps.valueAt(ia);
if (app.isPersistent() && !evenPersistent) {//如果是persistent进程,则不会被kill掉
// we don't kill persistent processes
continue;
}
if (app.isRemoved()) {//应用被移除,则不会被kill
if (doit) {
procs.add(app);
}
continue;
}
// Skip process if it doesn't meet our oom adj requirement.
if (app.mState.getSetAdj() < minOomAdj) {//进程setAdj < minOomAdj则不会被kill,minOomAdj值为-10000
// Note it is still possible to have a process with oom adj 0 in the killed
// processes, but it does not mean misjudgment. E.g. a bound service process
// and its client activity process are both in the background, so they are
// collected to be killed. If the client activity is killed first, the service
// may be scheduled to unbind and become an executing service (oom adj 0).
continue;
}
// If no package is specified, we call all processes under the
// give user id.
if (packageName == null) {//如果没有指定包,下面情况不会被kill。
if (userId != UserHandle.USER_ALL && app.userId != userId) {//非UserHandle.USER_ALL同时, 且进程的userId不相等,不kill,因为多用户模型下,不同用户下不能相互杀
continue;
}
if (appId >= 0 && UserHandle.getAppId(app.uid) != appId) {
continue;
}
// Package has been specified, we want to hit all processes
// that match it. We need to qualify this by the processes
// that are running under the specified app and user ID.
} else {//包名不为null
final boolean isDep = app.getPkgDeps() != null
&& app.getPkgDeps().contains(packageName);//进程的pkgDeps中不包含该packageName,则会被杀
if (!isDep && UserHandle.getAppId(app.uid) != appId) {
continue;
}
if (userId != UserHandle.USER_ALL && app.userId != userId) {
continue;
}
if (!app.getPkgList().containsKey(packageName) && !isDep) {
continue;
}
}
// Process has passed all conditions, kill it!//以下情况全部会被kill
if (!doit) {
return true;
}
if (setRemoved) {
app.setRemoved(true);
}
procs.add(app);//将会被kill的进程添加到procs
}
}
int N = procs.size();
for (int i=0; i<N; i++) {
removeProcessLocked(procs.get(i), callerWillRestart, allowRestart,
reasonCode, subReason, reason);
/**
removeProcessLocked方法的主要功能:
1.从mProcessNames, mPidsSelfLocked队列移除该进程;
2.移除进程启动超时的消息PROC_START_TIMEOUT_MSG;
3.调用app.killLocked()来杀进程会同时调用Process.kill和Process.killProcessGroup.
4.调用handleAppDiedLocked()来清理进程相关的信息
*/
}
killAppZygotesLocked(packageName, appId, userId, false /* force */);//查看是否有任何app zygotes 正在为此 packageName / UID 组合运行,如果有,则将其杀死。
mService.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_PROCESS_END);//更新oomadj,reason是OOM_ADJ_REASON_PROCESS_END
return N > 0;
}
一般地force-stop会指定包名,该方法会遍历当前所有运行中的进程mProcessNames,以下条件同时都不满足的进程,则会成为被杀的目标进程:(也就是说满足以下任一条件都可以免死)
(1)persistent进程:
(2)进程setAdj < minOomAdj(默认为-10000):
(3)非UserHandle.USER_ALL同时, 且进程的userId不相等:多用户模型下,不同用户下不能相互杀;
(4)进程没有依赖该packageName, 且进程的AppId不相等;
(5)进程没有依赖该packageName, 且该packageName没有运行在该进程.
也就是就是:
(1)forceStop不杀系统persistent进程;
(2)当指定用户userId时,不杀其他用户空间的进程;
除此之外,以下情况则必然会成为被杀进程:
(1)进程已标记remove=true的进程,则会被杀;
(2)进程的pkgDeps中包含该packageName,则会被杀;
(3)进程的pkgList中包含该packageName,且该进程与包名所指定的AppId相等则会被杀;
进程的pkgList是在启动组件或者创建进程的过程向该队列添加的,代表的是该应用下有组件运行在该进程。那么pkgDeps是指该进程所依赖的包名,调用ClassLoader的过程添加。
3.bringDownDisabledPackageServicesLocked():停止该pakcage所涉及的服务
frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
boolean bringDownDisabledPackageServicesLocked(String packageName, Set<String> filterByClasses,
int userId, boolean evenPersistent, boolean doit) {
boolean didSomething = false;
if (mTmpCollectionResults != null) {//mTmpCollectionResults这个是符合条件的services
mTmpCollectionResults.clear();
}
if (userId == UserHandle.USER_ALL) {
for (int i = mServiceMap.size() - 1; i >= 0; i--) {
didSomething |= collectPackageServicesLocked(packageName, filterByClasses,
evenPersistent, doit, mServiceMap.valueAt(i).mServicesByInstanceName);//收集满足条件的service到mTmpCollectionResults并kill
if (!doit && didSomething) {
return true;
}
if (doit && filterByClasses == null) {
forceStopPackageLocked(packageName, mServiceMap.valueAt(i).mUserId);
}
}
} else {
ServiceMap smap = mServiceMap.get(userId);
if (smap != null) {
ArrayMap<ComponentName, ServiceRecord> items = smap.mServicesByInstanceName;
didSomething = collectPackageServicesLocked(packageName, filterByClasses,
evenPersistent, doit, items);
}
if (doit && filterByClasses == null) {
forceStopPackageLocked(packageName, userId);
}
}
if (mTmpCollectionResults != null) {
final int size = mTmpCollectionResults.size();
for (int i = size - 1; i >= 0; i--) {
bringDownServiceLocked(mTmpCollectionResults.get(i), true);//主要方法:遍历mTmpCollectionResults,通过bringDownServiceLocked来kill service
}
if (size > 0) {
mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
}
mTmpCollectionResults.clear();
}
return didSomething;
}
首先看哪些条件下的service符合条件
frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
private boolean collectPackageServicesLocked(String packageName, Set<String> filterByClasses,
boolean evenPersistent, boolean doit, ArrayMap<ComponentName, ServiceRecord> services) {
boolean didSomething = false;
for (int i = services.size() - 1; i >= 0; i--) {
ServiceRecord service = services.valueAt(i);
final boolean sameComponent = packageName == null
|| (service.packageName.equals(packageName)
&& (filterByClasses == null
|| filterByClasses.contains(service.name.getClassName())));//相同包名进程下的service
if (sameComponent
&& (service.app == null || evenPersistent || !service.app.isPersistent())) {//具有相同包名时,以下情况会被kill掉1.service没有绑定的app、2.evenPersistent为true 、3.service绑定的app不是persistent
if (!doit) {
return true;
}
didSomething = true;
Slog.i(TAG, " Force stopping service " + service);
if (service.app != null && !service.app.isPersistent()) {
stopServiceAndUpdateAllowlistManagerLocked(service);//stop 对应的service
}
service.setProcess(null, null, 0, null);
service.isolatedProc = null;
if (mTmpCollectionResults == null) {
mTmpCollectionResults = new ArrayList<>();
}
mTmpCollectionResults.add(service);
}
}
return didSomething;
}
然后通过bringDownServiceLocked()方法来kill掉所涉及的service
intent、广播和provider的流程类似,不做分析
需要注意:
广播:
(1)清理并行广播队列
(2)清理有序广播队列
provider:
当其他app使用该provider, 且建立stable的连接, 那么对于非persistent进程,则会由于依赖该provider的缘故而被杀.
intent:
主要移除待处理的intent。
补充:系统查杀进程方式
方法 | 方法所在类 | 使用层 | 描述 |
---|---|---|---|
System.exit(int status) | System.java | 应用层 | 退出虚拟机。status=0为正常退出、非0为异常退出。 |
Process.killProcess(int pid) | Process.java | 系统、应用层 | 调用sendSignal发送信号9,杀死指定pid进程。app调用只能自杀,系统能够杀掉指定pid进程。 |
Process.killProcessGroup(int uid, int pid) | Process.java | 系统 | 杀掉pid所在的进程组内的全部进程。 |
killApplicationProcess(String processName, int uid) | AMS | 系统 | 系统uid应用才能调用,binder call到客户端自杀:app.thread.scheduleSuicide(); |
killApplication(String pkg, int appId, int userId, String reason) | AMS | 系统 | 系统uid应用才能调用,会将目标应用强杀,做用于指定用户空间。 |
killAllBackgroundProcesses() | AMS | 系统 | 须要KILL_BACKGROUND_PROCESS权限,杀死系统全部优先级小于等于CACHED进程。 |
killBackgroundProcesses(final String packageName, int userId, String reason) | AMS | 系统、应用层 | 须要KILL_BACKGROUND_PROCESS权限,杀死指定package全部优先级小于等于SERVICE进程,做用于指定用户空间。 |
killProcessesBelowForeground(String reason) | AMS | 系统 | 系统uid应用才能调用,杀死系统全部优先级小于FOREGROUND的进程。 |
killPackageDependents(String packageName, int userId) | AMS | 系统 | 须要KILL_UID权限,杀死指定package全部优先级小于等于FOREGROUND进程,做用于指定用户空间。 |
killPids(int[] pids, String pReason, boolean secure) | AMS | 系统 | 系统UID应用才能调用,首先找出全部进程全部worstType(跟adj相关的一个判断值),杀死小于等于这个优先级的进程。很保守的查杀方法,并不保证进程被杀 |
killUid(int appId, int userId, String reason) | AMS | 系统、应用层 | 须要KILL_UID权限,杀死UID下全部进程,system_server与native进程除外 |
killAppAtUsersRequest(ProcessRecord app, Dialog fromDialog) | AMS | 系统 | 系统发生异常后调用,例如ANR ,多为用户主动触发 |
forceStopPackage(final String packageName, int userId) | AMS | 系统 | 须要FORCE_STOP_PACKAGE权限,杀死应用全部进程,并清除各个进程组件信息,做用于指定用户空间,强杀以后连根拔起,寸草不生。属于终极Kill。 |
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)