Android 重學系列 ActivityThread的初始化

前言

當我們了解了一個進程是怎么誕生的,一個Activity是怎么誕生的,那么在這兩個中間必定會存在Application的創(chuàng)建,其實在前面的文章已經和大家提到過關于ActivityThread和Application的初始化,本文就帶領大家來看看ActivityThread和Application是如何初始化,又是如何綁定的;應用自己的ClassLoader如何加載;資源R文件如何重載的;以及思考MultiDex的設計思路。

遇到問題可以來本文下討論:http://www.lxweimin.com/p/2b1d43ffeba6

正文

進程的初始化

讓我么回顧一下,當時Activity啟動流程在一文中,startSpecificActivityLocked檢測進程是否啟動一小節(jié),我當時忽略了當沒有進程時候的情況,本次因為是ActivityThread的初始化,那么必定會先走startProcessLocked先初始化進程。

        mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0,
                "activity", r.intent.getComponent(), false, false, true);

最后會走到下面這個方法。

文件:/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

   @GuardedBy("this")
    final ProcessRecord startProcessLocked(String processName, ApplicationInfo info,
            boolean knownToBeDead, int intentFlags, String hostingType, ComponentName hostingName,
            boolean allowWhileBooting, boolean isolated, int isolatedUid, boolean keepIfLarge,
            String abiOverride, String entryPoint, String[] entryPointArgs, Runnable crashHandler) {
        long startTime = SystemClock.elapsedRealtime();
        ProcessRecord app;
        if (!isolated) {
            app = getProcessRecordLocked(processName, info.uid, keepIfLarge);
            checkTime(startTime, "startProcess: after getProcessRecord");

            if ((intentFlags & Intent.FLAG_FROM_BACKGROUND) != 0) {
              ...
            } else {
               
                mAppErrors.resetProcessCrashTimeLocked(info);
                if (mAppErrors.isBadProcessLocked(info)) {
                    ...
                    mAppErrors.clearBadProcessLocked(info);
                    if (app != null) {
                        app.bad = false;
                    }
                }
            }
        } else {
            ...
        }

        // We don't have to do anything more if:
        // (1) There is an existing application record; and
        // (2) The caller doesn't think it is dead, OR there is no thread
        //     object attached to it so we know it couldn't have crashed; and
        // (3) There is a pid assigned to it, so it is either starting or
        //     already running.
        if (app != null && app.pid > 0) {
            if ((!knownToBeDead && !app.killed) || app.thread == null) {
...
                app.addPackage(info.packageName, info.versionCode, mProcessStats);
...
                return app;
            }

            // An application record is attached to a previous process,
            // clean it up now.

            killProcessGroup(app.uid, app.pid);
            handleAppDiedLocked(app, true, true);
        }

        String hostingNameStr = hostingName != null
                ? hostingName.flattenToShortString() : null;

        if (app == null) {
            app = newProcessRecordLocked(info, processName, isolated, isolatedUid);
            if (app == null) {
...
                return null;
            }
            app.crashHandler = crashHandler;
            app.isolatedEntryPoint = entryPoint;
            app.isolatedEntryPointArgs = entryPointArgs;
        } else {
    
            app.addPackage(info.packageName, info.versionCode, mProcessStats);
        }

        if (!mProcessesReady
                && !isAllowedWhileBooting(info)
                && !allowWhileBooting) {
            if (!mProcessesOnHold.contains(app)) {
                mProcessesOnHold.add(app);
            }

            return app;
        }

        final boolean success = startProcessLocked(app, hostingType, hostingNameStr, abiOverride);
        return success ? app : null;
    }

執(zhí)行啟動進程的行為,有如下幾種可能:

  • 1.本身還沒有就沒有誕生進程。
  • 2.已經誕生了,但是因為一些crash等問題,或者安裝好了一樣的進程需要重新啟動進程。

因此會先通過getProcessRecordLocked獲取對應的ProcessRecord。這個對象其實就是AMS中象征著進程的對象。這個對象會保存在一個SparseArray 中,因為剛好是以int型uid作為key存儲。

如果找到ProcessRecord,說明已經誕生出來過了。因此為了讓進程回歸到初始狀態(tài),會清空內部的錯誤棧信息,接著重新從ApplicationInfo讀取最新的進程對應的進程版本號等信息,接著殺掉進程組,handleAppDiedLocked處理App的死亡,主要是清空TaskRecord,記錄在ActivityStackSupervisor中所有的活躍Activity,銷毀對應進程WMS中的Surface。

如果沒有找到對應ProcessRecord,則調用newProcessRecordLocked創(chuàng)建一個新的進程對象,并把當前ProcessRecord添加到類型為SparseArray的mProcessMap。

接下來核心是另一個重載函數startProcessLocked。

AMS.startProcessLocked

    private final boolean startProcessLocked(ProcessRecord app, String hostingType,
            String hostingNameStr, boolean disableHiddenApiChecks, String abiOverride) {
        if (app.pendingStart) {
            return true;
        }
        long startTime = SystemClock.elapsedRealtime();
        if (app.pid > 0 && app.pid != MY_PID) {
            checkTime(startTime, "startProcess: removing from pids map");
            synchronized (mPidsSelfLocked) {
                mPidsSelfLocked.remove(app.pid);
                mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
            }
 
            app.setPid(0);
        }

....

            final String entryPoint = "android.app.ActivityThread";

            return startProcessLocked(hostingType, hostingNameStr, entryPoint, app, uid, gids,
                    runtimeFlags, mountExternal, seInfo, requiredAbi, instructionSet, invokeWith,
                    startTime);
        } catch (RuntimeException e) {
         ...
            return false;
        }
    }

中間設置很多準備啟動的參數,我們只需要關注剩下這些代碼。在這個當中,如果發(fā)現ProcessRecord不為空,則會拆調借助Handler實現的ANR為PROC_START_TIMEOUT_MSG的炸彈,不理解沒關系,等下就有全部流程。

