前面一系列文章我们分析了LauncherModel的工作过程,它会把数据绑定到桌面上。从今天开始我们来分析下Launcher的数据来源即Launcher数据库的实现。

一个完整的数据库实现都应该包括两方面的内容,第一是数据库实体SQLiteOpenHelper的实现,第二是数据库ContentProvider的实现。数据库的实体包含了数据库实体以及相关的操作,ContentProvider负责数据库内容的访问接口实现。

1、Launcher数据库的实现

  private static class DatabaseHelper extends SQLiteOpenHelper implements LayoutParserCallback {
        private final Context mContext;
        @Thunk final AppWidgetHost mAppWidgetHost;
        private long mMaxItemId = -1;
        private long mMaxScreenId = -1;

        private boolean mNewDbCreated = false;

        @Thunk LauncherProviderChangeListener mListener;

        DatabaseHelper(Context context) {
            super(context, LauncherFiles.LAUNCHER_DB, null, DATABASE_VERSION);
            mContext = context;
            mAppWidgetHost = new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID);

            // In the case where neither onCreate nor onUpgrade gets called, we read the maxId from
            // the DB here
            if (mMaxItemId == -1) {
                mMaxItemId = initializeMaxItemId(getWritableDatabase());
            }
            if (mMaxScreenId == -1) {
                mMaxScreenId = initializeMaxScreenId(getWritableDatabase());
            }
        }
        ...
     }

相信大家对SQLiteOpenHelper 的构造方法都比较了解我们主要看下Launcher相关的

    if (mMaxItemId == -1) {
        mMaxItemId = initializeMaxItemId(getWritableDatabase());
      }
    if (mMaxScreenId == -1) {
         mMaxScreenId = initializeMaxScreenId(getWritableDatabase());
    }

通常每一个数据库表都包含一个可以自增长的id字段,但Launcher比较特殊,id字段只作为数据库表的主键存在,因此,我们每一次在桌面上增加一个组件,都需要在当前最大的id号上加1,以做新的id号这就是mMaxItemId 。

在Launcher3之前。Launcher的桌面页数是固定的,随着Launcher3的到来,桌面的页数已经修改为可以动态增加了。如果当前桌面上已经没有额外的空间来加载新增的桌面组件,Launcher3将会根据当前最大的桌面页ID再增加一个桌面页。initializeMaxScreenId方法就是用来获取最大桌面页数的。

上面我们在构造函数中创建了Launcher的数据库,下面我们将在oncreate中创建表。代码如下:

@Override
        public void onCreate(SQLiteDatabase db) {
            if (LOGD) Log.d(TAG, "creating new launcher database");

            mMaxItemId = 1;
            mMaxScreenId = 0;
            mNewDbCreated = true;

            UserManagerCompat userManager = UserManagerCompat.getInstance(mContext);
            long userSerialNumber = userManager.getSerialNumberForUser(
                    UserHandleCompat.myUserHandle());

            db.execSQL("CREATE 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 " + userSerialNumber + "," +
                    "rank INTEGER NOT NULL DEFAULT 0," +
                    "options INTEGER NOT NULL DEFAULT 0" +
                    ");");
            addWorkspacesTable(db);

            // Database was just created, so wipe any previous widgets
            if (mAppWidgetHost != null) {
                mAppWidgetHost.deleteHost();

                /**
                 * Send notification that we've deleted the {@link AppWidgetHost},
                 * probably as part of the initial database creation. The receiver may
                 * want to re-call {@link AppWidgetHost#startListening()} to ensure
                 * callbacks are correctly set.
                 */
                new MainThreadExecutor().execute(new Runnable() {

                    @Override
                    public void run() {
                        if (mListener != null) {
                            mListener.onAppWidgetHostReset();
                        }
                    }
                });
            }

            // Fresh and clean launcher DB.
            mMaxItemId = initializeMaxItemId(db);
            setFlagEmptyDbCreated();

            // When a new DB is created, remove all previously stored managed profile information.
            ManagedProfileHeuristic.processAllUsers(Collections.<UserHandleCompat>emptyList(), mContext);
        }

