看我一波,Android获取进程名函数,代码优化到极致的操作!

Posted Android-until

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了看我一波,Android获取进程名函数,代码优化到极致的操作!相关的知识,希望对你有一定的参考价值。

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

一、获取进程名的常规方法,通过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入门…… 这些是挺重要。

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

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

最后对文章有何见解,或者有何技术问题,都可以在评论区一起留言讨论~

还有不同方向的自学Android路线、BAT面试题集合/面经、及系列技术文章视频等,资源持续更新中,免费获取:【github

开发者涨薪指南 48位大咖的思考法则、工作方式、逻辑体系

以上是关于看我一波,Android获取进程名函数,代码优化到极致的操作!的主要内容,如果未能解决你的问题,请参考以下文章

Android 逆向Android 进程注入工具开发 ( 注入代码分析 | 远程调用 目标进程中 libc.so 动态库中的 mmap 函数 三 | 等待远程函数执行完毕 | 寄存器获取返回值 )(代

Android 逆向Android 进程注入工具开发 ( 注入代码分析 | 获取 远程 目标进程 中的 /system/lib/libc.so 动态库中的 mmap 函数地址 )

Android 逆向Android 进程代码注入原理 ( 进程注入原理 | 远程调用流程 | 获取函数地址 | 设置 IP 寄存器 | mmap 申请内存 | 设置 SP 寄存器 )

Android 逆向Android 进程注入工具开发 ( 注入代码分析 | 获取 linker 中的 dlopen 函数地址 并 通过 远程调用 执行该函数 )

android应用锁之获取前台进程包名方法

Android 逆向Android 进程注入工具开发 ( 注入代码分析 | 获取注入的 libbridge.so 动态库中的 load 函数地址 并 通过 远程调用 执行该函数 )