一、Launcher 的角色:手机桌面的 "大管家"
如果把 Android 系统比作一个家,那么 Launcher 就是这个家的 "桌面管家"。它负责:
-
整理和展示桌面上的 "家具"(应用图标、小部件)
-
处理你对 "家具" 的操作(点击打开应用、拖动调整位置)
-
管理特殊 "通道"(抽屉、搜索栏、快捷方式)
而 SystemUI 则像是家里的 "公共设施管理者",负责管理状态栏、导航栏这些所有应用都能看到的公共区域。两者的关系就像管家和物业,既独立工作又密切配合。
二、Launcher 的启动:从唤醒到准备就绪的全过程
当你按下电源键唤醒手机时,Launcher 的启动流程就像管家开始 "上班":
java
// Launcher的主入口:LauncherActivity.java
public class LauncherActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 1. 初始化管家的"工作环境"
setContentView(R.layout.launcher_layout);
// 2. 获取系统服务,就像管家对接物业(SystemUI)
mSystemUiController = getSystemService(SystemUiController.class);
mSystemUiController.hideSystemBars(); // 隐藏导航栏准备展示桌面
// 3. 创建"家具管理员"
mAppDrawer = new AppDrawer(this);
mIconLayout = findViewById(R.id.icon_layout);
// 4. 加载"家具清单"(应用列表)
loadAppsFromDatabase();
// 5. 绘制桌面,就像管家摆放家具
updateHomeScreenLayout();
}
// 加载应用数据的核心方法
private void loadAppsFromDatabase() {
// 通过ContentProvider获取系统安装的应用列表
ContentResolver resolver = getContentResolver();
Cursor cursor = resolver.query(
PackageManager.STATUS_INSTALLED_APPS_URI,
null, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
do {
String appName = cursor.getString(cursor.getColumnIndex("app_name"));
String packageName = cursor.getString(cursor.getColumnIndex("package_name"));
// 创建应用图标对象
AppInfo appInfo = new AppInfo(packageName, appName);
mAllApps.add(appInfo);
} while (cursor.moveToNext());
cursor.close();
}
}
}
这段代码就像管家上班后的一系列准备工作:先布置好桌面环境,再对接物业(SystemUI)调整公共区域,最后从 "仓库"(数据库)取出所有应用图标并摆放到桌面上。
三、图标管理:Launcher 如何整理和展示应用图标
Launcher 管理图标就像管家整理桌面上的物品,有一套完整的流程:
java
// 图标布局管理器:Workspace.java
public class Workspace extends FrameLayout {
// 桌面网格布局的行列数(就像桌面上的格子)
private static final int CELLS_X = 5;
private static final int CELLS_Y = 5;
// 每个图标占据的格子数
private static final int ICON_WIDTH = 1;
private static final int ICON_HEIGHT = 1;
// 存储所有图标的容器
private final ArrayList<AppIcon> mIcons = new ArrayList<>();
public Workspace(Context context) {
super(context);
// 设置布局参数,就像规划桌面格子
setLayoutParams(new LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
}
// 添加图标到桌面
public void addIcon(AppInfo appInfo, int cellX, int cellY) {
// 创建图标视图
AppIcon icon = new AppIcon(getContext());
icon.setAppInfo(appInfo);
// 计算图标在桌面的位置(类似在格子里放物品)
LauncherLayout.LayoutParams params = new LauncherLayout.LayoutParams(
ICON_WIDTH, ICON_HEIGHT, cellX, cellY,
LauncherLayout.LayoutParams.FLAG_INVISIBLE);
addView(icon, params);
// 记录图标位置
mIcons.add(icon);
// 动画效果:图标从底部飞入桌面
icon.animate()
.translationY(0)
.alpha(1.0f)
.setDuration(300)
.start();
}
// 处理图标点击事件
public void onIconClicked(AppIcon icon) {
AppInfo appInfo = icon.getAppInfo();
// 创建启动应用的意图
Intent launchIntent = getPackageManager()
.getLaunchIntentForPackage(appInfo.packageName);
if (launchIntent != null) {
// 通知系统启动应用(像管家打开对应的房间门)
getContext().startActivity(launchIntent);
// 点击反馈:图标缩小一下
icon.animate()
.scaleX(0.9f)
.scaleY(0.9f)
.setDuration(100)
.withEndAction(() -> {
icon.animate()
.scaleX(1.0f)
.scaleY(1.0f)
.setDuration(100)
.start();
})
.start();
}
}
}
这里 Launcher 把桌面划分为 5x5 的格子,每个图标占据 1x1 的格子,就像在桌面上画好格子再摆放物品。当你点击图标时,Launcher 会创建一个 "开门指令"(Intent),告诉系统打开对应的应用。
四、与 SystemUI 的协作:管家与物业的配合
Launcher 不是孤立工作的,它需要和 SystemUI(系统状态栏、导航栏)协作,就像管家和物业配合管理公共区域:
java
// Launcher与SystemUI的交互示例:Home键事件处理
public class LauncherHomeKeyHandler {
private final Launcher mLauncher;
private final SystemUiController mSystemUiController;
public LauncherHomeKeyHandler(Launcher launcher) {
mLauncher = launcher;
// 获取SystemUI控制器,就像获取物业的管理权限
mSystemUiController = launcher.getSystemService(SystemUiController.class);
}
// 处理Home键按下事件
public boolean onHomeKeyDown() {
// 1. 通知SystemUI隐藏当前可能显示的系统界面(如最近任务)
mSystemUiController.hideSystemUI();
// 2. 如果当前在其他应用,切换回Launcher
if (!mLauncher.isInForeground()) {
mLauncher.bringToFront();
return true;
}
// 3. 如果已经在Launcher,执行特殊操作(如打开抽屉)
mLauncher.openAppDrawer();
return true;
}
// 处理状态栏点击事件(从SystemUI传递过来)
public void onStatusBarClicked() {
// 点击状态栏时,Launcher可以执行搜索等操作
mLauncher.showSearchBar();
}
}
当你按下 Home 键时,Launcher 会和 SystemUI 协作:SystemUI 负责隐藏导航栏等系统界面,Launcher 负责展示桌面。这种协作就像管家和物业一起处理住户的请求。
五、高级功能:抽屉、文件夹与拖拽排序
Launcher 还提供了更高级的功能,就像管家提供的增值服务:
java
// 应用抽屉:隐藏的"物品仓库"
public class AppDrawer {
private final Launcher mLauncher;
private RecyclerView mAppList;
private AppAdapter mAdapter;
public AppDrawer(Launcher launcher) {
mLauncher = launcher;
// 初始化抽屉界面
View drawerView = LayoutInflater.from(launcher)
.inflate(R.layout.app_drawer, launcher.getContentView(), false);
mAppList = drawerView.findViewById(R.id.app_list);
mAdapter = new AppAdapter(launcher.getAllApps());
mAppList.setAdapter(mAdapter);
}
// 打开抽屉
public void open() {
// 动画效果:抽屉从底部滑入
mAppList.animate()
.translationY(0)
.setDuration(300)
.start();
}
// 关闭抽屉
public void close() {
mAppList.animate()
.translationY(mAppList.getHeight())
.setDuration(300)
.start();
}
}
// 文件夹:图标收纳盒
public class Folder extends FrameLayout {
private final ArrayList<AppIcon> mContainedIcons = new ArrayList<>();
private TextView mFolderName;
public Folder(Context context) {
super(context);
// 加载文件夹布局
LayoutInflater.from(context).inflate(R.layout.folder, this);
mFolderName = findViewById(R.id.folder_name);
}
// 添加图标到文件夹
public void addIcon(AppIcon icon) {
mContainedIcons.add(icon);
addView(icon);
// 更新文件夹名称(如"工具"、"社交")
updateFolderName();
}
// 更新文件夹名称(根据内容自动命名)
private void updateFolderName() {
if (mContainedIcons.isEmpty()) {
mFolderName.setText("空文件夹");
return;
}
// 简单示例:根据第一个图标的类别命名
AppInfo firstApp = mContainedIcons.get(0).getAppInfo();
String category = getAppCategory(firstApp.packageName);
mFolderName.setText(category + "文件夹");
}
}
应用抽屉就像管家管理的仓库,平时隐藏在桌面下方,拉开后可以看到所有应用。文件夹则像收纳盒,把同类应用图标放在一起,让桌面更整洁。
六、Launcher 的核心工作流程总结
-
启动阶段:初始化界面 -> 加载应用数据 -> 布局桌面图标
-
交互阶段:
- 处理图标点击:创建 Intent 启动应用
- 处理 Home 键:与 SystemUI 协作切换到桌面
- 处理拖拽操作:调整图标位置或放入文件夹
-
协作阶段:
-
与 SystemUI 共享状态(如桌面是否显示)
-
接收 SystemUI 传递的事件(如状态栏点击)
-
通过这些流程,Launcher 就像一个尽职尽责的管家,让手机桌面既美观又易用。如果你想进一步探索,可以从 Android 源码中的packages/apps/Launcher3目录开始,那里藏着更多管家的 "工作细节"。