在設置的眾多參數中有一個關鍵的參數entryPoint,字符串設置為android.app.ActivityThread。這種設計在Flutter也是這樣,告訴你hook的類以及方法是什么。

    private boolean startProcessLocked(String hostingType, String hostingNameStr, String entryPoint,
            ProcessRecord app, int uid, int[] gids, int runtimeFlags, int mountExternal,
            String seInfo, String requiredAbi, String instructionSet, String invokeWith,
            long startTime) {
        app.pendingStart = true;
        app.killedByAm = false;
        app.removed = false;
        app.killed = false;
        final long startSeq = app.startSeq = ++mProcStartSeqCounter;
        app.setStartParams(uid, hostingType, hostingNameStr, seInfo, startTime);
        if (mConstants.FLAG_PROCESS_START_ASYNC) {
            mProcStartHandler.post(() -> {
                try {
                    synchronized (ActivityManagerService.this) {
                        final String reason = isProcStartValidLocked(app, startSeq);
                        if (reason != null) {
                            app.pendingStart = false;
                            return;
                        }
                        app.usingWrapper = invokeWith != null
                                || SystemProperties.get("wrap." + app.processName) != null;
                        mPendingStarts.put(startSeq, app);
                    }
                    final ProcessStartResult startResult = startProcess(app.hostingType, entryPoint,
                            app, app.startUid, gids, runtimeFlags, mountExternal, app.seInfo,
                            requiredAbi, instructionSet, invokeWith, app.startTime);
                    synchronized (ActivityManagerService.this) {
                        handleProcessStartedLocked(app, startResult, startSeq);
                    }
                } catch (RuntimeException e) {
...
                    }
                }
            });
            return true;
        } else {
...
            return app.pid > 0;
        }
    }

在這個方法中,把fork進程分為兩個步驟:

  • 1.一般的startProcess調用Process.start通信Zygote進程,fork一個新進程。如果探測是hostType是webview則調用startWebView啟動webview
  • 2.handleProcessStartedLocked處理后續(xù)處理。

第一步驟中,我花了大量的篇幅描述了Zygote是如何孵化進程的,詳細可以閱讀我的第一篇重學系列系統(tǒng)啟動到Activity(下)這里就不多贅述,本文的讀者只需要明白通過Zygote孵化之后就誕生了一個App進程,并且調用了ActivityThread類中的main方法;而WebView同樣是由類似的方式管理通過一個WebViewZygote的對象孵化出來,有機會和大家聊聊。

我們來看看之前沒有聊過的第二步驟handleProcessStartedLocked。

AMS.handleProcessStartedLocked

經過上面的Socket通信之后,我們就能確切的獲取到孵化進程是否成功,去做后續(xù)處理。

    private boolean handleProcessStartedLocked(ProcessRecord app, int pid, boolean usingWrapper,
            long expectedStartSeq, boolean procAttached) {
        mPendingStarts.remove(expectedStartSeq);
...
        mBatteryStatsService.noteProcessStart(app.processName, app.info.uid);
...

        try {
            AppGlobals.getPackageManager().logAppProcessStartIfNeeded(app.processName, app.uid,
                    app.seInfo, app.info.sourceDir, pid);
        } catch (RemoteException ex) {
            // Ignore
        }

        if (app.persistent) {
            Watchdog.getInstance().processStarted(app.processName, pid);
        }

  ...
        app.setPid(pid);
        app.usingWrapper = usingWrapper;
        app.pendingStart = false;
        ProcessRecord oldApp;
        synchronized (mPidsSelfLocked) {
            oldApp = mPidsSelfLocked.get(pid);
        }
...
        synchronized (mPidsSelfLocked) {
            this.mPidsSelfLocked.put(pid, app);
//裝載ANR炸彈
            if (!procAttached) {
                Message msg = mHandler.obtainMessage(PROC_START_TIMEOUT_MSG);
                msg.obj = app;
                mHandler.sendMessageDelayed(msg, usingWrapper
                        ? PROC_START_TIMEOUT_WITH_WRAPPER : PROC_START_TIMEOUT);
            }
        }
        return true;
    }

在過程中,電源服務收集啟動進程的日志,把pid以及ProcessRecord存儲到類型為map的mPidsSelfLocked。最后會設置一個PROC_START_TIMEOUT_MSG的延時消息發(fā)送到Handler中。而這個延時事件就是10秒。

假如我們試一試超過這個事件會怎么樣?

進程啟動超時異常

case PROC_START_TIMEOUT_MSG: {
                ProcessRecord app = (ProcessRecord)msg.obj;
                synchronized (ActivityManagerService.this) {
                    processStartTimedOutLocked(app);
                }
            } 
    private final void processStartTimedOutLocked(ProcessRecord app) {
        final int pid = app.pid;
        boolean gone = false;
        synchronized (mPidsSelfLocked) {
            ProcessRecord knownApp = mPidsSelfLocked.get(pid);
            if (knownApp != null && knownApp.thread == null) {
                mPidsSelfLocked.remove(pid);
                gone = true;
            }
        }

        if (gone) {
                    pid, app.uid, app.processName);
            removeProcessNameLocked(app.processName, app.uid);
            if (mHeavyWeightProcess == app) {
                mHandler.sendMessage(mHandler.obtainMessage(CANCEL_HEAVY_NOTIFICATION_MSG,
                        mHeavyWeightProcess.userId, 0));
                mHeavyWeightProcess = null;
            }
            mBatteryStatsService.noteProcessFinish(app.processName, app.info.uid);

            cleanupAppInLaunchingProvidersLocked(app, true);
            mServices.processStartTimedOutLocked(app);
            app.kill("start timeout", true);
...
            removeLruProcessLocked(app);
            if (mBackupTarget != null && mBackupTarget.app.pid == pid) {
                mHandler.post(new Runnable() {
                @Override
                    public void run(){
                        try {
                            IBackupManager bm = IBackupManager.Stub.asInterface(
                                    ServiceManager.getService(Context.BACKUP_SERVICE));
                            bm.agentDisconnected(app.info.packageName);
                        } catch (RemoteException e) {
                            // Can't happen; the backup manager is local
                        }
                    }
                });
            }
            if (isPendingBroadcastProcessLocked(pid)) {

                skipPendingBroadcastLocked(pid);
            }
        } else {

        }
    }

能看到當啟動進程超時事件執(zhí)行的時候,會執(zhí)行如下幾個事情:

  • 1.清空mPidsSelfLocked中的緩存,清空mProcesMap中的緩存,如果是重量級進程,則會清空存儲在AMS重量級進程中的緩存
  • 2.關閉電源日志對應進程的的信息,清除已經那些等待進程初始化完畢之后要去加載的ContentProvider和Service
  • 3.接著執(zhí)行ProcessRecord的kill的方法,從Lru緩存中清除。
  • 4.關閉那些備份服務的鏈接,最后清除掉當前進程存放在正在監(jiān)聽的廣播并且準備發(fā)過來的信息。

因此,進程啟動也有ANR。當進程啟動超過10秒事件,也會理解退出。那么這個炸彈什么拆掉呢?接下來讓我們來聊聊,進程誕生之后ActivityThread的第一個方法。

ActivityThread的初始化

