Android Launcher 的故事:手机桌面的 "管家" 是如何工作的?

143 阅读5分钟

一、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 的核心工作流程总结

  1. 启动阶段:初始化界面 -> 加载应用数据 -> 布局桌面图标

  2. 交互阶段

    • 处理图标点击:创建 Intent 启动应用
    • 处理 Home 键:与 SystemUI 协作切换到桌面
    • 处理拖拽操作:调整图标位置或放入文件夹
  3. 协作阶段

    • 与 SystemUI 共享状态(如桌面是否显示)

    • 接收 SystemUI 传递的事件(如状态栏点击)

通过这些流程,Launcher 就像一个尽职尽责的管家,让手机桌面既美观又易用。如果你想进一步探索,可以从 Android 源码中的packages/apps/Launcher3目录开始,那里藏着更多管家的 "工作细节"。