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。我们在开发应用时,可以利用第一和第二、第三种思想,实现两个应用的资源共享,开发特殊需求的功能。