Launcher3-客制化Workspace的四种方法

Launcher的workspace加载布局资源有四种方式,加载入口是在LauncherProvider.java,理解好这四种加载方式,对我们开发launcher,定制workspace,两个应用直接资源的共享都有很好的帮助。

在launcher启动时创建ContentProvider,进而调用call方法

packages\apps\Launcher3\src\com\android\launcher3\LauncherProvider.java
public Bundle call(String method, final String arg, final Bundle extras) {
    if (Binder.getCallingUid() != Process.myUid()) {
        return null;
    }
    createDbIfNotExists();//创建DB
    switch (method) {
        ..............
        case LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES: {
            loadDefaultFavoritesIfNecessary();
            return null;
        }
       .................
    }
    return null;
}
packages\apps\Launcher3\src\com\android\launcher3\LauncherProvider.java
synchronized private void loadDefaultFavoritesIfNecessary() {
    if (getFlagEmptyDbCreated(getContext(), mOpenHelper.getDatabaseName())) {//从sharepreferences读取数据库是否为空
        Log.d(TAG, "loading default workspace");

        AppWidgetHost widgetHost = mOpenHelper.newLauncherWidgetHost();
        AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction(widgetHost);//客制化workspace的第一种方法
        if (loader == null) {
            loader = AutoInstallsLayout.get(getContext(), widgetHost, mOpenHelper);//客制化workspace的第二种方法
        }
        if (loader == null) {//客制化workspace的第三种方法
            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) {//客制化workspace的第四种方法
            loader = getDefaultLayoutParser(widgetHost);
        }
        .............
    }
}

workspace加载布局资源的四种方式,从第一种开始依次获取资源,如获取不到则获取下一种。

客制化workspace的第一种方法

private AutoInstallsLayout createWorkspaceLoaderFromAppRestriction(AppWidgetHost widgetHost) {
    Context ctx = getContext();
    InvariantDeviceProfile grid = LauncherAppState.getIDP(ctx);
    String authority = Settings.Secure.getString(ctx.getContentResolver(),
            "launcher3.layout.provider");//通过读取settings得到contentprovider的authority部分
    if (TextUtils.isEmpty(authority)) {
        return null;
    }

    ProviderInfo pi = ctx.getPackageManager().resolveContentProvider(authority, 0);
    if (pi == null) {
        Log.e(TAG, "No provider found for authority " + authority);
        return null;
    }
    Uri uri = new Uri.Builder().scheme("content").authority(authority).path("launcher_layout")
            .appendQueryParameter("version", "1")
            .appendQueryParameter("gridWidth", Integer.toString(grid.numColumns))
            .appendQueryParameter("gridHeight", Integer.toString(grid.numRows))
            .appendQueryParameter("hotseatSize", Integer.toString(grid.numHotseatIcons))
            .build();//依据authority和行列数等信息组合创建uri

    try (InputStream in = ctx.getContentResolver().openInputStream(uri)) {//通过流来读取contentprovider的信息
        // Read the full xml so that we fail early in case of any IO error.
        String layout = new String(IOUtils.toByteArray(in));
        XmlPullParser parser = Xml.newPullParser();
        parser.setInput(new StringReader(layout));//创建字符串流,并用pull解析

        Log.d(TAG, "Loading layout from " + authority);
        return new AutoInstallsLayout(ctx, widgetHost, mOpenHelper,
                ctx.getPackageManager().getResourcesForApplication(pi.applicationInfo),
                () -> parser, AutoInstallsLayout.TAG_WORKSPACE);
    } catch (Exception e) {
        Log.e(TAG, "Error getting layout stream from: " + authority , e);
        return null;
    }
}

客制化workspace的第二种方法

static final String ACTION_LAUNCHER_CUSTOMIZATION =
        "android.autoinstalls.config.action.PLAY_AUTO_INSTALL";
