文章

进程工具

进程工具

进程工具

获取进程名

获取进程名的常规方法,通过 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;
  }
}

综合优化方案

于是我们将三个方法结合。

  1. 我们优先通过 Application.getProcessName() 方法获取进程名。
  2. 如果获取失败,我们再反射 ActivityThread.currentProcessName() 获取进程名
  3. 如果失败,我们才通过常规方法 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 进行授权