Launcher 記錄自定義桌面

Launcher 記錄自定義桌面

自定義桌面數(shù)據(jù)的創(chuàng)建、更新和刪除。

前置文章

  1. Launcher的啟動過程
  2. Launcher界面結(jié)構(gòu)
  3. Launcher拖拽框架

前言

我們在使用手機的過程中,都會把我們手機的桌面布置成我們最喜歡,最習(xí)慣,最便捷的桌面。對手機而言,我們每一次對桌面的布置都會使數(shù)據(jù)發(fā)生變化,而數(shù)據(jù)在內(nèi)存中是臨時的,手機一旦關(guān)機,內(nèi)存的數(shù)據(jù)就全部丟失了,因此,我們需要把自定義桌面的數(shù)據(jù)保存起來。在 Launcher,選擇數(shù)據(jù)庫來保存自定義桌面的數(shù)據(jù)。本文以 Android 的 Launcher3 為例,簡述我們自定義桌面的時候,數(shù)據(jù)的產(chǎn)生、變化與刪除。在贅述自定義桌面前,筆者對手上的手機的桌面做了一些自定義的設(shè)置布置。如下圖:

device-2017-06-01-112006_00.PNG

上圖為第一屏

device-2017-06-01-112106_00.png

上圖為第二屏

device-2017-06-01-112124_00.png

上圖為第三屏

如上面的三張圖,都有一個底部常用的四個應(yīng)用的圖標(biāo),分別是撥號、短信、Chrome瀏覽器和照相機。另外,第一張圖,放了圖片管理應(yīng)用 Gallery 的圖標(biāo);第二張圖,放了一個時鐘的的窗口小部件(Widget)和 Calculator、Email 兩個應(yīng)用的圖標(biāo);第三張圖,則是放了一個音樂的窗口小部件。

數(shù)據(jù)保存方式

Launcher 采用 ContentProvider + database 的方式對自定義桌面的數(shù)據(jù)進(jìn)行管理。讀者可以閱讀文章《Android System Server大綱之ContentService和ContentProvider原理剖析 》對 ContentProvider 更深入的了解。

Provider 信息

在 AndroidManifest.xml 中定義如下:

<provider
    android:name="com.android.launcher3.LauncherProvider"
    android:authorities="com.android.launcher3.settings"
    android:exported="true"
    android:writePermission="com.android.launcher3.permission.WRITE_SETTINGS"
    android:readPermission="com.android.launcher3.permission.READ_SETTINGS" />

(代碼片段1)Provider 定義在文件 packages/apps/Launcher3/AndroidManifest.xml 中。

數(shù)據(jù)庫信息

我們知道,在 Android 操作數(shù)據(jù)庫時,都需要用到 SQLiteOpenHelper 來創(chuàng)建連接數(shù)據(jù)庫,其中需要傳入數(shù)據(jù)庫文件名字。如下:

public class LauncherProvider extends ContentProvider {
    protected static class DatabaseHelper extends SQLiteOpenHelper implements LayoutParserCallback {
        DatabaseHelper(Context context) {
            super(context, LauncherFiles.LAUNCHER_DB, null, DATABASE_VERSION);
            .....
        }
    }
}

(代碼片段2)定義在文件 packages/apps/Launcher3/src/com/android/launcher3/LauncherProvider.java 中。

上述第二個參數(shù) LauncherFiles.LAUNCHER_DB 便是數(shù)據(jù)庫文件名。定義如下:

public static final String LAUNCHER_DB = "launcher.db";

(代碼片段3)變量定義在文件 packages/apps/Launcher3/src/com/android/launcher3/LauncherFiles.java 中。

因此,自定義桌面的數(shù)據(jù)則保存在手機的 /data/data/com.android.launcher3/databases/launcher.db 中。

數(shù)據(jù)庫的初始化

我們第一次打開新的手機,或者恢復(fù)出廠設(shè)置后首次打開手機,Launcher 都有一個默認(rèn)的桌面,這些默認(rèn)的設(shè)置預(yù)設(shè)在 xml 文件中,當(dāng)我們第一次啟動 Launcher(讀者可以閱讀文章《Launcher的啟動過程 》了解 Launcher 的啟動)的時候,會創(chuàng)建數(shù)據(jù)庫,創(chuàng)建數(shù)據(jù)庫表,把默認(rèn)的設(shè)置寫入到數(shù)據(jù)庫中。

當(dāng)然,在 Android 的 SQLite 數(shù)據(jù)庫框架中,會自動創(chuàng)建 launcher.db 數(shù)據(jù)庫文件,我們直接創(chuàng)建需要的數(shù)據(jù)庫表即可。代碼如下:

