【源码剖析】Launcher 8.0 源码 (13) --- Launcher 启动流程 第六步之LoadWorkspace 第1小步(1)call LauncherProvider

Launcher8.0启动流程的第六步生成布局,Launcher启动流程中最重要,最复杂,也是网上各种关于Launcher讲解的最浓墨淡彩描述的部分。其中的一部分loadworkspace就非常复杂涉及很多代码。

从逻辑Loadworkspace一共可以分两小步,第1小步是获取数据库,如果有数据库则直接进入第2小步,如果数据库为空则从xml布局文件生成数据库。第2小步,从数据库读取信息存放到sBgDataModel中。Loadworkspace的结果就是sBgDataModel。

 

由于Loadworkspace的源码特别长,我手里的原生代码LauncherModel.java里面第890行到1381行,共计491行代码。

 

所以代码我会分批次一点一点且简化过后粘出来。 和前文的简化方法一致去掉性能相关代码,保留功能相关。比如,try catch ,判空,特殊情况的return,以及高概率为确定结果的if判断,都会省略掉。

 

loadWorkspace()的第1小步:判断数据库是否为空,如果为空,将默认布局读取到数据库。

第一小步的实际操作在LauncherProvider中,loadWorkspace通过call方法完成。

 

private void loadWorkspace() {
    final Context context = mContext;
    final ContentResolver contentResolver = context.getContentResolver();
    final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
    final boolean isSafeMode = pmHelper.isSafeMode();
    final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
    final DeepShortcutManager shortcutManager = DeepShortcutManager.getInstance(context);
    final boolean isSdCardReady = Utilities.isBootCompleted();
    final MultiHashMap<UserHandle, String> pendingPackages = new MultiHashMap<>();

if (clearDb) {
        LauncherSettings.Settings.call(contentResolver,
                LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
    }
    LauncherSettings.Settings.call(contentResolver,
            LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES);

 

首先是创建了一些对象,这些对象,在Launcher启动流程之前大多都已经创建过,这里是获取实例。而后,我们关注到有LauncherSettings call的两个方法。

其中一个基本走不到

LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB

还有一个则一定会运行:

LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES

 

这里是call的contentResolver的内容,而Launcher的contentprovider名字叫做LauncherProvider。所以,关注到LauncherProvider的call方法。

 

和以上两个call有关的代码如下:

@Override
public Bundle call(String method, final String arg, final Bundle extras) {

createDbIfNotExists();

   switch (method) {
        case LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG: {
            clearFlagEmptyDbCreated();
            return null;
        }
        case LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES: {
            loadDefaultFavoritesIfNecessary();
            return null;
        }
    }
}

 

以上是读取布局的方法。桌面布局有默认布局和自定义布局。默认布局是在首次开机,恢复出厂设置,清空桌面数据的时候。Launcher运行期间会把桌面布局存在数据库里,而开机时会去读取数据库,根据数据库来决定布局。

 

根据call方法,在call方法开始会检查是否有数据库,createDbIfNotExists(),如果没有则创建一次数据库,代码如下:

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

mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
    }

}

即如果数据库为空就会创建数据库。实际使用时,在首次开机,恢复出厂设置,清空桌面数据的时候数据库为空,这种情况下就会创建一个空的数据库。

DatabaseHelper(Context context, Handler widgetHostResetHandler) {
    this(context, widgetHostResetHandler, LauncherFiles.LAUNCHER_DB);
    if (!tableExists(Favorites.TABLE_NAME) || !tableExists(WorkspaceScreens.TABLE_NAME)) {
        addFavoritesTable(getWritableDatabase(), true);
        addWorkspacesTable(getWritableDatabase(), true);
    }
    initIds();
}

 

在其中创建数据库还创建两个table,图标和屏幕:addFavoritesTable,addWorkspacesTable

 

先看addFavoritesTable

private void addFavoritesTable(SQLiteDatabase db, boolean optional) {
    Favorites.addTableToDb(db, getDefaultUserSerial(), optional);
}

 

里面列出了用于储存图标参数的数据库的各项参数:id 、title 、intent 、container等

 

public static void addTableToDb(SQLiteDatabase db, long myProfileId, boolean optional) {
        String ifNotExists = optional ? " IF NOT EXISTS " : "";
        db.execSQL("CREATE TABLE " + ifNotExists + TABLE_NAME + " (" +
                "_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," +
                "iconPackage TEXT," +
                "iconResource TEXT," +
                "icon BLOB," +
                "appWidgetProvider TEXT," +
                "modified INTEGER NOT NULL DEFAULT 0," +
                "restored INTEGER NOT NULL DEFAULT 0," +
                "profileId INTEGER DEFAULT " + myProfileId + "," +
                "rank INTEGER NOT NULL DEFAULT 0," +
                "options INTEGER NOT NULL DEFAULT 0" +
                ");");
    }
}

 

这里解说一些重要数据库的含义:

