获取硬件参数是在InvariantDeviceProfile中进行的,确定布局参数是在DeviceProfile中进行的,本篇文章就对这个至关重要的一步进行详细分析
InvariantDeviceProfile构造方法
我们来看InvariantDeviceProfile的构造方法,构造方法一开始是获取是获取获取硬件参数
首先需要获取系统组件windowmanager、Display 、DisplayMetrics 。而后获取长和宽的px值通过屏幕密度切换成屏幕的实际物理尺寸。最终得到minWidthDps 和minHeightDps 这两个手机的实际长和宽。
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
DisplayMetrics dm = new DisplayMetrics();
display.getMetrics(dm);
Point smallestSize = new Point();
Point largestSize = new Point();
display.getCurrentSizeRange(smallestSize, largestSize);
// This guarantees that width < height
minWidthDps = Utilities.dpiFromPx(Math.min(smallestSize.x, smallestSize.y), dm);
minHeightDps = Utilities.dpiFromPx(Math.min(largestSize.x, largestSize.y), dm);
获取到手机的实际长宽以后就走到最关键的两个方法
ArrayList closestProfiles = findClosestDeviceProfiles(
minWidthDps, minHeightDps, getPredefinedDeviceProfiles(context));
InvariantDeviceProfile interpolatedDeviceProfileOut =
invDistWeightedInterpolate(minWidthDps, minHeightDps, closestProfiles);
这两个方法的参数都传入了手机的实际宽高minWidthDps, minHeightDps,不同的是在findClosestDeviceProfiles方法中调用了getPredefinedDeviceProfiles(context)这个方法。
其实这个方法主要做的事情就是解析xml布局R.xml.device_profiles返回一个类型为InvariantDeviceProfile的ArrayList集合。具体的代码比较多,就不贴了。
device_profiles的xml文件
对于这个xml文件是由很多profile文件组成,其中一个.
在这个profile中有很多的属性,包括最小的宽高minWidthDps和minHeightDps,布局的行列数,文件夹的行列数,allapp的行列数,图标大小,图标名字代销,快捷栏图标大小,快捷栏图标数,以及默认布局文件。这些参数都需要在后面获取到。
在这11个profile中从
launcher:minWidthDps="255" launcher:minHeightDps="300"
到
launcher:minWidthDps="1527"launcher:minHeightDps="2527"
所有的设备都包含了。
findClosestDeviceProfiles()
接着我们来回过头来看一些findClosestDeviceProfiles(minWidthDps, minHeightDps, getPredefinedDeviceProfiles(context))这个方法。
ArrayList findClosestDeviceProfiles(
final float width, final float height, ArrayList points) {
// Sort the profiles by their closeness to the dimensions
ArrayList pointsByNearness = points;
Collections.sort(pointsByNearness, new Comparator() {
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;
}
points就是我们通过解析R.xml.device_profiles这个布局来获得的类型为InvariantDeviceProfile的ArrayList,赋值给pointsByNearness ,然后进行排序,排序方式是调用Collections.sort的第二种方式自定义比较器来实现的关于Collections.sort的讲解友情链接比较的。在自定义比较器的方法中采用浮点类型的比较方法Float.compare(f1,f2),此方法返回值0,则f1在数值上等于f2; 返回值小于0,f1在数值上比f2小; 返回值大于0,f1在数值上比f2大.而真正的算法是在dist方法中。
@Thunk float dist(float x0, float y0, float x1, float y1) {
return (float) Math.hypot(x1 - x0, y1 - y0);
}
我们可以看到使用了Math.hypot,此方法返回的结果是sqrt(x2 +y2) ,也就是直角三角形的斜边长,其实就是手机屏幕的对角线的长度即手机尺寸。
dist方法得到的结果是读取的profile的斜边长度与手机屏幕的对角线的长度的一个差值。最后进行compare,差值小的在前面,差值大的在后面。之后将新的排序返回。这就是findClosestDeviceProfiles方法做的事情。
总结一下findClosestDeviceProfiles:通过将手机实际尺寸和理论的11个模板的理论尺寸进行比较,得到与目标的匹配度,匹配度越高,在closestProfiles的集合里面越靠前。
讲解完InvariantDeviceProfile构造方法中的findClosestDeviceProfiles方法,我们接着来看第二个精华的地方,那就是 invDistWeightedInterpolate(minWidthDps, minHeightDps, closestProfiles);这个方法
invDistWeightedInterpolate()
调用这个方法时传进去的参数是当前手机真实的宽和高,以及经过排序后得到的与目标匹配度由高到低的profiles集合。具体的操作在代码中进行了注解,其实,很多手机型号一致的,计算的时候不算多,有些许差别,计算出来的偏差值也不多,所以这个偏差值纠正就分析到这里。
InvariantDeviceProfile invDistWeightedInterpolate(float width, float height,
ArrayList points) {
float weights = 0;
//获取集合中的第一个InvariantDeviceProfile,也就是匹配度最高的那一个
InvariantDeviceProfile p = points.get(0);
//判断如果当前手机与profile的差值等于0,则直接返回这个InvariantDeviceProfile。
if (dist(width, height, p.minWidthDps, p.minHeightDps) == 0) {
return p;
}
//如果不相同的话就要计算偏差值然后给InvariantDeviceProfile进行赋值。
InvariantDeviceProfile out = new InvariantDeviceProfile();
for (int i = 0; i < points.size() && i < KNEARESTNEIGHBOR; ++i) {
p = new InvariantDeviceProfile(points.get(i));
float w = weight(width, height, p.minWidthDps, p.minHeightDps, WEIGHT_POWER);
weights += w;
out.add(p.multiply(w));
}
return out.multiply(1.0f/weights);
}
到这里这两个方法就讲解完了,然后我们再返回头去看InvariantDeviceProfile的构造方法接着往下走
InvariantDeviceProfile closestProfile = closestProfiles.get(0);
interpolatedDeviceProfileOut和closestProfile ,如果没有偏差值的话是完全相同的,指向同一个对象。
但此为止,InvariantDeviceProfile构造方法中,我们测量了当前手机的长宽,再由实际长宽和11个模板比较得到了最佳模板,以及和模板不一样的偏差值。
接下来构造方法就会进行赋值。
numRows = closestProfile.numRows;
numColumns = closestProfile.numColumns;
numHotseatIcons = closestProfile.numHotseatIcons;
defaultLayoutId = closestProfile.defaultLayoutId;
demoModeLayoutId = closestProfile.demoModeLayoutId;
numFolderRows = closestProfile.numFolderRows;
numFolderColumns = closestProfile.numFolderColumns;
minAllAppsPredictionColumns = closestProfile.minAllAppsPredictionColumns;
iconSize = interpolatedDeviceProfileOut.iconSize;
landscapeIconSize = interpolatedDeviceProfileOut.landscapeIconSize;
iconBitmapSize = Utilities.pxFromDp(iconSize, dm);
iconTextSize = interpolatedDeviceProfileOut.iconTextSize;
fillResIconDpi = getLauncherIconDensity(iconBitmapSize);
到这里根据手机的硬件参数,寻找到对应的模型,确定最终的布局的行列数就做完了,接下来则是正式构造我们需要使用的DeviceProfile。
赋值结束后就会构造DeviceProfile,因为手机屏幕有两个状态,横屏和竖屏,横屏和竖屏的区别就是长宽的不同,横屏的时候我们需要把边长长的设置成宽,竖屏是把长边设置为高。代码如下
Point realSize = new Point();
display.getRealSize(realSize);
// The real size never changes. smallSide and largeSide will remain the
// same in any orientation.
int smallSide = Math.min(realSize.x, realSize.y);
int largeSide = Math.max(realSize.x, realSize.y);
landscapeProfile = new DeviceProfile(context, this, smallestSize, largestSize,
largeSide, smallSide, true /* isLandscape */);
portraitProfile = new DeviceProfile(context, this, smallestSize, largestSize,
smallSide, largeSide, false /* isLandscape */);
创建DeviceProfile,就是创建实际的硬件模型,具体的操作我们就来看一下DeviceProfile的构造方法。
public DeviceProfile(Context context, InvariantDeviceProfile inv,
Point minSize, Point maxSize,
int width, int height, boolean isLandscape) {
this.inv = inv;
this.isLandscape = isLandscape;
Resources res = context.getResources();
DisplayMetrics dm = res.getDisplayMetrics();
// Constants from resources确定硬件的类型,是平板,电视还是手机
isTablet = res.getBoolean(R.bool.is_tablet);
isLargeTablet = res.getBoolean(R.bool.is_large_tablet);
isPhone = !isTablet && !isLargeTablet;
// Some more constants判断横竖屏进行桌面hotseat的位置设置。
transposeLayoutWithOrientation =
res.getBoolean(R.bool.hotseat_transpose_layout_with_orientation);
context = getContext(context, isVerticalBarLayout()
? Configuration.ORIENTATION_LANDSCAPE
: Configuration.ORIENTATION_PORTRAIT);
res = context.getResources();
//准备各种参数的px值
ComponentName cn = new ComponentName(context.getPackageName(),
this.getClass().getName());
...
省略部分代码
...
//对每个图标进行更详细的设置
// Calculate all of the remaining variables.
updateAvailableDimensions(dm, res);
...
省略部分代码
...
//对每个图标进行更详细的设置
computeAllAppsButtonSize(context);
mBadgeRenderer = new BadgeRenderer(context, iconSizePx);
}
到此桌面的所有显示的内容的尺寸已经设置好。DeviceProfile也就构建完成。那么对于InvariantDeviceProfile是根据硬件来获得手机布局,而DeviceProfile则是根据手机布局,来设置具体的内容的硬件配置。两个相结合就完成了Launcher源码启动过程第一步之InvariantDeviceProfile获取硬件参数,确认布局参数。
结合上一篇,再通过这一篇的具体讲解,我们就知道了Launcher的源码启动过程第一步创建LauncherAppState 对象具体做了些什么:获取硬件参数InvariantDeviceProfile,通过具体的参数,计算出行列,和尺寸,得到符合标准的DeviceProfile布局,然后创建保存图片的缓存IconCatch和WidgetPreviewLoader以及其对应的操作数据库的类IconDB和CacheDb,最后注册广播LauncherModel来监听手机应用的变化。