一個app到底會創建多少個Application對象

語言: CN / TW / HK

問題背景

最近跟羣友討論一個技術問題:

交流1

一個應用開啟了多進程,最終到底會創建幾個application對象,執行幾次onCreate()方法?

有的羣友根據自己的想法給出了猜想

交流2

甚至有的羣友直接諮詢起了ChatGPT

chatgpt1.jpg

但至始至終都沒有一個最終的結論。於是乎,為了弄清這個問題,我決定先寫個demo測試得出結論,然後從源碼着手分析原因

Demo驗證

首先創建了一個app項目,開啟多進程

```xml

<application
    android:name=".DemoApplication"
    android:allowBackup="true"
    android:dataExtractionRules="@xml/data_extraction_rules"
    android:fullBackupContent="@xml/backup_rules"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/Theme.Demo0307"
    tools:targetApi="31">
    <!--android:process 開啟多進程並設置進程名-->
    <activity
        android:name=".MainActivity"
        android:exported="true"
        android:process=":remote">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>

    </activity>
</application>

```

然後在DemoApplication的onCreate()方法打印application對象的地址,當前進程名稱

```java public class DemoApplication extends Application { private static final String TAG = "jasonwan";

@Override
public void onCreate() {
    super.onCreate();
    Log.d(TAG, "Demo application onCreate: " + this + ", processName=" + getProcessName(this));
}

private String getProcessName(Application app) {
    int myPid = Process.myPid();
    ActivityManager am = (ActivityManager) app.getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
    List<ActivityManager.RunningAppProcessInfo> runningAppProcesses = am.getRunningAppProcesses();
    for (ActivityManager.RunningAppProcessInfo runningAppProcess : runningAppProcesses) {
        if (runningAppProcess.pid == myPid) {
            return runningAppProcess.processName;
        }
    }
    return "null";
}

} ```

運行,得到的日誌如下

2023-03-07 11:15:27.785 19563-19563/com.jason.demo0307 D/jasonwan: Demo application onCreate: com.jason.demo0307.DemoApplication@fb06c2d, processName=com.jason.demo0307:remote

查看當前應用所有進程

查看進程1

説明此時app只有一個進程,且只有一個application對象,對象地址為@fb06c2d

現在我們將進程增加到多個,看看情況如何

```xml

<application
    android:name=".DemoApplication"
    android:allowBackup="true"
    android:dataExtractionRules="@xml/data_extraction_rules"
    android:fullBackupContent="@xml/backup_rules"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/Theme.Demo0307"
    tools:targetApi="31">
    <!--android:process 開啟多進程並設置進程名-->
    <activity
        android:name=".MainActivity"
        android:exported="true"
        android:process=":remote">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>

    </activity>
    <activity
        android:name=".TwoActivity"
        android:process=":remote2" />
    <activity
        android:name=".ThreeActivity"
        android:process=":remote3" />
    <activity
        android:name=".FourActivity"
        android:process=":remote4" />
    <activity
        android:name=".FiveActivity"
        android:process=":remote5" />
</application>

```

邏輯是點擊MainActivity啟動TwoActivity,點擊TwoActivity啟動ThreeActivity,以此類推。最後我們運行,啟動所有Activity得到的日誌如下

2023-03-07 11:25:35.433 19955-19955/com.jason.demo0307 D/jasonwan: Demo application onCreate: com.jason.demo0307.DemoApplication@fb06c2d, processName=com.jason.demo0307:remote 2023-03-07 11:25:43.795 20001-20001/com.jason.demo0307 D/jasonwan: Demo application onCreate: com.jason.demo0307.DemoApplication@fb06c2d, processName=com.jason.demo0307:remote2 2023-03-07 11:25:45.136 20046-20046/com.jason.demo0307 D/jasonwan: Demo application onCreate: com.jason.demo0307.DemoApplication@fb06c2d, processName=com.jason.demo0307:remote3 2023-03-07 11:25:45.993 20107-20107/com.jason.demo0307 D/jasonwan: Demo application onCreate: com.jason.demo0307.DemoApplication@fb06c2d, processName=com.jason.demo0307:remote4 2023-03-07 11:25:46.541 20148-20148/com.jason.demo0307 D/jasonwan: Demo application onCreate: com.jason.demo0307.DemoApplication@fb06c2d, processName=com.jason.demo0307:remote5