文件:/frameworks/base/core/java/android/app/ActivityThread.java

    public static void main(String[] args) {
...
        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

ActivityThread的main方法中會先初始化一個prepareMainLooper,并且進行l(wèi)oop的事件循環(huán)。

在這個過程中,能看到之前我在上一篇文章Handler中聊過的Looper日志打印。在這個過程中調用了ActivityThread的attch方法進一步綁定,這里綁定了什么呢?先來看看attach方法。

ActivityThread.attach

  private void attach(boolean system, long startSeq) {
        sCurrentActivityThread = this;
        mSystemThread = system;
        if (!system) {
            ViewRootImpl.addFirstDrawHandler(new Runnable() {
                @Override
                public void run() {
                    ensureJitEnabled();
                }
            });
            android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
                                                    UserHandle.myUserId());
            RuntimeInit.setApplicationObject(mAppThread.asBinder());
            final IActivityManager mgr = ActivityManager.getService();
            try {
                mgr.attachApplication(mAppThread, startSeq);
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
            // Watch for getting close to heap limit.
            BinderInternal.addGcWatcher(new Runnable() {
                @Override public void run() {
                    if (!mSomeActivitiesChanged) {
                        return;
                    }
                    Runtime runtime = Runtime.getRuntime();
                    long dalvikMax = runtime.maxMemory();
                    long dalvikUsed = runtime.totalMemory() - runtime.freeMemory();
                    if (dalvikUsed > ((3*dalvikMax)/4)) {
                        if (DEBUG_MEMORY_TRIM) Slog.d(TAG, "Dalvik max=" + (dalvikMax/1024)
                                + " total=" + (runtime.totalMemory()/1024)
                                + " used=" + (dalvikUsed/1024));
                        mSomeActivitiesChanged = false;
                        try {
                            mgr.releaseSomeActivities(mAppThread);
                        } catch (RemoteException e) {
                            throw e.rethrowFromSystemServer();
                        }
                    }
                }
            });
        } else {
            ....
        }

        // add dropbox logging to libcore
        DropBox.setReporter(new DropBoxReporter());

        ViewRootImpl.ConfigChangedCallback configChangedCallback
                = (Configuration globalConfig) -> {
            synchronized (mResourcesManager) {
                // We need to apply this change to the resources immediately, because upon returning
                // the view hierarchy will be informed about it.
                if (mResourcesManager.applyConfigurationToResourcesLocked(globalConfig,
                        null /* compat */)) {
                    updateLocaleListFromAppContext(mInitialApplication.getApplicationContext(),
                            mResourcesManager.getConfiguration().getLocales());

                    // This actually changed the resources! Tell everyone about it.
                    if (mPendingConfiguration == null
                            || mPendingConfiguration.isOtherSeqNewer(globalConfig)) {
                        mPendingConfiguration = globalConfig;
                        sendMessage(H.CONFIGURATION_CHANGED, globalConfig);
                    }
                }
            }
        };
        ViewRootImpl.addConfigCallback(configChangedCallback);
    }

當我們有之前的基礎之后,讀懂這一段就比較簡單了:

  • 1.注冊ViewRootImpl在第一次調用onDraw時候的回調,此時要打開虛擬機的jit模式。注意這個jit并不是Skia SkSL的jit模式,只是虛擬機的jit模式。不過在Android 9.0中是空實現。
  • 2.把ApplicationThread 綁定到AMS
  • 3.通過BinderInternal不斷的監(jiān)聽Java堆中是使用情況,如果使用情況超過3/4則會釋放一部分的Activity
  • 4.添加DropBox的打印器。DropBox實際上使用的是DropBoxManagerService,他會把日志記錄在/data/system/dropbox中。一般記錄一些一場行為,如anr,crash,watchdog(系統(tǒng)進程異常),native_crash,lowmem(低內存),strict_mode等待日志。
  • 5.添加當資源環(huán)境發(fā)生配置時候的回調,能看到此時會根據Locale語言環(huán)境切換資源

我們先看看第二點看看這個過程中做了什么。

綁定ApplicationThread

            final IActivityManager mgr = ActivityManager.getService();
            try {
                mgr.attachApplication(mAppThread, startSeq);
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }

這里能看到實際上是把ApplicationThread綁定到AMS。

private class ApplicationThread extends IApplicationThread.Stub

ApplicationThread其實是一個Binder對象。

如果熟悉Activity啟動流程的朋友就能明白,其實IActivityManager指的就是ActivityManagerService。這里不再介紹Binder如何運作的,我們直接看到ActivityManagerService中。

AMS的attachApplicationLocked

文件:/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

     @Override
    public final void attachApplication(IApplicationThread thread, long startSeq) {
        synchronized (this) {
            int callingPid = Binder.getCallingPid();
            final int callingUid = Binder.getCallingUid();
            final long origId = Binder.clearCallingIdentity();
            attachApplicationLocked(thread, callingPid, callingUid, startSeq);
            Binder.restoreCallingIdentity(origId);
        }
    }

Binder.clearCallingIdentity以及Binder.getCallingPid的使用一般會和restoreCallingIdentity一起使用。當我們需要使用Binder通信到自己的進程的時候會這么使用。這兩個Clear方法其實就是清空遠程端的Binder的Pid和Uid并返回到java層,restoreCallingIdentity把這兩個對象設置回來。

這里的核心方法就是attachApplicationLocked。

AMS.attachApplicationLocked

 private final boolean attachApplicationLocked(IApplicationThread thread,
            int pid, int callingUid, long startSeq) {

        // Find the application record that is being attached...  either via
        // the pid if we are running in multiple processes, or just pull the
        // next app record if we are emulating process with anonymous threads.
        ProcessRecord app;
        long startTime = SystemClock.uptimeMillis();
        if (pid != MY_PID && pid >= 0) {
            synchronized (mPidsSelfLocked) {
                app = mPidsSelfLocked.get(pid);
            }
        } else {
            app = null;
        }

        // It's possible that process called attachApplication before we got a chance to
        // update the internal state.
        if (app == null && startSeq > 0) {
            final ProcessRecord pending = mPendingStarts.get(startSeq);
            if (pending != null && pending.startUid == callingUid
                    && handleProcessStartedLocked(pending, pid, pending.usingWrapper,
                            startSeq, true)) {
                app = pending;
            }
        }

...


        if (app.thread != null) {
            handleAppDiedLocked(app, true, true);
        }

        // Tell the process all about itself.


        final String processName = app.processName;
        try {
            AppDeathRecipient adr = new AppDeathRecipient(
                    app, pid, thread);
            thread.asBinder().linkToDeath(adr, 0);
            app.deathRecipient = adr;
        } catch (RemoteException e) {
...
            return false;
        }


        app.makeActive(thread, mProcessStats);
        app.curAdj = app.setAdj = app.verifiedAdj = ProcessList.INVALID_ADJ;
        app.curSchedGroup = app.setSchedGroup = ProcessList.SCHED_GROUP_DEFAULT;
        app.forcingToImportant = null;
        updateProcessForegroundLocked(app, false, false);
        app.hasShownUi = false;
        app.debugging = false;
        app.cached = false;
        app.killedByAm = false;
        app.killed = false;


        app.unlocked = StorageManager.isUserKeyUnlocked(app.userId);

        mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);

        boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
        List<ProviderInfo> providers = normalMode ? generateApplicationProvidersLocked(app) : null;

        if (providers != null && checkAppInLaunchingProvidersLocked(app)) {
            Message msg = mHandler.obtainMessage(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG);
            msg.obj = app;
            mHandler.sendMessageDelayed(msg, CONTENT_PROVIDER_PUBLISH_TIMEOUT);
        }


        try {
...
            if (app.isolatedEntryPoint != null) {
...
            } else if (app.instr != null) {
                ...
            } else {
                thread.bindApplication(processName, appInfo, providers, null, profilerInfo,
                        null, null, null, testMode,
                        mBinderTransactionTrackingEnabled, enableTrackAllocation,
                        isRestrictedBackupMode || !normalMode, app.persistent,
                        new Configuration(getGlobalConfiguration()), app.compat,
                        getCommonServicesLocked(app.isolated),
                        mCoreSettingsObserver.getCoreSettingsLocked(),
                        buildSerial, isAutofillCompatEnabled);
            }
            if (profilerInfo != null) {
                profilerInfo.closeFd();
                profilerInfo = null;
            }
            checkTime(startTime, "attachApplicationLocked: immediately after bindApplication");
            updateLruProcessLocked(app, false, null);
            checkTime(startTime, "attachApplicationLocked: after updateLruProcessLocked");
            app.lastRequestedGc = app.lastLowMemory = SystemClock.uptimeMillis();
        } catch (Exception e) {
    ...
            return false;
        }

        // Remove this record from the list of starting applications.
        mPersistentStartingProcesses.remove(app);
        if (DEBUG_PROCESSES && mProcessesOnHold.contains(app)) Slog.v(TAG_PROCESSES,
                "Attach application locked removing on hold: " + app);
        mProcessesOnHold.remove(app);

        boolean badApp = false;
        boolean didSomething = false;

        // See if the top visible activity is waiting to run in this process...
        if (normalMode) {
            try {
                if (mStackSupervisor.attachApplicationLocked(app)) {
                    didSomething = true;
                }
            } catch (Exception e) {
                Slog.wtf(TAG, "Exception thrown launching activities in " + app, e);
                badApp = true;
            }
        }

        // Find any services that should be running in this process...
        if (!badApp) {
            try {
                didSomething |= mServices.attachApplicationLocked(app, processName);
                checkTime(startTime, "attachApplicationLocked: after mServices.attachApplicationLocked");
            } catch (Exception e) {
      ...
            }
        }

        // Check if a next-broadcast receiver is in this process...
        if (!badApp && isPendingBroadcastProcessLocked(pid)) {
            try {
                didSomething |= sendPendingBroadcastsLocked(app);
                checkTime(startTime, "attachApplicationLocked: after sendPendingBroadcastsLocked");
            } catch (Exception e) {
...
            }
        }

        // Check whether the next backup agent is in this process...
        if (!badApp && mBackupTarget != null && mBackupTarget.app == app) {
...
            notifyPackageUse(mBackupTarget.appInfo.packageName,
                             PackageManager.NOTIFY_PACKAGE_USE_BACKUP);
            try {
                thread.scheduleCreateBackupAgent(mBackupTarget.appInfo,
                        compatibilityInfoForPackageLocked(mBackupTarget.appInfo),
                        mBackupTarget.backupMode);
            } catch (Exception e) {
...
            }
        }

...
        if (!didSomething) {
            updateOomAdjLocked();
        }

        return true;
    }

