アプリはいくつの Application オブジェクトを作成しますか?

問題の背景

最近、友人グループと技術的な問題について話し合いました。

交換1

一个应用开启了多进程,最终到底会创建几个application对象,执行几次onCreate()方法?

一部の友人グループは、自分の考えに基づいて推測を行いました

交換 2

一部のグループの友人は ChatGPT に直接相談しました

chatgpt1.jpg

しかし、最初から最後まで最終的な結論はありません。そこで、この問題を明らかにするために、デモ テストを作成して結論を​​導き出し、ソース コードから理由を分析することにしました。

デモ検証

最初にアプリ プロジェクトを作成し、マルチプロセスを開始します

<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <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>

</manifest>
复制代码

次に、DemoApplication の onCreate() メソッドで、アプリケーション オブジェクトのアドレスと現在のプロセスの名前を出力します。

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 を表示

これは、この時点でアプリにプロセスが 1 つしかなく、アプリケーション オブジェクトが 1 つだけあり、オブジェクト アドレスが @fb06c2d であることを意味します。

次に、プロセスを複数に増やし、それがどのように進むかを確認します

<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <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>

</manifest>
复制代码

ロジックは、 click MainActivityto launch TwoActivity、 click TwoActivityto launchThreeActivityなどです。最後に、すべてのアクティビティを実行して開始し、次のログを取得します

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を見る

このとき、アプリには 5 つのプロセスがありますが、アプリケーション オブジェクトのアドレスは @fb06c2d であり、同じアドレスは同じオブジェクトであることを意味します。

いくつのプロセスが開始されても、作成されるアプリケーション オブジェクトは 1 つだけであると結論付けることができますか? この結論にジャンプすることはできません. 属性を削除して再度実行しますMainActivity.process得られたログは次のとおりです.

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を見る

この時点で、アプリには 5 つのプロセスがありますが、アプリケーション オブジェクトは 2 つあり、オブジェクト アドレスは @5d49e29 と @fb06c2d であり、子プロセスのアプリケーション オブジェクトは同じです。

上記のすべてのプロセスの親プロセス ID は 678 であり、このプロセスは zygote プロセスです。

受精卵プロセス

上記のテスト結果によると、次の結論を導き出すことができます。

  • 結論 1: 1 つのプロセスは 1 つのApplicationオブジェクトのみを作成し、メソッドを 1 回実行します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()方法中

	@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的创建过程

	@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显然并不成立,只是测试的偶然性导致的。

可真的是这样子的吗,这也太颠覆我的三观了,为此我跟群友讨论了这个问题:

不同进程中的多个对象,内存地址相同,是否代表这些对象都是同一个对象?

グループフレンドの考え方は、javaで取得した仮想メモリアドレスが全て同じというもので、仮想メモリアドレスが同じだからといって同じオブジェクトというわけではなく、物理メモリアドレスが同じというだけで同じということになります。メモリ空間, つまりそれらは同じオブジェクト. , 物理メモリアドレスと仮想メモリアドレスの間にはマッピング関係があります. 同時に, javaで物理メモリアドレスを取得する方法が与えられています. Androidはobject address , 主にUnsafeこのクラスを使用して操作します. このクラスの機能の1つは, システムメモリリソースに直接アクセスすることです. 詳細な説明については, Java-Unsafe のマジッククラスを参照してください. この操作は安全でないため, 非公開としてマークされていますが,この API はリフレクションで呼び出せるとのことで、レジスターを担当している部署の上司に聞きに行ったところ、グループの友達の考えは肯定的だったので、コードを追加して、その物理メモリ アドレスを取得しようとしました。同じかどうかを確認するオブジェクト

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

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "DemoApplication=" + this + ", address=" + addressOf(this) + ", pid=" + Process.myPid());
    }

    //获取对象的真实物理地址
    public static long addressOf(Object o) {
        Object[] array = new Object[]{o};
        long objectAddress = -1;
        try {
            Class cls = Class.forName("sun.misc.Unsafe");
            Field field = cls.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            Object unsafe = field.get(null);
            Class unsafeCls = unsafe.getClass();
            Method arrayBaseOffset = unsafeCls.getMethod("arrayBaseOffset", Object.class.getClass());
            int baseOffset = (int) arrayBaseOffset.invoke(unsafe, Object[].class);
            Method size = unsafeCls.getMethod("addressSize");
            int addressSize = (int) size.invoke(unsafe);
            switch (addressSize) {
                case 4:
                    Method getInt = unsafeCls.getMethod("getInt", Object.class, long.class);
                    objectAddress = (int) getInt.invoke(unsafe, array, baseOffset);
                    break;
                case 8:
                    Method getLong = unsafeCls.getMethod("getLong", Object.class, long.class);
                    objectAddress = (long) getLong.invoke(unsafe, array, baseOffset);
                    break;
                default:
                    throw new Error("unsupported address size: " + addressSize);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return objectAddress;
    }
}
复制代码

実行後、次のログが取得されます

2023-03-10 11:01:54.043 6535-6535/com.jason.demo0307 D/jasonwan: DemoApplication=com.jason.demo0307.DemoApplication@930d275, address=8050489105119022792, pid=6535
2023-03-10 11:02:22.610 6579-6579/com.jason.demo0307 D/jasonwan: DemoApplication=com.jason.demo0307.DemoApplication@331b3b9, address=8050489105119027136, pid=6579
2023-03-10 11:02:36.369 6617-6617/com.jason.demo0307 D/jasonwan: DemoApplication=com.jason.demo0307.DemoApplication@331b3b9, address=8050489105119029912, pid=6617
2023-03-10 11:02:39.244 6654-6654/com.jason.demo0307 D/jasonwan: DemoApplication=com.jason.demo0307.DemoApplication@331b3b9, address=8050489105119032760, pid=6654
2023-03-10 11:02:40.841 6692-6692/com.jason.demo0307 D/jasonwan: DemoApplication=com.jason.demo0307.DemoApplication@331b3b9, address=8050489105119036016, pid=6692
2023-03-10 11:02:52.429 6729-6729/com.jason.demo0307 D/jasonwan: DemoApplication=com.jason.demo0307.DemoApplication@331b3b9, address=8050489105119038720, pid=6729
复制代码

アプリケーションの仮想メモリ アドレスは同じですが、どちらも 331b3b9 ですが、実際の物理アドレスは異なります. これまでのところ、最終的な結論を引き出すことができます:

  • 単一プロセス、1 つのアプリケーション オブジェクトの作成、onCreate()メソッドの1 回の実行
  • マルチプロセス (N)、N 個のアプリケーション オブジェクトを作成し、onCreate()メソッドをN 回実行する

おすすめ

転載: juejin.im/post/7208345469658415159