Android多进程app中Application回调onCreate()方法被执行多次分析及解决

本文探讨了Android应用中多进程的问题,特别是Application类在不同进程中的初始化问题。介绍了通过getRunningAppProcesses()方法和UsageStatsManager类获取当前进程名称的技术,并提出了一种新的开源方案来解决这一难题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题描述

最近工作中碰到一个问题,在优化app,使用DDMS查看Application log过程中看到,app启动了三个进程,一个主进程,两个附带的进程。如下图可看到一个app启动的三个进程。
启动三个进程

自定义Application回调方法onCreate()被执行了3次。开始不知是何原因。

相关知识

android:process

从Android开发者文档中的manifest中进程配置android:process可以获知:
正常情况下,应用程序的所有组件运行在一个默认的进程名下,因此不需要使用这个属性。但在需要的情况下,可以通过使用这个属性来覆盖默认进程,这样一个app就跨越多个进程。

如果这个属性值以冒号(“:”)开始,说明新进程相对于应用程序是一个私有进程,且组件运行在此进程中。若属性值以小写字符开始,那么新进程即是一个全局进程,组件运行在这个全局进程中。这也意味着其他应用程序组件可以与此进程进行通信,减少资源使用。

标签<application>android:process属性可以为整个app内组件设置一个默认的运行进程。

manifest中组件标签<activity><service><provider>, <receiver>都支持配置android:process,即每个组件均可以创建运行在自己的一个新进程中。

Application类

我们可以自定义继承Application类来实现自己的Application,然后在其中的onCreate()方法中进行一定的初始化工作。

若自定义了Application类,那么需要注意的就是这个类在当app中有多个进程时,每个进程启动时都会初始化一次Application。在Android中很不幸的就是我们无法为每个新创建的进程来分别创建一个Application类。
多个进程启动时Application状态


解决方案

第一:getRunningAppProcesses()

每个进程对应一个application,这样可以通过针对特定进程名,进行相应的初始化工作,避免资源浪费,执行时间消耗。

因为Application的执行时间影响着首个activity,service等的启动时间。即Application执行时间越长,首个组件(activity)启动时间越晚,给用户造成的感觉就是应用启动速度特别慢。
3个进程启动耗时

可以看出,Application回调方法onCreate()被执行3次,均执行耗时操作,这样造成了在点击应用logo后,到看到进入app,首个页面(Activity)启动,耗时将近6s,外加处理器速度,在较慢的机器上,这个时间可能更长,甚至超过10s。

目前较多采用的方法既是所提到的根据具体进程来进行相应的初始化工作,核心的获取对应进程的方法如下:

/**
 * 获取进程名。
 * 由于app是一个多进程应用,因此每个进程被os创建时,    
 * onCreate()方法均会被执行一次,
 * 进行辨别初始化,针对特定进程进行相应初始化工作,
 * 此方法可以提高一半启动时间。
 *
 * @param context 上下文环境对象
 *
 * @return 获取此进程的进程名
 */
private String getProcessName(Context context) {
    ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    List<ActivityManager.RunningAppProcessInfo> runningAppProcesses = am.getRunningAppProcesses();
    if (runningAppProcesses == null) {
        return "";
    }

    for (ActivityManager.RunningAppProcessInfo runningAppProcess : runningAppProcesses) {
        if (runningAppProcess.pid == android.os.Process.myPid()
                && !TextUtils.isEmpty(runningAppProcess.processName)) {
            return runningAppProcess.processName;
        }
    }
    return "";
}

这样经过测试,在不同的进程被创建时,进行不同的工作,执行时间可以缩短一半。
修改3个进程启动耗时

虽然这种方法在某些版本上可以奏效,在但Android 5.0+版本中,由于Google开始收紧对Android底层权限管理,在趋势上方法getRunningAppProcesses()将会被毙掉。因为已经在一些版本的环境中,此方法返回null。

第二:UsageStatsManager