經過壓縮流程,我們只需要關注這些代碼邏輯。

  • 1.從mPidsSelfLocked緩存對象中獲取對應pid的ProcessRecord對象
  • 2.如果此時找不到對應的ProcessRecord,則嘗試通過handleProcessStartedLocked說明可能添加遺漏了,就嘗試再添加到緩存中。一般走不到這里面。
  • 3.如果當前的ProcessRecord還包含著thread對象,說明這個進程其實已經啟動過了,而且還是經歷了重新啟動進程。因此需要通過handleAppDiedLocked先清理一次之前進程所有留在AMS,WMS中的緩存
  • 4.重新綁定Binder死亡監(jiān)聽;同時設置ProcessRecord中的數據,把ApplicationThread和ProcessRecord綁定起來。
  • 5.通過mHandler .removeMessages拆除進程啟動超時炸彈
  • 6.跨進程調用ApplicationThread的bindApplication方法。
  • 7.啟動那些之前已經在TaskRecord中活躍過的Activity,Service,Broadcast,ContentProvider。一般這種情況是指進程因為crash等原因重新啟動。
  • 8.調整adj值。這個值是當前進程的活躍度等級。

ApplicationThread.bindApplication

文件:frameworks/base/core/java/android/app/ActivityThread.java

        public final void bindApplication(String processName, ApplicationInfo appInfo,
                List<ProviderInfo> providers, ComponentName instrumentationName,
                ProfilerInfo profilerInfo, Bundle instrumentationArgs,
                IInstrumentationWatcher instrumentationWatcher,
                IUiAutomationConnection instrumentationUiConnection, int debugMode,
                boolean enableBinderTracking, boolean trackAllocation,
                boolean isRestrictedBackupMode, boolean persistent, Configuration config,
                CompatibilityInfo compatInfo, Map services, Bundle coreSettings,
                String buildSerial, boolean autofillCompatibilityEnabled) {

            if (services != null) {
                if (false) {
                    // Test code to make sure the app could see the passed-in services.
                    for (Object oname : services.keySet()) {
                        if (services.get(oname) == null) {
                            continue; // AM just passed in a null service.
                        }
                        String name = (String) oname;

                        // See b/79378449 about the following exemption.
                        switch (name) {
                            case "package":
                            case Context.WINDOW_SERVICE:
                                continue;
                        }

                        if (ServiceManager.getService(name) == null) {
                            Log.wtf(TAG, "Service " + name + " should be accessible by this app");
                        }
                    }
                }

                // Setup the service cache in the ServiceManager
                ServiceManager.initServiceCache(services);
            }

            setCoreSettings(coreSettings);

            AppBindData data = new AppBindData();
            data.processName = processName;
            data.appInfo = appInfo;
            data.providers = providers;
            data.instrumentationName = instrumentationName;
            data.instrumentationArgs = instrumentationArgs;
            data.instrumentationWatcher = instrumentationWatcher;
            data.instrumentationUiAutomationConnection = instrumentationUiConnection;
            data.debugMode = debugMode;
            data.enableBinderTracking = enableBinderTracking;
            data.trackAllocation = trackAllocation;
            data.restrictedBackupMode = isRestrictedBackupMode;
            data.persistent = persistent;
            data.config = config;
            data.compatInfo = compatInfo;
            data.initProfilerInfo = profilerInfo;
            data.buildSerial = buildSerial;
            data.autofillCompatibilityEnabled = autofillCompatibilityEnabled;
            sendMessage(H.BIND_APPLICATION, data);
        }

