Launcher3分析<一>
前言
最近需要實現一個自己的Launcher,就藉機學習下原生的Launcher原始碼。
多個Launcher
原始碼裡有Launcher,Launcher2,Launcher3。那它們有什麼區別呢。
launcher不支援桌面小工具動畫效果,launcher2添加了動畫效果和3D初步效果支援。
Android 4.4 (KK)開始Launcher預設使用Launcher3,Launcher3較Launcher2 UI 有部分調整,主要包括:
- 狀態列透明,App List 透Wallpaper;
- 增加overview模式,可以調整workspace上頁面的前後順序;
- 動態管理螢幕數量;
- widget列表與app list分開顯示;
-
預設不支援預置appwidget,需要使用者指定許可權;
-
提供類似小米只有workspace的桌面機制 ;
-
Wallpaper 的程式碼全部搬移到Launcher包;
-
類似Cling等細節的小變化;
那我們就直接研究最新的Launcher3
Launcher3
Launcher3介紹
Launcher是開機啟動的第一個應用程式,用來展示應用列表和快捷方式、小部件等。
Launcher3原始碼解析
AndroidManifest.xml
一些標籤屬性

123.png
LauncherApplication
public class LauncherApplication extends Application { @Override public void onCreate() { super.onCreate(); LauncherAppState.setApplicationContext(this); LauncherAppState.getInstance(); } @Override public void onTerminate() { super.onTerminate(); LauncherAppState.getInstance().onTerminate(); } }
初始化LauncherAppState類,在繼續看LauncherAppState類。
private LauncherAppState() { ... // set sIsScreenXLarge and mScreenDensity *before* creating icon cache mIsScreenLarge = isScreenLarge(sContext.getResources()); mScreenDensity = sContext.getResources().getDisplayMetrics().density; mWidgetPreviewCacheDb = new WidgetPreviewLoader.CacheDb(sContext); mIconCache = new IconCache(sContext); mAppFilter = AppFilter.loadByName(sContext.getString(R.string.app_filter_class)); mModel = new LauncherModel(this, mIconCache, mAppFilter); // Register intent receivers IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); filter.addAction(Intent.ACTION_PACKAGE_CHANGED); filter.addDataScheme("package"); sContext.registerReceiver(mModel, filter); filter = new IntentFilter(); filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); filter.addAction(Intent.ACTION_LOCALE_CHANGED); filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); sContext.registerReceiver(mModel, filter); filter = new IntentFilter(); filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED); sContext.registerReceiver(mModel, filter); filter = new IntentFilter(); filter.addAction(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED); sContext.registerReceiver(mModel, filter); // Register for changes to the favorites ContentResolver resolver = sContext.getContentResolver(); resolver.registerContentObserver(LauncherSettings.Favorites.CONTENT_URI, true, mFavoritesObserver); }
初始化中讀取配置,註冊廣播,例項化LauncherModel。Launcher2原始碼 LauncherModel建立是在LauncherApplication ,Launcher3移到這個類來了。
/** * Maintains in-memory state of the Launcher. It is expected that there should be only one * LauncherModel object held in a static. Also provide APIs for updating the database state * for the Launcher. */
單例,處理資料庫。這個類繼承BroadcastReceiver ,我們來重點看下這個類實現;
LauncherModel
private static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader"); static { sWorkerThread.start(); } private static final Handler sWorker = new Handler(sWorkerThread.getLooper());
建立一個執行緒,用來處理
LoaderTask 載入
- workspace icons
- widgets
- all apps icons
PackageUpdatedTask 用來更新applist.
看一下構造方法
LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) { final Context context = app.getContext(); mAppsCanBeOnRemoveableStorage = Environment.isExternalStorageRemovable(); mApp = app; mBgAllAppsList = new AllAppsList(iconCache, appFilter); mIconCache = iconCache; mDefaultIcon = Utilities.createIconBitmap( mIconCache.getFullResDefaultActivityIcon(), context); final Resources res = context.getResources(); Configuration config = res.getConfiguration(); mPreviousConfigMcc = config.mcc; // SIM卡相關 }
傳入LauncherAppState 來獲取 context,WidgetPreviewLoader.CacheDb操作資料庫;
IconCache類是用來快取app圖示
LauncherModle繼承BroadcastReceiver 我們看下onReceive做了什麼處理;
/** * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and * ACTION_PACKAGE_CHANGED. */ @Override public void onReceive(Context context, Intent intent) { ... if (Intent.ACTION_PACKAGE_CHANGED.equals(action) || Intent.ACTION_PACKAGE_REMOVED.equals(action) || Intent.ACTION_PACKAGE_ADDED.equals(action)) { final String packageName = intent.getData().getSchemeSpecificPart(); final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); int op = PackageUpdatedTask.OP_NONE; if (packageName == null || packageName.length() == 0) { // they sent us a bad intent return; } if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) { op = PackageUpdatedTask.OP_UPDATE; } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { if (!replacing) { op = PackageUpdatedTask.OP_REMOVE; } // else, we are replacing the package, so a PACKAGE_ADDED will be sent // later, we will update the package at this time } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { if (!replacing) { op = PackageUpdatedTask.OP_ADD; } else { op = PackageUpdatedTask.OP_UPDATE; } } if (op != PackageUpdatedTask.OP_NONE) { enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName })); } //移動APP完成之後,發出的廣播 } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { // First, schedule to add these apps back in. String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_ADD, packages)); // Then, rebind everything. startLoaderFromBackground(); //正在移動APP時,發出的廣播 } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); enqueuePackageUpdated(new PackageUpdatedTask( PackageUpdatedTask.OP_UNAVAILABLE, packages)); } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) { //裝置當前區域設定已更改時發出的廣播 // If we have changed locale we need to clear out the labels in all apps/workspace. forceReload(); //裝置當前設定被改變時發出的廣播(包括的改變:介面語言,裝置方向,等 } else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) { // Check if configuration change was an mcc/mnc change which would affect app resources // and we would need to clear out the labels in all apps/workspace. Same handling as // above for ACTION_LOCALE_CHANGED Configuration currentConfig = context.getResources().getConfiguration(); if (mPreviousConfigMcc != currentConfig.mcc) { Log.d(TAG, "Reload apps on config change. curr_mcc:" + currentConfig.mcc + " prevmcc:" + mPreviousConfigMcc); forceReload(); } // Update previousConfig mPreviousConfigMcc = currentConfig.mcc; } else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action) || SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED.equals(action)) { if (mCallbacks != null) { Callbacks callbacks = mCallbacks.get(); if (callbacks != null) { callbacks.bindSearchablesChanged(); } } } }
接收應用安裝解除安裝後的廣播,當接收到廣播呼叫enqueuePackageUpdated來啟動這個任務。
public void run() { final Context context = mApp.getContext(); final String[] packages = mPackages; final int N = packages.length; switch (mOp) { case OP_ADD: for (int i=0; i<N; i++) { if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]); mBgAllAppsList.addPackage(context, packages[i]); } break; case OP_UPDATE: for (int i=0; i<N; i++) { if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]); mBgAllAppsList.updatePackage(context, packages[i]); WidgetPreviewLoader.removePackageFromDb( mApp.getWidgetPreviewCacheDb(), packages[i]); } break; case OP_REMOVE: case OP_UNAVAILABLE: for (int i=0; i<N; i++) { if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]); mBgAllAppsList.removePackage(packages[i]); WidgetPreviewLoader.removePackageFromDb( mApp.getWidgetPreviewCacheDb(), packages[i]); } break; } .... if (added != null) { // Ensure that we add all the workspace applications to the db Callbacks cb = mCallbacks != null ? mCallbacks.get() : null; if (!AppsCustomizePagedView.DISABLE_ALL_APPS) { addAndBindAddedApps(context, new ArrayList<ItemInfo>(), cb, added); } else { final ArrayList<ItemInfo> addedInfos = new ArrayList<ItemInfo>(added); addAndBindAddedApps(context, addedInfos, cb, added); } } }
安裝應用向AllAppsList新增應用資訊;
解除安裝應用向AllAppsList 刪除資料,並刪除資料庫的資料。
呼叫addAndBindAddedApps方法 :處理新新增的應用程式並首先將它們新增到資料庫中;
callbacks.bindAppsAdded(addedWorkspaceScreensFinal, addNotAnimated, addAnimated, allAppsApps);
回撥到Launcher 主Activity中,這個callback是Launcher初始化後呼叫的,我們後面在介紹。
定義
public interface Callbacks { //如果Launcher在載入完成之前被強制暫停,那麼需要通過這個回撥方法通知Launcher //在它再次顯示的時候重新執行載入過程 public boolean setLoadOnResume(); //獲取當前螢幕序號 public int getCurrentWorkspaceScreen(); //啟動桌面資料繫結 public void startBinding(); //批量繫結桌面元件:快捷方式列表,列表的開始位置,列表結束的位置,是否使用動畫 public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end, boolean forceAnimateIcons); //批量繫結桌面頁,orderedScreenIds 序列化後的桌面頁列表 public void bindScreens(ArrayList<Long> orderedScreenIds); public void bindAddScreens(ArrayList<Long> orderedScreenIds); //批量繫結資料夾,folders 資料夾對映列表 public void bindFolders(LongArrayMap<FolderInfo> folders); //繫結任務完成 public void finishBindingItems(); //批量繫結小部件,info 需要繫結到桌面上的小部件資訊 public void bindAppWidget(LauncherAppWidgetInfo info); //繫結應用程式列表介面的應用程式資訊,apps 需要繫結到應用程式列表中的應用程式列表 public void bindAllApplications(ArrayList<AppInfo> apps); // Add folders in all app list. public void bindAllApplications2Folder(ArrayList<AppInfo> apps, ArrayList<ItemInfo> items); //批量新增元件 public void bindAppsAdded(ArrayList<Long> newScreens, ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated, ArrayList<AppInfo> addedApps); //批量更新應用程式相關的快捷方式或者入口 public void bindAppsUpdated(ArrayList<AppInfo> apps); public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated, ArrayList<ShortcutInfo> removed, UserHandleCompat user); public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets); public void bindRestoreItemsChange(HashSet<ItemInfo> updates); // 從桌面移除一些元件,當應用程式被移除或者禁用的時候呼叫 public void bindComponentsRemoved(ArrayList<String> packageNames, ArrayList<AppInfo> appInfos, UserHandleCompat user, int reason); public void bindAllPackages(WidgetsModel model); //全域性搜尋或者搜尋屬性更新 public void bindSearchablesChanged(); public boolean isAllAppsButtonRank(int rank); /** * 指示正在繫結的頁面 * @param page桌面頁序號 */ public void onPageBoundSynchronously(int page); //輸出當前Launcher資訊到本地檔案中 public void dumpLogsToLocalData(); }
介面都是在Launcher這個類實現的。
總結
- LauncherModel 是一個廣播接收者BroadcastReceiver,監聽應用的安裝、解除安裝、移動等事件。
- 啟動LoaderTask 初始化Launcher資料
Over