Quantcast
Viewing all articles
Browse latest Browse all 5930

Launcher 记录自定义桌面

Launcher 记录自定义桌面

自定义桌面数据的创建、更新和删除。

前置文章

  1. Launcher的启动过程
  2. Launcher界面结构
  3. Launcher拖拽框架

前言

我们在使用手机的过程中,都会把我们手机的桌面布置成我们最喜欢,最习惯,最便捷的桌面。对手机而言,我们每一次对桌面的布置都会使数据发生变化,而数据在内存中是临时的,手机一旦关机,内存的数据就全部丢失了,因此,我们需要把自定义桌面的数据保存起来。在 Launcher,选择数据库来保存自定义桌面的数据。本文以 Android 的 Launcher3 为例,简述我们自定义桌面的时候,数据的产生、变化与删除。在赘述自定义桌面前,笔者对手上的手机的桌面做了一些自定义的设置布置。如下图:

Image may be NSFW.
Clik here to view.

上图为第一屏

Image may be NSFW.
Clik here to view.
这里写图片描述

上图为第二屏

Image may be NSFW.
Clik here to view.
这里写图片描述

上图为第三屏

如上面的三张图,都有一个底部常用的四个应用的图标,分别是拨号、短信、Chrome浏览器和照相机。另外,第一张图,放了图片管理应用 Gallery 的图标;第二张图,放了一个时钟的的窗口小部件(Widget)和 Calculator、Email 两个应用的图标;第三张图,则是放了一个音乐的窗口小部件。

数据保存方式

Launcher 采用 ContentProvider + database 的方式对自定义桌面的数据进行管理。读者可以阅读文章《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 中。

数据库信息

我们知道,在 Android 操作数据库时,都需要用到 SQLiteOpenHelper 来创建连接数据库,其中需要传入数据库文件名字。如下:

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 中。

上述第二个参数 LauncherFiles.LAUNCHER_DB 便是数据库文件名。定义如下:

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

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

因此,自定义桌面的数据则保存在手机的 /data/data/com.android.launcher3/databases/launcher.db 中。

数据库的初始化

我们第一次打开新的手机,或者恢复出厂设置后首次打开手机,Launcher 都有一个默认的桌面,这些默认的设置预设在 xml 文件中,当我们第一次启动 Launcher(读者可以阅读文章《Launcher的启动过程 》了解 Launcher 的启动)的时候,会创建数据库,创建数据库表,把默认的设置写入到数据库中。

当然,在 Android 的 SQLite 数据库框架中,会自动创建 launcher.db 数据库文件,我们直接创建需要的数据库表即可。代码如下:

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 的时候,通过调用方法 tableExists() 判断数据库表 TABLE_FAVORITES=favorites、 TABLE_WORKSPACE_SCREENS=workspaceScreens 是否存在。如果不存在,则调用 addFavoritesTable() 和 addWorkspacesTable() 分别创建数据表 favorites 和 workspaceScreens。addFavoritesTable() 的实现如下:

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 中。

如上创建数据表 favorites 的 SQL 语句,favorites 数据库表保存了应用图标的位置等信息,在后文中我们在赘述每个字段的作用。addWorkspacesTable() 的实现如下:

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 中。

相对数据表 favorites,workspaceScreens 的结构显得简单不少,只有几个字段,workspaceScreens 保存的是 workspace 的个数等信息。对 workspace 不熟悉的读者,可以先阅读文章《 Launcher界面结构 》。

在 packages/apps/Launcher3/src/com/android/launcher3/LauncherModel.java 的 loadWorkspace() 方法中会加载默认的预设置项,本文就不再展述这儿过程。

数据库表字段定义

favorites 表

Image may be NSFW.
Clik here to view.
这里写图片描述

workspacescreens 表

Image may be NSFW.
Clik here to view.
这里写图片描述

产生自定义桌面数据

当在 workspace 新增一个应用图标或者小部件的时候,或者在文件夹中新增一个应用图标,就会产生新的自定义桌面数据。下文我们将会论述:从菜单中拖拽一个应用图标到 workspace,添加一个窗口小部件到 workspace,在文件夹中新增一个应用图标,三种方式的过程。

Workspace中新增应用图标