查看當前應用所有進程

查看進程2

此時app有5個進程,但application對象地址均為@fb06c2d,地址相同意味着它們是同一個對象。

那是不是就可以得出結論,無論啟動多少個進程都只會創建一個application對象呢?並不能妄下此定論,我們將MainActivityprocess屬性去掉再運行,得到的日誌如下

2023-03-07 11:32:10.156 20318-20318/com.jason.demo0307 D/jasonwan: Demo application onCreate: com.jason.demo0307.DemoApplication@5d49e29, processName=com.jason.demo0307 2023-03-07 11:32:15.143 20375-20375/com.jason.demo0307 D/jasonwan: Demo application onCreate: com.jason.demo0307.DemoApplication@fb06c2d, processName=com.jason.demo0307:remote2 2023-03-07 11:32:16.477 20417-20417/com.jason.demo0307 D/jasonwan: Demo application onCreate: com.jason.demo0307.DemoApplication@fb06c2d, processName=com.jason.demo0307:remote3 2023-03-07 11:32:17.582 20463-20463/com.jason.demo0307 D/jasonwan: Demo application onCreate: com.jason.demo0307.DemoApplication@fb06c2d, processName=com.jason.demo0307:remote4 2023-03-07 11:32:18.882 20506-20506/com.jason.demo0307 D/jasonwan: Demo application onCreate: com.jason.demo0307.DemoApplication@fb06c2d, processName=com.jason.demo0307:remote5

查看當前應用所有進程

查看進程3

此時app有5個進程,但有2個application對象,對象地址為@5d49e29和@fb06c2d,且子進程的application對象都相同。

上述所有進程的父進程ID為678,而此進程正是zygote進程

zygote進程

根據上面的測試結果我們目前能得出的結論:

  • 結論1:單進程只創建一個Application對象,執行一次onCreate()方法;
  • 結論2:多進程至少創建2個Application對象,執行多次onCreate()方法,幾個進程就執行幾次;

結論2為什麼説至少創建2個,因為我在集成了JPush的商業項目中測試發現,JPush創建的進程跟我自己創建的進程,Application地址是不同的。

jpush進程

這裏三個進程,分別創建了三個Application對象,對象地址分別是@f31ba9d,@2c586f3,@fb06c2d

源碼分析

這裏需要先了解App的啟動流程,具體可以參考《App啟動流程》

Application的創建位於frameworks/base/core/java/android/app/ActivityThread.javahandleBindApplication()方法中

```java @UnsupportedAppUsage private void handleBindApplication(AppBindData data) { long st_bindApp = SystemClock.uptimeMillis(); //省略部分代碼

    // Note when this process has started.
    //設置進程啟動時間
    Process.setStartTimes(SystemClock.elapsedRealtime(), SystemClock.uptimeMillis());

    //省略部分代碼

    // send up app name; do this *before* waiting for debugger
    //設置進程名稱
    Process.setArgV0(data.processName);
    //省略部分代碼

    // Allow disk access during application and provider setup. This could
    // block processing ordered broadcasts, but later processing would
    // probably end up doing the same disk access.
    Application app;
    final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
    final StrictMode.ThreadPolicy writesAllowedPolicy = StrictMode.getThreadPolicy();
    try {
        // If the app is being launched for full backup or restore, bring it up in
        // a restricted environment with the base application class.
        //此處開始創建application對象,注意參數2為null
        app = data.info.makeApplication(data.restrictedBackupMode, null);

        //省略部分代碼
        try {
            if ("com.jason.demo0307".equals(app.getPackageName())){
                Log.d("jasonwan", "execute app onCreate(), app=:"+app+", processName="+getProcessName(app)+", pid="+Process.myPid());
            }
            //執行application的onCreate方法()
            mInstrumentation.callApplicationOnCreate(app);
        } catch (Exception e) {
            if (!mInstrumentation.onException(app, e)) {
                throw new RuntimeException(
                  "Unable to create application " + app.getClass().getName()
                  + ": " + e.toString(), e);
            }
        }
    } finally {
        // If the app targets < O-MR1, or doesn't change the thread policy
        // during startup, clobber the policy to maintain behavior of b/36951662
        if (data.appInfo.targetSdkVersion < Build.VERSION_CODES.O_MR1
                || StrictMode.getThreadPolicy().equals(writesAllowedPolicy)) {
            StrictMode.setThreadPolicy(savedPolicy);
        }
    }
    //省略部分代碼
}

```

