Launcher3 模块化-组件化
Source: Dev.to
目录
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 架构


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

在 MVP 架构下,Model 加载基础的 item‑element 数据,同时获取诸如图标缓存等辅助数据。所有数据最终会回传给 Launcher 类或与 widget 相关的视图进行处理。
Source: …
4. 结构化拆分
最初的想法是把 Launcher3 拆分成若干模块,每个模块负责特定功能(例如应用图标、部件)。然而,在实际拆分过程中发现,Launcher3 的代码结构高度相互依赖。例如,桌面图标和部件都依赖 DragLayer 类。把它们放在同一个模块并不能降低整体复杂度。
因此,项目被划分为以下模块:
| 模块 | 负责内容 |
|---|---|
| item_foundation | 项目元素(图标、文字等)的核心数据结构和逻辑。包括 DragLayer,用于拖拽图标和部件的基础视图。 |
| PaginationScrollView | 分页滚动视图的实现,处理页面切换、位置预测及相关行为。 |
| pop_exchange | 部件视图生成、底部部件列表实现及相关功能。 |
| data_persistence | 应用图标、部件及其他持久化数据的存储与读取。 |
| router | 跨模块方法调用。 |
| utils | 通用工具类(日志、线程池等)。 |
整体逻辑架构

(如果图片显示异常,请检查 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 智能推荐功能。
- 确保数据合规。
