天天看點

android launcher源碼分析,Android 9.0 Launcher源碼分析(二)——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() {

@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于是采用了分批加載、分批綁定的做法。這是大家在應用開發時可以借鑒的一種優化方案。整體的加載綁定流程如下。

android launcher源碼分析,Android 9.0 Launcher源碼分析(二)——Launcher應用啟動流程,資料加載與綁定...

我們以其中的加載與綁定桌面内容為例來進行說明,後面的三步在弄明白第一步如何做之後也就是業務邏輯上的差異,不再贅述。

加載桌面内容,調用函數為loadWorkspace()。這個函數很長,這裡就不貼代碼了。簡述一下其流程。

首先我們要知道一個BgDataModel類,這個類用于把所有資料對應的執行個體管理起來。如下面代碼,可以看到有workspaceItems(所有應用圖示資料對應的ItemInfo),appWidgets(所有AppWidgets資料對應的LauncherAppWidgetInfo)等等。

public final LongArrayMap itemsIdMap = new LongArrayMap<>();

public final ArrayList workspaceItems = new ArrayList<>();

public final ArrayList appWidgets = new ArrayList<>();

public final LongArrayMap folders = new LongArrayMap<>();

public final ArrayList workspaceScreens = new ArrayList<>();

public final Map pinnedShortcutCounts = new HashMap<>();

public boolean hasShortcutHostPermission;

public final MultiHashMap 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 currentWorkspaceItems = new ArrayList<>();

ArrayList otherWorkspaceItems = new ArrayList<>();

ArrayList currentAppWidgets = new ArrayList<>();

ArrayList 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 workspaceItems,

final ArrayList 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 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布局相關内容。