Launcher3 模块化-组件化

发布: (2025年12月21日 GMT+8 20:32)
8 min read
原文: Dev.to

Source: Dev.to

Chuck
Chuck

目录

1. 背景

智能座舱桌面系统通常基于 Android AOSP Launcher3 源代码进行定制。然而,Launcher3 的代码库过于庞大,导致定制成本高昂。为降低这些成本,本项目将 Launcher3 拆分为若干独立模块,便于单独定制。

在蓬勃发展的 Vibe Coding 趋势背景下,这一定制项目可能显得多余——甚至是愚蠢的。我也有同感。在将 Launcher3 导入 Cursor 并提供具体需求后,Cursor 并未能有效降低开发难度,也未能理解整体项目背景或特定功能点的逻辑上下文。

因此,在编程工具 TRAE 的帮助下,完成了本项目的初始版本。

2. 开发环境

硬件

  • Allwinner H618 开发板
  • 2 GB DDR3 内存
  • 16 GB eMMC 存储

软件

  • Android 12.0

开发工具

  • Android Studio Otter 2 Feature Drop | 2025.2.2
  • Mac Pro Tahoe
  • Gradle 8.13
  • JVM‑openjdk 版本 “17.0.17” (2025‑10‑21 LTS)

PaginationScrollViewDemo on GitHub

3. Launcher3 评审

3.1 UI 架构

Launcher3 UI 逻辑架构

Launcher3 UI 对于 item‑icon‑parent 的逻辑架构

所有桌面元素都聚合在单一视图中,这导致在跟踪特定视图类型的问题时,通常需要排除其他类型,包括 UI 和逻辑层面的内容。

3.2 数据架构

Launcher3 数据 逻辑架构

在 MVP 架构下,Model 加载基础的 item‑element 数据,同时获取诸如图标缓存等辅助数据。所有数据最终会回传给 Launcher 类或与 widget 相关的视图进行处理。

Source:

4. 结构化拆分

最初的想法是把 Launcher3 拆分成若干模块,每个模块负责特定功能(例如应用图标、部件)。然而,在实际拆分过程中发现,Launcher3 的代码结构高度相互依赖。例如,桌面图标和部件都依赖 DragLayer 类。把它们放在同一个模块并不能降低整体复杂度。

因此,项目被划分为以下模块:

模块负责内容
item_foundation项目元素(图标、文字等)的核心数据结构和逻辑。包括 DragLayer,用于拖拽图标和部件的基础视图。
PaginationScrollView分页滚动视图的实现,处理页面切换、位置预测及相关行为。
pop_exchange部件视图生成、底部部件列表实现及相关功能。
data_persistence应用图标、部件及其他持久化数据的存储与读取。
router跨模块方法调用。
utils通用工具类(日志、线程池等)。

整体逻辑架构

Launcher3 New Module Logical Architecture

(如果图片显示异常,请检查 URL 或替换为正确的链接。)

4.1 功能转化

4.1.1 数据持久化

在原始 Launcher3 中,ContentProvider 用于数据处理。这里仍然沿用,但原有逻辑被废弃并重新组织。数据的增删改查逻辑被抽象并集中放置,以避免感知上的混乱。

4.1.2 部件裁剪

部件涉及数据读取、预览视图生成、拖拽排除、删除等功能。这里将核心功能抽离,不再与桌面图标一起处理。对于通用驾驶舱桌面开发而言,定制部件并不重要,因此将其移除。

4.1.3 文件夹

在我迄今遇到的驾驶舱项目中,文件夹功能很少使用,故暂不实现。

4.1.4 快捷方式

快捷方式直接使用应用图标,并指定应用启动的 Activity

4.1.5 应用状态

在我看到的项目中,系统自带应用商店,通常不与原生安装器交互来分发应用状态(安装、更新、卸载等)。这里将其废弃;通过提供的数据操作类即可直接实现添加、更新和删除。

4.1.6 应用图标

在原始 Launcher3 中,图标通过 IconCache 数据库进行缓存。这里改为存放在单表中统一处理。

5. 可以做什么

  • 分页滚动视图
  • 小部件列表

这两个功能覆盖了约 80 % 的驾驶舱桌面使用场景。它们可以作为独立的应用列表(实现左右分页和拖拽切页)或作为小部件列表(实现小部件编辑或拖拽添加/删除小部件)。

在当前项目中,开发者只需专注于数据组装和 UI 实现,无需担心数据层。

6. 如何使用

在首页,你可以按如下方式进行设置:

// Define the number of rows and columns for icons
final int row = 3;
final int column = 5;

// Get screen resolution
DisplayMetrics displayMetrics = new DisplayMetrics();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
    getWindowManager().getDefaultDisplay().getRealMetrics(displayMetrics);
} else {
    getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
}

// Get screen width and height, then divide by rows and columns respectively
int screenWidth = displayMetrics.widthPixels;
int screenHeight = displayMetrics.heightPixels;
LogUtils.d(TAG, "device screen size: " + screenWidth + "x" + screenHeight);

PaginationProfile paginationProfile = new PaginationProfile.Builder()
        .setHeightPx(screenHeight)               // view height = screen height
        .setWidthPx(screenWidth)                 // view width = screen width
        .setCellHeightPx(360)                    // icon height = 360 px (view height / rows)
        .setCellWidthPx(384)                     // icon width  = 384 px (view width / columns)
        .setNumColumns(column)                   // number of columns = 5
        .setNumRows(row)                         // number of rows    = 3
        .setIconDrawablePaddingPx(2)            // padding between icon and text
        .setDefaultPageSpacingPx(2)              // default page spacing
        .setEdgeMarginPx(2)                      // edge margin
        .setIconSizePx(108)                      // icon size
        .setIconTextSizePx(16)                   // text size
        .setCellTextColor(getColor(R.color.black)) // text color
        .setWorkspacePadding(new Rect(0, 0, 0, 0)) // workspace padding
        .setSaveDataInDb(true)                   // persist data in DB
        .build();

setContentView(R.layout.activity_main);

// Get the paginated scroll view instance
PaginationScrollView paginationScrollView = findViewById(R.id.pagination_scroll_view);

// Initialize the widget‑list popup view (comment out if not needed)
PopUpExchangeView.getInstance().init(this);

// Determine whether data has been saved to DB
SharedPreferences sp = getSharedPreferences("PaginationScrollView", MODE_PRIVATE);
boolean dataSaved = sp.getBoolean("data_saved", false);
if (dataSaved) {
    paginationScrollView.bindItems();               // load from DB
} else {
    paginationScrollView.bindItems(fruits);         // bind sample data
    sp.edit().putBoolean("data_saved", true).apply(); // remember that we saved it
}

// Set click listener for the widget‑list popup button
FloatingActionButton popupButton = findViewById(R.id.show_pop_window);
popupButton.setOnClickListener(v -> {
    PopUpExchangeView.getInstance().showOrClosePopWindow();
});

进入全屏模式
退出全屏模式

7. 功能待改进

  • 删除和更新项目及部件数据

8. 其他

  • 基于项目操作记录添加数据跟踪,以分析用户行为。
  • 基于行为数据添加 AI 推荐功能。
  • 基于地理围栏添加 AI 智能推荐功能。
  • 确保数据合规。
Back to Blog

相关文章

阅读更多 »