在這個過程中,實際上是初始化了ServiceManager一個隱藏的SystemService管理類,獲取從AMS傳輸過來的字符串進行初始化。

學習到現在出現兩個可以從App應用端獲取系統(tǒng)SystemServer進程中的服務。

  • SystemServiceRegistry 我們常用的Context.getService就是從這里面獲取的
  • ServiceManager一個不面向開發(fā)者的系統(tǒng)服務緩存

那么這兩個是什么關系呢?其實閱讀過我之前的文章就知道。在SystemServer初始化各種服務之后,會把每一個服務通過addService添加到本進程的ServiceManager中,換句話說,就是直接存放了一個key為服務名和value為IBinder的map值。

SystemServiceRegistry則是面向開發(fā)者的key,value的緩存IBinder的數據結構。可以通過Context.getSystemService獲取。初始化AppBindData數據,發(fā)送BIND_APPLICATION,到主線程的Handler。

ActivityThread.handleBindApplication

private void handleBindApplication(AppBindData data) {
        // Register the UI Thread as a sensitive thread to the runtime.
    ...
        synchronized (mResourcesManager) {
            /*
             * Update the system configuration since its preloaded and might not
             * reflect configuration changes. The configuration object passed
             * in AppBindData can be safely assumed to be up to date
             */
            mResourcesManager.applyConfigurationToResourcesLocked(data.config, data.compatInfo);
            mCurDefaultDisplayDpi = data.config.densityDpi;

            // This calls mResourcesManager so keep it within the synchronized block.
            applyCompatConfiguration(mCurDefaultDisplayDpi);
        }

        data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);

        if (agent != null) {
            handleAttachAgent(agent, data.info);
        }

        /**
         * Switch this process to density compatibility mode if needed.
         */
        if ((data.appInfo.flags&ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES)
                == 0) {
            mDensityCompatMode = true;
            Bitmap.setDefaultDensity(DisplayMetrics.DENSITY_DEFAULT);
        }
        updateDefaultDensity();

       ...
        final InstrumentationInfo ii;
        if (data.instrumentationName != null) {
            try {
                ii = new ApplicationPackageManager(null, getPackageManager())
                        .getInstrumentationInfo(data.instrumentationName, 0);
            } catch (PackageManager.NameNotFoundException e) {
        ...
            }

            mInstrumentationPackageName = ii.packageName;
            mInstrumentationAppDir = ii.sourceDir;
            mInstrumentationSplitAppDirs = ii.splitSourceDirs;
            mInstrumentationLibDir = getInstrumentationLibrary(data.appInfo, ii);
            mInstrumentedAppDir = data.info.getAppDir();
            mInstrumentedSplitAppDirs = data.info.getSplitAppDirs();
            mInstrumentedLibDir = data.info.getLibDir();
        } else {
            ii = null;
        }

        final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
        updateLocaleListFromAppContext(appContext,
                mResourcesManager.getConfiguration().getLocales());

...


        if (SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false)) {
            BaseDexClassLoader.setReporter(DexLoadReporter.getInstance());
        }

...

        // Continue loading instrumentation.
        if (ii != null) {

...
        } else {
            mInstrumentation = new Instrumentation();
            mInstrumentation.basicInit(this);
        }

...

        // 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;
...
        try {
            // If the app is being launched for full backup or restore, bring it up in
            // a restricted environment with the base application class.
            app = data.info.makeApplication(data.restrictedBackupMode, null);

            // Propagate autofill compat state
            app.setAutofillCompatibilityEnabled(data.autofillCompatibilityEnabled);

            mInitialApplication = app;

            // don't bring up providers in restricted mode; they may depend on the
            // app's custom Application class
            if (!data.restrictedBackupMode) {
                if (!ArrayUtils.isEmpty(data.providers)) {
                    installContentProviders(app, data.providers);
                    // For process that contains content providers, we want to
                    // ensure that the JIT is enabled "at some point".
                    mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);
                }
            }

            // Do this after providers, since instrumentation tests generally start their
            // test thread at this point, and we don't want that racing.
            try {
                mInstrumentation.onCreate(data.instrumentationArgs);
            }
            catch (Exception e) {
                ...
            }
            try {
                mInstrumentation.callApplicationOnCreate(app);
            } catch (Exception e) {
                ...
            }
        } finally {
            ...
        }

...
        }
    }

這個方法有點長,我們關注比較核心的邏輯:

  • 1.首先獲取mResourcesManager實例(ActivityThread構造函數實例化),給底層的資源設置dpi得到配置,更加詳細的,可以看看資源加載系列

  • 2.接著通過getPackageInfoNoCheck獲取LoadedApk對象,這個對象在插件化一文中聊過,本質是一個apk在內存中的表現對象

  • 3.通過createAppContext創(chuàng)建一個ContextImpl,這就是在整個App進程中第一個創(chuàng)建的Context

  • 4.創(chuàng)建Instrumentation對象,這個對象一般是用于自動化測試的中間鍵。

  • 5.通過makeApplication創(chuàng)建Application對象,接著通過installContentProviders啟動所有的ContextProvider對象

  • 6.回調mInstrumentation的ApplicationCreate方法,該方法最后會調用到Application的onCreate中。

在這個過程中,我們稍微看到了一處比較迷惑的地方。Application在bindApplication中執(zhí)行了一次。其實我們在創(chuàng)建Activity時候performLaunchActivity中又創(chuàng)建了一次。

Application app = r.packageInfo.makeApplication(false, mInstrumentation);

以及在bindApplication中

app = data.info.makeApplication(data.restrictedBackupMode, null);

這兩次有什么區(qū)別呢?假設這是一次,從零開始正常啟動的進程,而不是重新啟動的啟動的。此時restrictedBackupMode這個標志代表重新啟動時候的備份標志位。當我們是第一次啟動進程的時候,這個標志位默認false。

為了更加徹底理解LoadedApk對象,我們先來看看Android是如何通過getPackageInfoNoCheck創(chuàng)建這個對象

getPackageInfoNoCheck創(chuàng)建LoadedApk

    public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
            CompatibilityInfo compatInfo) {
        return getPackageInfo(ai, compatInfo, null, false, true, false);
    }

