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 资源中定义的具体数值,确保不同屏幕尺寸下的间距一致。

相关推荐
Antonio9153 小时前
【音视频】Android NDK 与.so库适配
android·音视频
sun00770012 小时前
android ndk编译valgrind
android
AI视觉网奇13 小时前
android studio 断点无效
android·ide·android studio
jiaxi的天空13 小时前
android studio gradle 访问不了
android·ide·android studio
No Silver Bullet14 小时前
android组包时会把从maven私服获取的包下载到本地吗
android
catchadmin14 小时前
PHP serialize 序列化完全指南
android·开发语言·php
tangweiguo0305198716 小时前
Kable使用指南:Android BLE开发的现代化解决方案
android·kotlin
00后程序员张18 小时前
iOS App 混淆与资源保护:iOS配置文件加密、ipa文件安全、代码与多媒体资源防护全流程指南
android·安全·ios·小程序·uni-app·cocoa·iphone
柳岸风19 小时前
Android Studio Meerkat | 2024.3.1 Gradle Tasks不展示
android·ide·android studio