标签:
<category android:name="android.intent.category.HOME" />
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(); } }
public static void setApplicationContext(Context context) { if (sContext != null) { Log.w(Launcher.TAG, "setApplicationContext called twice! old=" + sContext + " new=" + context); } sContext = context.getApplicationContext(); }
private LauncherAppState() { if (sContext == null) { throw new IllegalStateException("LauncherAppState inited before app context set"); } Log.v(Launcher.TAG, "LauncherAppState inited"); if (sContext.getResources().getBoolean(R.bool.debug_memory_enabled)) { MemoryTracker.startTrackingMe(sContext, "L"); } // set sIsScreenXLarge and mScreenDensity *before* creating icon cache mIsScreenLarge = isScreenLarge(sContext.getResources());// 判断是否大屏幕 mScreenDensity = sContext.getResources().getDisplayMetrics().density;// 像素密度 recreateWidgetPreviewDb();// 小部件和快捷图标数据库操作对象 mIconCache = new IconCache(sContext);// 应用图标缓存对象 mAppFilter = AppFilter.loadByName(sContext.getString(R.string.app_filter_class)); mBuildInfo = BuildInfo.loadByName(sContext.getString(R.string.build_info_class)); mModel = new LauncherModel(this, mIconCache, mAppFilter);//初始化LauncherModel final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(sContext); launcherApps.addOnAppsChangedCallback(mModel); // Register intent receivers IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_LOCALE_CHANGED);//Locale发生了变化,如中英文的切换 filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);//Configuration发生变化,如横竖屏切换 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);// 观察桌面快捷方式是否发生变化 }
public class Launcher extends Activity implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks, View.OnTouchListener, PageSwitchListener, LauncherProviderChangeListener {Launcher继承于Activity,实现各个接口,这样就构成了整个Launcher的展示(桌面和抽屉)和操作(点击、长按、拖拽等等),从onCreate开始分析。
// LauncherCallbacks在LauncherExtension中实现,LauncherExtension继承于Launcher,是Launcher的扩展, // 用来扩展Launcher的功能,但是目前没有启用,android:enabled="false",涉及到相关的内容就不需要关注了。 if (mLauncherCallbacks != null) { mLauncherCallbacks.preOnCreate(); }
LauncherCallbacks在LauncherExtension中实现,LauncherExtension继承于Launcher,是Launcher的扩展,用来扩展Launcher的功能,但是目前没有启用(android:enabled="false"),涉及到相关的内容就直接忽略掉。
LauncherAppState.setApplicationContext(getApplicationContext()); LauncherAppState app = LauncherAppState.getInstance(); LauncherAppState.getLauncherProvider().setLauncherProviderChangeListener(this);前面两行跟LauncherApplication中一样的,第三行设置了LauncherProviderChangeListener监听,用来监听LauncherProvider中数据变化,该监听接口在Launcher中实现,
@Override public void onLauncherProviderChange() { if (mLauncherCallbacks != null) { mLauncherCallbacks.onLauncherProviderChange(); } }在LauncherExtension中这个方法的实现为空,就当是一个预留,在回到onCreate中,
// Lazy-initialize the dynamic grid DeviceProfile grid = app.initDynamicGrid(this); // the LauncherApplication should call this, but in case of Instrumentation it might not be present yet mSharedPrefs = getSharedPreferences(LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE);// 初始化SharedPreference,文件名为com.android.launcher3.prefs mIsSafeModeEnabled = getPackageManager().isSafeMode();// 是否是安全模式,关机启动Recovery Mode可以选择打开安全模式 mModel = app.setLauncher(this);// 获取LauncherModel对象,在LauncherAppState已经初始化 mIconCache = app.getIconCache();// 获取IconCache对象,在LauncherAppState已经初始化 mIconCache.flushInvalidIcons(grid);// 清除尺寸不符的icon缓存对象 mDragController = new DragController(this);// 初始化DragController对象,DragController用来处理拖拽操作 mInflater = getLayoutInflater();// 获取LayoutInflater mStats = new Stats(this); mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);// 获取AppWidgetManager实例 mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);// LauncherAppWidgetHost,桌面插件宿主 mAppWidgetHost.startListening();// 开启LauncherAppWidgetHost的监听,以便发生变化时做出响应 // If we are getting an onCreate, we can actually preempt onResume and unset mPaused here, // this also ensures that any synchronous binding below doesn't re-trigger another // LauncherModel load. mPaused = false;
还是一些初始化的工作,看注释就可以了。
checkForLocaleChange();// 检查Locale的变化
// 检查Locale是否发生变化,获取系统当前的Local和Launcher中保存的相比较,如果Locale、mcc、mnc中有一个发生了变化就认为Locale变化了 private void checkForLocaleChange() { if (sLocaleConfiguration == null) {// 启动Launcher时sLocaleConfiguration肯定为null,先初始化,然后读取保存的值,赋值给sLocaleConfiguration,再回调checkForLocaleChange new AsyncTask<Void, Void, LocaleConfiguration>() { @Override protected LocaleConfiguration doInBackground(Void... unused) { LocaleConfiguration localeConfiguration = new LocaleConfiguration(); readConfiguration(Launcher.this, localeConfiguration); return localeConfiguration; } @Override protected void onPostExecute(LocaleConfiguration result) { sLocaleConfiguration = result; checkForLocaleChange(); // recursive, but now with a locale configuration } }.execute(); return; } final Configuration configuration = getResources().getConfiguration();// 读取系统当前的Configuration,里面保存了Locale、mcc、mnc final String previousLocale = sLocaleConfiguration.locale; final String locale = configuration.locale.toString(); final int previousMcc = sLocaleConfiguration.mcc; final int mcc = configuration.mcc; final int previousMnc = sLocaleConfiguration.mnc; final int mnc = configuration.mnc; boolean localeChanged = !locale.equals(previousLocale) || mcc != previousMcc || mnc != previousMnc;// 比较 if (localeChanged) {// 如果发生了变化,将新的值写入到launcher.preferences文件中 sLocaleConfiguration.locale = locale; sLocaleConfiguration.mcc = mcc; sLocaleConfiguration.mnc = mnc; mIconCache.flush(); final LocaleConfiguration localeConfiguration = sLocaleConfiguration; new AsyncTask<Void, Void, Void>() { public Void doInBackground(Void ... args) { writeConfiguration(Launcher.this, localeConfiguration); return null; } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null); } } private static class LocaleConfiguration { public String locale;// 语言环境,如zh_CN public int mcc = -1;// 移动国家码 public int mnc = -1;// 移动网络码 }
readConfiguration和writeConfiguration时读文件和写文件,代码就不贴了。
回到onCreate中,
setContentView(R.layout.launcher);
Launcher是一个Activity,当然也需要加载布局了,这里layout-land和layout-port中都有launcher.xml,是为了区分横竖屏切换的情况,一般手机屏幕比较小,Launcher固定竖屏,但是在平板中,是可以横竖屏切换的,这里我们只要看竖屏port目录下的就可以了。
setupViews();// 获取控件并初始化 grid.layout(this);// 放置布局中的各个控件 registerContentObservers();// 注册内容观察者,AppWidgetResetObserver,监听AppWidget是否重置,以便做出响应的处理 lockAllApps();// 空方法 mSavedState = savedInstanceState; restoreState(mSavedState);// 从保存的状态恢复
上面的代码片段也都有注释没啥说的,继续往下看,
// 调运startLoader方法加载app if (!mRestoring) {// 判断是否正在恢复,如果第一次启动mSavedState为null,restoreState直接返回,mRestoring为false,然后执行大括号代码 if (DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE) { // If the user leaves launcher, then we should just load items asynchronously when // they return. mModel.startLoader(true, 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(true, mWorkspace.getRestorePage()); } }
这一段代码是用来异步加载桌面的应用快捷图标、小部件和所有应用图标,是最重要的一步,待会儿单独分析其流程。
// For handling default keys mDefaultKeySsb = new SpannableStringBuilder(); Selection.setSelection(mDefaultKeySsb, 0); IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); registerReceiver(mCloseSystemDialogsReceiver, filter); // On large interfaces, we want the screen to auto-rotate based on the current orientation // 对大屏幕,希望Launcher可以横竖屏切换 unlockScreenOrientation(true); if (mLauncherCallbacks != null) { mLauncherCallbacks.onCreate(savedInstanceState); if (mLauncherCallbacks.hasLauncherOverlay()) { ViewStub stub = (ViewStub) findViewById(R.id.launcher_overlay_stub); mLauncherOverlayContainer = (InsettableFrameLayout) stub.inflate(); mLauncherOverlay = mLauncherCallbacks.setLauncherOverlayView( mLauncherOverlayContainer, mLauncherOverlayCallbacks); mWorkspace.setLauncherOverlay(mLauncherOverlay); } } // 第一次启动时加载用户提示界面 if (shouldShowIntroScreen()) { showIntroScreen(); } else { showFirstRunActivity(); showFirstRunClings(); }
第一次启动时的提示界面,没啥说的,自己开发应用时可以借鉴下。
三、数据加载和展示
在Launcher的onCreate过程中会加载数据,我们将这个过程单独拎出来,startLoader在LauncherModel中实现,
代码位置:launcher3\src\main\java\com\android\launcher3\LauncherModel.java
public void startLoader(boolean isLaunching, int synchronousBindPage) { startLoader(isLaunching, synchronousBindPage, LOADER_FLAG_NONE); } public void startLoader(boolean isLaunching, int synchronousBindPage, int loadFlags) { 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. // 清除延迟执行的绑定app的线程 synchronized (mDeferredBindRunnables) { 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) {// Callbacks在Launcher中实现 // 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, loadFlags); if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE && mAllAppsLoaded && mWorkspaceLoaded) {// launcher不在前台运行 && 所有app已经加载 && workspace已经加载 mLoaderTask.runBindSynchronousPage(synchronousBindPage); } else { sWorkerThread.setPriority(Thread.NORM_PRIORITY); sWorker.post(mLoaderTask); } } } }
执行到mLoaderTask执行块,直接看到LoaderTask的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); } // 分两步执行:1.loading workspace 2.loading workspace 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();// loading all apps之前等待 // 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 (LauncherAppState.isDisableAllApps()) { // 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; } }
分两大步执行:
第一步:加载和绑定workspace--loadAndBindWorkspace
/** Returns whether this is an upgrade path */ 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和bindWorkspace才是实际操作。
1.loadWorkspace(加载Workspace上要显示的数据)
loadWorkspace方法非常长,代码就不全部贴出了,但是执行步骤还是非常清晰的。1)初始化后面要用到的对象实例
final Context context = mContext; final ContentResolver contentResolver = context.getContentResolver();// 用来保存加载数据后的一些信息 final PackageManager manager = context.getPackageManager();// 初始化PackageManager final AppWidgetManager widgets = AppWidgetManager.getInstance(context);// 初始化AppWidgetManager final boolean isSafeMode = manager.isSafeMode();// 是否安全模式启动 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context); final boolean isSdCardReady = context.registerReceiver(null, new IntentFilter(StartupReceiver.SYSTEM_READY)) != null;// SdCard是否就绪 LauncherAppState app = LauncherAppState.getInstance(); DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); int countX = (int) grid.numColumns;// workspace列数 int countY = (int) grid.numRows;// workspace行数
2)加载默认配置
LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary();
// 加载默认的配置,保存到数据库中 synchronized public void loadDefaultFavoritesIfNecessary() { String spKey = LauncherAppState.getSharedPreferencesKey(); SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE); // 判断数据库是否未创建, if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) { Log.d(TAG, "loading default workspace"); AutoInstallsLayout loader = AutoInstallsLayout.get(getContext(), mOpenHelper.mAppWidgetHost, mOpenHelper); if (loader == null) { final Partner partner = Partner.get(getContext().getPackageManager()); if (partner != null && partner.hasDefaultLayout()) { final Resources partnerRes = partner.getResources(); int workspaceResId = partnerRes.getIdentifier(Partner.RES_DEFAULT_LAYOUT, "xml", partner.getPackageName()); if (workspaceResId != 0) { loader = new DefaultLayoutParser(getContext(), mOpenHelper.mAppWidgetHost, mOpenHelper, partnerRes, workspaceResId); } } } final boolean usingExternallyProvidedLayout = loader != null; if (loader == null) { loader = getDefaultLayoutParser(); } // Populate favorites table with initial favorites if ((mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader) <= 0) && usingExternallyProvidedLayout) { // Unable to load external layout. Cleanup and load the internal layout. createEmptyDB(); mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), getDefaultLayoutParser()); } clearFlagEmptyDbCreated(); } }
如果数据库还没有创建,就会加载默认的配置(default_workspace_xxx.xml),并保存到数据库中。
3)读取数据库,获取需要加载的应用快捷方式和AppWidget
整个读取的过程是在一个同步代码块中,在此之前我们先看几个重要的全局变量,
sBgWorkspaceItems--保存ItemInfo
sBgAppWidgets--保存AppWidget
sBgFolders--存放文件夹
sBgItemsIdMap--保存ItemInfo和其Id
sBgDbIconCache--应用图标
sBgWorkspaceScreens--保存Workspace
a)遍历cursor,读取每一个app信息,根据itemType不同类型,分类保存到刚才的几个变量中。分这几种类型:ITEM_TYPE_APPLICATION、ITEM_TYPE_SHORTCUT、ITEM_TYPE_SHORTCUT、ITEM_TYPE_APPWIDGET
b)读取完数据库之后,将需要移除和更新的item进行移除和更新;
c)读取workspace screen数据库信息,如果有未使用过的则将其从数据库中移除。
2.bindWorkspace
应用信息读取完之后,刚才的几个变量中就存储了该信息,然后将其绑定到workspace中去,这个过程也是很复杂的,我们一步一步来看。
1)不直接使用上面提到的几个全局变量,重新定义局部变量来处理
// Save a copy of all the bg-thread collections // 不直接操作全局变量,将其赋值给局部变量 ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>(); ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<LauncherAppWidgetInfo>(); HashMap<Long, FolderInfo> folders = new HashMap<Long, FolderInfo>(); HashMap<Long, ItemInfo> itemsIdMap = new HashMap<Long, ItemInfo>(); ArrayList<Long> orderedScreenIds = new ArrayList<Long>(); synchronized (sBgLock) { workspaceItems.addAll(sBgWorkspaceItems); appWidgets.addAll(sBgAppWidgets); folders.putAll(sBgFolders); itemsIdMap.putAll(sBgItemsIdMap); orderedScreenIds.addAll(sBgWorkspaceScreens); } final boolean isLoadingSynchronously = synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE; int currScreen = isLoadingSynchronously ? synchronizeBindPage : oldCallbacks.getCurrentWorkspaceScreen(); if (currScreen >= orderedScreenIds.size()) { // There may be no workspace screens (just hotseat items and an empty page). currScreen = PagedView.INVALID_RESTORE_PAGE; } final int currentScreen = currScreen;// 当前screen final long currentScreenId = currentScreen < 0 ? INVALID_SCREEN_ID : orderedScreenIds.get(currentScreen);// 当前screen id // Load all the items that are on the current page first (and in the process, unbind // all the existing workspace items before we call startBinding() below. unbindWorkspaceItemsOnMainThread();// 先解除绑定
2)根据item中的screenID将items分成当前screen和其他screen,并进行排序
// Separate the items that are on the current screen, and all the other remaining items ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<ItemInfo>();// 存放当前workspace上的items ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<ItemInfo>();// 存放除当前workspace之外的items ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<LauncherAppWidgetInfo>();// 存放当前workspace上的appwidgets ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<LauncherAppWidgetInfo>();// 存放除当前workspace之外的appwidgets HashMap<Long, FolderInfo> currentFolders = new HashMap<Long, FolderInfo>();// 存放当前workspace上的folder HashMap<Long, FolderInfo> otherFolders = new HashMap<Long, FolderInfo>();// 存放除当前workspace之外的folder filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems, otherWorkspaceItems);// 过滤items,区分当前screen和其他screen上的items filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets, otherAppWidgets);// 同上 filterCurrentFolders(currentScreenId, itemsIdMap, folders, currentFolders, otherFolders);// 同上 sortWorkspaceItemsSpatially(currentWorkspaceItems);// 对workspace上的items进行排序,按照从上到下和从左到右的顺序 sortWorkspaceItemsSpatially(otherWorkspaceItems);// 同上
3)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);代码位置:launcher3\src\main\java\com\android\launcher3\Launcher.java
/** * Refreshes the shortcuts shown on the workspace. * * Implementation of the method from LauncherModel.Callbacks. */ public void startBinding() { setWorkspaceLoading(true); // 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(); } }
4)绑定workspace screen
bindWorkspaceScreens(oldCallbacks, orderedScreenIds);
private void bindWorkspaceScreens(final Callbacks oldCallbacks, final ArrayList<Long> orderedScreens) { final Runnable r = new Runnable() { @Override public void run() { Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null) { callbacks.bindScreens(orderedScreens); } } }; runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE); }
有两个参数,一个是Callback对象,回调方法都在Launcher中实现,另一个是已经排序好的screen id,
代码位置:launcher3\src\main\java\com\android\launcher3\Launcher.java
@Override public void bindScreens(ArrayList<Long> orderedScreenIds) { bindAddScreens(orderedScreenIds); // If there are no screens, we need to have an empty screen // 如果没有需要添加screen,那我们就添加一个空白的screen if (orderedScreenIds.size() == 0) { mWorkspace.addExtraEmptyScreen(); } // Create the custom content page (this call updates mDefaultScreen which calls // setCurrentPage() so ensure that all pages are added before calling this). if (hasCustomContentToLeft()) { mWorkspace.createCustomContentContainer(); populateCustomContentContainer(); } }
@Override public void bindAddScreens(ArrayList<Long> orderedScreenIds) { // Log to disk Launcher.addDumpLog(TAG, "11683562 - bindAddScreens()", true); Launcher.addDumpLog(TAG, "11683562 - orderedScreenIds: " + TextUtils.join(", ", orderedScreenIds), true); int count = orderedScreenIds.size(); for (int i = 0; i < count; i++) { mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(orderedScreenIds.get(i)); } }
通过for循环遍历,分别插入一个新的workspace screen,该方法在Workspace中实现,
代码位置:launcher3\src\main\java\com\android\launcher3\Workspace.java
public long insertNewWorkspaceScreenBeforeEmptyScreen(long screenId) { // Find the index to insert this view into. If the empty screen exists, then // insert it before that. int insertIndex = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID); if (insertIndex < 0) { insertIndex = mScreenOrder.size(); } return insertNewWorkspaceScreen(screenId, insertIndex); }
// 插入一个新的workspace screen public long insertNewWorkspaceScreen(long screenId, int insertIndex) { // Log to disk Launcher.addDumpLog(TAG, "11683562 - insertNewWorkspaceScreen(): " + screenId + " at index: " + insertIndex, true); if (mWorkspaceScreens.containsKey(screenId)) { throw new RuntimeException("Screen id " + screenId + " already exists!"); } CellLayout newScreen = (CellLayout) mLauncher.getLayoutInflater().inflate(R.layout.workspace_screen, null); newScreen.setOnLongClickListener(mLongClickListener); newScreen.setOnClickListener(mLauncher); newScreen.setSoundEffectsEnabled(false); mWorkspaceScreens.put(screenId, newScreen); mScreenOrder.add(insertIndex, screenId); addView(newScreen, insertIndex); return screenId; }
其实就是创建一个CellLayout,然后添加到Workspace中。
5)Workspace绑定完成之后,就是将items、widgets和folders放到上面去
// Load items on the current page bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets, currentFolders, null); if (isLoadingSynchronously) { r = new Runnable() { public void run() { Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null && currentScreen != PagedView.INVALID_RESTORE_PAGE) { callbacks.onPageBoundSynchronously(currentScreen); } } }; runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE); } // Load all the remaining pages (if we are loading synchronously, we want to defer this // work until after the first render) synchronized (mDeferredBindRunnables) { mDeferredBindRunnables.clear(); } bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders, (isLoadingSynchronously ? mDeferredBindRunnables : null));
当前页和其它页分别加载,都是调运bindWorkspaceItems来实现的,看一下该方法的实现过程,
a)批量加载itmes
// 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);// 批量绑定,批量大小为ITEMS_CHUNK,如果一共少于ITEMS_CHUNK,那就一次全部绑定 final Runnable r = new Runnable() { @Override public void run() { Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null) { callbacks.bindItems(workspaceItems, start, start+chunkSize, false); } } }; if (postOnMainThread) { synchronized (deferredBindRunnables) { deferredBindRunnables.add(r); } } else { runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE); } }
回调到Launcher的bindItems方法,
/** * Bind the items start-end from the list. * * Implementation of the method from LauncherModel.Callbacks. */ public void bindItems(final ArrayList<ItemInfo> shortcuts, final int start, final int end, final boolean forceAnimateIcons) { Runnable r = new Runnable() { public void run() { bindItems(shortcuts, start, end, forceAnimateIcons); } }; if (waitUntilResume(r)) {// 当Launcher处于pause状态时,不进行绑定,待resume时再执行 return; } // Get the list of added shortcuts and intersect them with the set of shortcuts here final AnimatorSet anim = LauncherAnimUtils.createAnimatorSet(); final Collection<Animator> bounceAnims = new ArrayList<Animator>(); final boolean animateIcons = forceAnimateIcons && canRunNewAppsAnimation(); Workspace workspace = mWorkspace; long newShortcutsScreenId = -1; for (int i = start; i < end; i++) { final ItemInfo item = shortcuts.get(i); // Short circuit if we are loading dock items for a configuration which has no dock if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT && mHotseat == null) {// 如果绑定的item要放置在Hotseat,但是又没有配置Hotseat,直接跳过 continue; } // 根据item的类型,区分绑定 switch (item.itemType) { case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:// 快捷图标 ShortcutInfo info = (ShortcutInfo) item; View shortcut = createShortcut(info);// 创建快捷图标视图 /* * TODO: FIX collision case */ if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { CellLayout cl = mWorkspace.getScreenWithId(item.screenId); if (cl != null && cl.isOccupied(item.cellX, item.cellY)) {// 判断item将要放置的位置是否被占据了 View v = cl.getChildAt(item.cellX, item.cellY); Object tag = v.getTag(); String desc = "Collision while binding workspace item: " + item + ". Collides with " + tag; if (LauncherAppState.isDogfoodBuild()) { throw (new RuntimeException(desc)); } else { Log.d(TAG, desc); } } } workspace.addInScreenFromBind(shortcut, item.container, item.screenId, item.cellX, item.cellY, 1, 1);// 将快捷图标添加到Workspace screen的指定位置,占据一格 if (animateIcons) { // Animate all the applications up now shortcut.setAlpha(0f); shortcut.setScaleX(0f); shortcut.setScaleY(0f); bounceAnims.add(createNewAppBounceAnimation(shortcut, i)); newShortcutsScreenId = item.screenId; } break; case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:// 文件夹 FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this, (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()), (FolderInfo) item, mIconCache);// 创建文件夹图标 workspace.addInScreenFromBind(newFolder, item.container, item.screenId, item.cellX, item.cellY, 1, 1); break; default: throw new RuntimeException("Invalid Item Type"); } } if (animateIcons) { // Animate to the correct page if (newShortcutsScreenId > -1) { long currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage()); final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newShortcutsScreenId); final Runnable startBounceAnimRunnable = new Runnable() { public void run() { anim.playTogether(bounceAnims); anim.start(); } }; if (newShortcutsScreenId != currentScreenId) { // We post the animation slightly delayed to prevent slowdowns // when we are loading right after we return to launcher. mWorkspace.postDelayed(new Runnable() { public void run() { if (mWorkspace != null) { mWorkspace.snapToPage(newScreenIndex); mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY); } } }, NEW_APPS_PAGE_MOVE_DELAY); } else { mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY); } } } workspace.requestLayout(); }
根据item的类型,分别加载,首先获取item信息,创建快捷图标,然后将快捷图标放置到指定位置addInScreenFromBind,
// At bind time, we use the rank (screenId) to compute x and y for hotseat items. // See implementation for parameter definition. void addInScreenFromBind(View child, long container, long screenId, int x, int y, int spanX, int spanY) { addInScreen(child, container, screenId, x, y, spanX, spanY, false, true); }
/** * Adds the specified child in the specified screen. The position and dimension of * the child are defined by x, y, spanX and spanY. * * @param child The child to add in one of the workspace's screens. * @param screenId The screen in which to add the child. * @param x The X position of the child in the screen's grid. * @param y The Y position of the child in the screen's grid. * @param spanX The number of cells spanned horizontally by the child. * @param spanY The number of cells spanned vertically by the child. * @param insert When true, the child is inserted at the beginning of the children list. * @param computeXYFromRank When true, we use the rank (stored in screenId) to compute * the x and y position in which to place hotseat items. Otherwise * we use the x and y position to compute the rank. */ void addInScreen(View child, long container, long screenId, int x, int y, int spanX, int spanY, boolean insert, boolean computeXYFromRank) { if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { if (getScreenWithId(screenId) == null) { Log.e(TAG, "Skipping child, screenId " + screenId + " not found"); // DEBUGGING - Print out the stack trace to see where we are adding from new Throwable().printStackTrace(); return; } } if (screenId == EXTRA_EMPTY_SCREEN_ID) { // This should never happen throw new RuntimeException("Screen id should not be EXTRA_EMPTY_SCREEN_ID"); } final CellLayout layout;// 先要获取快捷图标的父视图 if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { layout = mLauncher.getHotseat().getLayout(); child.setOnKeyListener(new HotseatIconKeyEventListener()); // Hide folder title in the hotseat if (child instanceof FolderIcon) { ((FolderIcon) child).setTextVisible(false); } if (computeXYFromRank) { x = mLauncher.getHotseat().getCellXFromOrder((int) screenId); y = mLauncher.getHotseat().getCellYFromOrder((int) screenId); } else { screenId = mLauncher.getHotseat().getOrderInHotseat(x, y); } } else { // Show folder title if not in the hotseat if (child instanceof FolderIcon) { ((FolderIcon) child).setTextVisible(true); } layout = getScreenWithId(screenId); child.setOnKeyListener(new IconKeyEventListener()); } ViewGroup.LayoutParams genericLp = child.getLayoutParams(); CellLayout.LayoutParams lp;// 设置布局参数 if (genericLp == null || !(genericLp instanceof CellLayout.LayoutParams)) { lp = new CellLayout.LayoutParams(x, y, spanX, spanY); } else { lp = (CellLayout.LayoutParams) genericLp; lp.cellX = x; lp.cellY = y; lp.cellHSpan = spanX; lp.cellVSpan = spanY; } if (spanX < 0 && spanY < 0) { lp.isLockedToGrid = false; } // Get the canonical child id to uniquely represent this view in this screen ItemInfo info = (ItemInfo) child.getTag(); int childId = mLauncher.getViewIdForItem(info); boolean markCellsAsOccupied = !(child instanceof Folder);// 添加快捷图标的时候,是否需要标记所占据的位置,除了folder外,都需要标记 if (!layout.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) { // TODO: This branch occurs when the workspace is adding views // outside of the defined grid // maybe we should be deleting these items from the LauncherModel? Launcher.addDumpLog(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout", true); } if (!(child instanceof Folder)) { child.setHapticFeedbackEnabled(false); child.setOnLongClickListener(mLongClickListener); } if (child instanceof DropTarget) { mDragController.addDropTarget((DropTarget) child);// 添加拖拽对象 } }
先获取快捷图标的父视图,分Hotseat和Desktop;设置布局参数,确定快捷图标放置的位置;父视图将快捷图标添加到指定位置,
代码位置:launcher3\src\main\java\com\android\launcher3\CellLayout.java
public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params, boolean markCells) { final LayoutParams lp = params; // Hotseat icons - remove text if (child instanceof BubbleTextView) {// 如果是在Hotseat上的图标,将文字去除 BubbleTextView bubbleChild = (BubbleTextView) child; bubbleChild.setTextVisibility(!mIsHotseat); } child.setScaleX(getChildrenScale()); child.setScaleY(getChildrenScale()); // Generate an id for each view, this assumes we have at most 256x256 cells // per workspace screen if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {// 判断位置是否合法 // If the horizontal or vertical span is set to -1, it is taken to // mean that it spans the extent of the CellLayout if (lp.cellHSpan < 0) lp.cellHSpan = mCountX; if (lp.cellVSpan < 0) lp.cellVSpan = mCountY; child.setId(childId); mShortcutsAndWidgets.addView(child, index, lp);// 最终放入指定位置 if (markCells) markCellsAsOccupiedForView(child);// 标记所占据的位置,除了folder外,都需要标记 return true; } return false; }最后设置触摸反馈和长安监听以及拖拽对象的添加。
b)加载folder
// Bind the folders if (!folders.isEmpty()) { final Runnable r = new Runnable() { public void run() { Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null) { callbacks.bindFolders(folders); } } }; if (postOnMainThread) { synchronized (deferredBindRunnables) { deferredBindRunnables.add(r); } } else { runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE); } }也是回调到Launcher的bindFolders方法.
/** * Implementation of the method from LauncherModel.Callbacks. */ public void bindFolders(final HashMap<Long, FolderInfo> folders) { Runnable r = new Runnable() { public void run() { bindFolders(folders); } }; if (waitUntilResume(r)) { return; } sFolders.clear(); sFolders.putAll(folders); }这样就结束了,将folders添加到sFolders的HaspMap中。可能有点奇怪,怎么没有像绑定Workspace items那样将其添加到父视图中?因为之前的过程已经添加过了,对于folder而言,它的快捷图标也是保存在workspaceItems中的,这里绑定folders只是获取folders的信息,用于对文件夹的操作,并不需要将其添加到父视图中。
c)加载widget
// Bind the widgets, one at a time N = appWidgets.size(); for (int i = 0; i < N; i++) { final LauncherAppWidgetInfo widget = appWidgets.get(i); final Runnable r = new Runnable() { public void run() { Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null) { callbacks.bindAppWidget(widget); } } }; if (postOnMainThread) { deferredBindRunnables.add(r); } else { runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE); } }回调到Launcher的bindAppWidget方法中去了,
/** * Add the views for a widget to the workspace. * * Implementation of the method from LauncherModel.Callbacks. */ public void bindAppWidget(final LauncherAppWidgetInfo item) { Runnable r = new Runnable() { public void run() { bindAppWidget(item); } }; if (waitUntilResume(r)) { return; } final long start = DEBUG_WIDGETS ? SystemClock.uptimeMillis() : 0; if (DEBUG_WIDGETS) { Log.d(TAG, "bindAppWidget: " + item); } final Workspace workspace = mWorkspace; AppWidgetProviderInfo appWidgetInfo; if (!mIsSafeModeEnabled && ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0) && ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) != 0)) { appWidgetInfo = mModel.findAppWidgetProviderInfoWithComponent(this, item.providerName);// 获取AppWidgetProviderInfo if (appWidgetInfo == null) { if (DEBUG_WIDGETS) { Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId + " belongs to component " + item.providerName + ", as the povider is null"); } LauncherModel.deleteItemFromDatabase(this, item);// 如果系统中找不到该AppWidget,将其从数据库中删除 return; } // Note: This assumes that the id remap broadcast is received before this step. // If that is not the case, the id remap will be ignored and user may see the // click to setup view. PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(appWidgetInfo, null, null);// 创建PendingAddWidgetInfo实例 pendingInfo.spanX = item.spanX; pendingInfo.spanY = item.spanY; pendingInfo.minSpanX = item.minSpanX; pendingInfo.minSpanY = item.minSpanY; Bundle options = AppsCustomizePagedView.getDefaultOptionsForWidget(this, pendingInfo);// 获取appwidget最大最小宽度、高度 int newWidgetId = mAppWidgetHost.allocateAppWidgetId();// 给AppWidget分配一个id boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(newWidgetId, appWidgetInfo, options);// 用AppWidgetManager来绑定AppWidget // TODO consider showing a permission dialog when the widget is clicked. if (!success) {// 如果绑定失败了,删除刚刚分配的id,并将其从数据库中移除,不在继续执行 mAppWidgetHost.deleteAppWidgetId(newWidgetId); if (DEBUG_WIDGETS) { Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId + " belongs to component " + item.providerName + ", as the launcher is unable to bing a new widget id"); } LauncherModel.deleteItemFromDatabase(this, item); return; } item.appWidgetId = newWidgetId; // If the widget has a configure activity, it is still needs to set it up, otherwise // the widget is ready to go. item.restoreStatus = (appWidgetInfo.configure == null) ? LauncherAppWidgetInfo.RESTORE_COMPLETED : LauncherAppWidgetInfo.FLAG_UI_NOT_READY; LauncherModel.updateItemInDatabase(this, item);// 更新AppWidget在数据库中的信息 } if (!mIsSafeModeEnabled && item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) { final int appWidgetId = item.appWidgetId; appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId); if (DEBUG_WIDGETS) { Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component " + appWidgetInfo.provider); } item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);// 创建AppWidget视图 } else { appWidgetInfo = null; PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item, mIsSafeModeEnabled); view.updateIcon(mIconCache); item.hostView = view; item.hostView.updateAppWidget(null); item.hostView.setOnClickListener(this); } item.hostView.setTag(item); item.onBindAppWidget(this); workspace.addInScreen(item.hostView, item.container, item.screenId, item.cellX, item.cellY, item.spanX, item.spanY, false);// 将AppWidget添加到Workspace的指定位置 addWidgetToAutoAdvanceIfNeeded(item.hostView, appWidgetInfo); workspace.requestLayout(); if (DEBUG_WIDGETS) { Log.d(TAG, "bound widget id="+item.appWidgetId+" in " + (SystemClock.uptimeMillis()-start) + "ms"); } }其做法跟添加items类似的,就不在赘述了。最后通知items的绑定完成,// Tell the workspace that we're done binding items r = new Runnable() { public void run() { Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null) { callbacks.finishBindingItems(isUpgradePath); } // If we're profiling, ensure this is the last thing in the queue. if (DEBUG_LOADERS) { Log.d(TAG, "bound workspace in " + (SystemClock.uptimeMillis()-t) + "ms"); } mIsLoadingAndBindingWorkspace = false; } }; if (isLoadingSynchronously) { synchronized (mDeferredBindRunnables) { mDeferredBindRunnables.add(r); } } else { runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE); }/** * Callback saying that there aren't any more items to bind. * * Implementation of the method from LauncherModel.Callbacks. */ public void finishBindingItems(final boolean upgradePath) { Runnable r = new Runnable() { public void run() { finishBindingItems(upgradePath); } }; if (waitUntilResume(r)) { return; } if (mSavedState != null) { if (!mWorkspace.hasFocus()) { mWorkspace.getChildAt(mWorkspace.getCurrentPage()).requestFocus(); } mSavedState = null; } mWorkspace.restoreInstanceStateForRemainingPages(); setWorkspaceLoading(false); // workspace已经加载结束了 sendLoadingCompleteBroadcastIfNecessary();// 第一次加载完成时会发送广播 // If we received the result of any pending adds while the loader was running (e.g. the // widget configuration forced an orientation change), process them now. if (sPendingAddItem != null) { final long screenId = completeAdd(sPendingAddItem); // TODO: this moves the user to the page where the pending item was added. Ideally, // the screen would be guaranteed to exist after bind, and the page would be set through // the workspace restore process. mWorkspace.post(new Runnable() { @Override public void run() { mWorkspace.snapToScreenId(screenId);// // 滑动指定的screenId的screen上 } }); sPendingAddItem = null; } if (upgradePath) { mWorkspace.getUniqueComponents(true, null); mIntentsOnWorkspaceFromUpgradePath = mWorkspace.getUniqueComponents(true, null); } PackageInstallerCompat.getInstance(this).onFinishBind(); if (mLauncherCallbacks != null) { mLauncherCallbacks.finishBindingItems(upgradePath); } }这样整个loadAndBindWorkspace过程就结束了,接着下一步。
第二步:loadAndBindAllApps
private void loadAndBindAllApps() { if (DEBUG_LOADERS) { Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded); } if (!mAllAppsLoaded) {// 如果还没有加载,就加载所有APP,否则是需要绑定就行了 loadAllApps(); synchronized (LoaderTask.this) { if (mStopped) { return; } mAllAppsLoaded = true; } } else { onlyBindAllApps(); } }分了两种情况,如果所有app已经加载过了,就只需要绑定就行了,否则的话,加载所有app,第一次启动肯定是加载所有的,我们按照这种情况来分析。
private void 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<UserHandleCompat> 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<LauncherActivityInfoCompat> 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<String> packagesAdded = prefs.getStringSet(shortcutsSetKey, Collections.EMPTY_SET); HashSet<String> newPackageSet = new HashSet<String>(); 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<AppInfo> added = mBgAllAppsList.added;// 获取自上次更新(notify()广播)后新增加的应用清单,如果是开机初次启动Launcher,那么added就是mBgAllAppsList mBgAllAppsList.added = new ArrayList<AppInfo>();// 将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"); } } public void dumpState() { synchronized (sBgLock) { Log.d(TAG, "mLoaderTask.mContext=" + mContext); Log.d(TAG, "mLoaderTask.mIsLaunching=" + mIsLaunching); Log.d(TAG, "mLoaderTask.mStopped=" + mStopped); Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished); Log.d(TAG, "mItems size=" + sBgWorkspaceItems.size()); } } }1.获取需要显示到Launcher中的app列表,创建app图标
2.绑定app--bindAllApplications
/** * Add the icons for all apps. * * Implementation of the method from LauncherModel.Callbacks. */ public void bindAllApplications(final ArrayList<AppInfo> 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); } }首先判断是否禁用所有app,就是所有应用都显示在桌面上,我们这里没有禁用所有app页面,直接到else代码块,// 设置需要显示的app,并排序,更新数据 public void setApps(ArrayList<AppInfo> list) { if (!LauncherAppState.isDisableAllApps()) { mApps = list; Collections.sort(mApps, LauncherModel.getAppNameComparator()); updatePageCountsAndInvalidateData(); } }我们跟一下updatePageCountsAndInvalidateData方法,看看到底怎么个过程,
private void updatePageCountsAndInvalidateData() { if (mInBulkBind) { mNeedToUpdatePageCountsAndInvalidateData = true; } else { updatePageCounts(); invalidateOnDataChange(); mNeedToUpdatePageCountsAndInvalidateData = false; } }updatePageCounts是计算并更新需要的page数量,直接看invalidateOnDataChange方法,
/** * We should call thise method whenever the core data changes (mApps, mWidgets) so that we can * appropriately determine when to invalidate the PagedView page data. In cases where the data * has yet to be set, we can requestLayout() and wait for onDataReady() to be called in the * next onMeasure() pass, which will trigger an invalidatePageData() itself. */ private void invalidateOnDataChange() { if (!isDataReady()) { // The next layout pass will trigger data-ready if both widgets and apps are set, so // request a layout to trigger the page data when ready. requestLayout(); } else { cancelAllTasks(); invalidatePageData(); } }protected void invalidatePageData() { invalidatePageData(-1, false); }protected void invalidatePageData(int currentPage, boolean immediateAndOnly) { if (!mIsDataReady) { return; } if (mContentIsRefreshable) { // Force all scrolling-related behavior to end forceFinishScroller(); // Update all the pages syncPages(); // We must force a measure after we've loaded the pages to update the content width and // to determine the full scroll width measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); // Set a new page as the current page if necessary // 设置当前显示页 if (currentPage > -1) { setCurrentPage(Math.min(getPageCount() - 1, currentPage)); } // Mark each of the pages as dirty final int count = getChildCount(); mDirtyPageContent.clear(); for (int i = 0; i < count; ++i) { mDirtyPageContent.add(true); } // Load any pages that are necessary for the current window of views loadAssociatedPages(mCurrentPage, immediateAndOnly); requestLayout(); } if (isPageMoving()) { // If the page is moving, then snap it to the final position to ensure we don't get // stuck between pages snapToDestination(); } }这个方法是重点,也分了几步来执行,我们分步来看:1)syncPages是一个抽象方法,在AppsCustomizePagedView中实现,代码位置:launcher3\src\main\java\com\android\launcher3\AppsCustomizePagedView.java@Override public void syncPages() { disablePagedViewAnimations(); // 移除所有视图和任务 removeAllViews(); cancelAllTasks(); Context context = getContext(); if (mContentType == ContentType.Applications) { for (int i = 0; i < mNumAppsPages; ++i) { AppsCustomizeCellLayout layout = new AppsCustomizeCellLayout(context); setupPage(layout); addView(layout, new PagedView.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); } } else if (mContentType == ContentType.Widgets) { for (int j = 0; j < mNumWidgetPages; ++j) { PagedViewGridLayout layout = new PagedViewGridLayout(context, mWidgetCountX, mWidgetCountY); setupPage(layout); addView(layout, new PagedView.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); } } else { throw new RuntimeException("Invalid ContentType"); } enablePagedViewAnimations(); }app和widget来分别添加,我们这里只需了解如何添加app页面的,widget完全类似。
a)生成AppsCustomizeCellLayout对象b)setupPage,设置page// 设置page的表格、背景色 private void setupPage(AppsCustomizeCellLayout layout) { layout.setGridSize(mCellCountX, mCellCountY);// 设置页面表格数 // Note: We force a measure here to get around the fact that when we do layout calculations // immediately after syncing, we don't have a proper width. That said, we already know the // expected page width, so we can actually optimize by hiding all the TextView-based // children that are expensive to measure, and let that happen naturally later. setVisibilityOnChildren(layout, View.GONE); int widthSpec = MeasureSpec.makeMeasureSpec(mContentWidth, MeasureSpec.AT_MOST); int heightSpec = MeasureSpec.makeMeasureSpec(mContentHeight, MeasureSpec.AT_MOST); layout.measure(widthSpec, heightSpec); // 设置page背景色 Drawable bg = getContext().getResources().getDrawable(R.drawable.quantum_panel); if (bg != null) { bg.setAlpha(mPageBackgroundsVisible ? 255: 0); layout.setBackground(bg); } setVisibilityOnChildren(layout, View.VISIBLE); }c)addView,将CellLayout添加到page中2)重新测量,设置当前页等
// We must force a measure after we've loaded the pages to update the content width and // to determine the full scroll width measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); // Set a new page as the current page if necessary // 设置当前显示页 if (currentPage > -1) { setCurrentPage(Math.min(getPageCount() - 1, currentPage)); }3)loadAssociatedPagesprotected void loadAssociatedPages(int page, boolean immediateAndOnly) { if (mContentIsRefreshable) { final int count = getChildCount(); if (page < count) { int lowerPageBound = getAssociatedLowerPageBound(page); int upperPageBound = getAssociatedUpperPageBound(page); if (DEBUG) Log.d(TAG, "loadAssociatedPages: " + lowerPageBound + "/" + upperPageBound); // First, clear any pages that should no longer be loaded for (int i = 0; i < count; ++i) { Page layout = (Page) getPageAt(i); if ((i < lowerPageBound) || (i > upperPageBound)) { if (layout.getPageChildCount() > 0) { layout.removeAllViewsOnPage(); } mDirtyPageContent.set(i, true); } } // Next, load any new pages for (int i = 0; i < count; ++i) { if ((i != page) && immediateAndOnly) { continue; } if (lowerPageBound <= i && i <= upperPageBound) { if (mDirtyPageContent.get(i)) { syncPageItems(i, (i == page) && immediateAndOnly); mDirtyPageContent.set(i, false); } } } } } }先清除不需要加载的pages,然后加载page及items--syncPageItems,也是一个抽象方法在AppsCustomizePagedView中实现,@Override public void syncPageItems(int page, boolean immediate) { if (mContentType == ContentType.Widgets) { syncWidgetPageItems(page, immediate); } else { syncAppsPageItems(page, immediate); } }// 添加items到page上 public void syncAppsPageItems(int page, boolean immediate) { // ensure that we have the right number of items on the pages final boolean isRtl = isLayoutRtl();// 是否从右向左排列,一般都是从左向右 int numCells = mCellCountX * mCellCountY;// 每页的格数,及可加载的app数量 int startIndex = page * numCells;// 开始位置 int endIndex = Math.min(startIndex + numCells, mApps.size());// 结束位置,如果不满页,就是app数量的总数 AppsCustomizeCellLayout layout = (AppsCustomizeCellLayout) getPageAt(page);// 根据page号获取AppsCustomizeCellLayout layout.removeAllViewsOnPage(); ArrayList<Object> items = new ArrayList<Object>(); ArrayList<Bitmap> images = new ArrayList<Bitmap>(); for (int i = startIndex; i < endIndex; ++i) {// 循环添加items AppInfo info = mApps.get(i); BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(R.layout.apps_customize_application, layout, false); icon.applyFromApplicationInfo(info); icon.setOnClickListener(mLauncher); icon.setOnLongClickListener(this); icon.setOnTouchListener(this); icon.setOnKeyListener(this); icon.setOnFocusChangeListener(layout.mFocusHandlerView); // icon.setTextColor(Color.WHITE); // modify text color int index = i - startIndex; // 就算item所在表格位置 int x = index % mCellCountX; int y = index / mCellCountX; if (isRtl) { x = mCellCountX - x - 1; } layout.addViewToCellLayout(icon, -1, i, new CellLayout.LayoutParams(x,y, 1,1), false); items.add(info); images.add(info.iconBitmap); } enableHwLayersOnVisiblePages(); }代码中都有注释,比较好理解,先计算需要放置的位置,然后创建BubbleTextView实例,最后将其添加到page中。整个setApps的过程还是非常长的,最终目的就是将app显示到所有app列表中。接着会执行onPackagesUpdated,package的更新操作,就不再展开了。Widget也是类似的。这样加载allapp的过程就结束了。
标签:
原文地址:http://blog.csdn.net/dingfengnupt88/article/details/51800057