Container:判断属于当前图标属于哪里:包括文件夹、workspace和hotseat。其中如果图标属于文件夹则,图标的container值就是其id值。

Intent:点击的时候启动的目标。

cellX 和cellY :图标起始于第几行第几列。

spanX 和spanY :widget占据格子数。

itemType :区分具体类型。类型包括,图标,文件夹,widget等

 

接着是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" +");");
}

 

这个table是储存屏幕数的。这两者构成了Launcher的整个数据库。

 

所以,Launcher的数据库由保存屏幕数和保存桌面上的图标(包括文件夹、widget等)组成。

 

回到loadWorkspace()方法中来,loadWorkspace()第一小步创建数据库,而后,会进行一个简单的判断clearDb,这里会在一些特殊情况下才为ture。正常情况是不会去call这个方法。

 

而这个call是删除EMPTY_DATABASE_CREATED开关,实际等同于此开关置为false。

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

 

于是,在loadWorkspace()的开始实际进行的第一个操作是call LauncherProvider来判断是否有桌面布局数据库,从而好读取数据。如果没有用户布局数据则采用loadDefaultFavoritesIfNecessary()方法。实际上没有用户布局数据的场景就是第一次创建数据库的场景。所以loadDefaultFavoritesIfNecessary的含义是读取默认布局,仅在首次开机,恢复出厂设置或清除Launcher数据的时候使用。

 

以上是第一小步的第一个操作,判断有没有用户数据。

 

 

synchronized private void loadDefaultFavoritesIfNecessary() {
    SharedPreferences sp = Utilities.getPrefs(getContext());
    if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) {
        Log.d(TAG, "loading default workspace");
        AppWidgetHost widgetHost = mOpenHelper.newLauncherWidgetHost();
        AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction(widgetHost);


        if (loader == null) {
            loader = AutoInstallsLayout.get(getContext(),widgetHost, mOpenHelper);
        }


        if (loader == null) {
            final Partner partner = Partner.get(getContext().getPackageManager());
            if (partner != null && partner.hasDefaultLayout()) {
                final Resources partnerRes = partner.getResources();
                int workspaceResId = partnerRes.getIdentifier(Partner.RES_DEFAULT_LAYOUT,
                        "xml", partner.getPackageName());
                if (workspaceResId != 0) {
                    loader = new DefaultLayoutParser(getContext(), widgetHost,
                            mOpenHelper, partnerRes, workspaceResId);
                }
            }
        }
        final boolean usingExternallyProvidedLayout = loader != null;


        if (loader == null) {
            loader = getDefaultLayoutParser(widgetHost);
        }


        mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
        if ((mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader) <= 0)
                && usingExternallyProvidedLayout) {
            mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
            mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(),
                    getDefaultLayoutParser(widgetHost));
        }
        clearFlagEmptyDbCreated();
    }
}

 

 

 

loadDefaultFavoritesIfNecessary方法分为获取loader,和将读取的布局存入数据库。

 

loadDefaultFavoritesIfNecessary第一小步获取loader对象:

 

首先要分析一个重要方法 :

这个是获取AutoInstallsLayout 的方法。

AutoInstallsLayout.get(ctx, packageName, targetResources,
                    widgetHost, mOpenHelper);

获取AutoInstallsLayout方法,首先获取layoutName,这个名字就是xml名字。

在原生代码res/xml/ 文件夹下面有default_workspace.xml  default_workspace_3x3.xml

default_workspace_4x4.xml default_workspace_5x5.xml default_workspace_5x6.xml  一共5个布局文件。

 

下面则是采用多个方式来获取布局xml,因为不知道xml文件的具体名字所以采用递进的方法来获取。

 

源码如下:

static AutoInstallsLayout get(Context context, String pkg, Resources targetRes,
        AppWidgetHost appWidgetHost, LayoutParserCallback callback) {
    InvariantDeviceProfile grid = LauncherAppState.getIDP(context);

      String layoutName = String.format(Locale.ENGLISH, FORMATTED_LAYOUT_RES_WITH_HOSTEAT,
            (int) grid.numColumns, (int) grid.numRows, (int) grid.numHotseatIcons);
    int layoutId = targetRes.getIdentifier(layoutName, "xml", pkg);

    // Try with only grid size
    if (layoutId == 0) {
        Log.d(TAG, "Formatted layout: " + layoutName
                + " not found. Trying layout without hosteat");
        layoutName = String.format(Locale.ENGLISH, FORMATTED_LAYOUT_RES,
                (int) grid.numColumns, (int) grid.numRows);
        layoutId = targetRes.getIdentifier(layoutName, "xml", pkg);
    }

    // Try the default layout
    if (layoutId == 0) {
        Log.d(TAG, "Formatted layout: " + layoutName + " not found. Trying the default layout");
        layoutId = targetRes.getIdentifier(LAYOUT_RES, "xml", pkg);
    }

    if (layoutId == 0) {
        Log.e(TAG, "Layout definition not found in package: " + pkg);
        return null;
    }


    return new AutoInstallsLayout(context, appWidgetHost, callback, targetRes, layoutId,
            TAG_WORKSPACE);
}

 

 

