Android Launcher分析和修改4——初始化加載數據


    上面一篇文章說了Launcher是如何被啟動的,Launcher啟動的過程主要是加載界面數據然后顯示出來,

界面數據都是系統APP有關的數據,都是從Launcher的數據庫讀取,下面我們詳細分析Launcher如何加載數據。

在Launcher.java的onCreate()方法里面,調用了開始加載數據接口:

//Edited by mythou
//http://www.cnblogs.com/mythou/
//加載啟動數據 if (!mRestoring)
{ mModel.startLoader(
this, true); }

mModel是LauncherModel的對象,由此可見,數據加載主要是在LauncherModel類里面實現的。

 

1、Callbacks接口

LauncherModel里面,需要先分析一個Callbacks接口。

 
         
//Edited by mythou
//http://www.cnblogs.com/mythou/
 public interface Callbacks { public boolean setLoadOnResume(); public int getCurrentWorkspaceScreen(); public void startBinding(); public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end); public void bindFolders(HashMap<Long,FolderInfo> folders); public void finishBindingItems(); public void bindAppWidget(LauncherAppWidgetInfo info); public void bindAllApplications(ArrayList<ApplicationInfo> apps); public void bindAppsAdded(ArrayList<ApplicationInfo> apps); public void bindAppsUpdated(ArrayList<ApplicationInfo> apps); public void bindAppsRemoved(ArrayList<ApplicationInfo> apps, boolean permanent); public void bindPackagesUpdated(); public boolean isAllAppsVisible(); public void bindSearchablesChanged(); }

Callbacks接口提供了很多接口,用於返回相關的數據給Launcher模塊,下面我們對每個接口作用做個闡釋。

setLoadOnResume() :當Launcher.java類的Activity處於onPause的時候,如果重新恢復,需要調用onResume,此時需要在onResume調用這個接口,恢復Launcher數據。

getCurrentWorkspace():獲取屏幕序號(0~4)

startBinding():通知Launcher開始加載數據。清空容器數據,重新加載

bindItems(ArrayList<ItemInfo> shortcuts, int start, int end):加載App shortcut、Live Folder、widget到Launcher相關容器。

bindFolders(HashMap<Long, FolderInfo> folders):加載folder的內容

finishBindingItems():數據加載完成。

bindAppWidget(LauncherAppWidgetInfo item):workspace加載APP 快捷方式

bindAllApplications(final ArrayList<ApplicationInfo> apps):所有應用列表接着APP圖標數據

bindAppsAdded(ArrayList<ApplicationInfo> apps):通知Launcher新安裝了一個APP,更新數據。

bindAppsUpdated(ArrayList<ApplicationInfo> apps):通知Launcher一個APP更新了。(覆蓋安裝)

bindAppsRemoved(ArrayList<ApplicationInfo> apps, boolean permanent):通知Launcher,應用被刪除

bindPackagesUpdated():多個應用更新。

isAllAppsVisible():返回所有應用列表是否可見狀態。

bindSearchablesChanged():Google搜索欄或者刪除區域發生變化時通知Launcher

 

2、數據加載流程

Launcher.java類繼承了Callbacks接口,並實現了該接口。LauncherModel里面會調用這些接口,反饋數據和狀態給Launcher。數據加載總體分為兩部分,一部分是加載workspace的數據,另一部分是加載All APP界面的數據。

下面是一個加載數據流程圖:

 

3、startLoader()

下面我們先分析startLoader()接口,startLoader主要是啟動了一個線程,用於加載數據。


