Launcher3之Workspace数据库首次初始化分析

引言

在Android手机上,我们通常说的桌面其实就是launcher应用,更狭义一点就是workspace,workspace是桌面在实现时的抽象定义。桌面上显示的应用图标、文件夹和小部件都是显示在workspace中的,我们可以增删应用快捷图标,增删文件夹,增删小部件,但是这些桌面上显示的元素状态是怎么保存下来的呢?答案是,launcher使用了一个专门的数据库保存了这些状态,以便下次重启后依然能按照最新的变动显示。

相关实现类介绍

LauncherProvider:虽然launcher.db存储的数据,基本都是供launcher应用本身访问,但是原生launcher中基本所有的workspace相关数据库操作,都是通过这个provider统一访问的。

DatabaseHelper:LauncherProvider的静态内部类,该类继承自SQLiteOpenHelper,封装了数据库相关操作。

workspace数据存储基本围绕这两个类进行,学习launcher是怎么也绕不开的。

正文

1、launcher.db创建过程分析

先看下LauncherProvider onCreate生命周期方法:

    @Override
    public boolean onCreate() {
        if (FeatureFlags.IS_DOGFOOD_BUILD) {
            Log.d(TAG, "Launcher process started");
        }
        mListenerHandler = new Handler(mListenerWrapper);

        // The content provider exists for the entire duration of the launcher main process and
        // is the first component to get created.
        MainProcessInitializer.initialize(getContext().getApplicationContext());
        return true;
    }

从打印的log内容和注释可以知道,LauncherProvider是launcher中第一个被初始化的组件,可以看出,workspace的显示应该是需要基于数据存储的,不然没必要优先加载,尤其在launcher这样用户体验很重要的应用当中。

LauncherProvider在onCreate时并没有立即创建数据库和执行建表操作,那是在什么地方呢?
我们看LauncherProvider另一个方法:

    protected synchronized void createDbIfNotExists() {
        if (mOpenHelper == null) {
            mOpenHelper = new DatabaseHelper(getContext(), mListenerHandler);

            if (RestoreDbTask.isPending(getContext())) {
                if (!RestoreDbTask.performRestore(mOpenHelper)) {
                    mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
                }
                // Set is pending to false irrespective of the result, so that it doesn't get
                // executed again.
                RestoreDbTask.setPending(getContext(), false);
            }
        }
    }

通观launcher,就这一个地方实例化了DatabaseHelper,其他需要操作数据库的地方,都必须调用此方法先创建数据库。如LauncherProvider中query方法中的使用:

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
            String[] selectionArgs, String sortOrder) {
        createDbIfNotExists();
		......
		......        

        return result;
    }

继续跟进去看下上面实例化DatabaseHelper的构造函数:

        DatabaseHelper(Context context, Handler widgetHostResetHandler) {
            this(context, widgetHostResetHandler, LauncherFiles.LAUNCHER_DB);
            // Table creation sometimes fails silently, which leads to a crash loop.
            // This way, we will try to create a table every time after crash, so the device
            // would eventually be able to recover.
            if (!tableExists(Favorites.TABLE_NAME) || !tableExists(WorkspaceScreens.TABLE_NAME)) {
                Log.e(TAG, "Tables are missing after onCreate has been called. Trying to recreate");
                // This operation is a no-op if the table already exists.
                addFavoritesTable(getWritableDatabase(), true);
                addWorkspacesTable(getWritableDatabase(), true);
            }

            initIds();
        }

        /**
         * Constructor used in tests and for restore.
         */
        public DatabaseHelper(
                Context context, Handler widgetHostResetHandler, String tableName) {
            super(context, tableName, SCHEMA_VERSION);
            mContext = context;
            mWidgetHostResetHandler = widgetHostResetHandler;
        }

咋一看有两个构造函数,上面实例化时只用到了第一个,但是通过调用第一个会间接调用到第二个构造函数,进而调用到SQLiteOpenHelper中的父构造函数。
到这里,在DatabaseHelper构造函数被执行完后,launcher.db其实已经创建出来了,下面接着看数据中的建表过程。

2、workspaceScreens和favorites表创建过程分析

