Launcher在应用启动的时候,需要加载AppWidget,shortcut等内容项,通过调用LauncherModel.startLoader(),开始加载的工作。launcherModel中加载好的内容会通过
LauncherModel.Callbacks接口的回调函数将数据传给需要的组件,那先来看看Callbacks的定义:
- 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();
- }
简单的了解下每个方法的用途:setLoadOnResume() 由于Launcher继承自Activity,因此Launcher可能会处于paused状态(onPause()被调用),则有可能在这段时间内资源可能
发生了改变,如应用被删除或新应用安装,因此需要在onResume()中调用此方法进行重新加载。
getCurrentWorkspace() 获取当前屏幕的序号
startBinding() 通知Launcher加载开始,并更新Workspace上的shortcuts
bindItems(ArrayList<ItemInfo> shortcuts, int start, int end) 加载一批内容项到Workspace,加载的内容项包括,Application、shortcut、folder。
bindFolders(HashMap<Long, FolderInfo> folders) 加载folder的内容
finishBindingItems() 通知Launcher加载结束。
bindAppWidget(LauncherAppWidgetInfo item) 加载AppWidget到Workspace
bindAllApplications(final ArrayList<ApplicationInfo> apps) 在All Apps页加载所有应用的Icon
bindAppsAdded(ArrayList<ApplicationInfo> apps) 通知Launcher一个新的应用被安装,并加载这个应用
bindAppsUpdated(ArrayList<ApplicationInfo> apps) 通知Launcher一个应用发生了更新
bindAppsRemoved(ArrayList<ApplicationInfo> apps, boolean permanent) 通知Launcher一个应用被删除了
bindPackagesUpdated() 通知Launcher多个应用发生了更新
isAllAppsVisible()用于在加载的过程中记录当前Launcher的状态,返回true则当前显示的All Apps
bindSearchablesChanged()当搜索/删除框状态发生改变时调用
了解了每个方法的作用之后,就可以开始进一步的分析了。
首先让我们回顾一下整个加载过程的流程是怎样的
通过在Launcher中调用LauncherModel.startLoader()方法,开始加载内容。
- public void startLoader(Context context, boolean isLaunching) {
- synchronized (mLock) {
- ......
-
- if (mCallbacks != null && mCallbacks.get() != null) {
- ......
- mLoaderTask = new LoaderTask(context, isLaunching);
- sWorkerThread.setPriority(Thread.NORM_PRIORITY);
- sWorker.post(mLoaderTask);
- }
- }
- }
mLoaderTask是一个Runnable,被添加到消息队列之后,它的run() 方法会被调用。- public void run() {
- ......
- keep_running: {
- ......
- if (loadWorkspaceFirst) {
- ......
- loadAndBindWorkspace();
- } else {
- ......
- }
-
- if (mStopped) {
- break keep_running;
- }
-
- ......
- waitForIdle();
-
-
- if (loadWorkspaceFirst) {
- ......
- loadAndBindAllApps();
- } else {
- ......
- }
- ......
- }
- ......
- }
加载的工作由两部分组成,第一部分是为Workspace加载内容,第二部分则是为AllApps加载内容。每一部分的加载又可以分为两个步骤:1、由LauncherModel完成,主要
工作是从数据库中读取信息,并且按类别将内容项分装到不同的数据结构中。2、由Launcher来完成,通过LauncherModel.Callbacks接口定义的回调方法,从LauncherModel
中获取的数据,将其显示到桌面。
一、Workspace内容加载
run()中首先会调用loadAndBindWorkspace()方法开始Workspace的加载工作。
- private void loadAndBindWorkspace() {
- ...
- if (!mWorkspaceLoaded) {
- loadWorkspace();
- synchronized (LoaderTask.this) {
- if (mStopped) {
- return;
- }
- mWorkspaceLoaded = true;
- }
- }
-
-
- bindWorkspace();
- }
因为WorkspaceLoaded=false,所以会调用loadWorkspace()读取内容数据,等数据读取完毕之后,再调用bindWorkspace()将数据
加载到Workspace中。
- private void loadWorkspace() {
- ......
-
-
- sWorkspaceItems.clear();
-
-
- sAppWidgets.clear();
-
-
- sFolders.clear();
-
-
- sItemsIdMap.clear();
- sDbIconCache.clear();
-
- final ArrayList<Long> itemsToRemove = new ArrayList<Long>();
-
- final Cursor c = contentResolver.query(
- LauncherSettings.Favorites.CONTENT_URI, null, null, null, null);
-
-
-
-
-
-
-
-
- final ItemInfo occupied[][][] =
- new ItemInfo[Launcher.SCREEN_COUNT + 1][mCellCountX + 1][mCellCountY + 1];
-
- try {
- ......
-
- while (!mStopped && c.moveToNext()) {
- try {
- int itemType = c.getInt(itemTypeIndex);
-
- switch (itemType) {
- case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
- case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
- intentDescription = c.getString(intentIndex);
- try {
- intent = Intent.parseUri(intentDescription, 0);
- } catch (URISyntaxException e) {
- continue;
- }
-
- if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
- info = getShortcutInfo(manager, intent, context, c, iconIndex,
- titleIndex, mLabelCache);
- } else {
- info = getShortcutInfo(c, context, iconTypeIndex,
- iconPackageIndex, iconResourceIndex, iconIndex,
- titleIndex);
- }
-
- if (info != null) {
- ......
-
-
-
- if (!checkItemPlacement(occupied, info)) {
- break;
- }
-
- switch (container) {
- case LauncherSettings.Favorites.CONTAINER_DESKTOP:
- case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
-
-
-
- sWorkspaceItems.add(info);
- break;
- default:
-
-
-
- FolderInfo folderInfo =
- findOrMakeFolder(sFolders, container);
- folderInfo.add(info);
- break;
- }
-
-
- sItemsIdMap.put(info.id, info);
-
-
-
- queueIconToBeChecked(sDbIconCache, info, c, iconIndex);
- } else {
-
-
-
-
- id = c.getLong(idIndex);
- Log.e(TAG, "Error loading shortcut " + id + ", removing it");
- contentResolver.delete(LauncherSettings.Favorites.getContentUri(
- id, false), null, null);
- }
- break;
-
- case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
- id = c.getLong(idIndex);
- FolderInfo folderInfo = findOrMakeFolder(sFolders, id);
- .....
-
- if (!checkItemPlacement(occupied, folderInfo)) {
- break;
- }
- switch (container) {
- case LauncherSettings.Favorites.CONTAINER_DESKTOP:
- case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
-
- sWorkspaceItems.add(folderInfo);
- break;
- }
-
- sItemsIdMap.put(folderInfo.id, folderInfo);
-
- sFolders.put(folderInfo.id, folderInfo);
- break;
-
- case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
-
- int appWidgetId = c.getInt(appWidgetIdIndex);
- id = c.getLong(idIndex);
-
- final AppWidgetProviderInfo provider =
- widgets.getAppWidgetInfo(appWidgetId);
-
- if (!isSafeMode && (provider == null || provider.provider == null ||
- provider.provider.getPackageName() == null)) {
- ......
- itemsToRemove.add(id);
- } else {
- appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId);
- ......
-
- container = c.getInt(containerIndex);
- ......
- appWidgetInfo.container = c.getInt(containerIndex);
-
-
- if (!checkItemPlacement(occupied, appWidgetInfo)) {
- break;
- }
-
- sItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
-
- sAppWidgets.add(appWidgetInfo);
- }
- break;
- }
- } catch (Exception e) {
- ......
- }
- }
- } finally {
- c.close();
- }
-
- ......
- }
loadWorkspace的工作就是从ContentProvider获取指定URI中的数据,并将它们分类存放到指定的数据结构中。分类的标准有两条:1、item的类型。包括ITEM_TYPE_APPLICATION ,ITEM_TYPE_SHORTCUT ,ITEM_TYPE_FOLDER,ITEM_TYPE_APPWIDGET四类。2、item所属的容器。包括CONTAINER_DESKTOP,
CONTAINER_HOTSEAT以及其它(主要指文件夹)。LauncherModel在读取完数据之后,通过LauncherModel.bindWorkspace()将数据传给到Launcher。进入LauncherModel.bindWorkspace()中:
- private void bindWorkspace() {
- ......
- mHandler.post(new Runnable() {
- public void run() {
- Callbacks callbacks = tryGetCallbacks(oldCallbacks);
- if (callbacks != null) {
-
- callbacks.startBinding();
- }
- }
- });
-
- ......
- 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);
- }
- }
- });
- }
- ......
- mHandler.post(new Runnable() {
- public void run() {
- Callbacks callbacks = tryGetCallbacks(oldCallbacks);
- if (callbacks != null) {
-
- callbacks.bindFolders(folders);
- }
- }
- });
- ......
- 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);
- }
- }
- });
- }
- }
-
- 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);
- }
- }
- });
- }
- }
-
- mHandler.post(new Runnable() {
- public void run() {
- Callbacks callbacks = tryGetCallbacks(oldCallbacks);
- if (callbacks != null) {
-
- callbacks.finishBindingItems();
- }
- }
- });
- ......
- }
可以看到,Launcher的内容绑定分为五步:分别对应着startBinding()、bindItems()、bindFolders()、bindAppWidgets()、finishBindingItems()的调用
Step1:调用Callbacks.startBinding()
由于Launcher实现了Callbacks接口,Launcher中的startBinding()被调用,进入Launcher.startBinding();
-
-
-
-
-
- public void startBinding() {
- final Workspace workspace = mWorkspace;
-
- mWorkspace.clearDropTargets();
- int count = workspace.getChildCount();
- for (int i = 0; i < count; i++) {
-
- final CellLayout layoutParent = (CellLayout) workspace.getChildAt(i);
- layoutParent.removeAllViewsInLayout();
- }
- if (mHotseat != null) {
- mHotseat.resetLayout();
- }
- }
从方法中的内容我们可以看到,当被通知开始加载Workspace中内容时,Launcher重置了Workspace中的内容,Hotseat也通过resetLayout方法进行重置。
- void resetLayout() {
- mContent.removeAllViewsInLayout();
-
-
- Context context = getContext();
- LayoutInflater inflater = LayoutInflater.from(context);
- BubbleTextView allAppsButton = (BubbleTextView)
- inflater.inflate(R.layout.application, mContent, false);
- ......
-
-
-
- int x = getCellXFromOrder(sAllAppsButtonRank);
- int y = getCellYFromOrder(sAllAppsButtonRank);
- mContent.addViewToCellLayout(allAppsButton, -1, 0, new CellLayout.LayoutParams(x,y,1,1),
- true);
- }
Hotseat中清空了装载的内容,然后重新加载allAppsButton。从这里也可以看到allAppsButton是固定到了Hotseat中,不同于Hotseat中的其他控件。
Step2:调用Callbacks.bindItems(ArrayList<ItemInfo> shortcuts, int start, int end)
准备工作完成之后,现在可以开始正式的加载工作了,首先被调用的是bindItems()方法
-
-
-
-
-
- public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end) {
- setLoadOnResume();
-
- final Workspace workspace = mWorkspace;
- for (int i=start; i<end; i++) {
- final ItemInfo item = shortcuts.get(i);
- ......
- switch (item.itemType) {
- case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
- case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
- View shortcut = createShortcut((ShortcutInfo)item);
- workspace.addInScreen(shortcut, item.container, item.screen, item.cellX,
- item.cellY, 1, 1, false);
- 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.addInScreen(newFolder, item.container, item.screen, item.cellX,
- item.cellY, 1, 1, false);
- break;
- }
- }
- workspace.requestLayout();
- }
通过这个方法,将application、shortcut、folder三种item通过Workspace.addInScreen()添加到Workspace中-
-
-
-
-
-
-
-
-
-
-
-
- void addInScreen(View child, long container, int screen, int x, int y, int spanX, int spanY,
- boolean insert) {
- ......
-
-
- final CellLayout layout;
- if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
- layout = mLauncher.getHotseat().getLayout();
- child.setOnKeyListener(null);
-
- ......
- if (screen < 0) {
- screen = mLauncher.getHotseat().getOrderInHotseat(x, y);
- } else {
-
-
-
- x = mLauncher.getHotseat().getCellXFromOrder(screen);
- y = mLauncher.getHotseat().getCellYFromOrder(screen);
- }
- } else {
-
- if (child instanceof FolderIcon) {
- ((FolderIcon) child).setTextVisible(true);
- }
-
- layout = (CellLayout) getChildAt(screen);
- child.setOnKeyListener(new IconKeyEventListener());
- }
-
- CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
- if (lp == null) {
- lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
- } else {
- lp.cellX = x;
- lp.cellY = y;
- lp.cellHSpan = spanX;
- lp.cellVSpan = spanY;
- }
-
- if (spanX < 0 && spanY < 0) {
- lp.isLockedToGrid = false;
- }
-
-
- int childId = LauncherModel.getCellLayoutChildId(container, screen, x, y, spanX, spanY);
- boolean markCellsAsOccupied = !(child instanceof Folder);
-
- if (!layout.addViewToCellLayout(child, insert ? 0 : -1, childId, lp, markCellsAsOccupied)) {
- ......
- }
-
- if (!(child instanceof Folder)) {
- child.setHapticFeedbackEnabled(false);
- child.setOnLongClickListener(mLongClickListener);
- }
- if (child instanceof DropTarget) {
- mDragController.addDropTarget((DropTarget) child);
- }
- }
通过addInScreen()就能将child添加到指定的CellLayout中去。CellLayout共有六个,Workspace中五个,Hotseat一个。
Step3:调用Callbacks.bindFolders(HashMap<Long, FolderInfo> folders)
Launcher.bindFolders()中的代码只有三行:
- public void bindFolders(HashMap<Long, FolderInfo> folders) {
- setLoadOnResume();
- sFolders.clear();
- sFolders.putAll(folders);
- }
获取到当前的Folder的映射表。Step4:调用Callbacks.bindAppWidgets(LauncherAppWidgetInfo item)
现在开始加载AppWidget到Workspace:
-
-
-
-
-
- public void bindAppWidget(LauncherAppWidgetInfo item) {
- setLoadOnResume();
-
- ......
- final Workspace workspace = mWorkspace;
-
- final int appWidgetId = item.appWidgetId;
- final AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
- ......
-
- item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
-
- item.hostView.setAppWidget(appWidgetId, appWidgetInfo);
- item.hostView.setTag(item);
-
- workspace.addInScreen(item.hostView, item.container, item.screen, item.cellX,
- item.cellY, item.spanX, item.spanY, false);
-
- addWidgetToAutoAdvanceIfNeeded(item.hostView, appWidgetInfo);
-
- workspace.requestLayout();
-
- ......
- }
先获取到AppWidget的相关信息之后,调用Workspace.addInScreen()添加到Workspace。AppWidget是Android系统的一大特色,可
以在桌面上快捷的获取实时信息和对一些指定应用进行控制。AppWidget是需要自动更新的(如果应用中设置了更新),因此除了
将其添加到桌面我们需要更具需要设置自动更新。进而调用addWidgetToAutoAdvanceifNeeded()来实现此功能,关于如何实现自动
更新AppWidget的话题,本文暂不做分析。bindAppWidgets()一共被调用两次,这样做的目的是增加流畅感,第一次调用的时候为
当前显示的分屏添加AppWidget,第二次调用的时候为其他未显示的分屏添加AppWidget。这样就给用户带来了一种流畅的用户体验。
Step5:调用Callbacks.finishBindingItems()
通过上面的操作,所有item就已经悉数被添加到Workspace当中,此时调用finishBindingItems()通知Launcher添加完毕。
-
-
-
-
-
- ublic void finishBindingItems() {
- setLoadOnResume();
- ......
- mWorkspaceLoading = false;
-
-
-
- for (int i = 0; i < sPendingAddList.size(); i++) {
- completeAdd(sPendingAddList.get(i));
- }
- sPendingAddList.clear();
- ......
- mWorkspace.post(mBuildLayersRunnable);
当Workspace正在加载的时候,有一些操作发生却还未执行,在finishBindingItems()中来执行这些操作,调用completeAdd()来完成
还未来得及完成的操作。紧接着又向Workspace的消息队列里加入了mBuildLayersRunnable,mBuildLayersRunnable是Runnable的
一个实例,它的功能就是迫使每个View都完成渲染的工作,即及时的现实到桌面中现好了所有需要的内容了。那下一步就是需要向
All Apps页中加载内容了。
二、AllApps的内容加载
回到mLoaderTask.run()方法中,当bindWorkspace()执行结束之后,并通过waitForIdle()确认加载完成之后,就会调用
loadAndBindAllApps()来为AllApps页面加载内容。
- private void loadAndBindAllApps() {
- ......
- if (!mAllAppsLoaded) {
-
- loadAllAppsByBatch();
- ......
- } else {
-
- onlyBindAllApps();
- }
- }
AllApps中的加载过程和Workspace中的加载过程大致是相同的,只是All Apps的加载和绑定过程被放到同一个方法loadAllAppsByBatch()中执行:
-
-
-
- private void loadAllAppsByBatch() {
- ......
-
-
- 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();
- ......
-
- apps = packageManager.queryIntentActivities(mainIntent, 0);
- ......
-
- N = apps.size();
- ......
- if (mBatchSize == 0) {
-
- batchSize = N;
- } else {
- batchSize = mBatchSize;
- }
-
- ......
-
- Collections.sort(apps,
- new LauncherModel.ShortcutNameComparator(packageManager, mLabelCache));
- ......
- }
-
- ......
-
- startIndex = i;
-
- for (int j=0; i<N && j<batchSize; j++) {
-
- mAllAppsList.add(new ApplicationInfo(packageManager, apps.get(i),
- mIconCache, mLabelCache));
- i++;
- }
-
-
- final boolean first = i <= batchSize;
- final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
- final ArrayList<ApplicationInfo> added = mAllAppsList.added;
-
-
- mAllAppsList.added = new ArrayList<ApplicationInfo>();
-
- mHandler.post(new Runnable() {
- public void run() {
- ......
-
- if (callbacks != null) {
- if (first) {
- callbacks.bindAllApplications(added);
- } else {
- callbacks.bindAppsAdded(added);
- }
- ......
- } else {
- ......
- }
- }
- });
- ......
- }
- ......
- }
过程还是挺简单的,首先当然是查询所有的App了,通过向PackagedManager发送指定的Intent就能够获得安装好的应用的信息。查
询完毕之后,将数据封装到ArrayList<ApplicationInfo>对象中,然后通过Callbacks.bindAllApplication()或Callbacks.bindAppsAdded()
将数据传给Launcher。Launcher中的操作也比加载Workspace时简单多,毕竟这里只需要加载Icon。
-
-
-
-
-
- public void bindAllApplications(final ArrayList<ApplicationInfo> apps) {
- ......
-
-
-
- mAppsCustomizeTabHost.post(new Runnable() {
- public void run() {
- if (mAppsCustomizeContent != null) {
- mAppsCustomizeContent.setApps(apps);
- }
- }
- });
- }
-
-
-
-
-
- public void bindAppsAdded(ArrayList<ApplicationInfo> apps) {
- setLoadOnResume();
- ......
-
- if (mAppsCustomizeContent != null) {
- mAppsCustomizeContent.addApps(apps);
- }
- }
这样All Apps页面的加载也完成了。
到这一步,Launcher内容的加载过程也就完成了。
0 0