//Edited by mythou
//http://www.cnblogs.com/mythou/
public void startLoader(Context context, boolean isLaunching) { synchronized (mLock) { //............... if (mCallbacks != null && mCallbacks.get() != null) { isLaunching = isLaunching || stopLoaderLocked(); mLoaderTask = new LoaderTask(context, isLaunching); sWorkerThread.setPriority(Thread.NORM_PRIORITY); sWorker.post(mLoaderTask); } } }

startLoader主要是啟動LoaderTask線程里面的run方法。sWorker是一個Handle對象,用於啟動線程的run方法。

 

 4、LoaderTask的run()方法


//Edited by mythou
//http://www.cnblogs.com/mythou/
public void run() {        //............ keep_running: {
         //...............

         //加載當前頁面的數據,先把一頁的數據加載完成,
//主要是為了增加程序流暢性,提高用戶體驗
if (loadWorkspaceFirst) { if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace"); loadAndBindWorkspace(); } else { if (DEBUG_LOADERS) Log.d(TAG, "step 1: special: loading all apps"); loadAndBindAllApps(); } if (mStopped) { break keep_running; } // THREAD_PRIORITY_BACKGROUND設置線程優先級為后台,
         //這樣當多個線程並發后很多無關緊要的線程分配的CPU時間將會減少,有利於主線程的處理
synchronized (mLock) { if (mIsLaunching) { if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to BACKGROUND"); android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); } }
         //等待線程空閑的時候,繼續加載其他頁面數據 waitForIdle();
//加載剩余頁面的數據,包含workspace和all app頁面 if (loadWorkspaceFirst) { if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps"); loadAndBindAllApps(); } else { if (DEBUG_LOADERS) Log.d(TAG, "step 2: special: loading workspace"); loadAndBindWorkspace(); } // Restore the default thread priority after we are done loading items synchronized (mLock) { android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); } } }

上面是經過簡化的LoaderTask的run方法代碼,其實主要就兩部分操作,第一部分操作,加載當前頁面的數據

(當前workspace頁面或者當前All APP頁面的數據)然后等待線程空閑的時候,再加載剩余的頁面數據。

代碼上面加了關鍵注釋,可以結合代碼分析。這樣做主要目的是增加Launcher啟動的速度,讓用戶覺得系統初始化速度

較快,有較好的用戶體驗。先把用戶看見的界面初始化完畢,然后再開一個后台線程慢慢加載其他的數據。

下面我們分別分析workspace和All APP加載和綁定。

 

5、workspace加載數據

loadAndBindWorkspace()方法主要就是執行loadWorkspace()和 bindWorkspace()方法。
下面分別對這兩個方法進行分析。

//Edited by mythou
//http://www.cnblogs.com/mythou/
private void loadWorkspace() {        //..........

       //清空容器,存放界面不同的元素,App快捷方式、widget、folder synchronized (sBgLock) { sBgWorkspaceItems.clear(); sBgAppWidgets.clear(); sBgFolders.clear(); sBgItemsIdMap.clear(); sBgDbIconCache.clear(); final ArrayList
<Long> itemsToRemove = new ArrayList<Long>(); final Cursor c = contentResolver.query( LauncherSettings.Favorites.CONTENT_URI, null, null, null, null); // +1 for the hotseat (it can be larger than the workspace) // Load workspace in reverse order to ensure that latest items are loaded first (and // before any earlier duplicates)
        
  //表示屏幕上的位置,
         //第一維表示分屏的序號,其中最后一個代表Hotseat             
         //第二維表示x方向方格的序號         
         //第三維表示y方向方格的序號
                final ItemInfo occupied[][][] =
                        new ItemInfo[Launcher.SCREEN_COUNT + 1][mCellCountX + 1][mCellCountY + 1];
          //讀取數據庫響應鍵值列序號 try {
                    final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
                    final int intentIndex = c.getColumnIndexOrThrow
                            (LauncherSettings.Favorites.INTENT);
                    final int titleIndex = c.getColumnIndexOrThrow
                            (LauncherSettings.Favorites.TITLE);
                    final int iconTypeIndex = c.getColumnIndexOrThrow(
                            LauncherSettings.Favorites.ICON_TYPE);
            //...........
          
while (!mStopped && c.moveToNext()) { try { int itemType = c.getInt(itemTypeIndex); switch (itemType) {
                
 //item類型為ITEM_TYPE_APPLICATION或者ITEM_TYPE_SHORTCUT  
                //container為CONTAINER_DESKTOP或者CONTAINER_HOTSEAT
                //把當前的item添加到sWorkspaceItems中
                case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                      emType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
                                    info = getShortcutInfo(manager, intent, context, c, iconIndex,
                                            titleIndex, mLabelCache);
                                } else {
                                    info = getShortcutInfo(c, context, iconTypeIndex,
                                            iconPackageIndex, iconResourceIndex, iconIndex,
                                            titleIndex);
                    
                                    switch (container) {
                                    case LauncherSettings.Favorites.CONTAINER_DESKTOP:
                                    case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
                        //添加數據 sBgWorkspaceItems.add(info);
break; default: //如果item的屬性是folder,添加到folder,創建forder FolderInfo folderInfo = findOrMakeFolder(sBgFolders, container); folderInfo.add(info); break; } sBgItemsIdMap.put(info.id, info); } else { } break;                 //item類型為文件夾,添加 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: id = c.getLong(idIndex); FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
                   //.........
sBgItemsIdMap.put(folderInfo.id, folderInfo); sBgFolders.put(folderInfo.id, folderInfo); break;                  //Widget添加 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: // Read all Launcher-specific widget details int appWidgetId = c.getInt(appWidgetIdIndex); id = c.getLong(idIndex);                    //........... sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo); sBgAppWidgets.add(appWidgetInfo); break; } } catch (Exception e) { Log.w(TAG, "Desktop items loading interrupted:", e); } } } finally { c.close(); } } }

 workspace的數據加載總的來說也是按照元素屬性來區分加載,分為App快捷方式、Widget、Folder元素。

這幾個元素分別加載到不同的容器里面。其中sItemsIdMap保存所有元素的id和ItemInfo組成的映射。其他

元素分別加載到3個不同的容器里面,用於后面綁定數據用。這里只給出了loadWorkspace的流程代碼,詳細代碼,

需要看源碼,還有很多細節。不過剛開始分析Launcher,我的原則是先把握整體流程和知道改動代碼,需要在哪里查找。

 

6、workspace綁定數據

Launcher的內容綁定分為五步:分別對應着startBinding()、bindItems()、bindFolders()、 bindAppWidgets()、

finishBindingItems()的調用。下面針對bindWorkspace做個簡單的流程分析。


//Edited by mythou
//http://www.cnblogs.com/mythou/

private void bindWorkspace() {

       //通知Launcher開始綁定數據 mHandler.post(new Runnable() { public void run() { Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null) { //綁定數據到launcher,Launcher回調,清空相關容器 OWL callbacks.startBinding(); } } }); //添加元素到workspace,主要是添加APP快捷方式 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); mHandler.post(new Runnable() { public void run() { Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null) { callbacks.bindItems(workspaceItems, start, start+chunkSize); } } }); } //文件夾綁定 final HashMap<Long, FolderInfo> folders = new HashMap<Long, FolderInfo>(sFolders); mHandler.post(new Runnable() { public void run() { Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null) { callbacks.bindFolders(folders); } } }); //分兩次加載widget ,當前界面和其他界面,增強用戶體驗OWL
     //其他頁面widget會在后台線程再次加載 final int currentScreen = oldCallbacks.getCurrentWorkspaceScreen(); N = sAppWidgets.size(); // once for the current screen for (int i=0; i<N; i++) { final LauncherAppWidgetInfo widget = sAppWidgets.get(i); if (widget.screen == currentScreen) { mHandler.post(new Runnable() { public void run() { Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null) { callbacks.bindAppWidget(widget); } } }); } } //加載其他看不見的屏幕widget for (int i=0; i<N; i++) { final LauncherAppWidgetInfo widget = sAppWidgets.get(i); if (widget.screen != currentScreen) { mHandler.post(new Runnable() { public void run() { Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null) { callbacks.bindAppWidget(widget); } } }); } } //加載完成,通知Launcher,已經完成數據加載 mHandler.post(new Runnable() { public void run() { Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null) { callbacks.finishBindingItems(); } } }); }

 上面就是Launcher的workspace綁定數據的過程,跟加載數據過程很相似,也是區分3中類型的元素進行加載。

下面我們總結一下,workspace的加載和綁定數據的過程。我們現在回頭看,可以發現,其實workspace里面就是

存放了3中數據ItemInfo、FolderInfo、LauncherAppWidgetInfo。分別對應我們的APP快捷方式、文件夾、Widget

數據。其中FolderInfo、LauncherAppWidgetInfo都是繼承了ItemInfo。數據加載過程,就是從Launcher的數據庫

讀取數據然后按元素屬性分別放到3個ArrayList里面。綁定數據過程就是把3個ArrayList的隊列關聯到Launcher界面里面。

 

 7、ALL APP數據加載綁定


//Edited by mythou
//http://www.cnblogs.com/mythou/

private void loadAllAppsByBatch() {//只有這兩個標記才需要顯示在所有程序列表 OWL final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); final PackageManager packageManager = mContext.getPackageManager(); List<ResolveInfo> apps = null; int N = Integer.MAX_VALUE; int startIndex; int i=0; int batchSize = -1; while (i < N && !mStopped) { if (i == 0) { mAllAppsList.clear(); final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; //過濾需要顯示的app apps = packageManager.queryIntentActivities(mainIntent, 0); if (DEBUG_LOADERS) { Log.d(TAG, "queryIntentActivities took " + (SystemClock.uptimeMillis()-qiaTime) + "ms"); } if (apps == null) { return; } N = apps.size(); if (DEBUG_LOADERS) { Log.d(TAG, "queryIntentActivities got " + N + " apps"); } if (N == 0) { // There are no apps?!? return; } //mBatchSize==0表示一次性加載所有的應用 if (mBatchSize == 0) { batchSize = N; } else { batchSize = mBatchSize; } } final boolean first = i <= batchSize; final Callbacks callbacks = tryGetCallbacks(oldCallbacks); final ArrayList<ApplicationInfo> added = mAllAppsList.added; mAllAppsList.added = new ArrayList<ApplicationInfo>();          //綁定加載所有的APP數據 mHandler.post(new Runnable() { public void run() { final long t = SystemClock.uptimeMillis(); if (callbacks != null) { if (first) { //一次性加載所以app,返回數據到launcher callbacks.bindAllApplications(added); } else { callbacks.bindAppsAdded(added); } if (DEBUG_LOADERS) { Log.d(TAG, "bound " + added.size() + " apps in " + (SystemClock.uptimeMillis() - t) + "ms"); } } else { Log.i(TAG, "not binding apps: no Launcher activity"); } } }); }

AllAPP的數據加載和綁定跟workspace的差不多,也是先加載數據然后綁定數據,通知Launcher。加載數據的時候

PackageManager獲取所有已經安裝的APK包信息,然后過濾只包含需要顯示在所有應用列表的應用,需要包含

ACTION_MAIN和CATEGORY_LAUNCHER兩個屬性。這個我們在編寫應用程序的時候都應該知道。

AllAPP加載跟workspace不同的地方是加載的同時,完成數據綁定的操作,也就是說第一次加載AllAPP頁面的數據,

會同時綁定數據到Launcher。第二次需要加載的時候,只會把數據直接綁定到Launcher,而不會重新搜索加載數據。

Launcher啟動加載和綁定數據就是這樣完成。綁定完數據,Launcher就可以運行。

 

系列文章:

 

Android Launcher分析和修改1——Launcher默認界面配置(default_workspace)

Android Launcher分析和修改2——Icon修改、界面布局調整、壁紙設置

Android Launcher分析和修改3——Launcher啟動和初始化

 

 

Edited by mythou

原創博文,轉載請標明出處:http://www.cnblogs.com/mythou/p/3165259.html 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM