Launcher3 桌面加载流程分析(上)
主入口Launcher
LauncherAppState
Launcher的onCreate里比较长,我们依次取代码片段来分析,看oncrate方法的这一段,初始化LauncherAppState
public void onCreate() {
...
LauncherAppState app = LauncherAppState.getInstance();
...
}
LauncherAppState是保存一些全局的,核心的对象。主要有整个Launcher的工作台workspace,Launcher的控制器LauncherModel,应用图标的缓存机制IconCache,设备的配置信息InvariantDeviceProfile等。
构造方法,首先初始化内存的追踪器TestingUtils,记录我们app的内存信息,这个工具在我们开发其他app分析内存信息时也是很有用的。
private LauncherAppState() {
...
if (TestingUtils.MEMORY_DUMP_ENABLED) {
TestingUtils.startTrackingMemory(sContext);
}
mInvariantDeviceProfile = new InvariantDeviceProfile(sContext);
mIconCache = new IconCache(sContext, mInvariantDeviceProfile);
mWidgetCache = new WidgetPreviewLoader(sContext, mIconCache);
mAppFilter = AppFilter.loadByName(sContext.getString(R.string.app_filter_class));
mModel = new LauncherModel(this, mIconCache, mAppFilter);
LauncherAppsCompat.getInstance(sContext).addOnAppsChangedCallback(mModel);
// Register intent receivers
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_LOCALE_CHANGED);
filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
// For handling managed profiles
filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED);
filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED);
filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_AVAILABLE);
filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_UNAVAILABLE);
sContext.registerReceiver(mModel, filter);
...
}
LauncherAppsCompat里添加了一个应用变化的回调,由LauncherModel实现接口,及时的响应数据变化。LauncherAppsCompat是获取所有应用,监听应用变化的一个抽象,Android 5.0前后的版本获取方式不一样了,这就是Launcher良好适配性的体现了。
LauncherAppsCompat.getInstance(sContext).addOnAppsChangedCallback(mModel);
Android 5.0以前的监听
private void registerForPackageIntents() {
IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addDataScheme("package");
mContext.registerReceiver(mPackageMonitor, filter);
filter = new IntentFilter();
filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
mContext.registerReceiver(mPackageMonitor, filter);
}
Android5.0后的监听
import android.content.pm.LauncherApps;
protected LauncherApps mLauncherApps;
public void addOnAppsChangedCallback(LauncherAppsCompat.OnAppsChangedCallbackCompat callback) {
WrappedCallback wrappedCallback = new WrappedCallback(callback);
synchronized (mCallbacks) {
mCallbacks.put(callback, wrappedCallback);
}
mLauncherApps.registerCallback(wrappedCallback);
}
除了TestingUtils,应用变化监听外,初始化两个核心对象IconCache,LauncherModel。LauncherModel添加了设备变更,用户信息变更的广播,这是因为当用户修改设备信息如语言,区域,用户信息等,LauncherModel会刷新数据,改变图标,标题等信息。
LauncherAppState的初始化到这里基本上就完成了。
DeviceProfile,InvariantDeviceProfile,Launcher的配置
我们继续看,下一步
LauncherAppState app = LauncherAppState.getInstance();
// Load configuration-specific DeviceProfile
mDeviceProfile = getResources().getConfiguration().orientation
== Configuration.ORIENTATION_LANDSCAPE ?
app.getInvariantDeviceProfile().landscapeProfile
: app.getInvariantDeviceProfile().portraitProfile;
通过Configuration获取横屏配置landscapeProfile 或者portraitProfile竖屏配置
DeviceProfile
DeviceProfile的数据都是来自InvariantDeviceProfile,封装一些工具方法,我们直接看重点InvariantDeviceProfile
InvariantDeviceProfile
InvariantDeviceProfile的初始化是在LauncherAppState构造里new出来的,调用的是
InvariantDeviceProfile(Context context) {
...
numRows = closestProfile.numRows;
numColumns = closestProfile.numColumns;
numHotseatIcons = closestProfile.numHotseatIcons;
hotseatAllAppsRank = (int) (numHotseatIcons / 2);
defaultLayoutId = closestProfile.defaultLayoutId;
numFolderRows = closestProfile.numFolderRows;
numFolderColumns = closestProfile.numFolderColumns;
minAllAppsPredictionColumns = closestProfile.minAllAppsPredictionColumns;
iconSize = interpolatedDeviceProfileOut.iconSize;
iconBitmapSize = Utilities.pxFromDp(iconSize, dm);
iconTextSize = interpolatedDeviceProfileOut.iconTextSize;
hotseatIconSize = interpolatedDeviceProfileOut.hotseatIconSize;
fillResIconDpi = getLauncherIconDensity(iconBitmapSize);
// If the partner customization apk contains any grid overrides, apply them
// Supported overrides: numRows, numColumns, iconSize
applyPartnerDeviceProfileOverrides(context, dm);
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 */);
}
可以看到,通过closestProfile和interpolatedDeviceProfileOut拿到了一系列配置项,如桌面的行,列,Hotseat(桌面底部固定的应用栏)的个数,Hotseat所有应用的位置,布局id,文件夹的行列,图标的大小等等。之后再将这些信息new出我们的DeviceProfile对象。
那问题来了,closestProfile和interpolatedDeviceProfileOut是什么??怎么计算出来的?
...
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<InvariantDeviceProfile> closestProfiles =
findClosestDeviceProfiles(minWidthDps, minHeightDps, getPredefinedDeviceProfiles());
InvariantDeviceProfile interpolatedDeviceProfileOut =
invDistWeightedInterpolate(minWidthDps, minHeightDps, closestProfiles);
...
可以发现,通过设备的宽高等信息,从各种分辨率的DeviceProfiles列表中找到最合适的配置信息,查找的方法如下,根据设备的宽高跟列表里的的最小宽高差值的平方根从小到大排序,第一个就是我们期望的配置
/**
* Returns the closest device profiles ordered by closeness to the specified width and height
*/
// Package private visibility for testing.
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;
}
@Thunk float dist(float x0, float y0, float x1, float y1) {
return (float) Math.hypot(x1 - x0, y1 - y0);
}
配置好的InvariantDeviceProfile列表信息如下,构造参数依次是,
配置名称,宽高,行数,列数,文件夹行数列数,图标大小,图标文本大小
hotseat配置资源文件,hotseat图标大小,默认页的资源文件
故,当我们有新机型没有适配,就可以在这里修改或新增配置
ArrayList<InvariantDeviceProfile> getPredefinedDeviceProfiles() {
ArrayList<InvariantDeviceProfile> predefinedDeviceProfiles = new ArrayList<>();
// width, height, #rows, #columns, #folder rows, #folder columns,
// iconSize, iconTextSize, #hotseat, #hotseatIconSize, defaultLayoutId.
predefinedDeviceProfiles.add(new InvariantDeviceProfile("Super Short Stubby",
255, 300, 2, 3, 2, 3, 3, 48, 13, 3, 48, R.xml.default_workspace_3x3));
predefinedDeviceProfiles.add(new InvariantDeviceProfile("Shorter Stubby",
255, 400, 3, 3, 3, 3, 3, 48, 13, 3, 48, R.xml.default_workspace_3x3));
predefinedDeviceProfiles.add(new InvariantDeviceProfile("Short Stubby",
275, 420, 3, 4, 3, 4, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4));
predefinedDeviceProfiles.add(new InvariantDeviceProfile("Stubby",
255, 450, 3, 4, 3, 4, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4));
predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus S",
296, 491.33f, 4, 4, 4, 4, 4, 48, 13, 5, 48, R.xml.default_workspace_4x4));
predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus 4",
359, 567, 4, 4, 4, 4, 4, DEFAULT_ICON_SIZE_DP, 13, 5, 56, R.xml.default_workspace_4x4));
predefinedDeviceProfiles.add(new InvariantDeviceProfile("Nexus 5",
335, 567, 5, 4, 5, 4