努力尽今夕,少年犹可夸。这篇文章主要讲述android7.x Launcher3源码解析---workspace和allapps加载流程相关的知识,希望能为你提供帮助。
Launcher系列目录:
一、android7.x Launcher3源码解析(
1)
—启动流程
二、android7.x Launcher3源码解析(
2)
—框架结构
三、android7.x Launcher3源码解析(
3)
—workspace和allapps加载流程
前两篇博客分别对Lancher的启动和Launcher的框架结构进行了一些分析,
这一篇,
将着重开始分析界面的加载流程。
1、整体流程 先上一张整体的流程图吧。(
图片看不清可以下载下来看或者右击新开个页面查看图片)
文章图片
先从Launcher.java的onCreate方法开始,
protected void onCreate(Bundle savedInstanceState) {
......
//建立LauncherAppState对象
LauncherAppState.setApplicationContext(getApplicationContext());
LauncherAppState app =
LauncherAppState.getInstance();
......
//建立LauncherModel对象
mModel =
app.setLauncher(this);
//一些其他对象初始化
......
setContentView(R.layout.launcher);
setupViews();
if (!mRestoring) {
if (DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE) {
// If the user leaves launcher, then we should just load items asynchronously when
// they return.
mModel.startLoader(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(mWorkspace.getRestorePage());
}
}
......
}
重点调用了LauncherModel的startLoader的方法, startLoader里面, 最重要的就是启动了LoaderTask,
mLoaderTask =
new LoaderTask(mApp.getContext(), synchronousBindPage);
我们接着分析LoaderTask的run方法。
public void run() {
......
keep_running: {
if (DEBUG_LOADERS) Log.d(TAG, "
step 1: loading workspace"
);
loadAndBindWorkspace();
if (mStopped) {
break keep_running;
}waitForIdle();
// second step
if (DEBUG_LOADERS) Log.d(TAG, "
step 2: loading all apps"
);
loadAndBindAllApps();
waitForIdle();
// third step
if (DEBUG_LOADERS) Log.d(TAG, "
step 3: loading deep shortcuts"
);
loadAndBindDeepShortcuts();
}......
}
这里一共就几步,
loadAndBindWorkspace
–>
waitForIdle()
—>
loadAndBindAllApps()
—>
waitForIdle()
—>
loadAndBindDeepShortcuts()
3步加载流程里面都穿插了waitForIdle, 这个方法是干嘛的呢?
private void waitForIdle() {
......
synchronized (LoaderTask.this) {
final long workspaceWaitTime =
DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
mHandler.postIdle(new Runnable() {
public void run() {
synchronized (LoaderTask.this) {
mLoadAndBindStepFinished =
true;
if (DEBUG_LOADERS) {
Log.d(TAG, "
done with previous binding step"
);
}
LoaderTask.this.notify();
}
}
});
while (!mStopped &
&
!mLoadAndBindStepFinished) {
try {
// Just in case mFlushingWorkerThread changes but we aren'
t woken up,
// wait no longer than 1sec at a time
this.wait(1000);
} catch (InterruptedException ex) {
// Ignore
}
}
......
}
}
load数据时我们是去UI线程中处理的,UI线程正常情况下是不能阻塞的, 否则有可能产生ANR, 这将严重影响用户体验。所有这里LoaderTask在将结果发送给UI线程之后, 为了保证界面绑定任务可以高效的完成, 往往会将自己的任务暂停下来, 等待UI线程处理完成。
分析下这个方法:
首先, 创建一个UI线程闲时执行的任务, 这个任务负责设置某些关键的控制标志, 并将其通过PostIdle方法加入处理器的消息队列中。一旦任务得到执行, 就会将mLoadAndBindStepFinished 置为true, 以控制即将来临的有条件的无限等待。 最后 设置一个有条件的无限等待, 等待来自UI线程的指示。
2、workspace的加载流程 从总流程图上可以看到, workspace的加载流程主要分为
loadWorkspace();
和bindWorkspace(mPageToBindFirst);
a、loadWorkspace() loadWorkspace()的代码实在是太多了, 这里就不全部贴出来了, 主要功能就是负责从数据库表中读取数据并转译为Launcher桌面项的数据结构。
以下为步骤:
1、进行一些预处理
2、加载默认值
LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary()
之前Launcher2的
loadDefaultFavoritesIfNecessary
这个方法,
是这样加载默认布局的:
workspaceResId =
sp.getInt(DEFAULT_WORKSPACE_RESOURCE_ID, R.xml.default_workspace);
但是Launcher3中, 是这样加载的:
int workspaceResId =
partnerRes.getIdentifier(Partner.RES_DEFAULT_LAYOUT,
"
xml"
, partner.getPackageName());
这个地方, 我暂时还没理解, 到底默认布局是哪个?
【android7.x Launcher3源码解析---workspace和allapps加载流程】3、初始化数据
清空之前的内存数据
/** Clears all the sBg data structures */
private void clearSBgDataStructures() {
synchronized (sBgLock) {
sBgWorkspaceItems.clear();
sBgAppWidgets.clear();
sBgFolders.clear();
sBgItemsIdMap.clear();
sBgWorkspaceScreens.clear();
}
}
4、查询ContentProvider, 返回favorites表的结果集
final HashMap<
String, Integer>
installingPkgs =
PackageInstallerCompat
.getInstance(mContext).updateAndGetActiveSessionCache();
final ArrayList<
Long>
itemsToRemove =
new ArrayList<
Long>
();
final ArrayList<
Long>
restoredRows =
new ArrayList<
Long>
();
final Uri contentUri =
LauncherSettings.Favorites.CONTENT_URI;
if (DEBUG_LOADERS) Log.d(TAG, "
loading model from "
+
contentUri);
final Cursor c =
contentResolver.query(contentUri, null, null, null, null);
5、根据不同的类型, 将数据保存到对应的arrayList中。
类型包含下面这几种:
ITEM_TYPE_APPLICATION
ITEM_TYPE_SHORTCUTITEM_TYPE_FOLDERITEM_TYPE_APPWIDGET
ITEM_TYPE_CUSTOM_APPWIDGET
根据以上类型, 将数据保存到一下全局变量里面
sBgItemsIdMap, sBgWorkspaceItemsm, sBgAppWidgets, sBgFolders
另外, 如果有空的文件夹、空的屏幕, 也会delete掉, 最终, 把所有屏幕加载进全局变量sBgWorkspaceScreens中。
......
// Remove any empty folder
for (long folderId : LauncherAppState.getLauncherProvider()
.deleteEmptyFolders()) {
sBgWorkspaceItems.remove(sBgFolders.get(folderId));
sBgFolders.remove(folderId);
sBgItemsIdMap.remove(folderId);
}...... sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
// Remove any empty screens
ArrayList<
Long>
unusedScreens =
new ArrayList<
Long>
(sBgWorkspaceScreens);
for (ItemInfo item: sBgItemsIdMap) {
long screenId =
item.screenId;
if (item.container =
=
LauncherSettings.Favorites.CONTAINER_DESKTOP &
&
unusedScreens.contains(screenId)) {
unusedScreens.remove(screenId);
}
}// If there are any empty screens remove them, and update.
if (unusedScreens.size() !=
0) {
sBgWorkspaceScreens.removeAll(unusedScreens);
updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
}
b、bindWorkspace bindWorkspace的功能是将上面获取到的数据由Launcher显示出来。
步骤:
1、首先复制数据:
synchronized (sBgLock) {
workspaceItems.addAll(sBgWorkspaceItems);
appWidgets.addAll(sBgAppWidgets);
orderedScreenIds.addAll(sBgWorkspaceScreens);
folders =
sBgFolders.clone();
itemsIdMap =
sBgItemsIdMap.clone();
}
2、装载数据并排序
//装载桌面项数据
filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
otherWorkspaceItems);
//装载widget
filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets,
otherAppWidgets);
//装载文件夹
filterCurrentFolders(currentScreenId, itemsIdMap, folders, currentFolders,
otherFolders);
//排序
sortWorkspaceItemsSpatially(currentWorkspaceItems);
sortWorkspaceItemsSpatially(otherWorkspaceItems);
3、开始绑定
文章图片
就上个流程图吧。
3、应用程序apps的加载 加载apps的主要流程, 最上面那张流程图已经给出了, 如果所有app没有加载, 则
loadAllApps();
,
不然直接onlyBindAllApps();
1、loadAllApps( )
文章图片
根据代码, 画了下流程图, 但是我不明白userProfile是个什么鬼?
2、onlyBindAllApps() 这里的函数比绑定workspace简单多了, 直接通知Launcher绑定
Runnable r =
new Runnable() {
public void run() {
final long t =
SystemClock.uptimeMillis();
final Callbacks callbacks =
tryGetCallbacks(oldCallbacks);
if (callbacks !=
null) {
callbacks.bindAllApplications(list);
callbacks.bindAllPackages(widgetList);
}
if (DEBUG_LOADERS) {
Log.d(TAG, "
bound all "
+
list.size() +
"
apps from cache in "
+
(SystemClock.uptimeMillis()-t) +
"
ms"
);
}
}
};
看一下Launcher的
bindAllApplications
函数,
当然这个函数在loadAllApps函数里面也有,
就是如何绑定数据来显示呢?
Launcher.java的
bindAllApplications
函数里面会给AllAppsContainerView设置数据if (mAppsView !=
null) {
mAppsView.setApps(apps);
}
再跟代码到AllAppsContainerView,
public void setApps(List<
AppInfo>
apps) {
mApps.setApps(apps);
}
这个apps在哪里用到呢? 比较明显的就是onFinishInflate()函数,
.....
// Load the all apps recycler view
mAppsRecyclerView =
(AllAppsRecyclerView) findViewById(R.id.apps_list_view);
mAppsRecyclerView.setApps(mApps);
mAppsRecyclerView.setLayoutManager(mLayoutManager);
mAppsRecyclerView.setAdapter(mAdapter);
mAppsRecyclerView.setHasFixedSize(true);
.....
设置给了recyclerView, 很显然, Launcher的应用程序界面就是一个自定义的RecyclerView, 给这个recyclerview绑定的adpter是AllAppsGridAdapter, 看一下这个adpter的
onCreateViewHolder
函数,
很明显,
这里根据不同的类型加载了不同的布局(
应用程序界面有app和文件夹,
头上还有个搜索框,
用recyclerview的这个功能是最容易实现的)
,
关于Recyclerview如何可以根据不同的类型加载不同的布局,
可以参考我很久之前写的博客 RecyclerView的不同position加载不同View实现。好了, Launcher3的workspace和应用程序apps的加载流程就讲到这, 后面还会对Launcher里面的内容做具体的分析。
推荐阅读
- Android小部件Widget----全解析
- Android 使用PopupWindow实现弹出更多的菜单
- Android DataBinding库(MVVM设计模式)
- Android 关于java.util.NoSuchElementException错误
- Android控件介绍
- Android中Alarm的机制
- Android判断App是否在前台运行
- Android控件onClick事件
- mongodb与其它数据库的比较(它们有什么区别())