getPackageInfoNoCheck最后會調用getPackageInfo方法。

    private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
            ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
            boolean registerPackage) {
        final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid));
        synchronized (mResourcesManager) {
            WeakReference<LoadedApk> ref;
            if (differentUser) {
                // Caching not supported across users
                ref = null;
            } else if (includeCode) {
                ref = mPackages.get(aInfo.packageName);
            } else {
                ref = mResourcePackages.get(aInfo.packageName);
            }

            LoadedApk packageInfo = ref != null ? ref.get() : null;
            if (packageInfo == null || (packageInfo.mResources != null
                    && !packageInfo.mResources.getAssets().isUpToDate())) {
                ...
                packageInfo =
                    new LoadedApk(this, aInfo, compatInfo, baseLoader,
                            securityViolation, includeCode &&
                            (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);

                if (mSystemThread && "android".equals(aInfo.packageName)) {
                    packageInfo.installSystemApplicationInfo(aInfo,
                            getSystemContext().mPackageInfo.getClassLoader());
                }

                if (differentUser) {
                    // Caching not supported across users
                } else if (includeCode) {
                    mPackages.put(aInfo.packageName,
                            new WeakReference<LoadedApk>(packageInfo));
                } else {
                    mResourcePackages.put(aInfo.packageName,
                            new WeakReference<LoadedApk>(packageInfo));
                }
            }
            return packageInfo;
        }
    }

在這個過程中其實并沒有太多特殊處理本質上LoadedApk保存了ApplicationInfo這些從AMS中解析出來包中所有的信息。并且把LoadedApk保存到mResourcePackages的這個帶著弱引用的map中。

初始化Application

文件 :/frameworks/base/core/java/android/app/LoadedApk.java

    public Application makeApplication(boolean forceDefaultAppClass,
            Instrumentation instrumentation) {
        if (mApplication != null) {
            return mApplication;
        }

        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")) {
                initializeJavaContextClassLoader();
            }
            ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);
            appContext.setOuterContext(app);
        } catch (Exception e) {
           ...
        }
        mActivityThread.mAllApplications.add(app);
        mApplication = app;

        if (instrumentation != null) {
            try {
                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);
                }
            }
        }

        // Rewrite the R 'constants' for all library apks.
        SparseArray<String> packageIdentifiers = getAssets(mActivityThread)
                .getAssignedPackageIdentifiers();
        final int N = packageIdentifiers.size();
        for (int i = 0; i < N; i++) {
            final int id = packageIdentifiers.keyAt(i);
            if (id == 0x01 || id == 0x7f) {
                continue;
            }

            rewriteRValues(getClassLoader(), packageIdentifiers.valueAt(i), id);
        }

        return app;
    }

能看到在LoadApk對象中會全局緩存一個Application對象。之后每一次調用這個方法讀取之后,就會調用這個方法直接獲取緩存中的Application對象。

當沒有設置Application的class則會有默認的class,或者打開了備份服務之后強制使用默認Application。

接下來可以把分為幾個步驟:

  • 1.getClassLoader 裝載ClassLoader,如果是系統(tǒng)應用則initializeJavaContextClassLoader裝載另一個classLoader
  • 2.createAppContext創(chuàng)建一個context對象
  • 3.newApplication反射生成Application對象,并且調用了attachBaseContext方法,并且把Context和Application互相綁定
  • 4.如果傳入了instrumentation,最會調用Application.onCreate方法
  • 5.rewriteRValues重寫共享資源庫中的資源id。這種資源庫在資源章節(jié)中有聊過,這種資源共享庫,packageId往往是0x00,此時的id將會依據編譯的順序獲取,加載時候也是根據加載順序獲取,為此Android系統(tǒng)做了一個LookupTable進行一次映射。而rewriteRValues的作用就是反射這種共享資源庫中的R文件的onResourceLoaded方法,重寫里面的packageid,讓app可以查找。稍后做進一步解析。

接著從classLoader的加載開始聊。

getClassLoader 裝載Android的ClassLoader
    public ClassLoader getClassLoader() {
        synchronized (this) {
            if (mClassLoader == null) {
                createOrUpdateClassLoaderLocked(null /*addedPaths*/);
            }
            return mClassLoader;
        }
    }
    private void createOrUpdateClassLoaderLocked(List<String> addedPaths) {
        if (mPackageName.equals("android")) {
            if (mClassLoader != null) {
                return;
            }

            if (mBaseClassLoader != null) {
                mClassLoader = mBaseClassLoader;
            } else {
                mClassLoader = ClassLoader.getSystemClassLoader();
            }
            mAppComponentFactory = createAppFactory(mApplicationInfo, mClassLoader);

            return;
        }


        if (!Objects.equals(mPackageName, ActivityThread.currentPackageName()) && mIncludeCode) {
            try {
                ActivityThread.getPackageManager().notifyPackageUse(mPackageName,
                        PackageManager.NOTIFY_PACKAGE_USE_CROSS_PACKAGE);
            } catch (RemoteException re) {
                throw re.rethrowFromSystemServer();
            }
        }

        if (mRegisterPackage) {
            try {
                ActivityManager.getService().addPackageDependency(mPackageName);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }


        final List<String> zipPaths = new ArrayList<>(10);
        final List<String> libPaths = new ArrayList<>(10);

        boolean isBundledApp = mApplicationInfo.isSystemApp()
                && !mApplicationInfo.isUpdatedSystemApp();


        final String defaultSearchPaths = System.getProperty("java.library.path");
        final boolean treatVendorApkAsUnbundled = !defaultSearchPaths.contains("/vendor/lib");
        if (mApplicationInfo.getCodePath() != null
                && mApplicationInfo.isVendor() && treatVendorApkAsUnbundled) {
            isBundledApp = false;
        }

        makePaths(mActivityThread, isBundledApp, mApplicationInfo, zipPaths, libPaths);

        String libraryPermittedPath = mDataDir;

        if (isBundledApp) {

            libraryPermittedPath += File.pathSeparator
                    + Paths.get(getAppDir()).getParent().toString();

            libraryPermittedPath += File.pathSeparator + defaultSearchPaths;
        }

        final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths);

...

        final String zip = (zipPaths.size() == 1) ? zipPaths.get(0) :
                TextUtils.join(File.pathSeparator, zipPaths);



        boolean needToSetupJitProfiles = false;
        if (mClassLoader == null) {
..

            mClassLoader = ApplicationLoaders.getDefault().getClassLoader(zip,
                    mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath,
                    libraryPermittedPath, mBaseClassLoader,
                    mApplicationInfo.classLoaderName);
            mAppComponentFactory = createAppFactory(mApplicationInfo, mClassLoader);
...
            needToSetupJitProfiles = true;
        }

        if (!libPaths.isEmpty() && SystemProperties.getBoolean(PROPERTY_NAME_APPEND_NATIVE, true)) {
...
            try {
                ApplicationLoaders.getDefault().addNative(mClassLoader, libPaths);
            } finally {
...
            }
        }


        List<String> extraLibPaths = new ArrayList<>(3);
        String abiSuffix = VMRuntime.getRuntime().is64Bit() ? "64" : "";
        if (!defaultSearchPaths.contains("/vendor/lib")) {
            extraLibPaths.add("/vendor/lib" + abiSuffix);
        }
        if (!defaultSearchPaths.contains("/odm/lib")) {
            extraLibPaths.add("/odm/lib" + abiSuffix);
        }
        if (!defaultSearchPaths.contains("/product/lib")) {
            extraLibPaths.add("/product/lib" + abiSuffix);
        }
        if (!extraLibPaths.isEmpty()) {
...
            try {
                ApplicationLoaders.getDefault().addNative(mClassLoader, extraLibPaths);
            } finally {
...
            }
        }

        if (addedPaths != null && addedPaths.size() > 0) {
            final String add = TextUtils.join(File.pathSeparator, addedPaths);
            ApplicationLoaders.getDefault().addPath(mClassLoader, add);
            needToSetupJitProfiles = true;
        }


        if (needToSetupJitProfiles && !ActivityThread.isSystem()) {
            setupJitProfileSupport();
        }
    }v