在这里 首先创建了一张名为favorites的表,还是看上面的代码吧 ,就不在复制一份了,这个表主要记录桌面组件自身的信息以及在桌面上的属性信息。我们看下这张表的主要字段含义。

  • _id 这是每个桌面组件在favorites表中的唯一标示,也是favorites表的主键,这个特殊的地方就是它不是自增长类型,所有只能手动确保这个字段的唯一性。

  • title 表示应用程序快捷方式的标题,

  • intent 只有在桌面上摆放的是应用程序快捷方式的时候,该字段才会有值,别的情况下它是没有值的,因为应用程序的快捷方式涉及到应用程序的启动,而对于启动应用程序而言,Intent是至关重要的。

  • container 用于标示一个快捷方式处于什么样的容器中,目前Launcher提供了两种不同的容器,分别是热键区CONTAINER_HOTSEAT,取值为-101 和桌面容器CONTAINER_DESKTOP 取值为-100.

  • screen 用于标示快捷方式所在的屏幕ID。

  • cellX cellY 这两个整型字段用来表示桌面组件在桌面容器中的位置信息。举个栗子 如果Launcher将桌面区域分为5X6,那么cellX的取值范围就是0-4 cellY取值范围就是0-5.

  • spanX spanY 这两个字段用来表示桌面组件所占据的桌面范围信息,以cellX和cellY中的栗子说明 spanX取值范围是1-5 spanY的取值范围是1-6 如果X轴和Y轴取值大于或者小于这个范围,那么组件是无法加载到桌面上的。

  • itemType 用来表示快捷方式的类型。

    • ITEM_TYPE_APPLICTION值为0 意味着这个快捷方式来自应用程序
    • ITEM_TYPE_SHORTCUT 值为1 意味着这个快捷方式来自用用程序创建的快捷方式 比如联系人的快捷方式
    • ITEM_TYPE_FOLDER 值为2 意味着这个组件是一个文件夹
    • ITEM_TYPE_LIVE_FOLDER 值为3 意味着这个组件为一个实时文件夹
    • ITEM_TYPE_APPWIDGET 值为4 意味着这个组件是一个桌面小部件
    • ITEM_TYPE_WIDGET_CLOCK 值为1000 意味着这个组件是一个时钟桌面小部件
    • ITEM_TYPE_WIDGET_SEARCH 值为1001 意味着这个组件是搜索桌面小部件
    • ITEM_TYPE_WIDGET_PHOTO_FRAME 值为1002 意味着这个组件是相册桌面小部件
  • appWidgetId 该字段在favorites中被定义为不能为空并且默认值是-1的整型字段 桌面上除了可以加载不同的应用程序快捷方式以及文件夹等常见的桌面组件外,还可以加载应用程序提供的桌面小部件 这也是Android的特色之一,而桌面小部件主要依赖AppWidgetHost才能运行,每个应用程序都可以创建自己的AppWidgetHost来加载其他或者本应用提供的桌面小部件,每一个桌面小部件在其加载的AppWidgetHost中都被赋予了一个ID,来标示这个桌面小部件,appWidgetId 就是为了保存Launcher创建的AppWidgetHost中某一个桌面小部件的ID 如果桌面加载的并非小部件这个id将是-1 否则为大于或者等于0的值。

  • iconType 表示当此快捷方式需要图标的时候,可能需要保持的图标信息。

  • iconPackage iconResource : iconPackage 描述了图标来用的应用程序包名,iconResource 记录了该资源的ID号

  • icon 用于保持图片的实体

  • uri 当桌面的快捷方式为一个网页链接的时候,这个字段将会保持这个链接的地址否则为null

  • profileId 当前桌面组件所属的用户ID

创建好数据表后,Launcher需要在第一次启动或者数据库被清理的情况下创建页面配置信息表,这张表的目的是保存当前桌面中包含的桌面页的信息

private void addWorkspacesTable(SQLiteDatabase db) {
            db.execSQL("CREATE TABLE " + TABLE_WORKSPACE_SCREENS + " (" +
                    LauncherSettings.WorkspaceScreens._ID + " INTEGER PRIMARY KEY," +
                    LauncherSettings.WorkspaceScreens.SCREEN_RANK + " INTEGER," +
                    LauncherSettings.ChangeLogColumns.MODIFIED + " INTEGER NOT NULL DEFAULT 0" +
                    ");");
        }

以上就是Launcher数据库的创建过程。

2、接下来我们看下Launcher的ContentProvider

Launcher的数据库操作都封装在LauncherProvider中,我们在做应用程序开发的时候要对外提供数据,都是使用ContentProvider对数据进行一次包装,然后通过它对外提供数据,这是因为数据库文件往往被创建在应用程序的私有空间,通过ContentProvider可实现跨进程间的访问。Launcher也采用了这种方式。