private static final String FORMATTED_LAYOUT_RES_WITH_HOSTEAT = "default_layout_%dx%d_h%s";
private static final String FORMATTED_LAYOUT_RES = "default_layout_%dx%d";
private static final String LAYOUT_RES = "default_layout";
packages\apps\Launcher3\src\com\android\launcher3\AutoInstallsLayout.java
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;
    }
    String pkg = customizationApkInfo.first;
    Resources targetRes = customizationApkInfo.second;
    InvariantDeviceProfile grid = LauncherAppState.getIDP(context);

    String layoutName = String.format(Locale.ENGLISH, FORMATTED_LAYOUT_RES_WITH_HOSTEAT,
        grid.numColumns, grid.numRows, grid.numHotseatIcons);//通过设备的行列、hotseat组合成文件名,即default_layout_%dx%d_h%s
    int layoutId = targetRes.getIdentifier(layoutName, "xml", pkg);//从pkg这个包里面获取default_layout_%dx%d_h%s.xml的id

    // Try with only grid size
    if (layoutId == 0) {//default_layout_%dx%d_h%s.xml的id获取失败
        Log.d(TAG, "Formatted layout: " + layoutName
                + " not found. Trying layout without hosteat");
        layoutName = String.format(Locale.ENGLISH, FORMATTED_LAYOUT_RES,
            grid.numColumns, grid.numRows);//通过设备的行列组合成文件名,即default_layout_%dx%d
        layoutId = targetRes.getIdentifier(layoutName, "xml", pkg);
    }

    // Try the default layout
    if (layoutId == 0) {//default_layout_%dx%d.xml的id获取失败
        Log.d(TAG, "Formatted layout: " + layoutName + " not found. Trying the default layout");
        layoutId = targetRes.getIdentifier(LAYOUT_RES, "xml", pkg);//从pkg这个包里面获取default_layout.xml的id
    }

    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);
}
static Pair<String, Resources> findSystemApk(String action, PackageManager pm) {
    final Intent intent = new Intent(action);//action为android.autoinstalls.config.action.PLAY_AUTO_INSTALL
    for (ResolveInfo info : pm.queryBroadcastReceivers(intent, 0)) {//查找系统中含有action的广播
        if (info.activityInfo != null &&
                (info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {//action的包需为系统应用
            final String packageName = info.activityInfo.packageName;
            try {
                final Resources res = pm.getResourcesForApplication(packageName);//通过包名得到资源对象
                return Pair.create(packageName, res);//返回包名和资源对象
            } catch (NameNotFoundException e) {
                Log.w(TAG, "Failed to find resources for " + packageName);
            }
        }
    }
    return null;
}

在客制化workspace的第二种方法里面先查找系统的应用含有android.autoinstalls.config.action.PLAY_AUTO_INSTALL这个action的广播,得到这个广播的包名和资源对象,然后用包名和资源对象分别获取以有指定行列数和hotseat、行列数、都没指定的default_layout的xml文件,如获取到则用pull进行解析。

客制化workspace的第三种方法

public static final String RES_DEFAULT_LAYOUT = "partner_default_layout";
synchronized private void loadDefaultFavoritesIfNecessary() {
...................
        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());//获取partner_default_layout.xml的资源id
                if (workspaceResId != 0) {
                    loader = new DefaultLayoutParser(getContext(), widgetHost,
                            mOpenHelper, partnerRes, workspaceResId);
                }
            }
        }
.......................
}
private static final String
        ACTION_PARTNER_CUSTOMIZATION = "com.android.launcher3.action.PARTNER_CUSTOMIZATION";
public static synchronized Partner get(PackageManager pm) {
    if (!sSearched) {
        Pair<String, Resources> apkInfo = Utilities.findSystemApk(ACTION_PARTNER_CUSTOMIZATION, pm);//获取系统应用广播action为com.android.launcher3.action.PARTNER_CUSTOMIZATION的应用
        if (apkInfo != null) {
            sPartner = new Partner(apkInfo.first, apkInfo.second);
        }
        sSearched = true;
    }
    return sPartner;
}

第三种方式跟第二种比较类似,获取到含有指定广播的应用包名和资源后,直接获取partner_default_layout.xml的id,然后通过pull解析.

客制化workspace的第四种方法

private DefaultLayoutParser getDefaultLayoutParser(AppWidgetHost widgetHost) {
    InvariantDeviceProfile idp = LauncherAppState.getIDP(getContext());
    int defaultLayout = idp.defaultLayoutId;

    UserManagerCompat um = UserManagerCompat.getInstance(getContext());
    if (um.isDemoUser() && idp.demoModeLayoutId != 0) {
        defaultLayout = idp.demoModeLayoutId;
    }

    return new DefaultLayoutParser(getContext(), widgetHost,
            mOpenHelper, getContext().getResources(), defaultLayout);
}

第四种方式就是直接获取Launcher下xml的文件

总结:Launcher的workspace加载布局资源第一种方式是通过ContetProver读取文件流实现的;第二和第三种是通过查询系统应用指定广播的action得到包名,然后通过包名获取到指定的xml文件;第四种则是直接解析Launcher目录下的default_workspace_xxx.xml。我们在开发应用时,可以利用第一和第二、第三种思想,实现两个应用的资源共享,开发特殊需求的功能。

想要学习更多编程,欢迎关注公众号:android全贯通

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值