在這個過程實際上做的事情就一件就是插件化一文中提到過的Android中ClassLoader的組成.


classloader.jpg

在應用進程初期還不會存在Application ClassLoader對象。只有開始實例化Application的時候,才會裝載Android應用獨有的ClassLoader對象。還能看到,如果包名是系統(tǒng)的android開始,則不會加載應用的ClassLoader對象。

當是應用的時候,判斷到當前的App是BundleApp,說明有部分代碼是在Google服務上,會預先設置好未來下載dex的目錄,之后會從里面加載。

接著執(zhí)行如下2個事情:

  • 1.ApplicationLoaders.getDefault().getClassLoader根據從PMS中解析出來的ApplicationInfo中所有資源的路徑,生成一個Application ClassLoader
  • 2.ApplicationLoaders.getDefault().addNative 添加系統(tǒng)中得到so,以及應用中的so。并且調用系統(tǒng)中過的so庫
ApplicationLoaders.getDefault().getClassLoader
    private ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled,
                                       String librarySearchPath, String libraryPermittedPath,
                                       ClassLoader parent, String cacheKey,
                                       String classLoaderName) {

        ClassLoader baseParent = ClassLoader.getSystemClassLoader().getParent();

        synchronized (mLoaders) {
            if (parent == null) {
                parent = baseParent;
            }

            if (parent == baseParent) {
                ClassLoader loader = mLoaders.get(cacheKey);
                if (loader != null) {
                    return loader;
                }

                ClassLoader classloader = ClassLoaderFactory.createClassLoader(
                        zip,  librarySearchPath, libraryPermittedPath, parent,
                        targetSdkVersion, isBundled, classLoaderName);

                GraphicsEnvironment.getInstance().setLayerPaths(
                        classloader, librarySearchPath, libraryPermittedPath);

                mLoaders.put(cacheKey, classloader);
                return classloader;
            }

            ClassLoader loader = ClassLoaderFactory.createClassLoader(
                    zip, null, parent, classLoaderName);
            return loader;
        }
    }

此時的parent在新建一個應用Application的時候為null。此時parent就是獲取getSystemClassLoader的parent。

此時baseParent就必定是parent。先嘗試從mLoaders通過key(此時為apk包路徑名)找緩存,最后通過ClassLoaderFactory.createClassLoader創(chuàng)造classLoader,最后添加到緩存中。

那么我們就有必要看看著幾個ClassLoader是否是我在插件化所說那樣的。

ClassLoader的構成

文件:/libcore/ojluni/src/main/java/java/lang/ClassLoader.java

public abstract class ClassLoader {

    static private class SystemClassLoader {
        public static ClassLoader loader = ClassLoader.createSystemClassLoader();
    }
    private static ClassLoader createSystemClassLoader() {
        String classPath = System.getProperty("java.class.path", ".");
        String librarySearchPath = System.getProperty("java.library.path", "");
        return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance());
    }

    @CallerSensitive
    public static ClassLoader getSystemClassLoader() {
        return SystemClassLoader.loader;
    }

}

getSystemClassLoader通過一個單例獲取一個SystemClassLoader,而這個系統(tǒng)加載器就是PathClassLoader。而這個對象就是繼承關系如下:

class PathClassLoader extends BaseDexClassLoader 
public class BaseDexClassLoader extends ClassLoader 

此時能看到一切的Classloader基礎都是BaseDexClassLoader,而DexClassLoader用于加載外部dex文件也是如此。而PathClassLoader是系統(tǒng)中默認初始化好的類加載器,用于加載路徑下的dex文件。因此在系統(tǒng)應用中,默認都是PathClassLoader。

讓我們情景代入一下,getSystemClassLoader的parent實際上就是BootLoader,也就是上圖中啟動類加載器。接下來看看ClassLoader工廠的createClassLoader方法。

ClassLoaderFactory.createClassLoader
    public static boolean isPathClassLoaderName(String name) {
        return name == null || PATH_CLASS_LOADER_NAME.equals(name) ||
                DEX_CLASS_LOADER_NAME.equals(name);
    }

    public static ClassLoader createClassLoader(String dexPath,
            String librarySearchPath, ClassLoader parent, String classloaderName) {
        if (isPathClassLoaderName(classloaderName)) {
            return new PathClassLoader(dexPath, librarySearchPath, parent);
        } else if (isDelegateLastClassLoaderName(classloaderName)) {
            return new DelegateLastClassLoader(dexPath, librarySearchPath, parent);
        }

        throw new AssertionError("Invalid classLoaderName: " + classloaderName);
    }


    public static ClassLoader createClassLoader(String dexPath,
            String librarySearchPath, String libraryPermittedPath, ClassLoader parent,
            int targetSdkVersion, boolean isNamespaceShared, String classloaderName) {

        final ClassLoader classLoader = createClassLoader(dexPath, librarySearchPath, parent,
                classloaderName);

        boolean isForVendor = false;
        for (String path : dexPath.split(":")) {
            if (path.startsWith("/vendor/")) {
                isForVendor = true;
                break;
            }
        }

        String errorMessage = createClassloaderNamespace(classLoader,
                                                         targetSdkVersion,
                                                         librarySearchPath,
                                                         libraryPermittedPath,
                                                         isNamespaceShared,
                                                         isForVendor);

        return classLoader;
    }

