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

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

1、获取进程名的常规方法,经过ActivityManager

在多进程的APP中,经常须要知道当前进程是主进程仍是后台进程?仍是什么进程。android


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


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来获取进程名,网上也能搜索到不少人推荐这个用法。
web

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


/**
* 经过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;
}

可是,大叔要说,这个方法不是最优解。微信

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

可是,大叔要说,这个方法不是最优解。编辑器

2、经过ActivityManager获取当前进程名的弊端

ActivityManager.getRunningAppProcesses() 方法须要跨进程通讯,效率不高ide

须要 和 系统进程的 ActivityManagerService 通讯。必然会致使该方法调用耗时。函数

拿到RunningAppProcessInfo的列表以后,还须要遍历一遍找到与当前进程的信息。

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

固然这个耗时影响很小。

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

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

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

在咱们开头描述的使用场景中,出现进程名获取失败的状况,将会是很是恐怖。
一旦致使进程中的某些组件没有初始化,整个进程大几率是要gg了。
3、寻求更优解
方法一:大叔发现,在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(nullnew Object[0]);
      if (invoke instanceof String) {
        processName = (String) invoke;
      }
    } catch (Throwable e) {
    }
    return processName;
  }
}

4、将三种方法结合获得一个更优方案

因而咱们将三个方法结合。

  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(nullnew 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;
  }
}

5、简单的测试下性能

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

在模拟器上作的测试,模拟器配置以下:

测试代码以下:

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毫秒

6、总结

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


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


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


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


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


做者:IT互联网大叔

连接:https://juejin.im/post/6877127949452050446



-- END --


进技术交流群,扫码添加个人微信:Byte-Flow



获取视频教程和源码



推荐:

字节流动 OpenGL ES 技术交流群来啦

FFmpeg + OpenGL ES 实现 3D 全景播放器

FFmpeg + OpenGLES 实现视频解码播放和视频滤镜

一文掌握 YUV 图像的基本处理

Android OpenGL ES 从入门到精通系统性学习教程

OpenGL ES 实现动态(水波纹)涟漪效果


以为不错,点个在看呗~

本文分享自微信公众号 - 字节流动(google_developer)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索