Launcher3的数据加载过程涉及到了两个主要的类LauncherProvider和LauncherModel。
LauncherProvider
LauncherProvider继承自ContentProvider类,内部基于数据库存储实现了ContentProvider的CRUD接口,这个类主要用于更新数据库的数据。LauncherProvider内部维护了两张数据表,favorites(用于存储workspace上的内容信息)和workspaceScreens(用于存储screen的排序和页数的信息)。
favorites表的创建语句:
db.execSQL("CREATE TABLE favorites (" +
"_id INTEGER PRIMARY KEY," + // id
"title TEXT," + // 名字
"intent TEXT," + // intent的字符串描述
"container INTEGER," + // 容器类型,desktop或者hotseat
"screen INTEGER," + // 所在的屏幕号
"cellX INTEGER," + // item左上方所在的格子x位置
"cellY INTEGER," + // item左上方所在的格子y位置
"spanX INTEGER," + // item横向占用的格子数
"spanY INTEGER," + // item纵向占用的格子数
"itemType INTEGER," + // item的类型,app,widget,folder
"appWidgetId INTEGER NOT NULL DEFAULT -1," + // widget的id
"isShortcut INTEGER," + // 是否是快捷方式
"iconType INTEGER," + ...
"iconPackage TEXT," +
"iconResource TEXT," +
"icon BLOB," +
"uri TEXT," +
"displayMode INTEGER," +
"appWidgetProvider TEXT," +
"modified INTEGER NOT NULL DEFAULT 0" +
");");
favorites用于存储workspace上的内容信息,它存储了内容的类型和所在的位置等信息。
workspaceScreens表的创建语句:
db.execSQL("CREATE TABLE " + TABLE_WORKSPACE_SCREENS + " (" +
LauncherSettings.WorkspaceScreens._ID + " INTEGER," +
LauncherSettings.WorkspaceScreens.SCREEN_RANK + " INTEGER," +
LauncherSettings.ChangeLogColumns.MODIFIED + " INTEGER NOT NULL DEFAULT 0" +
");");
LauncherModel
当从数据库获取了数据后,需要一个数据对象在内存中维护这些信息。Launcher3中定义了一系列的数据类,如下图所示:
AppInfo:代表所有应用界面的一个app
FolderInfo:代表一个文件夹
LauncherAppWidgetInfo:代表一个widget
ShortcutInfo:代表workspace和folder里的app
LauncherModel用于维护内存中的数据,通过ContentResolver来更新LauncherProvider的数据,即数据库的数据。LauncherModel内部有个继承自Runnable的LoaderTask引用,Launcher的数据加载就是在这个类中执行,当调用LauncherModel.startLoader()时就会执行LoaderTask.run()方法,开始数据的加载工作。
LauncherModel内部维护了几个列表,用于存储从数据库获取的数据:
// sBgItemsIdMap存储了所有的ItemInfos (shortcuts, folders, and widgets),以item的id作为key
static final HashMap<Long, ItemInfo> sBgItemsIdMap = new HashMap<Long, ItemInfo>();
// sBgWorkspaceItems将作为参数传递给bindItems, 存储了在workspace上显示的所有app和folder信息
static final ArrayList<ItemInfo> sBgWorkspaceItems = new ArrayList<ItemInfo>();
// sBgAppWidgets存储了workspace上的widget信息,作为参数传递给bindAppWidget()
static final ArrayList<LauncherAppWidgetInfo> sBgAppWidgets = new ArrayList<LauncherAppWidgetInfo>();
// sBgFolders存储了workspace上的folder信息,作为参数传递给bindFolders()
static final HashMap<Long, FolderInfo> sBgFolders = new HashMap<Long, FolderInfo>();
// sBgDbIconCache存储了在数据库维护的item的图片
static final HashMap<Object, byte[]> sBgDbIconCache = new HashMap<Object, byte[]>();
// sBgWorkspaceScreens按顺序存储了workspace上的屏幕
static final ArrayList<Long> sBgWorkspaceScreens = new ArrayList<Long>();
LauncherModel的结构图:
Workspace数据的加载过程
Launcher3的数据加载分为两部分,一个是Workspace页面的数据:Launcher最主要的界面,用户可自由编辑内容(widget,app, folder,screen),第二部分是所有应用界面。下面我们先来看Workspace的数据加载过程,通过在LoaderTask.run()方法里调用loadAndBindWorkspace()开始整个过程,下面是具体代码:
/** Returns whether this is an upgrade path */
private boolean loadAndBindWorkspace() {
mIsLoadingAndBindingWorkspace = true;
boolean isUpgradePath = false;
if (!mWorkspaceLoaded) {
// Load the workspace
isUpgradePath = loadWorkspace();
synchronized (LoaderTask.this) {
if (mStopped) {
return isUpgradePath;
}
mWorkspaceLoaded = true;
}
}
// Bind the workspace
bindWorkspace(-1, isUpgradePath);
return isUpgradePath;
}
整个过程分为两步,load和bind,完成第一步后会判断任务是否终止,如果是则不继续下一步。代码中用到了synchronized块,因为这个过程涉及到多线程处理。我们先看loadWorkspace(),也就是workspace的数据加载过程,以下是大体流程:
1. 如果数据库为空,先加载xml的数据
加载时,首先会判断数据库的类容是否为空,如果是,会先从一个xml文件中加载workspace的类容,Launcher3的默认xml文件路径为res/xml/default_workspace.xml,里面定义了默认的app和widget信息,这个过程由loadFavorates()实现。
默认xml的内容如下:
<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
<!-- Far-left screen [0] -->
<!-- Left screen [1] -->
<appwidget
launcher:packageName="com.android.settings"
launcher:className="com.android.settings.widget.SettingsAppWidgetProvider"
launcher:screen="1"
launcher:x="0"
launcher:y="3"
launcher:spanX="4"
launcher:spanY="1" />
<!-- Right screen [3] -->
<favorite
launcher:packa