在這個工廠中,能看到如果傳遞下來的名字是null或者名字是PathClassLoader或者DexClassLoader則會默認創(chuàng)建PathClassLoader,并且通過native方法createClassloaderNamespace加載native層ClassLoader。

而ClassLoader有一個機制叫做雙親委托,當前的ClassLoader不會立即查找類而是不斷委托的根部,最后找不到才往下層查找。

如果從雙親委托的角度來看,本質上一般的App應用中,一層是BootClassLoader就是指Bootstrap,另一層就是PathClassLoader就是我們的App類加載器。

Android的R文件重載

有一個方法rewriteRValues很多人都忽視掉。一般來說這個方法很少走。但是,如果是跟著我上一篇文章資源加載文章走下來,就會知道,其實在Android中有一種特殊的庫,是只有資源沒有代碼的資源共享庫。而這種庫的packageID一般都為0x00.通過底層構建LookUpTable,把編譯時期和加載時期不一致的packageID映射起來。然而這樣還是沒辦法正常找到資源,因為我們通常會通過R.xx.xxx來加載資源共享庫。因此需要把資源共享庫中的內容,合并到當前包名才行。

    private void rewriteRValues(ClassLoader cl, String packageName, int id) {
        final Class<?> rClazz;
        try {
            rClazz = cl.loadClass(packageName + ".R");
        } catch (ClassNotFoundException e) {
            return;
        }

        final Method callback;
        try {
            callback = rClazz.getMethod("onResourcesLoaded", int.class);
        } catch (NoSuchMethodException e) {
            return;
        }

        Throwable cause;
        try {
            callback.invoke(null, id);
            return;
        } catch (IllegalAccessException e) {
            cause = e;
        } catch (InvocationTargetException e) {
            cause = e.getCause();
        }

        throw new RuntimeException("Failed to rewrite resource references for " + packageName,
                cause);
    }

能看到會反射調用當前包名對應的R文件中onResourcesLoaded方法。如果我們翻開R.java是根本找不到這個方法的。只有當鏈接了資源共享庫的R.java才會存在。

為此,我翻閱了AS的打包工具aapt的源碼,有機會可以嘗試著詳細的過一遍源碼。這里先上一個資源R文件生成流程的時序圖:


aapt R.java生成的工作流程.png

從上圖可以得知,其核心邏輯如下:當aapt的main方法解析到p參數中引用的資源,將會調用doPackage,開始嘗試的調用writeSymbolClass打包生成R.java文件,把每一個R文件中資源的int值賦值上。

在doPackage中一個核心邏輯就是

err = writeResourceSymbols(bundle, assets, assets->getPackage(), true,            
bundle->getBuildSharedLibrary() || bundle->getBuildAppAsSharedLibrary());

能看到只要你編譯的是資源共享庫,而這個標志位的打開則是依賴aapt資源打包命令:"-shared-lib"。

那么這個方法做的事情是什么呢?其核心邏輯如下:

            Class<?>[] declaredClasses = rClazz.getDeclaredClasses();
            for (Class<?> clazz : declaredClasses) {
                try {
                    if (clazz.getSimpleName().equals("styleable")) {
                        for (Field field : clazz.getDeclaredFields()) {
                            if (field.getType() == int[].class) {
                                rewriteIntArrayField(field, id);
                            }
                        }

                    } else {
                        for (Field field : clazz.getDeclaredFields()) {
                            rewriteIntField(field, id);
                        }
                    }
                } catch (Exception e) {
...
                }
            }

能看到其實就是通過反射,把R文件中的packageId的值復寫,把非法的0x00復寫成當前資源庫加載之后的packageID的數值,這樣App就能正常訪問資源共享文件。

總結

在進程初始化過程中做了如下的事情:

  • 1.把每一個生成的進程分配一個ProcessRecord對象,以pid為key緩存起來
  • 2.每一個進程都會有自己ANR,只是這個時候進程不會出現自己ANR的框。在這個過程中AMS會有一個10秒延時事件,如果進程啟動后沒有即使拆除這個炸彈,將會退出。

在ActivityThread初始化過程中,做了如下的事情:

  • 1.把ActivityThread中作為跨進程通信的中介ApplicationThread和AMS對應的ProcessRecord綁定起來。
  • 2.重新綁定死亡監(jiān)聽
  • 3.拆除進程啟動炸彈
  • 4.調用bindApplication,初始化Application
  • 5.啟動之前已經啟動過的四大組件

在Application初始化過程中,做了如下幾個步驟:

  • 1.加載資源配置
  • 2.生成LoadedApk對象
    1. makeApplication 加載PathClassLoader,創(chuàng)建一個ContextImpl對象
  • 4.反射生成Application對象,并調用attachBaseContext
    1. rewriteRValues 重寫資源庫中的packageID。
  • 6.調用Applcation的onCreate方法。

思考

理解了這個邏輯之后,我們來聊聊MultiDex的原理。大家都知道Android加載dex有65536個方法的限制,此時一般解決的方式就是MultiDex。而這個庫內部在做什么呢?為什么使用的時候,需要把install方法放在attachBaseContext中呢?

本質上十分簡單,實際上做的就是dex的加載。這個過程就是hook PathClassLoader的pathList對象中的dexElements對象,把拆分出小的dex文件加載到主的dex中。

思路本質上就是化整為零,你一個dex超過65536方法對吧,那就拆成幾個小的,一一加載進來,這樣就變相突破了限制。

知道了MultiDex的原理之后,我們就不難理解為什么要把install方法放在attachBaseContext中。因為attachBaseContext這個回調是整個App生命周期最早的換掉。此時也剛好了加載了自己的PathClassLoader,連Application.onCreate都沒有調用,就能辦到在不影響業(yè)務的前提下,加載類。

那么這么做有什么壞處呢?在主線程加載dex文件會出現ANR等卡頓現象。有不少團隊為了解決這個問題,使用了異步線程或者異步進程調用install方法,但是這樣就會出現dex還沒加載到內存,但是想要調用了,就出現類無法找到異常。

為了處理這個問題,有些廠商就是hook Activity的啟動流程,當沒有找到這個類的時候將會出現一個中間頁或者等待等。

隨著眼界的開放,如果只是為了提升啟動速度,其實可以通過redex對dex進行重排,把一開始使用的dex排到主dex中,少用的dex排到其他分dex中,這樣異步調用MultiDex.install的方法就能跳過這些不自然的交互。

所有的核心技術都在我寫的插件化基礎框架都有解析過橫向淺析Small,RePlugin兩個插件化框架

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。