launcher.db数据库中包含两张表,分别是:

  • workspaceScreens:对应桌面的每一页,桌面可以左右滑动几页,就有几条记录。
  • favorites:用于存储桌面上显示的元素,包含应用快捷图标、文件夹、小部件,每一个元素对应一条记录。

上面DatabaseHelper的第一个构造函数中的建表的操作,看起来有点费解,为什么放在这个地方,不是应该放在onCreate中做吗?我们先放一放,先看看DatabaseHelper onCreate生命周期方法的实现:

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

            mMaxItemId = 1;
            mMaxScreenId = 0;

            addFavoritesTable(db, false);
            addWorkspacesTable(db, false);

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

果然,onCreate方法中也有建表操作。如果是首次创建数据库,DatabaseHelper在被实例化后,触发SQLiteDatabase的创建,同时会回调onCreate生命周期方法。

在看上面代码时,我产生了一个疑问?DatabaseHelper构造和onCreate方法中都包含了建表操作,不是重复了吗?
通过打印日志跟踪,发现实际建表操作是在onCreate方法中进行的,DatabaseHelper在调用父类构造函数后其实并不会触发数据库创建,真正触发的是getWritableDatabase方法。因此,DatabaseHelper构造函数中建表操作并不会执行,不会发生重复建表。
再次单独贴下上面DatabaseHelper构造函数中的代码,以示强调:

            if (!tableExists(Favorites.TABLE_NAME) || !tableExists(WorkspaceScreens.TABLE_NAME)) {
                Log.e(TAG, "Tables are missing after onCreate has been called. Trying to recreate");
                // This operation is a no-op if the table already exists.
                addFavoritesTable(getWritableDatabase(), true);
                addWorkspacesTable(getWritableDatabase(), true);
            }

看下addWorkspacesTable建表方法实现:

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

构造函数中,在addWorkspacesTable方法调用时传递的optional参数为true,避免了重复建表。

上面onCreate方法最后一行代码,也值得分析一下,方法实现如下:

        protected void onEmptyDbCreated() {
			......
			......

            // Set the flag for empty DB
            Utilities.getPrefs(mContext).edit().putBoolean(EMPTY_DATABASE_CREATED, true).commit();
        }

这里需要关注的就是最后一行代码,这里记录了一个EMPTY_DATABASE_CREATED标记,表示空数据库创建了,加这个标记有什么用呢?肯定有其他地方用到这个判断。

继续追踪代码,发现在loadWorkspace时,loadDefaultFavoritesIfNecessary方法用到了此标记:

    synchronized private void loadDefaultFavoritesIfNecessary() {
        SharedPreferences sp = Utilities.getPrefs(getContext());

        if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) {
            Log.d(TAG, "loading default workspace");
			......
			......

            clearFlagEmptyDbCreated();
        }
    }

    private void clearFlagEmptyDbCreated() {
        Utilities.getPrefs(getContext()).edit().remove(EMPTY_DATABASE_CREATED).commit();
    }

这里使用这个标记判断是否需要加载默认的workspace配置数据到数据库,最后一行代码clearFlagEmptyDbCreated方法调用,用于清空了这个标记,下次就不需要再次加载了。
从中得出一个结论,launcher正常在首次加载时,才会加载默认配置到数据库,其他情况是不会加载的。

3、DatabaseHelper onCreate生命周期之外创建表

        /**
         * Clears all the data for a fresh start.
         */
        public void createEmptyDB(SQLiteDatabase db) {
            try (SQLiteTransaction t = new SQLiteTransaction(db)) {
                db.execSQL("DROP TABLE IF EXISTS " + Favorites.TABLE_NAME);
                db.execSQL("DROP TABLE IF EXISTS " + WorkspaceScreens.TABLE_NAME);
                onCreate(db);
                t.commit();
            }
        }

DatabaseHelper中还单独封装了上面的方法,用于在一些数据异常情况下,删除表并重新创建空表,重新载入数据,避免launcher应用发生异常。

总结

到这里,大体分析完了launcher.db和表的创建过程,此篇暂时分析到这,具体loadDefaultFavoritesIfNecessary是如何加载默认配置到数据库的,下篇我们再详细分析。

猜你喜欢

转载自blog.csdn.net/qinhai1989/article/details/84797849
今日推荐