温馨提示: 每一个ContentProvider的创建都需要比应用程序创建的更早,当在
应用程序清单文件中配置了ContentProvider节点的时候,当应用程序第一次启动
时,框架层就回先将此ContentProvider创建并发布出去。主要就是调用了
ContentProvider的OnCreate方法

Launcher也是遵循这个原则的

 @Override
    public boolean onCreate() {
        final Context context = getContext();
        StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
        mOpenHelper = new DatabaseHelper(context);
        StrictMode.setThreadPolicy(oldPolicy);
        LauncherAppState.setLauncherProvider(this);
        return true;
    }

这里应该都好理解。

通过ContentProvider进行数据库操作都需要通过适当的URI,并配以不同的条件,Launcher的数据库提供者提供了一个专门用于保存并确保这些信息合法的类SqlArguments。

它有三个成员变量

  //需要查询的表名
  public final String table;
  //SQL的查询条件
  public final String where;
  //保存了where条件中所需的参数
  public final String[] args;

知道了成员变量的含义这个函数就很好理解了 这里就不在分析了。

准备知识都完成后 我们继续看下LauncherProvider的增删查改

  • 查询
 @Override
    public Cursor query(Uri uri, String[] projection, String selection,
            String[] selectionArgs, String sortOrder) {

        SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
        qb.setTables(args.table);

        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        Cursor result = qb.query(db, projection, args.where, args.args, null, null, sortOrder);
        result.setNotificationUri(getContext().getContentResolver(), uri);

        return result;
    }

这里需要注意的是,在Launcher的Provider创建的时候创建了mOpenHelper实例,当需要对数据库进行操作的时候,需要从中获取数据库的实例,只有通过这个实例才能进行查询操作。

  • 修改
  @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
    //更新参数:表名 where条件以及条件参数的设置
        SqlArguments args = new SqlArguments(uri, selection, selectionArgs);

        addModifiedTime(values);
        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        int count = db.update(args.table, values, args.where, args.args);
        //更新通知
        if (count > 0) notifyListeners();

        reloadLauncherIfExternal();
        return count;
    }
  • 增加
 @Override
        public Uri insert(Uri uri, ContentValues initialValues) {
            SqlArguments args = new SqlArguments(uri);

           ...

            SQLiteDatabase db = mOpenHelper.getWritableDatabase();
            addModifiedTime(initialValues);
            final long rowId = dbInsertAndCheck(mOpenHelper, db, args.table, null, initialValues);
            //插入成功rowId 是大于0的
            if (rowId < 0) return null;
            //如果插入成功则依据输入的URI为基础拼接上返回的id形成新的URI
            uri = ContentUris.withAppendedId(uri, rowId);
            notifyListeners();

            if (Utilities.ATLEAST_MARSHMALLOW) {
                reloadLauncherIfExternal();
            } else {
                // Deprecated behavior to support legacy devices which rely on provider callbacks.
                LauncherAppState app = LauncherAppState.getInstanceNoCreate();
                if (app != null && "true".equals(uri.getQueryParameter("isExternalAdd"))) {
                    app.reloadWorkspace();
                }

                String notify = uri.getQueryParameter("notify");
                if (notify == null || "true".equals(notify)) {
                    getContext().getContentResolver().notifyChange(uri, null);
                }
            }
            return uri;
    }
  • 删除
 @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        SqlArguments args = new SqlArguments(uri, selection, selectionArgs);

        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        int count = db.delete(args.table, args.where, args.args);
        if (count > 0) notifyListeners();

        reloadLauncherIfExternal();
        return count;
    }

好了以上就是Launcher数据库的实现了,当然Launcher除了提供这些常用的访问方式外,还在内部提供了一些接口工具,以便Launcher的其他组件可以方便的使用Launcher数据库功能。这些比较零散的知识就需要大家在实际开发中去分析了。

