Android Framework-Launcher-InvariantDeviceProfile

InvariantDeviceProfile

路径 packages/apps/Launcher3/src/com/android/launcher3/InvariantDeviceProfile.java

InvariantDeviceProfile 主要负责布局的加载和对应参数的保存,采用单例模式。

java 复制代码
public static final MainThreadInitializedObject<InvariantDeviceProfile> INSTANCE =
        new MainThreadInitializedObject<>(InvariantDeviceProfile::new);


 @TargetApi(23)
    private InvariantDeviceProfile(Context context) {
      //去取出缓存的 gridName 这里初始化的时候 默认是null 
        String gridName = getCurrentGridName(context);
      //我这里取出来是5_by_5
        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);
        }
        new DeviceGridState(this).writeToPrefs(context);

    //省略
    }

打印了下日志

shell 复制代码
2025-07-13 23:48:16.181  1477-1477  b/258560494             com.android.launcher3                D  InvariantDeviceProfile - setting newGridName: 5_by_5, gridName: null

因为我们已经知道gridName 是个null ,我们直接进入initGrid(context, gridName);

java 复制代码
private String initGrid(Context context, String gridName) {
    Info displayInfo = DisplayController.INSTANCE.get(context).getInfo();
    @DeviceType int deviceType = getDeviceType(displayInfo);

    ArrayList<DisplayOption> allOptions =
            getPredefinedDeviceProfiles(context, gridName, deviceType,
                    RestoreDbTask.isPending(context));
    DisplayOption displayOption =
            invDistWeightedInterpolate(displayInfo, allOptions, deviceType);
    initGrid(context, displayInfo, displayOption, deviceType);
    return displayOption.grid.name;
}
  • getDeviceType 根据当前设备信息 返回对应类型。
java 复制代码
public static final int TYPE_PHONE = 0;
public static final int TYPE_MULTI_DISPLAY = 1;
public static final int TYPE_TABLET = 2;

public static final String TAG_NAME = "grid-option";

private static @DeviceType int getDeviceType(Info displayInfo) {
    int flagPhone = 1 << 0;
    int flagTablet = 1 << 1;

    int type = displayInfo.supportedBounds.stream()
            .mapToInt(bounds -> displayInfo.isTablet(bounds) ? flagTablet : flagPhone)
            .reduce(0, (a, b) -> a | b);
    if ((type == (flagPhone | flagTablet)) && ENABLE_TWO_PANEL_HOME.get()) {
        // device has profiles supporting both phone and table modes
        return TYPE_MULTI_DISPLAY;
    } else if (type == flagTablet) {
        return TYPE_TABLET;
    } else {
        return TYPE_PHONE;
    }
}
  • getPredefinedDeviceProfiles 该方法根据传入的类型,去device_profiles.xml 下,获取 为 grid-optiondisplay-option 和的,并返回,对应参数含义,放在文章最后。
java 复制代码
private static ArrayList<DisplayOption> getPredefinedDeviceProfiles(Context context,
        String gridName, @DeviceType int deviceType, boolean allowDisabledGrid) {
    ArrayList<DisplayOption> profiles = new ArrayList<>();

    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) {
            if ((type == XmlPullParser.START_TAG)
                    && GridOption.TAG_NAME.equals(parser.getName())) {

                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) {
                        if ((type == XmlPullParser.START_TAG) && "display-option".equals(
                                parser.getName())) {
                            profiles.add(new DisplayOption(gridOption, context,
                                    Xml.asAttributeSet(parser)));
                        }
                    }
                }
            }
        }
    } catch (IOException | XmlPullParserException e) {
        throw new RuntimeException(e);
    }

    ArrayList<DisplayOption> filteredProfiles = new ArrayList<>();
    if (!TextUtils.isEmpty(gridName)) {
    //第一次默认是null  这里就不会进入了
        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;
}

device_profiles.xml

xml 复制代码
  <grid-option
        launcher:name="5_by_5"
        launcher:numRows="5"
        launcher:numColumns="6"
        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>
java 复制代码
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);
            numSearchContainerColumns = a.getInt(
                    R.styleable.GridDisplayOption_numSearchContainerColumns, numColumns);

            dbFile = a.getString(R.styleable.GridDisplayOption_dbFile);
          //省略
                  

        }
        
      
static final class DisplayOption {
    public final GridOption grid;

    private final float minWidthDps;
    private final float minHeightDps;
    private final boolean canBeDefault;

    private final PointF[] minCellSize = new PointF[COUNT_SIZES];

    private final PointF[] borderSpaces = new PointF[COUNT_SIZES];
    private final float[] horizontalMargin = new float[COUNT_SIZES];
 