實際創建過程在frameworks/base/core/java/android/app/LoadedApk.java中的makeApplication()方法中,LoadedApk顧名思義就是加載好的Apk文件,裏面包含Apk所有信息,像包名、Application對象,app所在的目錄等,這裏直接看application的創建過程

```java @UnsupportedAppUsage public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) { if ("com.jason.demo0307".equals(mApplicationInfo.packageName)) { Log.d("jasonwan", "makeApplication: mApplication="+mApplication+", pid="+Process.myPid()); } //如果已經創建過了就不再創建 if (mApplication != null) { return mApplication; }

    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "makeApplication");

    Application app = null;

    String appClass = mApplicationInfo.className;
    if (forceDefaultAppClass || (appClass == null)) {
        appClass = "android.app.Application";
    }

    try {
        java.lang.ClassLoader cl = getClassLoader();
        if (!mPackageName.equals("android")) {
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                    "initializeJavaContextClassLoader");
            initializeJavaContextClassLoader();
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        }
        ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
        //反射創建application對象
        app = mActivityThread.mInstrumentation.newApplication(
                cl, appClass, appContext);
        if ("com.jason.demo0307.DemoApplication".equals(appClass)){
            Log.d("jasonwan", "create application, app="+app+", processName="+mActivityThread.getProcessName()+", pid="+Process.myPid());
        }
        appContext.setOuterContext(app);
    } catch (Exception e) {
        Log.d("jasonwan", "fail to create application, "+e.getMessage());
        if (!mActivityThread.mInstrumentation.onException(app, e)) {
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            throw new RuntimeException(
                "Unable to instantiate application " + appClass
                + ": " + e.toString(), e);
        }
    }
    mActivityThread.mAllApplications.add(app);
    mApplication = app;

    if (instrumentation != null) {
        try {
            //第一次啟動創建時,instrumentation為null,不會執行onCreate()方法
            instrumentation.callApplicationOnCreate(app);
        } catch (Exception e) {
            if (!instrumentation.onException(app, e)) {
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                throw new RuntimeException(
                    "Unable to create application " + app.getClass().getName()
                    + ": " + e.toString(), e);
            }
        }
    }

    // 省略部分代碼
    return app;
}

```

為了看清application到底被創建了幾次,我在關鍵地方埋下了log,TAG為jasonwan的log是我自己加的,編譯驗證,得到如下log

