?桌面應用圖標流程
前言
本人工作上碰到這么一個需求,開發一款濾鏡引擎,將桌面上所有的圖標進行統一的濾鏡化,這就需要了解一下整個桌面去取圖標的過程,了解了整個過程,找到真正拿圖標的地方,在真正取圖標的地方將圖片進行替換,或者濾鏡化,之前分析情況,現在整理下,與大家分享。本文所用的代碼,是基于Android 5.1
桌面組件介紹
一級菜單
WorkSpace:他是一個ViewGroup,要想在桌面上顯示東西,就得往這個ViewGroup里添加自己的View
BubbleTextView:他是一個TextView,上方是圖標,下方是名稱,在桌面上的圖標都是由這個類表示
FolderIcon:他也是一個ViewGroup,用來表示桌面上的文件夾圖標,里面添加了縮略處理過的bitmap,他的背景圖片就是文件夾的形狀
HotSeat: 他是個FrameLayout,是桌面下方的固定快捷區,包含了幾個常用的圖標,中間的AllApp按鈕是固定位置,也是一個TextView
抽屜頁面 組件
PagedView:他是一個viewgroup,代表進入抽屜頁后的界面,應用圖標需要添加到這個viewgoup里面才能顯示,一個或幾個PagedView 承載了手機上所有的應用圖標
PagedViewIcon:他是一個TextView,和BubblTextView一樣,只是在抽屜容器里換了個名字
桌面加載圖標流程
先來看一張流程圖
桌面Activity 也就是Launcher.java 類,該類里面維護了一個 LauncherModel,該對象會在onCreate 方法中去調用startLoader() 方法,
下面看一下startLoader() 方法的源碼,
public void startLoader(boolean isLaunching, int synchronousBindPage) { synchronized (mLock) { if (DEBUG_LOADERS) { Log.d(TAG, "startLoader isLaunching=" + isLaunching); } // Clear any deferred bind-runnables from the synchronized load process // We must do this before any loading/binding is scheduled below. mDeferredBindRunnables.clear(); // Don't bother to start the thread if we know it's not going to do anything if (mCallbacks != null && mCallbacks.get() != null) { // If there is already one running, tell it to stop. // also, don't downgrade isLaunching if we're already running isLaunching = isLaunching || stopLoaderLocked(); // 這搞了一個異步任務去加載 mLoaderTask = new LoaderTask(mApp.getContext(), isLaunching); if (synchronousBindPage > -1 && mAllAppsLoaded && mWorkspaceLoaded) { mLoaderTask.runBindSynchronousPage(synchronousBindPage); } else { sWorkerThread.setPriority(Thread.NORM_PRIORITY); sWorker.post(mLoaderTask); } } } }
我們看到,這里面有個關鍵的類,loaderTask,見名只義,可以猜到這里面會起一個線程,去加載一些資源。看看里面去加載什么
LoaderTask.java
可以看到LoaderTask實現了Runnable接口,直接去看該類的run() 方法
public void run() { boolean isUpgrade = false; synchronized (mLock) { mIsLoaderTaskRunning = true; } // Optimize for end-user experience: if the Launcher is up and // running with the // All Apps interface in the foreground, load All Apps first. Otherwise, load the // workspace first (default). keep_running: { // Elevate priority when Home launches for the first time to avoid // starving at boot time. Staring at a blank home is not cool. synchronized (mLock) { if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to " + (mIsLaunching ? "DEFAULT" : "BACKGROUND")); android.os.Process.setThreadPriority(mIsLaunching ? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND); } if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace"); //加載一級菜單的方法 isUpgrade = loadAndBindWorkspace(); if (mStopped) { break keep_running; } // Whew! Hard work done. Slow us down, and wait until the UI thread has // settled down. synchronized (mLock) { if (mIsLaunching) { if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to BACKGROUND"); android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); } } waitForIdle(); // second step if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps"); //加載二級菜單里面的方法 loadAndBindAllApps(); // Restore the default thread priority after we are done loading items synchronized (mLock) { android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); } } // Update the saved icons if necessary if (DEBUG_LOADERS) Log.d(TAG, "Comparing loaded icons to database icons"); synchronized (sBgLock) { for (Object key : sBgDbIconCache.keySet()) { updateSavedIcon(mContext, (ShortcutInfo) key, sBgDbIconCache.get(key)); } sBgDbIconCache.clear(); } if (AppsCustomizePagedView.DISABLE_ALL_APPS) { // Ensure that all the applications that are in the system are // represented on the home screen. if (!UPGRADE_USE_MORE_APPS_FOLDER || !isUpgrade) { verifyApplications(); } } // Clear out this reference, otherwise we end up holding it until all of the // callback runnables are done. mContext = null; synchronized (mLock) { // If we are still the last one to be scheduled, remove ourselves. if (mLoaderTask == this) { mLoaderTask = null; } mIsLoaderTaskRunning = false; } }
可以看到在該類中主要有兩個方法,
loadAndBindWorkSpace(), WorkSpace是一級菜單里面的容器類,該方法是加載一及菜單的方法
loadAndBindAllapp() ,這是抽屜內二級菜單的加載方法
下面著重分析下這兩個方法的加載流程
loadAndBindWorkSpace()
這里加載主要分為兩個流程一個是 loadWorkSpace 另一個是 bindWorkSpace,可以看下源代碼
private boolean loadAndBindWorkspace() { mIsLoadingAndBindingWorkspace = true; // Load the workspace if (DEBUG_LOADERS) { Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded); } boolean isUpgradePath = false; if (!mWorkspaceLoaded) { isUpgradePath = loadWorkspace(); synchronized (LoaderTask.this) { if (mStopped) { return isUpgradePath; } mWorkspaceLoaded = true; } } // Bind the workspace bindWorkspace(-1, isUpgradePath); return isUpgradePath; }
可以看到并沒有直接去加載,而是先判斷了一些條件,然后去加載
loadWorkSpace() 方法比較長大概分為三步,
初始化后面要用到的對象實例
final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; final Context context = mContext; final ContentResolver contentResolver = context.getContentResolver(); final PackageManager manager = context.getPackageManager(); final AppWidgetManager widgets = AppWidgetManager.getInstance(context); final boolean isSafeMode = manager.isSafeMode(); LauncherAppState app = LauncherAppState.getInstance(); DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); int countX = (int) grid.numColumns; int countY = (int) grid.numRows;
加載默認配置,并保存數據庫中
synchronized public void loadDefaultFavoritesIfNecessary(int origWorkspaceResId) { String spKey = LauncherAppState.getSharedPreferencesKey(); SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE); if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) { int workspaceResId = origWorkspaceResId; // Use default workspace resource if none provided //如果workspaceResId=0,就會加載默認的配置(default_workspace_xxx.xml),并保存到數據庫中 if (workspaceResId == 0) { workspaceResId = sp.getInt(DEFAULT_WORKSPACE_RESOURCE_ID, R.xml.default_workspace); } // Populate favorites table with initial favorites SharedPreferences.Editor editor = sp.edit(); editor.remove(EMPTY_DATABASE_CREATED); if (origWorkspaceResId != 0) { editor.putInt(DEFAULT_WORKSPACE_RESOURCE_ID, origWorkspaceResId); } mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), workspaceResId); mOpenHelper.setFlagJustLoadedOldDb(); editor.commit(); } }
讀取數據庫,獲取需要加載的應用快捷方式
此段代碼較多,就是去讀取數據庫的一些操作,具體過程是根據一些不同的type 存到不同的list中。
bindWorkSpace()
應用信息讀取完之后,剛才的幾個變量中就存儲了該信息,然后將其綁定到workspace中去,這個過程也是很復雜的
創建局部變量,將全局變量的信息復制過來,單獨進行操作
ArrayList workspaceItems = new ArrayList(); ArrayList appWidgets = new ArrayList(); HashMap
根據item中的screenID將items分成當前screen和其他screen,并進行排序
// Separate the items that are on the current screen, and all the other remaining items ArrayList currentWorkspaceItems = new ArrayList();// 存放當前workspace上的items ArrayList otherWorkspaceItems = new ArrayList();// 存放除當前workspace之外的items ArrayList currentAppWidgets = new ArrayList();// 存放當前workspace上的appwidgets ArrayList otherAppWidgets = new ArrayList();// 存放除當前workspace之外的appwidgets HashMap
runnable執行塊,告訴workspace要開始綁定items了,startBinding方法在Launcher中實現,做一些清除工作
// Tell the workspace that we're about to start binding items r = new Runnable() { public void run() { Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null) { callbacks.startBinding(); } } }; runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
可以看一下實現類launcher中startBinding()方法
public void startBinding() { // If we're starting binding all over again, clear any bind calls we'd postponed in // the past (see waitUntilResume) -- we don't need them since we're starting binding // from scratch again mBindOnResumeCallbacks.clear(); // Clear the workspace because it's going to be rebound mWorkspace.clearDropTargets(); mWorkspace.removeAllWorkspaceScreens(); mWidgetsToAdvance.clear(); if (mHotseat != null) { mHotseat.resetLayout(); } }
可以看到主要做的是清除和重置工作
綁定workspace screen
bindWorkspaceScreens(oldCallbacks, orderedScreenIds); 具體實現在launcher中
Workspace綁定完成之后,就是將items、widgets和folders放到上面去
loadAndBindAllapp()
可以看到加載過程也是分為兩步:如果所有app已經加載過了,就只需要綁定就行了,否則的話,加載所有app,第一次啟動肯定是加載所有的,我們按照這種情況來分析
1.獲取需要顯示到Launcher中的app列表,創建app圖標
private void loadAndBindAllApps() { if (DEBUG_LOADERS) { Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded); } if (!mAllAppsLoaded) { loadAllApps(); synchronized (LoaderTask.this) { if (mStopped) { return; } mAllAppsLoaded = true; } } else { onlyBindAllApps(); } }
loadAllApps()
final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; final Callbacks oldCallbacks = mCallbacks.get(); if (oldCallbacks == null) { // This launcher has exited and nobody bothered to tell us. Just bail. Log.w(TAG, "LoaderTask running with no launcher (loadAllApps)"); return; } final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); final List profiles = mUserManager.getUserProfiles(); // Clear the list of apps mBgAllAppsList.clear();// 清除所有app列表 SharedPreferences prefs = mContext.getSharedPreferences( LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE); for (UserHandleCompat user : profiles) { // Query for the set of apps final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; List apps = mLauncherApps.getActivityList(null, user);// 獲取需要顯示在Launcher上的activity列表 if (DEBUG_LOADERS) { Log.d(TAG, "getActivityList took " + (SystemClock.uptimeMillis()-qiaTime) + "ms for user " + user); Log.d(TAG, "getActivityList got " + apps.size() + " apps for user " + user); } // Fail if we don't have any apps // TODO: Fix this. Only fail for the current user. if (apps == null || apps.isEmpty()) {// 沒有需要顯示的,直接返回 return; } // Sort the applications by name final long sortTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; Collections.sort(apps, new LauncherModel.ShortcutNameComparator(mLabelCache));// 排序 if (DEBUG_LOADERS) { Log.d(TAG, "sort took " + (SystemClock.uptimeMillis()-sortTime) + "ms"); } // Create the ApplicationInfos for (int i = 0; i < apps.size(); i++) { LauncherActivityInfoCompat app = apps.get(i); // This builds the icon bitmaps. mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache, mLabelCache));// 創建應用圖標對象,并添加到所有APP列表中 } if (ADD_MANAGED_PROFILE_SHORTCUTS && !user.equals(UserHandleCompat.myUserHandle())) { // Add shortcuts for packages which were installed while launcher was dead. String shortcutsSetKey = INSTALLED_SHORTCUTS_SET_PREFIX + mUserManager.getSerialNumberForUser(user); Set packagesAdded = prefs.getStringSet(shortcutsSetKey, Collections.EMPTY_SET); HashSet newPackageSet = new HashSet(); for (LauncherActivityInfoCompat info : apps) { String packageName = info.getComponentName().getPackageName(); if (!packagesAdded.contains(packageName) && !newPackageSet.contains(packageName)) { InstallShortcutReceiver.queueInstallShortcut(info, mContext); } newPackageSet.add(packageName); } prefs.edit().putStringSet(shortcutsSetKey, newPackageSet).commit(); } } // Huh? Shouldn't this be inside the Runnable below? final ArrayList added = mBgAllAppsList.added;// 獲取自上次更新(notify()廣播)后新增加的應用清單,如果是開機初次啟動Launcher,那么added就是mBgAllAppsList mBgAllAppsList.added = new ArrayList();// 將AllAppsList的added清空,不影響后續新增的app // Post callback on main thread mHandler.post(new Runnable() { public void run() { final long bindTime = SystemClock.uptimeMillis(); final Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null) { callbacks.bindAllApplications(added); if (DEBUG_LOADERS) { Log.d(TAG, "bound " + added.size() + " apps in " + (SystemClock.uptimeMillis() - bindTime) + "ms"); } } else { Log.i(TAG, "not binding apps: no Launcher activity"); } } }); if (DEBUG_LOADERS) { Log.d(TAG, "Icons processed in " + (SystemClock.uptimeMillis() - loadTime) + "ms"); }
綁定app--bindAllApplications
public void bindAllApplications(final ArrayList apps) { if (LauncherAppState.isDisableAllApps()) {// 判斷是否禁用所有app,就是所有應用都顯示在一級目錄 if (mIntentsOnWorkspaceFromUpgradePath != null) { if (LauncherModel.UPGRADE_USE_MORE_APPS_FOLDER) { getHotseat().addAllAppsFolder(mIconCache, apps, mIntentsOnWorkspaceFromUpgradePath, Launcher.this, mWorkspace); } mIntentsOnWorkspaceFromUpgradePath = null; } if (mAppsCustomizeContent != null) { mAppsCustomizeContent.onPackagesUpdated( LauncherModel.getSortedWidgetsAndShortcuts(this)); } } else { if (mAppsCustomizeContent != null) { mAppsCustomizeContent.setApps(apps); mAppsCustomizeContent.onPackagesUpdated(LauncherModel.getSortedWidgetsAndShortcuts(this)); } } if (mLauncherCallbacks != null) { mLauncherCallbacks.bindAllApplications(apps); } }
可以看到無論是一級桌面拿圖標,還是抽屜頁面拿圖標,都是去走,IconCache的getIcon()方法,
IconCache
getIcon()
public Bitmap getIcon(ComponentName component, ResolveInfo resolveInfo, HashMap labelCache) { synchronized (mCache) { if (resolveInfo == null || component == null) { return null; } CacheEntry entry = cacheLocked(component, resolveInfo, labelCache); return entry.icon; } }
這里判斷一下條件,會去走cacheLocked() 方法
cacheLocked()
private CacheEntry cacheLocked(ComponentName componentName, ResolveInfo info, HashMap labelCache) { CacheEntry entry = mCache.get(componentName); if (entry == null) { entry = new CacheEntry(); mCache.put(componentName, entry); ComponentName key = LauncherModel.getComponentNameFromResolveInfo(info); if (labelCache != null && labelCache.containsKey(key)) { entry.title = labelCache.get(key).toString(); } else { entry.title = info.loadLabel(mPackageManager).toString(); if (labelCache != null) { labelCache.put(key, entry.title); } } if (entry.title == null) { entry.title = info.activityInfo.name; } entry.icon = Utilities.createIconBitmap( getFullResIcon(info), mContext); } return entry; }
這個方法里面。就是最終去拿圖標的方法,里面去拿一些必要信息,去給entry賦值
?
評論
查看更多