protected static class DatabaseHelper extends SQLiteOpenHelper implements LayoutParserCallback {
    private final Context mContext;
    static final String TABLE_FAVORITES = LauncherSettings.Favorites.TABLE_NAME;
    static final String TABLE_WORKSPACE_SCREENS = LauncherSettings.WorkspaceScreens.TABLE_NAME;
    .....

    DatabaseHelper(Context context) {
        if (!tableExists(TABLE_FAVORITES) || !tableExists(TABLE_WORKSPACE_SCREENS)) {}
    }

(代碼片段4)定義在文件 packages/apps/Launcher3/src/com/android/launcher3/LauncherProvider.java 中。

如上面的代碼,實例化對象 DatabaseHelper 的時候,通過調(diào)用方法 tableExists() 判斷數(shù)據(jù)庫表 TABLE_FAVORITES=favorites、 TABLE_WORKSPACE_SCREENS=workspaceScreens 是否存在。如果不存在,則調(diào)用 addFavoritesTable() 和 addWorkspacesTable() 分別創(chuàng)建數(shù)據(jù)表 favorites 和 workspaceScreens。addFavoritesTable() 的實現(xiàn)如下:

private void addFavoritesTable(SQLiteDatabase db, boolean optional) {
    String ifNotExists = optional ? " IF NOT EXISTS " : "";
    db.execSQL("CREATE TABLE " + ifNotExists + TABLE_FAVORITES + " (" +
            "_id INTEGER PRIMARY KEY," +
            "title TEXT," +
            "intent TEXT," +
            "container INTEGER," +
            "screen INTEGER," +
            "cellX INTEGER," +
            "cellY INTEGER," +
            "spanX INTEGER," +
            "spanY INTEGER," +
            "itemType INTEGER," +
            "appWidgetId INTEGER NOT NULL DEFAULT -1," +
            "isShortcut INTEGER," +
            "iconType INTEGER," +
            "iconPackage TEXT," +
            "iconResource TEXT," +
            "icon BLOB," +
            "uri TEXT," +
            "displayMode INTEGER," +
            "appWidgetProvider TEXT," +
            "modified INTEGER NOT NULL DEFAULT 0," +
            "restored INTEGER NOT NULL DEFAULT 0," +
            "profileId INTEGER DEFAULT " + getDefaultUserSerial() + "," +
            "rank INTEGER NOT NULL DEFAULT 0," +
            "options INTEGER NOT NULL DEFAULT 0" +
            ");");
}

(代碼片段5)這個方法定義在文件 packages/apps/Launcher3/src/com/android/launcher3/LauncherProvider.java 中。

如上創(chuàng)建數(shù)據(jù)表 favorites 的 SQL 語句,favorites 數(shù)據(jù)庫表保存了應(yīng)用圖標(biāo)的位置等信息,在后文中我們在贅述每個字段的作用。addWorkspacesTable() 的實現(xiàn)如下:

private void addWorkspacesTable(SQLiteDatabase db, boolean optional) {
    String ifNotExists = optional ? " IF NOT EXISTS " : "";
    db.execSQL("CREATE TABLE " + ifNotExists + TABLE_WORKSPACE_SCREENS + " (" +
            LauncherSettings.WorkspaceScreens._ID + " INTEGER PRIMARY KEY," +
            LauncherSettings.WorkspaceScreens.SCREEN_RANK + " INTEGER," +
            LauncherSettings.ChangeLogColumns.MODIFIED + " INTEGER NOT NULL DEFAULT 0" +
            ");");
}

(代碼片段6)這個方法定義在文件 packages/apps/Launcher3/src/com/android/launcher3/LauncherProvider.java 中。

相對數(shù)據(jù)表 favorites,workspaceScreens 的結(jié)構(gòu)顯得簡單不少,只有幾個字段,workspaceScreens 保存的是 workspace 的個數(shù)等信息。對 workspace 不熟悉的讀者,可以先閱讀文章《 Launcher界面結(jié)構(gòu) 》。

在 packages/apps/Launcher3/src/com/android/launcher3/LauncherModel.java 的 loadWorkspace() 方法中會加載默認(rèn)的預(yù)設(shè)置項,本文就不再展述這兒過程。

數(shù)據(jù)庫表字段定義

favorites 表

database_table_favorites.jpg

workspacescreens 表

Screenshot from 2017-06-02 09:57:56.png

產(chǎn)生自定義桌面數(shù)據(jù)

當(dāng)在 workspace 新增一個應(yīng)用圖標(biāo)或者小部件的時候,或者在文件夾中新增一個應(yīng)用圖標(biāo),就會產(chǎn)生新的自定義桌面數(shù)據(jù)。下文我們將會論述:從菜單中拖拽一個應(yīng)用圖標(biāo)到 workspace,添加一個窗口小部件到 workspace,在文件夾中新增一個應(yīng)用圖標(biāo),三種方式的過程。

Workspace中新增應(yīng)用圖標(biāo)

在文章《Launcher拖拽框架》中,我們知道,拖拽一個應(yīng)用圖標(biāo)到 workspace 時,會調(diào)用 onDrop() 方法結(jié)束本次的拖拽,那么就需要在 onDrop() 方法中去完成拖拽后的一系列事務(wù),包括我們自定義桌面數(shù)據(jù)的產(chǎn)生和保存。

public void onDrop(final DragObject d) {
    ......
    if (d.dragSource != this) {
        final int[] touchXY = new int[] { (int) mDragViewVisualCenter[0],
                (int) mDragViewVisualCenter[1] };
        onDropExternal(touchXY, d.dragInfo, dropTargetLayout, false, d);
    }

(代碼片段7)這個方法定義在文件 packages/apps/Launcher3/src/com/android/launcher3/Workspace.java 中。

由于是從菜單里面拖拽出來的應(yīng)用圖標(biāo),因此會走 onDropExternal() 分支。

private void onDropExternal(final int[] touchXY, final Object dragInfo,
    final CellLayout cellLayout, boolean insertAtFirst, DragObject d) {
    .....
    // info: ItemInfo
    // container: -100
    // screenId: workspace 的id,workspace 的標(biāo)識
    // mTargetCell[0]:應(yīng)用圖標(biāo)的 X 軸方向矩陣位置
    // mTargetCell[1]:應(yīng)用坐標(biāo)的 Y 軸方向矩陣位置
    LauncherModel.addOrMoveItemInDatabase(mLauncher, info, container, screenId,
                    mTargetCell[0], mTargetCell[1]);

    addInScreen(view, container, screenId, mTargetCell[0], mTargetCell[1], info.spanX,
            info.spanY, insertAtFirst);
    cellLayout.onDropChild(view);
    .....
}

(代碼片段8)這個方法定義在文件 packages/apps/Launcher3/src/com/android/launcher3/Workspace.java 中。

在上面的方法中,調(diào)用 addOrMoveItemInDatabase(Context context, ItemInfo item, long container, long screenId, int cellX, int cellY) 方法保存數(shù)據(jù)到數(shù)據(jù)庫,container 確定是否是放在某個容器中的應(yīng)用圖標(biāo),如底部的 hotseat。-100 表示是 workspace 本身。screenId 確定應(yīng)用的圖標(biāo)放置到那個 workspace,cellX 和 cellY 確定應(yīng)用圖標(biāo)在 workspace 矩陣中的位置,workspace 矩陣一般是 33、44、55 等,分別表示 X 軸和 Y 軸方向應(yīng)用圖標(biāo)的數(shù)量。如下圖 33 的矩陣:

workspace矩陣.jpg

繼續(xù)往下分析 addOrMoveItemInDatabase() 方法:

static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container,
        long screenId, int cellX, int cellY) {
    if (item.container == ItemInfo.NO_ID) {
        // From all apps
        addItemToDatabase(context, item, container, screenId, cellX, cellY);
    } else {
        // From somewhere else
        moveItemInDatabase(context, item, container, screenId, cellX, cellY);
    }
}

(代碼片段9)這個方法定義在文件 packages/apps/Launcher3/src/com/android/launcher3/LauncherModel.java 中。

我們是從菜單中拖拽應(yīng)用,所以走 addItemToDatabase() 分支:

public static void addItemToDatabase(Context context, final ItemInfo item, final long container,
        final long screenId, final int cellX, final int cellY) {
    // 相關(guān)位置信息賦值到 ItemInfo 中
    item.container = container;
    item.cellX = cellX;
    item.cellY = cellY;
    .....
    final ContentValues values = new ContentValues();
    final ContentResolver cr = context.getContentResolver();
    // 設(shè)置鍵值對 values
    item.onAddToDatabase(context, values);
    // 最大 id + 1
    item.id = LauncherAppState.getLauncherProvider().generateNewItemId();
    // 設(shè)置 id 鍵值對到 values
    values.put(LauncherSettings.Favorites._ID, item.id);
    .....
    Runnable r = new Runnable() {
        public void run() {
            // 把自定義桌面數(shù)據(jù)插入數(shù)據(jù)庫
            cr.insert(LauncherSettings.Favorites.CONTENT_URI, values);
            .....
        }
    };
    runOnWorkerThread(r);
}

(代碼片段10)這個方法定義在文件 packages/apps/Launcher3/src/com/android/launcher3/LauncherModel.java 中。

在上面的方法中,調(diào)用 onAddToDatabase() 方法設(shè)置需要保存到數(shù)據(jù)庫的鍵值對,分別有:

void onAddToDatabase(Context context, ContentValues values) {
    super.onAddToDatabase(context, values);
    // 應(yīng)用圖標(biāo)名字
    String titleStr = title != null ? title.toString() : null;
    values.put(LauncherSettings.BaseLauncherColumns.TITLE, titleStr);
    // Intent,即點擊應(yīng)用圖標(biāo)的意圖,需要轉(zhuǎn)換成 String 類型的 Uri 保存
    String uri = promisedIntent != null ? promisedIntent.toUri(0)
            : (intent != null ? intent.toUri(0) : null);
    values.put(LauncherSettings.BaseLauncherColumns.INTENT, uri);
    values.put(LauncherSettings.Favorites.RESTORED, status);
    // 圖標(biāo)類型,應(yīng)用圖標(biāo)可更換
    if (customIcon) {
        values.put(LauncherSettings.BaseLauncherColumns.ICON_TYPE,
                LauncherSettings.BaseLauncherColumns.ICON_TYPE_BITMAP);
        writeBitmap(values, mIcon);
    } else {
        if (!usingFallbackIcon) {
            writeBitmap(values, mIcon);
        }
        if (iconResource != null) {
            values.put(LauncherSettings.BaseLauncherColumns.ICON_TYPE,
                    LauncherSettings.BaseLauncherColumns.ICON_TYPE_RESOURCE);
            values.put(LauncherSettings.BaseLauncherColumns.ICON_PACKAGE,
                    iconResource.packageName);
            values.put(LauncherSettings.BaseLauncherColumns.ICON_RESOURCE,
                    iconResource.resourceName);
        }
    }
}

(代碼片段11)這個方法定義在文件 packages/apps/Launcher3/src/com/android/launcher3/ShortcutInfo.java 中。

void onAddToDatabase(Context context, ContentValues values) {
    // item 類型,shortcut, widget 等
    values.put(LauncherSettings.BaseLauncherColumns.ITEM_TYPE, itemType);
    // container, screenId, cellX, cellY,參考上文
    values.put(LauncherSettings.Favorites.CONTAINER, container);
    values.put(LauncherSettings.Favorites.SCREEN, screenId);
    values.put(LauncherSettings.Favorites.CELLX, cellX);
    values.put(LauncherSettings.Favorites.CELLY, cellY);
    // 跨度,X Y 軸方向占用幾個 workspace 矩陣方格
    values.put(LauncherSettings.Favorites.SPANX, spanX);
    values.put(LauncherSettings.Favorites.SPANY, spanY);
    values.put(LauncherSettings.Favorites.RANK, rank);
}

(代碼片段12)這個方法定義在文件 packages/apps/Launcher3/src/com/android/launcher3/ItemInfo.java 中。

回到 addItemToDatabase() 方法(代碼片段10),設(shè)置完成鍵值對后,LauncherAppState.getLauncherProvider().generateNewItemId() 獲取一個 id,這個 id 比數(shù)據(jù)庫中最大的 id 大1,在 Launcher 中,每次添加一個新的 item 到 workspace,id 都是往上加 1。隨后調(diào)用 ContentProvider 的 insert() 方法插入到數(shù)據(jù)庫。

時序圖

all_app_drag_to_workspace.jpg

Workspace 中新增小部件(Widget)

由于過程和“Workspace中新增應(yīng)用圖標(biāo)”差不多,就不贅述和貼代碼了。

時序圖

widget_to_workspace.jpg

創(chuàng)建文件夾

兩個圖標(biāo)拖拽時放到一起,創(chuàng)建一個文件夾圖標(biāo),過程和上面的差不多,就不贅述和貼代碼了。

時序圖

folder_to_workspace.jpg

"")

更新自定義桌面數(shù)據(jù)

由于過程簡單,本文不再論述和貼代碼。

時序圖

update.jpg

刪除自定義桌面數(shù)據(jù)

由于過程簡單,本文不再論述和貼代碼。

時序圖

delete.jpg

總結(jié)

本文講述了 Launcher 在 workspace 中添加,移動,刪除應(yīng)用圖標(biāo)和窗口小部件時,所對應(yīng)的圖標(biāo)或者小部件位置等信息的變化和保存。每次 Launcher 啟動后從數(shù)據(jù)中加載這些數(shù)據(jù),對用戶自定義的桌面進(jìn)行恢復(fù)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容