Launcher3的布局加载流程
一、配置好InvariantDeviceProfile列表信息
Launcher.java
public void onCreate() {
LauncherAppState app = LauncherAppState.getInstance();
}
LauncherAppState.java
private LauncherAppState() {
mInvariantDeviceProfile = new InvariantDeviceProfile(sContext);
InvariantDeviceProfile.java
public InvariantDeviceProfile(Context context) {
//closestProfiles 和 interpolatedDeviceProfileOut是什么?
//1.getPredefinedDeviceProfiles--获取所有的profile信息
//2.findClosestDeviceProfiles---找到最适合的配置信息,计算后获取的第一个最接近
ArrayList<InvariantDeviceProfile> closestProfiles = findClosestDeviceProfiles(
minWidthDps, minHeightDps, getPredefinedDeviceProfiles(context));
InvariantDeviceProfile closestProfile = closestProfiles.get(0);
InvariantDeviceProfile interpolatedDeviceProfileOut =
invDistWeightedInterpolate(minWidthDps, minHeightDps, closestProfiles);
//通过closestProfile拿到桌面的一系列配置,行、hotseat个数等
numRows = closestProfile.numRows;
numColumns = closestProfile.numColumns;
numHotseatIcons = closestProfile.numHotseatIcons;
numFolderRows = closestProfile.numFolderRows;
numFolderColumns = closestProfile.numFolderColumns;
//通过interpolatedDeviceProfileOut拿到图标大小等
iconSize = interpolatedDeviceProfileOut.iconSize;
iconTextSize = interpolatedDeviceProfileOut.iconTextSize;
}
ArrayList<InvariantDeviceProfile> getPredefinedDeviceProfiles(Context context) {
ArrayList<InvariantDeviceProfile> profiles = new ArrayList<>();
try (XmlResourceParser parser = context.getResources().getXml(R.xml.device_profiles)) {
final int depth = parser.getDepth();
//解析device_profiles,里面有各种分辨率的profile,配置名称,宽高,行数,列数等等
}
return profiles;
}
device_profiles.xml中的一部分
<profile
launcher:name="Nexus 4"
launcher:minWidthDps="359"
launcher:minHeightDps="567"
launcher:numRows="4"
launcher:numColumns="4"
launcher:numFolderRows="4"
launcher:numFolderColumns="4"
launcher:iconSize="54"
launcher:iconTextSize="13.0"
launcher:numHotseatIcons="5"
launcher:defaultLayoutId="@xml/default_workspace_4x4"
/>
// width代表设备的宽;minWidthDps代表profile中宽
ArrayList<InvariantDeviceProfile> findClosestDeviceProfiles(
final float width, final float height, ArrayList<InvariantDeviceProfile> points) {
// Sort the profiles by their closeness to the dimensions
ArrayList<InvariantDeviceProfile> pointsByNearness = points;
Collections.sort(pointsByNearness, new Comparator<InvariantDeviceProfile>() {
public int compare(InvariantDeviceProfile a, InvariantDeviceProfile b) {
return Float.compare(dist(width, height, a.minWidthDps, a.minHeightDps),
dist(width, height, b.minWidthDps, b.minHeightDps));
}
});
return pointsByNearness;
}
//计算方法:hypot表示先求所有参数的和,再返回该和的平方跟
//1.(minWidthDps-width)的平方+(minHeightDps-height)的平方 =和
//2.在 求该和的平方跟(类似三角形斜边)
@Thunk float dist(float x0, float y0, float x1, float y1) {
return (float) Math.hypot(x1 - x0, y1 - y0);
}
二、加载流程
LoadTask.java
public void run() {
//1.加载数据
loadWorkspace();
//2.绑定数据
mResults.bindWorkspace();
}
private void loadWorkspace() {
Log.d(TAG, "loadWorkspace: loading default favorites");
LauncherSettings.Settings.call(contentResolver,
LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES); //"load_default_favorites"
//2.从数据库中读取配置文件信息
final LoaderCursor c = new LoaderCursor(contentResolver.query(
LauncherSettings.Favorites.CONTENT_URI, null, null, null, null), mApp);
//3.根据itemtype,走不同的case
ShortcutInfo info;
LauncherAppWidgetInfo appWidgetInfo;
switch (c.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
c.checkAndAddItem(info, mBgDataModel);
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
c.checkAndAddItem(folderInfo, mBgDataModel);
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
c.checkAndAddItem(appWidgetInfo, mBgDataModel);
}
LauncherProvider.java
case LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES: {
loadDefaultFavoritesIfNecessary();
return null;
}
LauncherProvider.java
/**
* Loads the default workspace based on the following priority scheme:
* 1) From the app restrictions
* 2) From a package provided by play store
* 3) From a partner configuration APK, already in the system image
* 4) The default configuration for the particular device
*/
synchronized private void loadDefaultFavoritesIfNecessary() {
SharedPreferences sp = Utilities.getPrefs(getContext());
if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) {
Log.d(TAG, "loading default workspace");
//1.应用约束
AppWidgetHost widgetHost = mOpenHelper.newLauncherWidgetHost();
AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction(widgetHost);
//2.从带有 android.autoinstalls.config.action.PLAY_AUTO_INSTALL Action的应用里获取workspace默认配置资源
if (loader == null) {
loader = AutoInstallsLayout.get(getContext(),widgetHost, mOpenHelper);
}
//3.从系统内置的partner应用里获取workspace默认配置
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(), widgetHost,
mOpenHelper, partnerRes, workspaceResId);
}
}
}
//4.调用getDefaultLayoutParser() 获取配置的InvariantDeviceProfile资源,默认会执行第四步
final boolean usingExternallyProvidedLayout = loader != null;
if (loader == null) {
loader = getDefaultLayoutParser(widgetHost);
}
//然后创建数据库,建表favorites和workspaceScrenns,加载数据
mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
// 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.
mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
//最后加载桌面资源
mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(),
getDefaultLayoutParser(widgetHost));
}
clearFlagEmptyDbCreated();
}
}
LauncherProvider.java
private DefaultLayoutParser getDefaultLayoutParser(AppWidgetHost widgetHost) {
InvariantDeviceProfile idp = LauncherAppState.getIDP(getContext());
int defaultLayout = idp.defaultLayoutId;
return new DefaultLayoutParser(getContext(), widgetHost,
mOpenHelper, getContext().getResources(), defaultLayout);
}
LauncherAppState.java
public static InvariantDeviceProfile getIDP(Context context) {
return LauncherAppState.getInstance(context).getInvariantDeviceProfile(); //设备信息
}
LauncherAppState.java
public InvariantDeviceProfile getInvariantDeviceProfile() {
return mInvariantDeviceProfile; //回到launcherAppState的构造方法就看见了
}
三、加载桌面数据资源配置
接上面的mOpenHelper.loadFavorites
LauncherProvider.java 中的内部类 DatabaseHelper.java
@Thunk int loadFavorites(SQLiteDatabase db, AutoInstallsLayout loader) {
ArrayList<Long> screenIds = new ArrayList<Long>();
//通过loadlayout解析布局信息
int count = loader.loadLayout(db, screenIds);
// Add the screens specified by the items above
Collections.sort(screenIds);
int rank = 0;
ContentValues values = new ContentValues();
for (Long id : screenIds) {
values.clear();
values.put(LauncherSettings.WorkspaceScreens._ID, id);
values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, rank);
//保存到数据库
if (dbInsertAndCheck(this, db, WorkspaceScreens.TABLE_NAME, null, values) < 0) {
throw new RuntimeException("Failed initialize screen table"+ "from default layout");
}
rank++;
}
return count;
}
AutoInstallsLayout.java
public int loadLayout(SQLiteDatabase db, ArrayList<Long> screenIds) {
return parseLayout(mLayoutId, screenIds);
}
AutoInstallsLayout.java
protected int parseLayout(int layoutId, ArrayList<Long> screenIds) throws XmlPullParserException, IOException {
//通过XmlResourceParser解析xml
XmlResourceParser parser = mSourceRes.getXml(layoutId);
//我们使用的是AutoInstallsLayout的子类DefaultLayoutParser,解析到信息后会保存到对应的数据库中。
ArrayMap<String, TagParser> tagParserMap = getLayoutElementsMap();
int count = 0;
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
count += parseAndAddNode(parser, tagParserMap, screenIds);
}
//返回解析的个数
return count;
}
AutoInstallsLayout.java
//不同的标签通过不同的解析对象处理
protected ArrayMap<String, TagParser> getLayoutElementsMap() {
ArrayMap<String, TagParser> parsers = new ArrayMap<>();
parsers.put(TAG_APP_ICON, new AppShortcutParser());
parsers.put(TAG_AUTO_INSTALL, new AutoInstallParser());
parsers.put(TAG_FOLDER, new FolderParser()); //文件夹解析器
parsers.put(TAG_APPWIDGET, new PendingWidgetParser());
parsers.put(TAG_SHORTCUT, new ShortcutParser(mSourceRes));
return parsers;
}
LauncherProvider.java 中的内部类 DatabaseHelper.java
@Thunk static long dbInsertAndCheck(DatabaseHelper helper,
SQLiteDatabase db, String table, String nullColumnHack, ContentValues values) {
helper.checkId(table, values);
return db.insert(table, nullColumnHack, values);
}
四、介绍默认的配置文件
default_workspace_4x4.xml
<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto">
<!-- Hotseat --> //规则一样
<include launcher:workspace="@xml/dw_phone_hotseat" />
<!-- Bottom row -->
<resolve //通过ResolveParser解析,包含内嵌标签
launcher:screen="0" //第0屏
launcher:x="0" //坐标x
launcher:y="-1" > //坐标y
<favorite launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_EMAIL;end" /> //favorite 一个app的信息,包含uri,或包名类名
<favorite launcher:uri="mailto:" />
</resolve>
</favorites>
五、bind worksapce 数据
以上loadworkspace就差不多了,接下来就该bind了,从上面的loadWorkSpace方法中知道,它把所有的数据都保存在了mBgDataModel里面。
LoaderResul.java
public void bindWorkspace() {
// Save a copy of all the bg-thread collections,这里不是遍历而是copy,避免后续的某个线程修改全局变量影响到其他的工作线程。
ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
final ArrayList<Long> orderedScreenIds = new ArrayList<>();
//1.把数据保存到集合中
synchronized (mBgDataModel) {
workspaceItems.addAll(mBgDataModel.workspaceItems);
appWidgets.addAll(mBgDataModel.appWidgets);
orderedScreenIds.addAll(mBgDataModel.workspaceScreens);
mBgDataModel.lastBindId++;
}
//2.filter和sort,将当前需要加载的页的数据,按screenid排序,保存在新的集合里
filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
otherWorkspaceItems);
filterCurrentWorkspaceItems(currentScreenId, appWidgets, currentAppWidgets,
otherAppWidgets);
sortWorkspaceItemsSpatially(currentWorkspaceItems);
sortWorkspaceItemsSpatially(otherWorkspaceItems);
callbacks.startBinding(); //改变workspace的状态,移除一些旧的View和数据
callbacks.bindScreens(orderedScreenIds); //1.bind screen
// Load items on the current page. //2.bind workspace和widgetbind
bindWorkspaceItems(currentWorkspaceItems, currentAppWidgets, mainExecutor);
//3.上述是bind 当前workspace,然后就是bind other Workspace,与上面的流程差不多一样
Launcher.java
public void startBinding() {
setWorkspaceLoading(true);
// Clear the workspace because it's going to be rebound
mWorkspace.clearDropTargets();
mWorkspace.removeAllWorkspaceScreens();
mAppWidgetHost.clearViews();
}
Launcher.java
public void bindScreens(ArrayList<Long> orderedScreenIds) {
bindAddScreens(orderedScreenIds);
}
Launcher.java
private void bindAddScreens(ArrayList<Long> orderedScreenIds) { //1.bind screen
int count = orderedScreenIds.size();
for (int i = 0; i < count; i++) {
long screenId = orderedScreenIds.get(i);
if (!FeatureFlags.QSB_ON_FIRST_SCREEN || screenId != Workspace.FIRST_SCREEN_ID) {
//根据screenid,调用此方法创建相对应的celllayout,并添加到workspace
mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(screenId);
}
}
}
WorkSpace.java
public void insertNewWorkspaceScreenBeforeEmptyScreen(long screenId) {
insertNewWorkspaceScreen(screenId, insertIndex);
}
WorkSpace.java
public CellLayout insertNewWorkspaceScreen(long screenId, int insertIndex) {
CellLayout newScreen = (CellLayout) LayoutInflater.from(getContext()).inflate(
R.layout.workspace_screen, this, false /* attachToRoot */);
mWorkspaceScreens.put(screenId, newScreen);
mScreenOrder.add(insertIndex, screenId);
addView(newScreen, insertIndex);
return newScreen;
}
LoaderResult.java
//2.bind workspace和widget
private void bindWorkspaceItems(final ArrayList<ItemInfo> workspaceItems,
final ArrayList<LauncherAppWidgetInfo> appWidgets,
final Executor executor) {
// Bind the workspace items
int N = workspaceItems.size();
for (int i = 0; i < N; i += ITEMS_CHUNK) {
callbacks.bindItems(workspaceItems.subList(start, start+chunkSize), false);
};
// Bind the widgets, one at a time
N = appWidgets.size();
for (int i = 0; i < N; i++) {
callbacks.bindItems(Collections.singletonList(widget), false);
};
}
Launcher.java
@Override
public void bindItems(final List<ItemInfo> items, final boolean forceAnimateIcons) {
for (int i = 0; i < end; i++) {
final ItemInfo item = items.get(i);
switch (item.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
ShortcutInfo info = (ShortcutInfo) item;
view = createShortcut(info);
break;
}
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: {
view = FolderIcon.fromXml(R.layout.folder_icon, this,
(ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
(FolderInfo) item);
break;
}
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: {
view = inflateAppWidget((LauncherAppWidgetInfo) item);
break;
}
}
workspace.requestLayout();
}
六、App的load与bind
上回分析了loadWorkspace和bindWorkspace,接着继续
LoaderTask.java
public void run() {
//1.加载数据
loadWorkspace();
//2.绑定数据
mResults.bindWorkspace();
//3.
loadAllApps();
//4.
mResults.bindAllApps();
}
LoaderTask.java
private void loadAllApps() {
final List<UserHandle> profiles = mUserManager.getUserProfiles();
// Clear the list of apps
mBgAllAppsList.clear();
for (UserHandle user : profiles) {
//1.拿到所有app
final List<LauncherActivityInfo> apps = mLauncherApps.getActivityList(null, user);
boolean quietMode = mUserManager.isQuietModeEnabled(user);
// Create the ApplicationInfos
for (int i = 0; i < apps.size(); i++) {
LauncherActivityInfo app = apps.get(i);
// 2.遍历添加到AllAppsList
mBgAllAppsList.add(new AppInfo(app, user, quietMode), app);
}
}
}
LoaderResults.java
public void bindAllApps() {
// shallow copy
@SuppressWarnings("unchecked")
final ArrayList<AppInfo> list = (ArrayList<AppInfo>) mBgAllAppsList.data.clone();
Runnable r = new Runnable() {
public void run() {
Callbacks callbacks = mCallbacks.get();
if (callbacks != null) {
//与workspace的bind一样,调launcher的回调
callbacks.bindAllApplications(list);
}
}
};
mUiExecutor.execute(r);
}
Launcher.java
public void bindAllApplications(ArrayList<AppInfo> apps) {
mAppsView.getAppsStore().setApps(apps);
}
参考文章:
https://www.cnblogs.com/yangjies145/p/14342541.html