负责解析xml/device_profiles.xml配置文件,根据设备信息找到合适的配置信息,并保存到自身属性中,比如桌面图标的列数和行数,文件夹图标的列数和行数,图标、文字大小,default_workspace_%dx%d.xml配置文件
在LauncherAppState构造函数会被初始化
public LauncherAppState(Context context, @Nullable String iconCacheFileName) {
mContext = context;
//初始化InvariantDeviceProfile
mInvariantDeviceProfile = InvariantDeviceProfile.INSTANCE.get(context);
mIconProvider = new LauncherIconProvider(context);
mIconCache = new IconCache(mContext, mInvariantDeviceProfile,
iconCacheFileName, mIconProvider);
mModel = new LauncherModel(context, this, mIconCache, new AppFilter(mContext),
iconCacheFileName != null);
mOnTerminateCallback.add(mIconCache::close);
}
@TargetApi(23)
private InvariantDeviceProfile(Context context) {
String gridName = getCurrentGridName(context);
String newGridName = initGrid(context, gridName);
if (!newGridName.equals(gridName)) {
LauncherPrefs.getPrefs(context).edit().putString(KEY_IDP_GRID_NAME, newGridName)
.apply();
Log.d("b/258560494", "InvariantDeviceProfile - setting newGridName: " + newGridName
+ ", gridName: " + gridName);
}
//解析完配置文件,找到合适的配置,写入到本地的sp中
new DeviceGridState(this).writeToPrefs(context);
DisplayController.INSTANCE.get(context).setPriorityListener(
(displayContext, info, flags) -> {
if ((flags & (CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS
| CHANGE_NAVIGATION_MODE)) != 0) {
onConfigChanged(displayContext);
}
});
}
public static String getCurrentGridName(Context context) {
return LauncherPrefs.getPrefs(context).getString(KEY_IDP_GRID_NAME, null);
}
private String initGrid(Context context, String gridName) {
Info displayInfo = DisplayController.INSTANCE.get(context).getInfo();
//获取设备类型,手机、平板
@DeviceType int deviceType = getDeviceType(displayInfo);
//解析device_profiles.xml文件,把解析后的数据存储在allOptions 中
ArrayList<DisplayOption> allOptions =
getPredefinedDeviceProfiles(context, gridName, deviceType,
RestoreDbTask.isPending(context));
//根据设备信息找到合适DisplayOption 配置
DisplayOption displayOption =
invDistWeightedInterpolate(displayInfo, allOptions, deviceType);
//把DisplayOption和GridOption相关的属性设置为全局变量,比如桌面可以显示几行几列图标
initGrid(context, displayInfo, displayOption, deviceType);
return displayOption.grid.name;
}
先看下device_profiles配置文件的写法
Launcher3/res/xml/device_profiles.xml
有grid-option和多个display-option标签
<profiles xmlns:launcher="http://schemas.android.com/apk/res-auto" >
...
<grid-option
launcher:name="5_by_5"
launcher:numRows="5"
launcher:numColumns="5"
launcher:numFolderRows="4"
launcher:numFolderColumns="4"
launcher:numHotseatIcons="5"
launcher:numExtendedHotseatIcons="6"
launcher:dbFile="launcher.db"
launcher:inlineNavButtonsEndSpacing="@dimen/taskbar_button_margin_split"
launcher:defaultLayoutId="@xml/default_workspace_5x5"
launcher:deviceCategory="phone|multi_display" >
<display-option
launcher:name="Large Phone"
launcher:minWidthDps="406"
launcher:minHeightDps="694"
launcher:iconImageSize="56"
launcher:iconTextSize="14.4"
launcher:allAppsBorderSpace="16"
launcher:allAppsCellHeight="104"
launcher:canBeDefault="true" />
<display-option
launcher:name="Large Phone Split Display"
launcher:minWidthDps="406"
launcher:minHeightDps="694"
launcher:iconImageSize="56"
launcher:iconTextSize="14.4"
launcher:allAppsBorderSpace="16"
launcher:allAppsCellHeight="104"
launcher:canBeDefault="true" />
<display-option
launcher:name="Shorter Stubby"
launcher:minWidthDps="255"
launcher:minHeightDps="400"
launcher:iconImageSize="48"
launcher:iconTextSize="13.0"
launcher:allAppsBorderSpace="16"
launcher:allAppsCellHeight="104"
launcher:canBeDefault="true" />
</grid-option>
...
</profiles>
getPredefinedDeviceProfiles(),解析配置文件
private static ArrayList<DisplayOption> getPredefinedDeviceProfiles(Context context,
String gridName, @DeviceType int deviceType, boolean allowDisabledGrid) {
ArrayList<DisplayOption> profiles = new ArrayList<>();
//xml文件解析
try (XmlResourceParser parser = context.getResources().getXml(R.xml.device_profiles)) {
final int depth = parser.getDepth();
int type;
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
//GridOption.TAG_NAME是grid-option
if ((type == XmlPullParser.START_TAG)
&& GridOption.TAG_NAME.equals(parser.getName())) {
//grid-option的属性交由GridOption处理
GridOption gridOption = new GridOption(context, Xml.asAttributeSet(parser),
deviceType);
if (gridOption.isEnabled || allowDisabledGrid) {
final int displayDepth = parser.getDepth();
while (((type = parser.next()) != XmlPullParser.END_TAG
|| parser.getDepth() > displayDepth)
&& type != XmlPullParser.END_DOCUMENT) {
//解析display-option标签
if ((type == XmlPullParser.START_TAG) && "display-option".equals(
parser.getName())) {
//DisplayOption获取display-option标签的属性值
//grid-option标签的属性值封装到GridOption,并赋值给DisplayOption
profiles.add(new DisplayOption(gridOption, context,
Xml.asAttributeSet(parser)));
}
}
}
}
}
} catch (IOException | XmlPullParserException e) {
throw new RuntimeException(e);
}
//比较参数gridName和配置文件中的是否一样
ArrayList<DisplayOption> filteredProfiles = new ArrayList<>();
if (!TextUtils.isEmpty(gridName)) {
for (DisplayOption option : profiles) {
if (gridName.equals(option.grid.name)
&& (option.grid.isEnabled || allowDisabledGrid)) {
filteredProfiles.add(option);
}
}
}
if (filteredProfiles.isEmpty()) {
if (gridName != null) {
Log.d("b/258560494", "No matching grid from for gridName: " + gridName
+ ", deviceType: " + deviceType);
}
// No grid found, use the default options
for (DisplayOption option : profiles) {
if (option.canBeDefault) {
filteredProfiles.add(option);
}
}
}
if (filteredProfiles.isEmpty()) {
throw new RuntimeException("No display option with canBeDefault=true");
}
return filteredProfiles;
}
GridOption
public GridOption(Context context, AttributeSet attrs, @DeviceType int deviceType) {
TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.GridDisplayOption);
name = a.getString(R.styleable.GridDisplayOption_name);
//多少行
numRows = a.getInt(R.styleable.GridDisplayOption_numRows, 0);
//多少列
numColumns = a.getInt(R.styleable.GridDisplayOption_numColumns, 0);
//数据库名称,上面的是launcher.db
dbFile = a.getString(R.styleable.GridDisplayOption_dbFile);
//
defaultLayoutId = a.getResourceId(
R.styleable.GridDisplayOption_defaultLayoutId, 0);
//应用图标列表列数
numAllAppsColumns = a.getInt(
R.styleable.GridDisplayOption_numAllAppsColumns, numColumns);
//Hotseat放置图标数量
numHotseatIcons = a.getInt(
R.styleable.GridDisplayOption_numHotseatIcons, numColumns);
//文件夹行数
numFolderRows = a.getInt(
R.styleable.GridDisplayOption_numFolderRows, numRows);
//文件夹列数
numFolderColumns = a.getInt(
R.styleable.GridDisplayOption_numFolderColumns, numColumns);
...
}
DisplayOption同理
initGrid
public int numRows;
public int numColumns;
public int numSearchContainerColumns;
/**
* Number of icons per row and column in the folder.
*/
public int numFolderRows;
public int numFolderColumns;
...
private void initGrid(Context context, Info displayInfo, DisplayOption displayOption,
@DeviceType int deviceType) {
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
GridOption closestProfile = displayOption.grid;
numRows = closestProfile.numRows;
numColumns = closestProfile.numColumns;
numSearchContainerColumns = closestProfile.numSearchContainerColumns;
dbFile = closestProfile.dbFile;
defaultLayoutId = closestProfile.defaultLayoutId;
demoModeLayoutId = closestProfile.demoModeLayoutId;
numFolderRows = closestProfile.numFolderRows;
numFolderColumns = closestProfile.numFolderColumns;
folderStyle = closestProfile.folderStyle;
isScalable = closestProfile.isScalable;
devicePaddingId = closestProfile.devicePaddingId;
this.deviceType = deviceType;
inlineNavButtonsEndSpacing = closestProfile.inlineNavButtonsEndSpacing;
mExtraAttrs = closestProfile.extraAttrs;
iconSize = displayOption.iconSizes;
float maxIconSize = iconSize[0];
for (int i = 1; i < iconSize.length; i++) {
maxIconSize = Math.max(maxIconSize, iconSize[i]);
}
iconBitmapSize = ResourceUtils.pxFromDp(maxIconSize, metrics);
fillResIconDpi = getLauncherIconDensity(iconBitmapSize);
iconTextSize = displayOption.textSizes;
minCellSize = displayOption.minCellSize;
borderSpaces = displayOption.borderSpaces;
horizontalMargin = displayOption.horizontalMargin;
numShownHotseatIcons = closestProfile.numHotseatIcons;
numDatabaseHotseatIcons = deviceType == TYPE_MULTI_DISPLAY
? closestProfile.numDatabaseHotseatIcons : closestProfile.numHotseatIcons;
hotseatColumnSpan = closestProfile.hotseatColumnSpan;
hotseatBarBottomSpace = displayOption.hotseatBarBottomSpace;
hotseatQsbSpace = displayOption.hotseatQsbSpace;
numAllAppsColumns = closestProfile.numAllAppsColumns;
numDatabaseAllAppsColumns = deviceType == TYPE_MULTI_DISPLAY
? closestProfile.numDatabaseAllAppsColumns : closestProfile.numAllAppsColumns;
allAppsCellSize = displayOption.allAppsCellSize;
allAppsBorderSpaces = displayOption.allAppsBorderSpaces;
allAppsIconSize = displayOption.allAppsIconSizes;
allAppsIconTextSize = displayOption.allAppsIconTextSizes;
inlineQsb = closestProfile.inlineQsb;
// If the partner customization apk contains any grid overrides, apply them
// Supported overrides: numRows, numColumns, iconSize
applyPartnerDeviceProfileOverrides(context, metrics);
final List<DeviceProfile> localSupportedProfiles = new ArrayList<>();
defaultWallpaperSize = new Point(displayInfo.currentSize);
SparseArray<DotRenderer> dotRendererCache = new SparseArray<>();
for (WindowBounds bounds : displayInfo.supportedBounds) {
localSupportedProfiles.add(new DeviceProfile.Builder(context, this, displayInfo)
.setIsMultiDisplay(deviceType == TYPE_MULTI_DISPLAY)
.setWindowBounds(bounds)
.setDotRendererCache(dotRendererCache)
.build());
// Wallpaper size should be the maximum of the all possible sizes Launcher expects
int displayWidth = bounds.bounds.width();
int displayHeight = bounds.bounds.height();
defaultWallpaperSize.y = Math.max(defaultWallpaperSize.y, displayHeight);
// We need to ensure that there is enough extra space in the wallpaper
// for the intended parallax effects
float parallaxFactor =
dpiFromPx(Math.min(displayWidth, displayHeight), displayInfo.getDensityDpi())
< 720
? 2
: wallpaperTravelToScreenWidthRatio(displayWidth, displayHeight);
defaultWallpaperSize.x =
Math.max(defaultWallpaperSize.x, Math.round(parallaxFactor * displayWidth));
}
supportedProfiles = Collections.unmodifiableList(localSupportedProfiles);
int numMinShownHotseatIconsForTablet = supportedProfiles
.stream()
.filter(deviceProfile -> deviceProfile.isTablet)
.mapToInt(deviceProfile -> deviceProfile.numShownHotseatIcons)
.min()
.orElse(0);
supportedProfiles
.stream()
.filter(deviceProfile -> deviceProfile.isTablet)
.forEach(deviceProfile -> {
deviceProfile.numShownHotseatIcons = numMinShownHotseatIconsForTablet;
deviceProfile.recalculateHotseatWidthAndBorderSpace();
});
ComponentName cn = new ComponentName(context.getPackageName(), getClass().getName());
defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
}