进程工具
进程工具
进程工具
获取进程名
获取进程名的常规方法,通过 ActivityManager
在多进程的 APP 中,常常需要知道当前进程是主进程还是后台进程?还是什么进程。
如下代码,是我们常见的一个用法,在进程启动时,根据进程名判断当前进程是哪个进程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 通过ActivityManager 获取进程名,需要IPC通信
*/
public static String getCurrentProcessNameByActivityManager(@NonNull Context context) {
int pid = Process.myPid();
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
if (am != null) {
List<ActivityManager.RunningAppProcessInfo> runningAppList = am.getRunningAppProcesses();
if (runningAppList != null) {
for (ActivityManager.RunningAppProcessInfo processInfo : runningAppList) {
if (processInfo.pid == pid) {
return processInfo.processName;
}
}
}
}
return null;
}
通过 ActivityManager 获取当前进程名的弊端
ActivityManager.getRunningAppProcesses()
方法需要跨进程通信,效率不高,可能导致 binder 调用 ANR;需要和系统进程的 ActivityManagerService 通信。必然会导致该方法调用耗时。- 拿到 RunningAppProcessInfo 的列表之后,还需要遍历一遍找到与当前进程的信息。显然额外的循环也会增加耗时;当然这个耗时影响很小。
- 最恐怖的是
ActivityManager.getRunningAppProcesses()
有可能调用失败,返回 null,也可能 AIDL 调用失败。
当然 ActivityManager.GetRunningAppProcesses () 调用失败是极低的概率。当你的 APP 用户量达到一定的数量级别时,一定会有用户遇到 ActivityManager.GetRunningAppProcesses () 调用失败的情况。出现进程名获取失败的情况,将会是非常恐怖。一旦导致进程中的某些组件没有初始化,整个进程大概率是要 gg 了。
API 28(Android 9) 新增 Application.GetProcessName ()
在 Android api 28 的时候新增了一个方法:Application.getProcessName()
1
2
3
4
5
6
7
8
9
10
11
12
public class ProcessUtil {
/**
* 通过Application新的API获取进程名,无需反射,无需IPC,效率最高。
*/
public static String getCurrentProcessNameByApplication() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
return Application.getProcessName();
}
return null;
}
}
Android 9 之前优化:ActivityThread.CurrentProcessName ()
ActivityThread.CurrentProcessName () 方法居然是 public static 的。ActivityThread 类是 @hide
的,app 无法直接调用。这个方法在 Android 4.3.1 上就已经有了这个方法了。在 Android 4.0.4 上没有找到 currentProcessName () 方法。
通过反射来实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ProcessUtil {
/**
* 通过反射ActivityThread获取进程名,避免了ipc
*/
public static String getCurrentProcessNameByActivityThread() {
String processName = null;
try {
final Method declaredMethod = Class.forName("android.app.ActivityThread", false, Application.class.getClassLoader())
.getDeclaredMethod("currentProcessName", (Class<?>[]) new Class[0]);
declaredMethod.setAccessible(true);
final Object invoke = declaredMethod.invoke(null, new Object[0]);
if (invoke instanceof String) {
processName = (String) invoke;
}
} catch (Throwable e) {
}
return processName;
}
}
综合优化方案
于是我们将三个方法结合。
- 我们优先通过
Application.getProcessName()
方法获取进程名。 - 如果获取失败,我们再反射
ActivityThread.currentProcessName()
获取进程名 - 如果失败,我们才通过常规方法
ActivityManager
来获取进程名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
public class ProcessUtil {
private static String currentProcessName;
/**
* @return 当前进程名
*/
@Nullable
public static String getCurrentProcessName(@NonNull Context context) {
if (!TextUtils.isEmpty(currentProcessName)) {
return currentProcessName;
}
//1)通过Application的API获取当前进程名
currentProcessName = getCurrentProcessNameByApplication();
if (!TextUtils.isEmpty(currentProcessName)) {
return currentProcessName;
}
//2)通过反射ActivityThread获取当前进程名
currentProcessName = getCurrentProcessNameByActivityThread();
if (!TextUtils.isEmpty(currentProcessName)) {
return currentProcessName;
}
//3)通过ActivityManager获取当前进程名
currentProcessName = getCurrentProcessNameByActivityManager(context);
return currentProcessName;
}
/**
* 通过Application新的API获取进程名,无需反射,无需IPC,效率最高。
*/
public static String getCurrentProcessNameByApplication() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
return Application.getProcessName();
}
return null;
}
/**
* 通过反射ActivityThread获取进程名,避免了ipc
*/
public static String getCurrentProcessNameByActivityThread() {
String processName = null;
try {
final Method declaredMethod = Class.forName("android.app.ActivityThread", false, Application.class.getClassLoader())
.getDeclaredMethod("currentProcessName", (Class<?>[]) new Class[0]);
declaredMethod.setAccessible(true);
final Object invoke = declaredMethod.invoke(null, new Object[0]);
if (invoke instanceof String) {
processName = (String) invoke;
}
} catch (Throwable e) {
e.printStackTrace();
}
return processName;
}
/**
* 通过ActivityManager 获取进程名,需要IPC通信
*/
public static String getCurrentProcessNameByActivityManager(@NonNull Context context) {
if (context == null) {
return null;
}
int pid = Process.myPid();
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
if (am != null) {
List<ActivityManager.RunningAppProcessInfo> runningAppList = am.getRunningAppProcesses();
if (runningAppList != null) {
for (ActivityManager.RunningAppProcessInfo processInfo : runningAppList) {
if (processInfo.pid == pid) {
return processInfo.processName;
}
}
}
}
return null;
}
}
Work-multiprocess 源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public class ProcessUtils {
private static final String TAG = Logger.tagWithPrefix("ProcessUtils");
private ProcessUtils() {
// Does nothing
}
/**
* @return The name of the active process.
*/ @Nullable
@SuppressLint({"PrivateApi", "DiscouragedPrivateApi"})
public static String getProcessName(@NonNull Context context) {
if (SDK_INT >= 28) {
return Application.getProcessName();
}
// Try using ActivityThread to determine the current process name.
try {
Class<?> activityThread = Class.forName(
"android.app.ActivityThread",
false, ProcessUtils.class.getClassLoader());
final Object packageName;
if (SDK_INT >= 18) {
Method currentProcessName = activityThread.getDeclaredMethod("currentProcessName");
currentProcessName.setAccessible(true);
packageName = currentProcessName.invoke(null);
} else {
Method getActivityThread = activityThread.getDeclaredMethod(
"currentActivityThread");
getActivityThread.setAccessible(true);
Method getProcessName = activityThread.getDeclaredMethod("getProcessName");
getProcessName.setAccessible(true);
packageName = getProcessName.invoke(getActivityThread.invoke(null));
}
if (packageName instanceof String) {
return (String) packageName;
}
} catch (Throwable exception) {
Logger.get().debug(TAG, "Unable to check ActivityThread for processName", exception);
}
// Fallback to the most expensive way
int pid = Process.myPid();
ActivityManager am =
(ActivityManager) context.getSystemService(ACTIVITY_SERVICE);
if (am != null) {
List<ActivityManager.RunningAppProcessInfo> processes = am.getRunningAppProcesses();
if (processes != null && !processes.isEmpty()) {
for (ActivityManager.RunningAppProcessInfo process : processes) {
if (process.pid == pid) {
return process.processName;
}
}
}
}
return null;
}
}
测试下性能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private fun testGetCurrentProcessNameByApplication(){
val beginTime = SystemClock.elapsedRealtimeNanos()
ProcessUtil.getCurrentProcessNameByApplication()
Log.i(TG, "getCurrentProcessNameByApplication duration=${SystemClock.elapsedRealtimeNanos() - beginTime}")
}
private fun testGetCurrentProcessNameByActivityThread(){
val beginTime = SystemClock.elapsedRealtimeNanos()
ProcessUtil.getCurrentProcessNameByActivityThread()
Log.i(TG, "getCurrentProcessNameByActivityThread duration=${SystemClock.elapsedRealtimeNanos() - beginTime}")
}
private fun testGetCurrentProcessNameByActivityManager(){
val beginTime = SystemClock.elapsedRealtimeNanos()
ProcessUtil.getCurrentProcessNameByActivityManager(this)
Log.i(TG, "getCurrentProcessNameByActivityManager duration=${SystemClock.elapsedRealtimeNanos() - beginTime}")
}
每个函数在调用前,都会重启 APP 并静置 1 分钟后才调用:
1
2
3
ProcessUtil.getCurrentProcessNameByApplication() 耗时 654000纳秒=0.654毫秒
ProcessUtil.getCurrentProcessNameByActivityThread() 耗时 1121000纳秒=1.121毫秒
ProcessUtil.getCurrentProcessNameByActivityManager() 耗时 1661000纳秒=1.661毫秒
App 杀死原因
从 WorkManager 库 ForceStopRunnable 中看到
- App 是否被强制杀死
- App 被杀死的原因:可以获取到最近的原因
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
/**
* @return {@code true} If the application was force stopped.
*/@SuppressLint("ClassVerificationFailure")
public static boolean isForceStopped(Context context) {
if (context == null) {
return false;
}
// Alarms get cancelled when an app is force-stopped starting at Eclair MR1.
// Cancelling of Jobs on force-stop was introduced in N-MR1 (SDK 25). // Even though API 23, 24 are probably safe, OEMs may choose to do // something different. try {
int flags = FLAG_NO_CREATE;
if (BuildCompat.isAtLeastS()) {
flags |= FLAG_MUTABLE;
}
PendingIntent pendingIntent = getPendingIntent(context, flags);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// We no longer need the alarm.
if (pendingIntent != null) {
pendingIntent.cancel();
}
ActivityManager activityManager =
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ApplicationExitInfo> exitInfoList =
activityManager.getHistoricalProcessExitReasons(
null /* match caller uid */,
0, // ignore
0 // ignore
);
if (exitInfoList != null && !exitInfoList.isEmpty()) {
for (int i = 0; i < exitInfoList.size(); i++) {
ApplicationExitInfo info = exitInfoList.get(i);
Log.d(TAG, "app exit reason =" + info.getReason());
if (info.getReason() == REASON_USER_REQUESTED) {
return true;
}
}
}
Log.w(TAG, "API>=30, none");
} else if (pendingIntent == null) {
Log.i(TAG, "pendingIntent=null");
setAlarm(context);
return true; }
return false;
} catch (SecurityException | IllegalArgumentException exception) {
// b/189975360 Some Samsung Devices seem to throw an IllegalArgumentException :( on
// API 30.
// Setting Alarms on some devices fails due to OEM introduced bugs in AlarmManager. // When this happens, there is not much WorkManager can do, other can reschedule // everything. Log.w(TAG, "Ignoring exception", exception);
return true; }
}
// All our alarms are use request codes which are > 0.
private static final int ALARM_ID = -1;
/**
* @param flags The {@link PendingIntent} flags.
* @return an instance of the {@link PendingIntent}.
*/private static PendingIntent getPendingIntent(Context context, int flags) {
Intent intent = getIntent(context);
return PendingIntent.getBroadcast(context, ALARM_ID, intent, flags);
}
@VisibleForTesting
static final String ACTION_FORCE_STOP_RESCHEDULE = "ACTION_FORCE_STOP_RESCHEDULE";
/**
* @return The instance of {@link Intent} used to keep track of force stops.
*/@VisibleForTesting
static Intent getIntent(Context context) {
Intent intent = new Intent();
intent.setComponent(new ComponentName(context, BroadcastReceiver.class));
intent.setAction(ACTION_FORCE_STOP_RESCHEDULE);
return intent;
}
private static final long TEN_YEARS = TimeUnit.DAYS.toMillis(10 * 365);
@SuppressLint({"ClassVerificationFailure", "ScheduleExactAlarm"})
static void setAlarm(Context context) {
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
// Using FLAG_UPDATE_CURRENT, because we only ever want once instance of this alarm.
int flags = FLAG_UPDATE_CURRENT;
if (BuildCompat.isAtLeastS()) {
flags |= FLAG_MUTABLE;
}
PendingIntent pendingIntent = getPendingIntent(context, flags);
long triggerAt = System.currentTimeMillis() + TEN_YEARS;
if (alarmManager != null) {
if (Build.VERSION.SDK_INT >= 19) {
alarmManager.setExact(RTC_WAKEUP, triggerAt, pendingIntent);
} else {
alarmManager.set(RTC_WAKEUP, triggerAt, pendingIntent);
}
}
}
/**
* A {@link android.content.BroadcastReceiver} which takes care of recreating the long lived
* alarm which helps track force stops for an application. This is the target of the alarm set * by ForceStopRunnable in {@linksetAlarm(Context)}.
* * @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static class BroadcastReceiver extends android.content.BroadcastReceiver {
@Override
public void onReceive(@NonNull Context context, @Nullable Intent intent) {
// Our alarm somehow got triggered, so make sure we reschedule it. This should really
// never happen because we set it so far in the future. if (intent != null) {
String action = intent.getAction();
if (ACTION_FORCE_STOP_RESCHEDULE.equals(action)) {
Log.d(
TAG,
"Rescheduling alarm that keeps track of force-stops.");
ProcessUtil.setAlarm(context);
}
}
}
}
设置组件可用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/**
* Helper class for common {@link PackageManager} functions
*/
public class PackageManagerHelper {
private static final String TAG = "PackageManagerHelper";
private PackageManagerHelper() {
}
/**
* Uses {@link PackageManager} to enable/disable a manifest-defined component
* * @param context {@link Context}
* @param klazz The class of component
* @param enabled {@code true} if component should be enabled
*/ public static void setComponentEnabled(
@NonNull Context context,
@NonNull Class<?> klazz,
boolean enabled) {
try {
PackageManager packageManager = context.getPackageManager();
ComponentName componentName = new ComponentName(context, klazz.getName());
packageManager.setComponentEnabledSetting(componentName,
enabled
? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
: PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
// Logger.get().debug(TAG,
// String.format("%s %s", klazz.getName(), (enabled ? "enabled" : "disabled")));
} catch (Exception exception) {
// Logger.get().debug(TAG, String.format("%s could not be %s", klazz.getName(),
// (enabled ? "enabled" : "disabled")), exception);
}
}
/**
* Convenience method for {@link isComponentExplicitlyEnabled(Context, String)}
*/ public static boolean isComponentExplicitlyEnabled(Context context, Class<?> klazz) {
return isComponentExplicitlyEnabled(context, klazz.getName());
}
/**
* Checks if a manifest-defined component is explicitly enabled * * @param context {@link Context}
* @param className {@link Class.getName()} name of component
* @return {@code true} if component is explicitly enabled
*/ public static boolean isComponentExplicitlyEnabled(Context context, String className) {
PackageManager packageManager = context.getPackageManager();
ComponentName componentName = new ComponentName(context, className);
int state = packageManager.getComponentEnabledSetting(componentName);
return state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
}
}
本文由作者按照 CC BY 4.0 进行授权