``` 啟動app,進入MainActivity 03-08 17:20:29.965 4069 4069 D jasonwan: makeApplication: mApplication=null, pid=4069 //創建application對象,地址為@c2f8311,當前進程id為4069 03-08 17:20:29.967 4069 4069 D jasonwan: create application, app=com.jason.demo0307.DemoApplication@c2f8311, processName=com.jason.demo0307, pid=4069 03-08 17:20:29.988 4069 4069 D jasonwan: execute app onCreate(), app=:com.jason.demo0307.DemoApplication@c2f8311, processName=com.jason.demo0307, pid=4069 03-08 17:20:29.989 4069 4069 D jasonwan: DemoApplication=com.jason.demo0307.DemoApplication@c2f8311, processName=com.jason.demo0307, pid=4069 03-08 17:20:36.614 4069 4069 D jasonwan: makeApplication: mApplication=com.jason.demo0307.DemoApplication@c2f8311, pid=4069

點擊MainActivity,跳轉到TwoActivity 03-08 17:20:39.686 4116 4116 D jasonwan: makeApplication: mApplication=null, pid=4116 //創建application對象,地址為@c2f8311,當前進程id為4116 03-08 17:20:39.687 4116 4116 D jasonwan: create application, app=com.jason.demo0307.DemoApplication@c2f8311, processName=com.jason.demo0307:remote2, pid=4116 03-08 17:20:39.688 4116 4116 D jasonwan: execute app onCreate(), app=:com.jason.demo0307.DemoApplication@c2f8311, processName=com.jason.demo0307:remote2, pid=4116 03-08 17:20:39.688 4116 4116 D jasonwan: DemoApplication=com.jason.demo0307.DemoApplication@c2f8311, processName=com.jason.demo0307:remote2, pid=4116 03-08 17:20:39.733 4116 4116 D jasonwan: makeApplication: mApplication=com.jason.demo0307.DemoApplication@c2f8311, pid=4116

點擊TwoActivity,跳轉到ThreeActivity 03-08 17:20:41.473 4147 4147 D jasonwan: makeApplication: mApplication=null, pid=4147 //創建application對象,地址為@c2f8311,當前進程id為4147 03-08 17:20:41.475 4147 4147 D jasonwan: create application, app=com.jason.demo0307.DemoApplication@c2f8311, processName=com.jason.demo0307:remote3, pid=4147 03-08 17:20:41.475 4147 4147 D jasonwan: execute app onCreate(), app=:com.jason.demo0307.DemoApplication@c2f8311, processName=com.jason.demo0307:remote3, pid=4147 03-08 17:20:41.476 4147 4147 D jasonwan: DemoApplication=com.jason.demo0307.DemoApplication@c2f8311, processName=com.jason.demo0307:remote3, pid=4147 03-08 17:20:41.519 4147 4147 D jasonwan: makeApplication: mApplication=com.jason.demo0307.DemoApplication@c2f8311, pid=4147

點擊ThreeActivity,跳轉到FourActivity 03-08 17:20:42.966 4174 4174 D jasonwan: makeApplication: mApplication=null, pid=4174 //創建application對象,地址為@c2f8311,當前進程id為4174 03-08 17:20:42.968 4174 4174 D jasonwan: create application, app=com.jason.demo0307.DemoApplication@c2f8311, processName=com.jason.demo0307:remote4, pid=4174 03-08 17:20:42.969 4174 4174 D jasonwan: execute app onCreate(), app=:com.jason.demo0307.DemoApplication@c2f8311, processName=com.jason.demo0307:remote4, pid=4174 03-08 17:20:42.969 4174 4174 D jasonwan: DemoApplication=com.jason.demo0307.DemoApplication@c2f8311, processName=com.jason.demo0307:remote4, pid=4174 03-08 17:20:43.015 4174 4174 D jasonwan: makeApplication: mApplication=com.jason.demo0307.DemoApplication@c2f8311, pid=4174

點擊FourActivity,跳轉到FiveActivity 03-08 17:20:44.426 4202 4202 D jasonwan: makeApplication: mApplication=null, pid=4202 //創建application對象,地址為@c2f8311,當前進程id為4202 03-08 17:20:44.428 4202 4202 D jasonwan: create application, app=com.jason.demo0307.DemoApplication@c2f8311, processName=com.jason.demo0307:remote5, pid=4202 03-08 17:20:44.429 4202 4202 D jasonwan: execute app onCreate(), app=:com.jason.demo0307.DemoApplication@c2f8311, processName=com.jason.demo0307:remote5, pid=4202 03-08 17:20:44.430 4202 4202 D jasonwan: DemoApplication=com.jason.demo0307.DemoApplication@c2f8311, processName=com.jason.demo0307:remote5, pid=4202 03-08 17:20:44.473 4202 4202 D jasonwan: makeApplication: mApplication=com.jason.demo0307.DemoApplication@c2f8311, pid=4202 ```

結果很震驚,我們在5個進程中創建的application對象,地址均為@c2f8311,也就是至始至終創建的都是同一個Application對象,那麼上面的結論2顯然並不成立,只是測試的偶然性導致的。

至此,我們可以得出最終結論

  • 單進程,創建1個application對象,執行一次onCreate()方法
  • 多進程(N),至少創建1個application對象,執行N次onCreate()方法

至於為什麼上面5個進程創建的application對象地址都相同,這個暫時沒搞清楚,可能跟linux中的進程組設計有關係,也就是説因為進程隔離的原因,這幾個application對象在不同的進程中有着相同的內存地址,但他們並不屬於同一個對象,如果這個猜想成立,那就意味着

  • 多進程(N),創建N個application對象,執行N次onCreate()方法

這僅僅是我個人的猜想,如果你對這一塊瞭解,可以在評論區談談你的見解!