美文网首页Android开发Android技术知识Android开发
android获取进程名函数,如何优化到极致?

android获取进程名函数,如何优化到极致?

作者: 木木玩Android | 来源:发表于2020-09-28 21:42 被阅读0次

    建议收藏本文,你的项目一定用的到。

    一、获取进程名的常规方法,通过ActivityManager

    在多进程的APP中,常常需要知道当前进程是主进程还是后台进程?还是什么进程。

    如下代码,是我们常见的一个用法,在进程启动时,根据进程名判断当前进程是哪个进程:

    public class MyApp extends Application {
      private static final String TG = "MyApp";
    
      @Override
      public void onCreate() {
        super.onCreate();
        //判断当前进程是否为主进程,那么初始化主进程
        if (isMainProcess()) {
          initMainProcess();
        }
      }
    
      private boolean isMainProcess() {
        //获取当前进程名,并与主进程对比,来判断是否为主进程
        String processName = ProcessUtil.getCurrentProcessName(this);
        Log.e(TG, "isMainProcess processName=" + processName);
        return BuildConfig.APPLICATION_ID.equals(processName);
      }
    
      private void initMainProcess() {
        Log.e(TG, "initMainProcess");
      }
    }
    复制代码
    

    通过ActivityManager来获取进程名,网上也能搜索到很多人推荐这个用法。

    但是,大叔要说,这个方法不是最优解。

    /**
    * 通过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获取当前进程名的弊端

    1. ActivityManager.getRunningAppProcesses() 方法需要跨进程通信,效率不高

      需要 和 系统进程的 ActivityManagerService 通信。必然会导致该方法调用耗时。

    2. 拿到RunningAppProcessInfo的列表之后,还需要遍历一遍找到与当前进程的信息。

      显然额外的循环也会增加耗时;

      当然这个耗时影响很小。

    3. 最恐怖的是 ActivityManager.getRunningAppProcesses() 有可能调用失败,返回null,也可能 AIDL 调用失败。

      当然ActivityManager.getRunningAppProcesses()调用失败是极低的概率。

      当你的APP用户量达到一定的数量级别时,一定会有用户遇到ActivityManager.getRunningAppProcesses()调用失败的情况。

      在我们开头描述的使用场景中,出现进程名获取失败的情况,将会是非常恐怖。

      一旦导致进程中的某些组件没有初始化,整个进程大概率是要gg了。

    三、寻求更优解

    方法一:大叔发现,在android api28的时候新增了一个方法:Application.getProcessName()

    Application.getProcessName()方法直接返回当前进程名。这不就是我们想要的API吗!

    但是这个方法只有在android9【也就是aip28】之后的系统才能调用。

    public class ProcessUtil {
    
      /**
      * 通过Application新的API获取进程名,无需反射,无需IPC,效率最高。
      */
      public static String getCurrentProcessNameByApplication() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
          return Application.getProcessName();
        }
        return null;
      }
    }
    复制代码
    

    android9以前的系统怎么办呢?

    android9以前的系统怎么办呢?

    android9以前的系统怎么办呢?

    方法二:ActivityThread.currentProcessName() 方法

    于是大叔好奇,看了看Application.getProcessName()的源码,他是如何实现的?

    public class Application extends ContextWrapper implements ComponentCallbacks2 {
      public static String getProcessName() {
        return ActivityThread.currentProcessName();
      }
    }
    复制代码
    

    我们发现了ActivityThread.currentProcessName()这个方法。

    于是大叔继续翻了下源码:

    /**
     * {@hide}
     */
    public final class ActivityThread extends ClientTransactionHandler {
    
      public static String currentProcessName() {
        //....
      }
    }
    
    复制代码
    

    奥利给,ActivityThread.currentProcessName()方法居然是public static的。

    奥利给,ActivityThread.currentProcessName()方法居然是public static的。

    奥利给,ActivityThread.currentProcessName()方法居然是public static的。

    但是,马上就发现:

    ActivityThread类是hide的,app无法直接调用。

    ActivityThread类是hide的,app无法直接调用。

    ActivityThread类是hide的,app无法直接调用。

    于是大叔继续翻源码,看看这个方法是什么时候新增的。

    大叔发现这个方法在android4.3.1上就已经有了这个方法了。

    在android4.0.4上没有找到currentProcessName()方法。

    那么意味着,我们是不是可以反射调用 ActivityThread.currentProcessName() ?

    答案当然是可以。

    于是我们在ProcessUtil工具类中实现了这个方法:

    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来获取进程名

    如下代码:

    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;
      }
    }
    复制代码
    

    五、简单的测试下性能

    大叔做了个简单的测试,测试下三种方法调用需要的时长:

    模拟器上做的测试,模拟器配置如下:

    测试代码如下:

    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分钟后才调用:

    输出日志如下:

    2020-09-27 18:30:03.323 14007-14007/com.android.study I/vzProcessUtilAct: getCurrentProcessNameByApplication duration=654000
    2020-09-27 18:31:02.029 14082-14082/com.android.study I/vzProcessUtilAct: getCurrentProcessNameByActivityThread duration=1121000
    2020-09-27 18:32:01.669 14150-14150/com.android.study I/vzProcessUtilAct: getCurrentProcessNameByActivityManager duration=1661000
    
    复制代码
    

    可以看到:

    ProcessUtil.getCurrentProcessNameByApplication() 耗时 654000纳秒=0.654毫秒

    ProcessUtil.getCurrentProcessNameByActivityThread() 耗时 1121000纳秒=1.121毫秒

    ProcessUtil.getCurrentProcessNameByActivityManager() 耗时 1661000纳秒=1.661毫秒

    六、总结

    有时候做开发就是这样,静下心来捋一捋,有时候会让你发现惊喜。

    这种惊喜,甚至比你追各种时髦技术的入门来的更踏实。

    快餐式入门,一会flutter入门,一会rxjava入门,一会儿kotlin入门…… 这些是挺重要。

    但是,深入代码细节,解决一个个问题的经验更加宝贵。

    解决这些问题的过程,形成的思维习惯,对一个程序员来说,这是生存之本。

    原文链接:https://juejin.im/post/6877127949452050446

    相关文章

      网友评论

        本文标题:android获取进程名函数,如何优化到极致?

        本文链接:https://www.haomeiwen.com/subject/njbjuktx.html