看我一波,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获取当前进程名的弊端
- ActivityManager.getRunningAppProcesses() 方法需要跨进程通信,效率不高 需要 和 系统进程的 ActivityManagerService 通信。必然会导致该方法调用耗时。
- 拿到RunningAppProcessInfo的列表之后,还需要遍历一遍找到与当前进程的信息。 显然额外的循环也会增加耗时; 当然这个耗时影响很小。
- 最恐怖的是 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;
}
}
四、将三种方法结合得到一个更优方案
于是我们将三个方法结合。
- 我们优先通过 Application.getProcessName() 方法获取进程名。
- 如果获取失败,我们再反射ActivityThread.currentProcessName()获取进程名
- 如果失败,我们才通过常规方法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】
- jboss EAP 6.2 + Message Drive Bean(MDB) 整合IBM Webshpere MQ 7.5
- 通过jenkins API去build一个job
- Django---分页器、中间件
- 启动jenkins服务错误
- 如果未来的AI拥有意识,你舍得不理它吗?
- centos下安装python3
- jboss:在standalone.xml中设置系统属性(system-properties)
- iptables
- Django-form表单
- 比较git commit 两个版本之间次数
- eclipse: workspace出错导致无法启用的解决
- 【node错误】/usr/bin/env: node: No such file or directory
- Django比较相等或者不相等的模板语法ifequal / ifnotequal
- 使用testNGListenter来自定义日志
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- 文件操作——读取
- YAML,另一种标记语言?不止是标记语言!
- Canal binlog 日志管理器与GTID简介
- RNA-Seq的Counts和FPKM数据如何转换成TPM?
- Java代码审计基础之反射
- 手把手教你用Python开发“剪刀石头布”小游戏【附源码】
- For语句
- 使用go语言制作dll封装Sprintf函数给VBA使用
- 同事埋了个坑:Insert into select语句把生产服务器炸了
- VBA使用API_01:读取文件
- 从0到1,撸一个IDEA插件,So Easy!
- VBA调用外部对象02:FileSystemObject
- VCS与Verdi的联合仿真
- ADC数据接入到AXI-Steam Interface
- Java面试必问:ThreadLocal终极篇 淦!