android M Launcher之数据库实现的更多相关文章

  1. android M Launcher之LauncherModel (二)

    上一篇我们通过LauncherModel的创建 ,实例化,以及与LauncherModel之间的沟通方式.初步了解了LauncherModel一些功能及用法,如果对LauncherModel一系列初始 ...

  2. Android学习---如何创建数据库,SQLite(onCreate,onUpgrade方法)和SQLiteStudio的使用

    一.android中使用什么数据库? SQLite是遵守ACID的关系数据库管理系统,它包含在一个相对小的C程式庫中.它是D.RichardHipp建立的公有领域项目.SQLite 是一个软件库,实现 ...

  3. Android 系统API实现数据库的增删改查和SQLite3工具的使用

    在<Android SQL语句实现数据库的增删改查>中介绍了使用sql语句来实现数据库的增删改查操作,本文介绍Android 系统API实现数据库的增删改查和SQLite3工具的使用. 系 ...

  4. Android版本升级同时Sqlite数据库的升级及之前数据的保留

    http://www.cnblogs.com/wang340/archive/2013/05/06/3063135.html http://www.eoeandroid.com/forum.php?m ...

  5. 【转】Android 使用ORMLite 操作数据库

    Android 使用ORMLite 操作数据库   用过ssh,s2sh的肯定不会陌生 ,应该一学就会 第一步: 下载ormlite-android-4.41.jar和ormlite-core-4.4 ...

  6. Xamarin.Android 使用 SQLiteOpenHelper 进行数据库操作

    一.前言 在手机中进行网络连接不仅是耗时也是耗电的,而耗电却是致命的.所以我们就需要数据库帮助我们存储离线数据,以便在用户未使用网络的情况下也可以能够使用应用的部分功能,而在需要网络连接的功能上采用提 ...

  7. Android存储之SQLite数据库

    Android存储之SQLite数据库数据库 创建数据库 package --; import android.content.Context; import android.database.sql ...

  8. android M Launcher之LauncherModel (三)

    通过前两篇的分析,我们已经知道了LauncherModel的初始化及工作流程,如果您还不熟悉的话请看前两篇博文 android M Launcher之LauncherModel (一) android ...

  9. Android 开发中 SQLite 数据库的使用

    SQLite 介绍 SQLite 一个非常流行的嵌入式数据库,它支持 SQL 语言,并且只利用很少的内存就有很好的性能.此外它还是开源的,任何人都可以使用它.许多开源项目((Mozilla, PHP, ...

随机推荐

  1. ashx页面怎么调用Handler的Session

    aspx里面直接可以用Session["Name"]进行赋值和取值,ashx中就得继承接口IRequiresSessionState.然后使用! 实现: public class ...

  2. 存储过程学习笔记(SQL数据库

    一.   存储过程简介 Sql Server的存储过程是一个被命名的存储在服务器上的Transacation-Sql语句集合,是封装重复性工作的一种方法,它支持用户声明的变量.条件执行和其他强大的编程 ...

  3. AutoCAD常用操作命令

    前言 最近工作需要使用AutoCAD画图,在这里记一下用到的一些常用操作,都是一些很基础的操作,希望对大家有帮助. 修剪 如果两条直线相交,你需要剪掉多余的部分,可以用修剪命令TR. 我们先画两条相交 ...

  4. OpenGL鼠标拖拽

    前序 前段时间学习3D MAX,一对比就发现差距是相当大.我也做了一个三维展示的小软件,但是拖拽操作非常不友好,如果场景的尺寸特别大,会导致拖不动,尺寸过小会导致轻轻拖一下,模型就不知道飞哪去了.我是 ...

  5. [LeetCode] Subarray Sum Equals K 子数组和为K

    Given an array of integers and an integer k, you need to find the total number of continuous subarra ...

  6. [CTSC 1999]拯救大兵瑞恩&[网络流24题]孤岛营救问题

    Description $1944$ 年,特种兵麦克接到国防部的命令,要求立即赶赴太平洋上的一个孤岛,营救被敌军俘虏的大兵瑞恩.瑞恩被关押在一个迷宫里,迷宫地形复杂,但幸好麦克得到了迷宫的地形图.迷宫 ...

  7. 【NOIP2016TG】solution

    传送门:https://www.luogu.org/problem/lists?name=&orderitem=pid&tag=83%7C33 D1T1(toys) 题意:有n个小人, ...

  8. [bzoj5016][Snoi2017]一个简单的询问

    来自FallDream的博客,未经允许,请勿转载,谢谢. 给你一个长度为N的序列ai,1≤i≤N和q组询问,每组询问读入l1,r1,l2,r2,需输出   get(l,r,x)表示计算区间[l,r]中 ...

  9. bzoj4361isn 容斥+bit优化dp

    4361: isn Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 375  Solved: 186[Submit][Status][Discuss] ...

  10. ds4700更换控制器导致磁盘无法识别-处理方法

    更换DS4700控制器的悲与喜 机型:DS4700     原微码:06.23.xx 更换部件:控制器 (使用的控制器微码07.60.52.00) 误操作过程: 1,关掉存储换控制器 --(兄弟们千万 ...