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源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索