Android桌面Launcher原始碼淺析

柳云居士發表於2024-05-31

Android啟動過程-萬字長文(Android14)中介紹了Android系統的啟動過程,本篇文章將繼續介紹桌面應用Launcher。

一、Launcher介紹

  • Android啟動過程-萬字長文(Android14)中提到Launcher是Android系統啟動後,由SystemServerActivity Manager Service (AMS)載入的第一個應用程式
  • Launcher又被稱為桌面程式,負責Android桌面的啟動和管理
  • 使用者使用的應用程式(App)都是透過Launcher來啟動的

二、下載及編譯

2.1 下載

  • 使用Git下載Launcher原始碼:
git clone https://android.googlesource.com/platform/packages/apps/Launcher3
  • 進入專案目錄
cd Launcher3
  • 切換到Android14分支
git checkout android14-release

2.2 編譯

使用AndroidStudio編譯下載好的Launcher3工程

編譯過程中遇到問題及解決方案可以參考以下部落格:

  • 基於android 11的Launcher3本地Android studio調通流程
  • AndroidStudio編譯除錯aosp11R 的Launcher3

三、原始碼解析

3.1 AndroidManifest.xml

在專案根目錄的AndroidManifest.xml,定義了Launcher做為桌面程式的屬性:

<application>
    <activity
        android:name="com.android.launcher3.Launcher"
        android:launchMode="singleTask">
        <intent-filter>
            <category android:name="android.intent.category.HOME" />
        </intent-filter>
    </activity>
</application>
  • android.intent.category.HOME: 告訴系統這是一個啟動器(Launcher)應用程式,系統在初始化完成後會透過ActivityTaskManagerServicegetHomeIntent方法獲取和啟動桌面程式。具體可參見Android啟動過程-萬字長文(Android14)
  • 開發人員也可以自己開發一個桌面程式(如微軟桌面),使用者安裝完成後,可以在系統設定中修改預設啟動的桌面程式

3.2 Launcher.java

Launcher.java是Launcher的啟動頁面,負責資源初始化和桌面UI建立

3.2.1 onCreate方法

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // 獲取 LauncherAppState 例項和模型
    LauncherAppState app = LauncherAppState.getInstance(this);
    mModel = app.getModel();
    
    // 初始化不變的裝置配置檔案
    InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
    initDeviceProfile(idp);
    idp.addOnChangeListener(this);

    // 獲取共享首選項和圖示快取
    mSharedPrefs = LauncherPrefs.getPrefs(this);
    mIconCache = app.getIconCache();

    // 建立無障礙代理
    mAccessibilityDelegate = createAccessibilityDelegate();

    // 初始化拖動控制器
    initDragController();
    
    // 建立所有應用程式控制器
    mAllAppsController = new AllAppsTransitionController(this);
    
    // 建立狀態管理器
    mStateManager = new StateManager<>(this, NORMAL);

    // 建立引導首選項
    mOnboardingPrefs = createOnboardingPrefs(mSharedPrefs);

    // 設定檢視
    setupViews();

    // 初始化Widget
    mAppWidgetManager = new WidgetManagerHelper(this);
    mAppWidgetHolder = createAppWidgetHolder();
    mAppWidgetHolder.startListening();

    // 設定內容檢視
    setContentView(getRootView());
    ComposeInitializer.initCompose(this);

}

3.2.2 setupViews方法

protected void setupViews() {
    // 建立根檢視
    inflateRootView(R.layout.launcher);

    // 獲取拖動層和焦點處理器
    mDragLayer = findViewById(R.id.drag_layer);
    mFocusHandler = mDragLayer.getFocusIndicatorHelper();
    
    // 獲取工作區、總覽皮膚和Hotseat
    mWorkspace = mDragLayer.findViewById(R.id.workspace);
    mWorkspace.initParentViews(mDragLayer);
    mOverviewPanel = findViewById(R.id.overview_panel);
    mHotseat = findViewById(R.id.hotseat);
    // 將工作區設定為Hotseat
    mHotseat.setWorkspace(mWorkspace);

    // 設定拖動層
    mDragLayer.setup(mDragController, mWorkspace);

    // 設定工作區
    mWorkspace.setup(mDragController);
    // 在工作區繫結之前,確保我們將桌布偏移鎖定到預設狀態,否則在RTL中我們將更新錯誤的偏移量
    mWorkspace.lockWallpaperToDefaultPage();
    mWorkspace.bindAndInitFirstWorkspaceScreen();
    mDragController.addDragListener(mWorkspace);

    // 獲取搜尋/刪除/解除安裝欄
    mDropTargetBar = mDragLayer.findViewById(R.id.drop_target_bar);

    // 設定應用程式檢視
    mAppsView = findViewById(R.id.apps_view);
    mAppsView.setAllAppsTransitionController(mAllAppsController);

    // 設定拖動控制器(拖動目標必須按優先順序的相反順序新增)
    mDropTargetBar.setup(mDragController);
    mAllAppsController.setupViews(mScrimView, mAppsView);

    // 如果啟用了點分頁,則設定工作區的分頁指示器
    if (SHOW_DOT_PAGINATION.get()) {
        mWorkspace.getPageIndicator().setShouldAutoHide(true);
        mWorkspace.getPageIndicator().setPaintColor(
                Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText)
                        ? Color.BLACK
                        : Color.WHITE);
    }
}

  • Workspace:工作區,也是我們常說的桌面區域,包括搜尋框,桌面,桌布
  • AppsView:應用程式列表
  • Widget:小元件

三、Workspace、AppsView和Widget示例

3.1 Workspace(工作區)

  • 結構說明

3.2 AppsView(應用程式檢視)

3.3 Widget(小元件)

四、點選App圖示的事件響應

4.1 觸發ItemClickHandler的onClick方法

  • ItemClickHandler負責處理桌面應用圖示的點選事件。
  • 桌面圖示的點選事件最終會觸發ItemClickHandleronClick方法
  • onClick方法最終會觸發startAppShortcutOrInfoActivity方法
/**
 * Class for handling clicks on workspace and all-apps items
 */
public class ItemClickHandler {
    private static void onClick(View v) {
        startAppShortcutOrInfoActivity(v, (AppInfo) tag, launcher);
    }
    
    // 通知launcher啟動Activity
    private static void startAppShortcutOrInfoActivity(View v, ItemInfo item, Launcher launcher) {
        launcher.startActivitySafely(v, intent, item);
    }
}

4.2 Launcher通知系統啟動App

Launcher.java的startActivitySafely方法中呼叫ActivityContext.java的startActivitySafely方法

public RunnableList startActivitySafely(View v, Intent intent, ItemInfo item) {
    RunnableList result = super.startActivitySafely(v, intent, item);
}

ActivityContext.java的startActivitySafely方法中呼叫了

public interface ActivityContext {
        default RunnableList startActivitySafely(
            View v, Intent intent, @Nullable ItemInfo item) {
            if (isShortcut) {
                // Shortcuts need some special checks due to legacy reasons.
                startShortcutIntentSafely(intent, optsBundle, item);
            }
        }
        
        default void startShortcutIntentSafely(Intent intent, Bundle optsBundle, ItemInfo info) {
            if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
                    // 透過快捷方式啟動
                    startShortcut(packageName, id, intent.getSourceBounds(), optsBundle, info.user);
                } else {
                    // 普通方式啟動,應用程式走這個分支
                    ((Context) this).startActivity(intent, optsBundle);
                }
        }
}

Android 應用快捷方式(Shortcut)官方文件

最終透過frameworks/base/core/java/android/app/Activity.java 原始碼地址中的startActivity方法啟動了對應的應用程式。

相關文章