Android 9.0 Launcher3原始碼分析(二)——Launcher應用啟動流程,資料載入與繫結
轉載請註明原地址: https://www.jianshu.com/p/725bdb3d08aa
上一篇文章中分析了系統是如何把桌面應用拉起的。(見 Android 9.0 Launcher3原始碼分析(一)——系統啟動Launcher流程 )
現在接上文,分析一下Launcher應用的啟動流程。
首先把Launcher的onCreate貼出來。
@Override protected void onCreate(Bundle savedInstanceState) { if (DEBUG_STRICT_MODE) { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectDiskReads() .detectDiskWrites() .detectNetwork()// or .detectAll() for all detectable problems .penaltyLog() .build()); StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() .detectLeakedSqlLiteObjects() .detectLeakedClosableObjects() .penaltyLog() .penaltyDeath() .build()); } TraceHelper.beginSection("Launcher-onCreate"); super.onCreate(savedInstanceState); TraceHelper.partitionSection("Launcher-onCreate", "super call"); LauncherAppState app = LauncherAppState.getInstance(this); mOldConfig = new Configuration(getResources().getConfiguration()); mModel = app.setLauncher(this); initDeviceProfile(app.getInvariantDeviceProfile()); mSharedPrefs = Utilities.getPrefs(this); mIconCache = app.getIconCache(); mAccessibilityDelegate = new LauncherAccessibilityDelegate(this); mDragController = new DragController(this); mAllAppsController = new AllAppsTransitionController(this); mStateManager = new LauncherStateManager(this); UiFactory.onCreate(this); mAppWidgetManager = AppWidgetManagerCompat.getInstance(this); mAppWidgetHost = new LauncherAppWidgetHost(this); mAppWidgetHost.startListening(); mLauncherView = LayoutInflater.from(this).inflate(R.layout.launcher, null); setupViews(); mPopupDataProvider = new PopupDataProvider(this); mRotationHelper = new RotationHelper(this); mAppTransitionManager = LauncherAppTransitionManager.newInstance(this); boolean internalStateHandled = InternalStateHandler.handleCreate(this, getIntent()); if (internalStateHandled) { if (savedInstanceState != null) { // InternalStateHandler has already set the appropriate state. // We dont need to do anything. savedInstanceState.remove(RUNTIME_STATE); } } restoreState(savedInstanceState); // We only load the page synchronously if the user rotates (or triggers a // configuration change) while launcher is in the foreground int currentScreen = PagedView.INVALID_RESTORE_PAGE; if (savedInstanceState != null) { currentScreen = savedInstanceState.getInt(RUNTIME_STATE_CURRENT_SCREEN, currentScreen); } if (!mModel.startLoader(currentScreen)) { if (!internalStateHandled) { // If we are not binding synchronously, show a fade in animation when // the first page bind completes. mDragLayer.getAlphaProperty(ALPHA_INDEX_LAUNCHER_LOAD).setValue(0); } } else { // Pages bound synchronously. mWorkspace.setCurrentPage(currentScreen); setWorkspaceLoading(true); } // For handling default keys setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); setContentView(mLauncherView); getRootView().dispatchInsets(); // Listen for broadcasts registerReceiver(mScreenOffReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF)); getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW, Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText)); if (mLauncherCallbacks != null) { mLauncherCallbacks.onCreate(savedInstanceState); } mRotationHelper.initialize(); TraceHelper.endSection("Launcher-onCreate"); }
我們從頭開始看,在super.onCreate過後,首先呼叫了 LauncherAppState.getInstance(this)
來初始化一個單例物件。LauncherAppState裡面儲存了一些比較常用的物件,方便其他地方通過單例來獲取,比如IconCache(圖示快取)、LauncherModel(負責資料載入和處理各種回撥)等。 getInstance
函式如下,注意這裡初始化使用的application的Context,因為單例作為static物件,生命週期是與application生命週期一樣長的,如果這裡使用了Activity的Context,會導致activity退出後,該Context依然被單例持有而無法回收,於是出現記憶體洩露。
public static LauncherAppState getInstance(final Context context) { if (INSTANCE == null) { if (Looper.myLooper() == Looper.getMainLooper()) { INSTANCE = new LauncherAppState(context.getApplicationContext()); } else { try { return new MainThreadExecutor().submit(new Callable<LauncherAppState>() { @Override public LauncherAppState call() throws Exception { return LauncherAppState.getInstance(context); } }).get(); } catch (InterruptedException|ExecutionException e) { throw new RuntimeException(e); } } } return INSTANCE; }
繼續看LauncherAppState的初始化過程。這裡面其實就是各個物件的例項建立過程,並且註冊了一些系統事件的監聽。
private LauncherAppState(Context context) { if (getLocalProvider(context) == null) { throw new RuntimeException( "Initializing LauncherAppState in the absence of LauncherProvider"); } Log.v(Launcher.TAG, "LauncherAppState initiated"); Preconditions.assertUIThread(); mContext = context; mInvariantDeviceProfile = new InvariantDeviceProfile(mContext); mIconCache = new IconCache(mContext, mInvariantDeviceProfile); mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache); mModel = new LauncherModel(this, mIconCache, AppFilter.newInstance(mContext)); LauncherAppsCompat.getInstance(mContext).addOnAppsChangedCallback(mModel); // Register intent receivers IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_LOCALE_CHANGED); // For handling managed profiles filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED); filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED); filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE); filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED); if (FeatureFlags.IS_DOGFOOD_BUILD) { filter.addAction(ACTION_FORCE_ROLOAD); } mContext.registerReceiver(mModel, filter); UserManagerCompat.getInstance(mContext).enableAndResetCache(); new ConfigMonitor(mContext).register(); if (!mContext.getResources().getBoolean(R.bool.notification_badging_enabled)) { mNotificationBadgingObserver = null; } else { // Register an observer to rebind the notification listener when badging is re-enabled. mNotificationBadgingObserver = new SettingsObserver.Secure( mContext.getContentResolver()) { @Override public void onSettingChanged(boolean isNotificationBadgingEnabled) { if (isNotificationBadgingEnabled) { NotificationListener.requestRebind(new ComponentName( mContext, NotificationListener.class)); } } }; mNotificationBadgingObserver.register(NOTIFICATION_BADGING); } }
回到剛才的Launcher建立流程,LauncherAppState初始化完成後,有這樣一句 mModel = app.setLauncher(this),
這裡呼叫了 mModel.initialize(launcher)
,這裡將傳過來的Callbacks物件(也就是Launcher,Launcher實現了Callbacks介面)儲存為了弱引用。同樣是基於避免記憶體洩露的考慮。還記得上文提到的LauncherAppState,LauncherModel是其內部的一個成員變數,生命週期也是比Launcher這個Activity要長的。
public void initialize(Callbacks callbacks) { synchronized (mLock) { Preconditions.assertUIThread(); mCallbacks = new WeakReference<>(callbacks); } }
繼續Launcher的create,之後是 initDeviceProfile(app.getInvariantDeviceProfile())
,DeviceProfile是與Launcher佈局相關的一個重要類,這裡面儲存了所有佈局相關資料比如圖示大小、頁面寬高、各種padding等等。然後建立一些其他物件後,終於inflate了R.layout.launcher。繼續往下就執行到一個關鍵函式 mModel.startLoader(currentScreen)
,前面執行的都是諸如物件建立、View的inflate等邏輯,並沒有涉及到資料相關的內容,此函式就是開啟Launcher資料載入的一個呼叫。
之後又是一些初始化的邏輯。所以我們前面囉嗦一大堆,其實onCreate乾的事情簡單說來就是初始化物件、載入佈局、註冊一些事件監聽、以及開啟資料載入。
接著看資料載入與繫結流程。資料載入的呼叫實際是這樣的startLoader()→startLoaderForResults()。從如下程式碼中可知,資料載入時在一個工作執行緒去做的,這是很正常的一個選擇,避免阻塞主執行緒。
public void startLoaderForResults(LoaderResults results) { synchronized (mLock) { stopLoader(); mLoaderTask = new LoaderTask(mApp, mBgAllAppsList, sBgDataModel, results); runOnWorkerThread(mLoaderTask); } } private static void runOnWorkerThread(Runnable r) { if (sWorkerThread.getThreadId() == Process.myTid()) { r.run(); } else { // If we are not on the worker thread, then post to the worker handler sWorker.post(r); } }
在工作執行緒跑的是一個LoaderTask類,實現了Runnable介面。我們直接來看其run函式的定義。
public void run() { synchronized (this) { // Skip fast if we are already stopped. if (mStopped) { return; } } TraceHelper.beginSection(TAG); try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) { TraceHelper.partitionSection(TAG, "step 1.1: loading workspace"); loadWorkspace(); verifyNotStopped(); TraceHelper.partitionSection(TAG, "step 1.2: bind workspace workspace"); mResults.bindWorkspace(); // Notify the installer packages of packages with active installs on the first screen. TraceHelper.partitionSection(TAG, "step 1.3: send first screen broadcast"); sendFirstScreenActiveInstallsBroadcast(); // Take a break TraceHelper.partitionSection(TAG, "step 1 completed, wait for idle"); waitForIdle(); verifyNotStopped(); // second step TraceHelper.partitionSection(TAG, "step 2.1: loading all apps"); loadAllApps(); TraceHelper.partitionSection(TAG, "step 2.2: Binding all apps"); verifyNotStopped(); mResults.bindAllApps(); verifyNotStopped(); TraceHelper.partitionSection(TAG, "step 2.3: Update icon cache"); updateIconCache(); // Take a break TraceHelper.partitionSection(TAG, "step 2 completed, wait for idle"); waitForIdle(); verifyNotStopped(); // third step TraceHelper.partitionSection(TAG, "step 3.1: loading deep shortcuts"); loadDeepShortcuts(); verifyNotStopped(); TraceHelper.partitionSection(TAG, "step 3.2: bind deep shortcuts"); mResults.bindDeepShortcuts(); // Take a break TraceHelper.partitionSection(TAG, "step 3 completed, wait for idle"); waitForIdle(); verifyNotStopped(); // fourth step TraceHelper.partitionSection(TAG, "step 4.1: loading widgets"); mBgDataModel.widgetsModel.update(mApp, null); verifyNotStopped(); TraceHelper.partitionSection(TAG, "step 4.2: Binding widgets"); mResults.bindWidgets(); transaction.commit(); } catch (CancellationException e) { // Loader stopped, ignore TraceHelper.partitionSection(TAG, "Cancelled"); } TraceHelper.endSection(TAG); }
非常清晰明瞭,一步一步通過註釋和Log都標出來了。Launcher裡面資料比較多,包括所有應用的圖示和應用資料,所有應用的Widget資料,桌面已新增的使用者資料等,隨著Android大版本演進,還有DeepShortcuts等新的資料型別。如果按照常規的載入做法,等載入資料完成後再顯示到View,耗時就太長了。為了優化體驗,Launcher於是採用了分批載入、分批繫結的做法。這是大家在應用開發時可以借鑑的一種優化方案。整體的載入繫結流程如下。

我們以其中的載入與繫結桌面內容為例來進行說明,後面的三步在弄明白第一步如何做之後也就是業務邏輯上的差異,不再贅述。
載入桌面內容,呼叫函式為 loadWorkspace()
。這個函式很長,這裡就不貼程式碼了。簡述一下其流程。
首先我們要知道一個BgDataModel類,這個類用於把所有資料對應的例項管理起來。如下面程式碼,可以看到有workspaceItems(所有應用圖示資料對應的ItemInfo),appWidgets(所有AppWidgets資料對應的LauncherAppWidgetInfo)等等。
public final LongArrayMap<ItemInfo> itemsIdMap = new LongArrayMap<>(); public final ArrayList<ItemInfo> workspaceItems = new ArrayList<>(); public final ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>(); public final LongArrayMap<FolderInfo> folders = new LongArrayMap<>(); public final ArrayList<Long> workspaceScreens = new ArrayList<>(); public final Map<ShortcutKey, MutableInt> pinnedShortcutCounts = new HashMap<>(); public boolean hasShortcutHostPermission; public final MultiHashMap<ComponentKey, String> deepShortcutMap = new MultiHashMap<>(); public final WidgetsModel widgetsModel = new WidgetsModel();
然後正式來看 loadWorkspace
。
- 通過
LauncherSettings.Favorites.CONTENT_URI
查詢Favorites表的所有內容,拿到cursor。 - 遍歷cursor,進行資料的整理。每一行資料都有一個對應的itemType,標誌著這一行的資料對應的是一個應用、還是一個Widget或資料夾等。不同的型別會進行不同的處理。
- 對於圖示型別(itemType是
ITEM_TYPE_SHORTCUT,ITEM_TYPE_APPLICATION,ITEM_TYPE_DEEP_SHORTCUT
),首先經過一系列判斷,判斷其是否還可用(比如應用在Launcher未啟動時被解除安裝導致不可用),不可用的話就標記為可刪除,繼續迴圈。如果可用的話,就根據當前cursor的內容,生成一個ShortcutInfo物件,儲存到BgDataModel。 - 對於資料夾型別(itemType是
ITEM_TYPE_FOLDER
),直接生成一個對應的FolderInfo物件,儲存到BgDataModel。 - 對於AppWidget(itemType是
ITEM_TYPE_APPWIDGET
,ITEM_TYPE_CUSTOM_APPWIDGET
),也需要經過是否可用的判斷,但是可用條件與圖示型別是有差異的。如果可用,生成一個LauncherAppWidgetInfo物件,儲存到BgDataModel。 - 經過上述流程,現在所有資料庫裡讀出的內容已經分類完畢,並且儲存到了記憶體(BgDataModel)中。然後開始處理之前標記為可刪除的內容。顯示從資料庫中刪除對應的行,然後還要判斷此次刪除操作是否帶來了其他需要刪除的內容。比如某個資料夾或者某一頁只有一個圖示,這個圖示因為某些原因被刪掉了,那麼此資料夾或頁面也需要被刪掉。
至此資料載入完畢,開始要進行綁定了,也就是 mResults.bindWorkspace()
此函式在LoaderResults類中。函式在執行資料繫結之前,會執行這樣一段程式碼。
// Separate the items that are on the current screen, and all the other remaining items ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>(); ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>(); ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>(); ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>(); filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems, otherWorkspaceItems); filterCurrentWorkspaceItems(currentScreenId, appWidgets, currentAppWidgets, otherAppWidgets); sortWorkspaceItemsSpatially(currentWorkspaceItems); sortWorkspaceItemsSpatially(otherWorkspaceItems);
這段程式碼做的事情是,把Launcher啟動後預設顯示出來那一頁所擁有的資料篩選到currentWorkspaceItems與currentAppWidgets,其他頁的資料篩選到otherWorkspaceItems與otherAppWidgets。然後對每個list,按照從上到下,從左到右的順序進行排序。然後可以開始綁定了。下面程式碼的Callbacks就是Launcher activity例項,首先通知Launcher要開始綁定了( callbacks.startBinding()
),然後先把空頁面新增到View tree中( callbacks.bindScreens(orderedScreenIds)
),之後先繫結預設頁的所有元素(下段程式碼的最後一句)。當然這些所有的操作都是通過mUiExecutor放到主執行緒執行的。
// Tell the workspace that we're about to start binding items r = new Runnable() { public void run() { Callbacks callbacks = mCallbacks.get(); if (callbacks != null) { callbacks.clearPendingBinds(); callbacks.startBinding(); } } }; mUiExecutor.execute(r); // Bind workspace screens mUiExecutor.execute(new Runnable() { @Override public void run() { Callbacks callbacks = mCallbacks.get(); if (callbacks != null) { callbacks.bindScreens(orderedScreenIds); } } }); Executor mainExecutor = mUiExecutor; // Load items on the current page. bindWorkspaceItems(currentWorkspaceItems, currentAppWidgets, mainExecutor);
最後一句的內容如下,也是通過callbacks呼叫在Launcher中的bindItems函式。
private void bindWorkspaceItems(final ArrayList<ItemInfo> workspaceItems, final ArrayList<LauncherAppWidgetInfo> appWidgets, final Executor executor) { // Bind the workspace items int N = workspaceItems.size(); for (int i = 0; i < N; i += ITEMS_CHUNK) { final int start = i; final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i); final Runnable r = new Runnable() { @Override public void run() { Callbacks callbacks = mCallbacks.get(); if (callbacks != null) { callbacks.bindItems(workspaceItems.subList(start, start+chunkSize), false); } } }; executor.execute(r); } // Bind the widgets, one at a time N = appWidgets.size(); for (int i = 0; i < N; i++) { final ItemInfo widget = appWidgets.get(i); final Runnable r = new Runnable() { public void run() { Callbacks callbacks = mCallbacks.get(); if (callbacks != null) { callbacks.bindItems(Collections.singletonList(widget), false); } } }; executor.execute(r); } }
bindItems函式我們看一下其中的關鍵程式碼。根據不同的itemType來生產不同的View,然後通過 addInScreenFromBind
函式將View add到相應的ViewGroup去。
@Override public void bindItems(final List<ItemInfo> items, final boolean forceAnimateIcons) { ... switch (item.itemType) { case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: { ShortcutInfo info = (ShortcutInfo) item; view = createShortcut(info); break; } case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: { view = FolderIcon.fromXml(R.layout.folder_icon, this, (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()), (FolderInfo) item); break; } case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: { view = inflateAppWidget((LauncherAppWidgetInfo) item); if (view == null) { continue; } break; } default: throw new RuntimeException("Invalid Item Type"); } ... workspace.addInScreenFromBind(view, item); ... }
預設頁的元素繫結完了,然後繼續繫結其他頁的元素。這裡我們從註釋也可以看出,Launcher為了讓預設頁儘快顯示,自定義了一個ViewOnDrawExecutor,這裡面會讓繫結其他頁的操作在繫結完第一頁的元素並且第一次onDraw執行完之後才執行。讀者有興趣的話可以去看看這個Executor的實現。
// In case of validFirstPage, only bind the first screen, and defer binding the // remaining screens after first onDraw (and an optional the fade animation whichever // happens later). // This ensures that the first screen is immediately visible (eg. during rotation) // In case of !validFirstPage, bind all pages one after other. final Executor deferredExecutor = validFirstPage ? new ViewOnDrawExecutor() : mainExecutor; mainExecutor.execute(new Runnable() { @Override public void run() { Callbacks callbacks = mCallbacks.get(); if (callbacks != null) { callbacks.finishFirstPageBind( validFirstPage ? (ViewOnDrawExecutor) deferredExecutor : null); } } }); bindWorkspaceItems(otherWorkspaceItems, otherAppWidgets, deferredExecutor);
經過其他頁的繫結之後,桌面資料的載入與繫結也就到此為止。接下來就是之前提到的另外三步後續載入與繫結內容了,不再贅述。但是在本文結束前,還想說一個值得一提的地方。
桌面資料的載入與繫結完之後,我們看這裡執行了一個waitForIdle的函式,然後才是繼續執行第二步。這個函式是做什麼的呢?
// Take a break TraceHelper.partitionSection(TAG, "step 1 completed, wait for idle"); waitForIdle(); verifyNotStopped(); // second step TraceHelper.partitionSection(TAG, "step 2.1: loading all apps"); loadAllApps();
我們看下它的實現。
protected synchronized void waitForIdle() { // Wait until the either we're stopped or the other threads are done. // This way we don't start loading all apps until the workspace has settled // down. LooperIdleLock idleLock = mResults.newIdleLock(this); // Just in case mFlushingWorkerThread changes but we aren't woken up, // wait no longer than 1sec at a time while (!mStopped && idleLock.awaitLocked(1000)); } public class LooperIdleLock implements MessageQueue.IdleHandler, Runnable { private final Object mLock; private boolean mIsLocked; public LooperIdleLock(Object lock, Looper looper) { mLock = lock; mIsLocked = true; if (Utilities.ATLEAST_MARSHMALLOW) { looper.getQueue().addIdleHandler(this); } else { // Looper.myQueue() only gives the current queue. Move the execution to the UI thread // so that the IdleHandler is attached to the correct message queue. new LooperExecutor(looper).execute(this); } } @Override public void run() { Looper.myQueue().addIdleHandler(this); } @Override public boolean queueIdle() { synchronized (mLock) { mIsLocked = false; mLock.notify(); } return false; } public boolean awaitLocked(long ms) { if (mIsLocked) { try { // Just in case mFlushingWorkerThread changes but we aren't woken up, // wait no longer than 1sec at a time mLock.wait(ms); } catch (InterruptedException ex) { // Ignore } } return mIsLocked; } }
這裡面涉及到一個應用啟動優化的技術。我們知道應用的啟動優化可以有延遲載入、懶載入、非同步載入等手段。而用一個名為 IdleHandler
的類,就可以比較方便的實現延遲載入。這個後面有空的話再來細說吧,本文就先到這裡。
下一篇將分析Launcher佈局相關內容。