首先是default_workspace_4x4x5.xml这种类型的名字,根据本手机的行数列数以及hotseat的个数来确定读取哪种布局

      String layoutName = String.format(Locale.ENGLISH, FORMATTED_LAYOUT_RES_WITH_HOSTEAT,
            (int) grid.numColumns, (int) grid.numRows, (int) grid.numHotseatIcons);
    int layoutId = targetRes.getIdentifier(layoutName, "xml", pkg);

 

其次根据default_workspace_4x4.xml这种类型的名字,根据本手机的行数列数来确定读取哪种布局

        layoutName = String.format(Locale.ENGLISH, FORMATTED_LAYOUT_RES,
                (int) grid.numColumns, (int) grid.numRows);
        layoutId = targetRes.getIdentifier(layoutName, "xml", pkg);

 

最后是直接默认的布局default_workspace.xml

 layoutId = targetRes.getIdentifier(LAYOUT_RES, "xml", pkg);

 

public AutoInstallsLayout(Context context, AppWidgetHost appWidgetHost,
        LayoutParserCallback callback, Resources res,
        int layoutId, String rootTag) {
    mContext = context;
    mAppWidgetHost = appWidgetHost;
    mCallback = callback;

    mPackageManager = context.getPackageManager();
    mValues = new ContentValues();
    mRootTag = rootTag;

    mSourceRes = res;
    mLayoutId = layoutId;

    mIdp = LauncherAppState.getIDP(context);
    mRowCount = mIdp.numRows;
    mColumnCount = mIdp.numColumns;
}

而后把有关信息保存在AutoInstallsLayout,返回给调用的程序。

 

总结,AutoInstallsLayout.get根据传入的参数,读取对应的xml文件。

 

 

于是,loadDefaultFavoritesIfNecessary第一小步获取loader对象,根据代码其实是获取各个场景下的布局,先是判断一些特殊情况,如果特殊情况没有xml布局,则读取本地res/xml的布局。

 

回到loadDefaultFavoritesIfNecessary代码:

synchronized private void loadDefaultFavoritesIfNecessary() {
    SharedPreferences sp = Utilities.getPrefs(getContext());
    if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) {
        Log.d(TAG, "loading default workspace");
        AppWidgetHost widgetHost = mOpenHelper.newLauncherWidgetHost();
        AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction(widgetHost);

 

Loader第一个是判断有没有Restriction。

 targetResources通过关键字RESTRICTION_PACKAGE_NAME 寻找,符合package名字为"workspace.configuration.package.name"的应用,如果有这个应用,则从其中获取布局。

源码如下:

private AutoInstallsLayout createWorkspaceLoaderFromAppRestriction(AppWidgetHost widgetHost) {
    Context ctx = getContext();
    UserManager um = (UserManager) ctx.getSystemService(Context.USER_SERVICE);
    Bundle bundle = um.getApplicationRestrictions(ctx.getPackageName());
    if (bundle == null) {
        return null;
    }
    String packageName = bundle.getString(RESTRICTION_PACKAGE_NAME);
    if (packageName != null) {
        try {
            Resources targetResources = ctx.getPackageManager()
                    .getResourcesForApplication(packageName);
            return AutoInstallsLayout.get(ctx, packageName, targetResources,
                    widgetHost, mOpenHelper);
        } catch (NameNotFoundException e) {
            return null;
        }
    }
    return null;
}

 

 

接下来是第二种loader的情况:AutoInstallsLayout。

 if (loader == null) {
            loader = AutoInstallsLayout.get(getContext(),widgetHost, mOpenHelper);
        }

依然是AutoInstallsLayout,这次是从intent 关键字ACTION_LAUNCHER_CUSTOMIZATION即是"android.autoinstalls.config.action.PLAY_AUTO_INSTALL"来获取,autoinstall可以在手机中集成对应工具,这样默认布局除了手机自带的应用外,还可以提供一些自动下载的应用。 

源码如下:

static AutoInstallsLayout get(Context context, AppWidgetHost appWidgetHost,
        LayoutParserCallback callback) {
    Pair<String, Resources> customizationApkInfo = Utilities.findSystemApk(
            ACTION_LAUNCHER_CUSTOMIZATION, context.getPackageManager());
    if (customizationApkInfo == null) {
        return null;
    }
    return get(context, customizationApkInfo.first, customizationApkInfo.second,
            appWidgetHost, callback);
}

 

当两种AutoInstallsLayout失败后,就到第三种loader是判断有没有第三方应用提供布局。这个功能是提供给提供布局的第三方应用。第三方应用提供一定的布局,然后清空Launcher的数据,之后Launcher的布局就和第三方一样了,

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值