    //省略
  • invDistWeightedInterpolate 可以简单理解为加权计算,调配出一套最合适当前屏幕的各种磁村,比如前面配置里 iconImageSize 为56dp,但是实际可能大点或者小点。
java 复制代码
private static DisplayOption invDistWeightedInterpolate(
        Info displayInfo, ArrayList<DisplayOption> points, @DeviceType int deviceType) {
    int minWidthPx = Integer.MAX_VALUE;
    int minHeightPx = Integer.MAX_VALUE;
    for (WindowBounds bounds : displayInfo.supportedBounds) {
        boolean isTablet = displayInfo.isTablet(bounds);
        if (isTablet && deviceType == TYPE_MULTI_DISPLAY) {
            // For split displays, take half width per page
            minWidthPx = Math.min(minWidthPx, bounds.availableSize.x / 2);
            minHeightPx = Math.min(minHeightPx, bounds.availableSize.y);

        } else if (!isTablet && bounds.isLandscape()) {
            // We will use transposed layout in this case
            minWidthPx = Math.min(minWidthPx, bounds.availableSize.y);
            minHeightPx = Math.min(minHeightPx, bounds.availableSize.x);
        } else {
            minWidthPx = Math.min(minWidthPx, bounds.availableSize.x);
            minHeightPx = Math.min(minHeightPx, bounds.availableSize.y);
        }
    }

    float width = dpiFromPx(minWidthPx, displayInfo.getDensityDpi());
    float height = dpiFromPx(minHeightPx, displayInfo.getDensityDpi());

    // Sort the profiles based on the closeness to the device size
    Collections.sort(points, (a, b) ->
            Float.compare(dist(width, height, a.minWidthDps, a.minHeightDps),
                    dist(width, height, b.minWidthDps, b.minHeightDps)));

    DisplayOption closestPoint = points.get(0);
    GridOption closestOption = closestPoint.grid;
    float weights = 0;

    if (dist(width, height, closestPoint.minWidthDps, closestPoint.minHeightDps) == 0) {
        return closestPoint;
    }

    DisplayOption out = new DisplayOption(closestOption);
    for (int i = 0; i < points.size() && i < KNEARESTNEIGHBOR; ++i) {
        DisplayOption p = points.get(i);
        float w = weight(width, height, p.minWidthDps, p.minHeightDps, WEIGHT_POWER);
        weights += w;
        out.add(new DisplayOption().add(p).multiply(w));
    }
    out.multiply(1.0f / weights);

    // Since the bitmaps are persisted, ensure that all bitmap sizes are not larger than
    // predefined size to avoid cache invalidation
    for (int i = INDEX_DEFAULT; i < COUNT_SIZES; i++) {
        out.iconSizes[i] = Math.min(out.iconSizes[i], closestPoint.iconSizes[i]);
    }

    return out;
}
  • initGrid

该方法主要是给 InvariantDeviceProfile 内部参数进行赋值,方便外部调用。

java 复制代码
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;
    //省略...
    }

常见属性解析

  • launcher:name="5_by_5" 布局方案的名称("5x5"),标识这是一个 "5 行 5 列" 的桌面网格布局方案。

  • launcher:numRows="5" 桌面主页面的行数(垂直方向可显示的图标行数),此处为 5 行。

  • launcher:numColumns="5" 桌面主页面的列数(水平方向可显示的图标列数),此处为 5 列。 (注:numRowsnumColumns共同决定桌面能容纳的图标总数,5x5 布局相比 3x3 能显示更多图标,适合大屏设备)。

  • launcher:numFolderRows="4" 桌面文件夹内部的行数(打开文件夹后,垂直方向可显示的图标行数)。

  • launcher:numFolderColumns="4" 桌面文件夹内部的列数(打开文件夹后,水平方向可显示的图标列数)。 (文件夹布局通常比桌面主布局更紧凑,4x4 适合在有限空间内显示更多应用)。

  • launcher:numHotseatIcons="5" 底部固定 Dock 栏(热座)可显示的图标数量,此处为 5 个(通常用于放置常用应用)。

  • launcher:numExtendedHotseatIcons="6" 扩展状态下 Dock 栏可显示的图标数量(可能用于大屏设备或横屏模式,允许放置更多常用应用)。

  • launcher:dbFile="launcher.db" 关联的数据库文件路径,用于存储该布局方案下的桌面图标位置、文件夹信息等用户配置(避免布局切换时丢失用户数据)。

  • launcher:defaultLayoutId="@xml/default_workspace_5x5" 默认桌面布局的 XML 资源路径,定义了首次使用该方案时,系统预装应用(如电话、短信)的默认位置。

  • launcher:deviceCategory="phone|multi_display" 该布局方案支持的设备类型:

    • phone:普通手机
    • multi_display:多屏设备(如折叠屏、外接显示器的设备)。
  • launcher:inlineNavButtonsEndSpacing="@dimen/taskbar_button_margin_split" 桌面底部导航按钮(如返回、主页键)与 Dock 栏图标的间距,引用了 dimens 资源中定义的具体数值,确保不同屏幕尺寸下的间距一致。

相关推荐
路上^_^1 小时前
安卓基础组件023-SharedPerferences
android
恋猫de小郭2 小时前
Fluttercon EU 2025 :Let‘s go far with Flutter
android·开发语言·flutter·ios·golang
Andytoms6 小时前
Android geckoview 集成,JS交互,官方demo
android·javascript·交互
2501_915909068 小时前
iOS 抓包工具有哪些?实战对比、场景分工与开发者排查流程
android·开发语言·ios·小程序·uni-app·php·iphone
锋风9 小时前
基于Binder的4种RPC调用
android
行墨10 小时前
CoordinatorLayout基本使用与分析—— Group 批量控制
android
行墨11 小时前
CoordinatorLayout基本使用与分析——水平偏移(Horizontal Bias)
android
私房菜11 小时前
Android dmabuf_dump 命令详解
android·libdmabufinfo·linmeminfo·dmabuf_dump
爱学啊11 小时前
1.Android Compose 基础系列:您的第一个 Kotlin 程序
android·kotlin·jetpack
maki07713 小时前
虚幻版Pico大空间VR入门教程 01 ——UE5 Android打包环境4.26~5.6
android·ue5·vr·虚幻·pico·大空间