使用类UsageStatsManager来获取运行的apps列表,但是使用这个类需要添加一个权限PACKAGE_USAGE_STATS,而此权限是系统权限,要使用必须到Settings应用中去针对应用进行授权(我们的app用户肯定不会愿意多次一步)。另外,据称有些OEM厂商已经删除了此项设置,换言之在Settings中找不到授权入口。

因此这个途径也就被毙掉了。

最终方案

经过几天google方案及针对可能的解决方法进行测试,下边的这个感觉比较靠谱。

这是一个开源项目,项目地址点击这里

在我们的Application中集成并测试了该方法。

源码中添加一个方法,类似于使用getRunningAppProcesses()方法一样:
解决方案代码

以下是在三个不同的Android版本进行测试结果:
Android 4.4.2测试结果

Anroid 5.1.1测试结果

Android 6.0测试结果

可以看到,针对这三个版本是可以达到相关初始化代码只执行一次的效果。这样可以缩短启动消耗的时间。

更多版本类型测试大家可以自行进行测试。

时间原因,此方案的代码及解决方法还没有来得及跟踪,有时间在做分析……

这种方案也有限制:

  • 一些版本的系统应用不包括在内,因为他们具有更高级别的SElinux context

  • 这种方法也不是getRunningAppProcesses()完全的替代,因为它无法给出集成的pkgListlruimportance信息;

  • 此库在7.0开发者预览版本上是无法起作用的。

    下面的一篇文章就来分析下此方案是如何解决读取进程的——>《Android获取运行进程解决方案分析》

### Android 应用程序启动过程源码解析 #### Zygote 进程初始化 Zygote 是 Android 系统中的第一个 Java 进程,负责创建并管理其他应用进程。其主要职责之一是提前加载常用的类和资源到内存中,以便后续的应用可以快速启动。 ```java public class ZygoteInit { public static void main(String argv[]) { // 初始化虚拟机环境和其他必要的准备工作 preload(); ... // 开始监听来自 AMS 的请求来创建新进程 registerZygoteSocket(socketName); runSelectLoop(abiList); } } ``` 此阶段完成了 JVM 和核心库的预加载工作[^4]。 #### System Server 启动 在 Zygote 完成初始化之后会孵化出 SystemServer 进程,该进程中包含了多个重要的服务组件,比如 ActivityManagerService (AMS),它用于管理和调度所有的应用程序活动生命周期事件。 ```java // frameworks/base/services/java/com/android/server/SystemServer.java public final class SystemServer { private void run() throws MethodAndArgsCaller, RuntimeException { ... // 创建并启动核心系统服务 startBootstrapServices(); // 包含了 AMS 的启动逻辑 mActivityManagerService = activityManager; } } ``` 这一步骤标志着 Android 操作系统的大部分功能已经准备就绪[^3]。 #### Application 加载与启动 当用户点击某个 APP 图标时,Launcher 将向 AMS 发送启动命令;随后 AMS 通知对应的 Package Manager Service 查找目标 APK 文件位置,并告知 Zygote 去 fork 出一个新的子进程作为即将运行的目标 App 实例。 一旦新的进程建立成功,则继续执行以下操作: - **ClassLoader** 加载指定包名下的 `Application` 类; - 执行 `attachBaseContext()` 方法完成上下文关联设置; - 调用 `onCreate()` 生命周期回调函数开启业务逻辑处理。 最终,在 performLaunchActivity 中调用了 ActivityThread.handleBindApplication 来绑定 application 对象至当前线程环境中[^2]。 ```java private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { ... java.lang.ClassLoader cl = appContext.getClassLoader(); ComponentInfo component = r.activityInfo; Object obj = cl.loadClass(component.name).newInstance(); ((Application)obj).attach(appContext); if (r.isPersistable()) { Instrumentation.callActivityOnCreate(activity, icicle); } else { activity.performCreate(icicle); } ... } @Override protected void attach(Context context) { super.attach(context); // 继续其它初始化... } ``` 此时根 Activity 已经被正确实例化并且进入了 onCreate 阶段,意味着应用程序正式进入前台可见状态[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

虚妄皆空

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值