在文章《Launcher拖拽框架》中,我们知道,拖拽一个应用图标到 workspace 时,会调用 onDrop() 方法结束本次的拖拽,那么就需要在 onDrop() 方法中去完成拖拽后的一系列事务,包括我们自定义桌面数据的产生和保存。

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 中。

由于是从菜单里面拖拽出来的应用图标,因此会走 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 的标识
    // mTargetCell[0]:应用图标的 X 轴方向矩阵位置
    // mTargetCell[1]:应用坐标的 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 中。

在上面的方法中,调用 addOrMoveItemInDatabase(Context context, ItemInfo item, long container, long screenId, int cellX, int cellY) 方法保存数据到数据库,container 确定是否是放在某个容器中的应用图标,如底部的 hotseat。-100 表示是 workspace 本身。screenId 确定应用的图标放置到那个 workspace,cellX 和 cellY 确定应用图标在 workspace 矩阵中的位置,workspace 矩阵一般是 3*3、4*4、5*5 等,分别表示 X 轴和 Y 轴方向应用图标的数量。如下图 3*3 的矩阵:

Image may be NSFW.
Clik here to view.
这里写图片描述

继续往下分析 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 中。

我们是从菜单中拖拽应用,所以走 addItemToDatabase() 分支:

public static void addItemToDatabase(Context context, final ItemInfo item, final long container,
        final long screenId, final int cellX, final int cellY) {
    // 相关位置信息赋值到 ItemInfo 中
    item.container = container;
    item.cellX = cellX;
    item.cellY = cellY;
    .....
    final ContentValues values = new ContentValues();
    final ContentResolver cr = context.getContentResolver();
    // 设置键值对 values
    item.onAddToDatabase(context, values);
    // 最大 id + 1
    item.id = LauncherAppState.getLauncherProvider().generateNewItemId();
    // 设置 id 键值对到 values
    values.put(LauncherSettings.Favorites._ID, item.id);
    .....
    Runnable r = new Runnable() {
        public void run() {
            // 把自定义桌面数据插入数据库
            cr.insert(LauncherSettings.Favorites.CONTENT_URI, values);
            .....
        }
    };
    runOnWorkerThread(r);
}

(代码片段10)这个方法定义在文件 packages/apps/Launcher3/src/com/android/launcher3/LauncherModel.java 中。

在上面的方法中,调用 onAddToDatabase() 方法设置需要保存到数据库的键值对,分别有:

void onAddToDatabase(Context context, ContentValues values) {
    super.onAddToDatabase(context, values);
    // 应用图标名字
    String titleStr = title != null ? title.toString() : null;
    values.put(LauncherSettings.BaseLauncherColumns.TITLE, titleStr);
    // Intent,即点击应用图标的意图,需要转换成 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);
    // 图标类型,应用图标可更换
    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),设置完成键值对后,LauncherAppState.getLauncherProvider().generateNewItemId() 获取一个 id,这个 id 比数据库中最大的 id 大1,在 Launcher 中,每次添加一个新的 item 到 workspace,id 都是往上加 1。随后调用 ContentProvider 的 insert() 方法插入到数据库。

时序图

Image may be NSFW.
Clik here to view.
这里写图片描述

Workspace 中新增小部件(Widget)

由于过程和“Workspace中新增应用图标”差不多,就不赘述和贴代码了。

时序图

Image may be NSFW.
Clik here to view.
这里写图片描述

创建文件夹

两个图标拖拽时放到一起,创建一个文件夹图标,过程和上面的差不多,就不赘述和贴代码了。

时序图

Image may be NSFW.
Clik here to view.
这里写图片描述

更新自定义桌面数据

由于过程简单,本文不再论述和贴代码。

时序图

Image may be NSFW.
Clik here to view.
这里写图片描述

删除自定义桌面数据

由于过程简单,本文不再论述和贴代码。

时序图

Image may be NSFW.
Clik here to view.
这里写图片描述

总结

本文讲述了 Launcher 在 workspace 中添加,移动,删除应用图标和窗口小部件时,所对应的图标或者小部件位置等信息的变化和保存。每次 Launcher 启动后从数据中加载这些数据,对用户自定义的桌面进行恢复。

作者:myfriend0 发表于2017/6/2 10:38:58 原文链接
阅读:34 评论:0 查看评论

Viewing all articles
Browse latest Browse all 5930

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>