標籤:recycle first shm parent second pen pack 返回 轉譯
Launcher系列目錄:
一、android7.x Launcher3原始碼解析(1)—啟動流程
二、android7.x Launcher3原始碼解析(2)—架構結構
三、android7.x Launcher3原始碼解析(3)—workspace和allapps載入流程
前兩篇部落格分別對Lancher的啟動和Launcher的架構結構進行了一些分析。這一篇。將著重開始分析介面的載入流程。
1、總體流程
先上一張總體的流程圖吧。(圖片看不清能夠下載下來看或者右擊新開個頁面查看圖片)
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvUGljYXNzb19M/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="這裡寫圖片描寫敘述" title="">
先從Launcher.java的onCreate方法開始。
protected void onCreate(Bundle savedInstanceState) { ...... //建立LauncherAppState對象 LauncherAppState.setApplicationContext(getApplicationContext()); LauncherAppState app = LauncherAppState.getInstance(); ...... //建立LauncherModel對象 mModel = app.setLauncher(this); //一些其它對象初始化 ...... setContentView(R.layout.launcher); setupViews(); if (!mRestoring) { if (DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE) { // If the user leaves launcher, then we should just load items asynchronously when // they return. mModel.startLoader(PagedView.INVALID_RESTORE_PAGE); } else { // We only load the page synchronously if the user rotates (or triggers a // configuration change) while launcher is in the foreground mModel.startLoader(mWorkspace.getRestorePage()); } } ......}
重點調用了LauncherModel的startLoader的方法,startLoader裡面。最重要的就是啟動了LoaderTask。mLoaderTask = new LoaderTask(mApp.getContext(), synchronousBindPage);
我們接著分析LoaderTask的run方法。
public void run() { ...... keep_running: { if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace"); loadAndBindWorkspace(); if (mStopped) { break keep_running; } waitForIdle(); // second step if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps"); loadAndBindAllApps(); waitForIdle(); // third step if (DEBUG_LOADERS) Log.d(TAG, "step 3: loading deep shortcuts"); loadAndBindDeepShortcuts(); } ...... }
這裡一共就幾步,loadAndBindWorkspace–>waitForIdle()—>loadAndBindAllApps()—>waitForIdle()—>loadAndBindDeepShortcuts()
3步載入流程裡面都穿插了waitForIdle,這種方法是幹嘛的呢?
private void waitForIdle() { ...... synchronized (LoaderTask.this) { final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; mHandler.postIdle(new Runnable() { public void run() { synchronized (LoaderTask.this) { mLoadAndBindStepFinished = true; if (DEBUG_LOADERS) { Log.d(TAG, "done with previous binding step"); } LoaderTask.this.notify(); } } }); while (!mStopped && !mLoadAndBindStepFinished) { try { // Just in case mFlushingWorkerThread changes but we aren‘t woken up, // wait no longer than 1sec at a time this.wait(1000); } catch (InterruptedException ex) { // Ignore } } ...... } }
load資料時我們是去UI線程中處理的,UI線程正常情況下是不能堵塞的,否則有可能產生ANR。這將嚴重影響使用者體驗。
全部這裡LoaderTask在將結果發送給UI線程之後,為了保證介面綁定任務能夠高效的完畢,往往會將自己的任務暫停下來,等待UI線程處理完畢。
分析下這種方法:
首先。建立一個UI線程閑時啟動並執行任務,這個任務負責設定某些關鍵的控制標誌。並將其通過PostIdle方法增加處理器的訊息佇列中。一旦任務得到運行。就會將mLoadAndBindStepFinished 置為true,以控制即將來臨的有條件的無限等待。 最後 設定一個有條件的無限等待,等待來自UI線程的指示。
2、workspace的載入流程
從總流程圖上能夠看到,workspace的載入流程主要分為loadWorkspace();和bindWorkspace(mPageToBindFirst);
a、loadWorkspace()
loadWorkspace()的代碼實在是太多了,這裡就不全部貼出來了。主要功能就是負責從資料庫表中讀取資料並轉譯為Launcher案頭項的資料結構。
以下為步驟:
1、進行一些預先處理
2、載入預設值
LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary()
之前Launcher2的loadDefaultFavoritesIfNecessary這種方法,是這樣載入預設布局的:
workspaceResId = sp.getInt(DEFAULT_WORKSPACE_RESOURCE_ID, R.xml.default_workspace);
可是Launcher3中,是這樣載入的:
int workspaceResId = partnerRes.getIdentifier(Partner.RES_DEFAULT_LAYOUT, "xml", partner.getPackageName());
這個地方,我臨時還沒理解。究竟預設布局是哪個?
3、初始化資料
清空之前的記憶體資料
/** Clears all the sBg data structures */ private void clearSBgDataStructures() { synchronized (sBgLock) { sBgWorkspaceItems.clear(); sBgAppWidgets.clear(); sBgFolders.clear(); sBgItemsIdMap.clear(); sBgWorkspaceScreens.clear(); } }
4、查詢ContentProvider,返回favorites表的結果集
final HashMap<String, Integer> installingPkgs = PackageInstallerCompat .getInstance(mContext).updateAndGetActiveSessionCache(); final ArrayList<Long> itemsToRemove = new ArrayList<Long>(); final ArrayList<Long> restoredRows = new ArrayList<Long>(); final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI; if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri); final Cursor c = contentResolver.query(contentUri, null, null, null, null);
5、依據不同的類型,將資料儲存到相應的arrayList中。
類型包括以下這幾種:
ITEM_TYPE_APPLICATIONITEM_TYPE_SHORTCUTITEM_TYPE_FOLDERITEM_TYPE_APPWIDGETITEM_TYPE_CUSTOM_APPWIDGET
依據以上類型,將資料儲存到一下全域變數裡面
sBgItemsIdMap,sBgWorkspaceItemsm,sBgAppWidgets,sBgFolders
另外。假設有空的目錄、空的螢幕,也會delete掉,終於,把全部螢幕載入進全域變數sBgWorkspaceScreens中。
...... // Remove any empty folder for (long folderId : LauncherAppState.getLauncherProvider() .deleteEmptyFolders()) { sBgWorkspaceItems.remove(sBgFolders.get(folderId)); sBgFolders.remove(folderId); sBgItemsIdMap.remove(folderId); } ...... sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext)); // Remove any empty screens ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens); for (ItemInfo item: sBgItemsIdMap) { long screenId = item.screenId; if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP && unusedScreens.contains(screenId)) { unusedScreens.remove(screenId); } } // If there are any empty screens remove them, and update. if (unusedScreens.size() != 0) { sBgWorkspaceScreens.removeAll(unusedScreens); updateWorkspaceScreenOrder(context, sBgWorkspaceScreens); }
b、bindWorkspace
bindWorkspace的功能是將上面擷取到的資料由Launcher顯示出來。
步驟:
1、首先複製資料:
synchronized (sBgLock) { workspaceItems.addAll(sBgWorkspaceItems); appWidgets.addAll(sBgAppWidgets); orderedScreenIds.addAll(sBgWorkspaceScreens); folders = sBgFolders.clone(); itemsIdMap = sBgItemsIdMap.clone(); }
2、裝載資料並排序
//裝載案頭項資料 filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems, otherWorkspaceItems); //裝載widget filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets, otherAppWidgets); //裝載目錄 filterCurrentFolders(currentScreenId, itemsIdMap, folders, currentFolders, otherFolders); //排序 sortWorkspaceItemsSpatially(currentWorkspaceItems); sortWorkspaceItemsSpatially(otherWorkspaceItems);
3、開始綁定
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvUGljYXNzb19M/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="這裡寫圖片描寫敘述" title="">
就上個流程圖吧。
3、應用程式apps的載入
載入apps的主要流程。最上面那張流程圖已經給出了,假設全部app沒有載入,則loadAllApps();,不然直接onlyBindAllApps();
1、loadAllApps()
依據代碼,畫了下流程圖,可是我不明確userProfile是個什麼鬼?
2、onlyBindAllApps()
這裡的函數比綁定workspace簡單多了,直接通知Launcher綁定
Runnable r = new Runnable() { public void run() { final long t = SystemClock.uptimeMillis(); final Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null) { callbacks.bindAllApplications(list); callbacks.bindAllPackages(widgetList); } if (DEBUG_LOADERS) { Log.d(TAG, "bound all " + list.size() + " apps from cache in " + (SystemClock.uptimeMillis()-t) + "ms"); } } };
看一下Launcher的bindAllApplications函數。當然這個函數在loadAllApps函數裡面也有。就是怎樣綁定資料來顯示呢?
Launcher.java的bindAllApplications函數裡面會給AllAppsContainerView設定資料
if (mAppsView != null) { mAppsView.setApps(apps); }
再跟代碼到AllAppsContainerView。
public void setApps(List<AppInfo> apps) { mApps.setApps(apps); }
這個apps在哪裡用到呢?比較明顯的就是onFinishInflate()函數,
..... // Load the all apps recycler view mAppsRecyclerView = (AllAppsRecyclerView) findViewById(R.id.apps_list_view); mAppsRecyclerView.setApps(mApps); mAppsRecyclerView.setLayoutManager(mLayoutManager); mAppsRecyclerView.setAdapter(mAdapter); mAppsRecyclerView.setHasFixedSize(true);.....
設定給了recyclerView。非常顯然,Launcher的應用程式介面就是一個自己定義的RecyclerView,給這個recyclerview綁定的adpter是AllAppsGridAdapter。看一下這個adpter的onCreateViewHolder函數,非常明顯。這裡依據不同的類型載入了不同的布局(應用程式介面有app和目錄。頭上還有個搜尋方塊。用recyclerview的這個功能是最easy實現的),關於Recyclerview怎樣能夠依據不同的類型載入不同的布局,能夠參考我非常久之前寫的部落格 RecyclerView的不同position載入不同View實現。
好了,Launcher3的workspace和應用程式apps的載入流程就說到這,後面還會對Launcher裡面的內容做詳細的分析。
android7.x Launcher3原始碼解析(3)---workspace和allapps載入流程