1.简介
- 整理下hotseat的默认数据加载流程,方便修改查看默认数据。
- 我们集成了谷歌套件,所以launcher里默认的数据被修改了
- 简单学习下hotseat控件的加载
2.LoaderTask.java
hotseat默认值加载逻辑
2.1.run
csharp
public void run() {
//..
try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
List<ShortcutInfo> allShortcuts = new ArrayList<>();
try {
//见2.2
loadWorkspace(allShortcuts, memoryLogger);
//..
2.2.loadWorkspace
typescript
private void loadWorkspace(List<ShortcutInfo> allDeepShortcuts, LoaderMemoryLogger logger) {
loadWorkspace(allDeepShortcuts, LauncherSettings.Favorites.CONTENT_URI,
null /* selection */, logger);
}
ini
protected void loadWorkspace(
List<ShortcutInfo> allDeepShortcuts,
Uri contentUri,
String selection,
@Nullable LoaderMemoryLogger logger) {
final Context context = mApp.getContext();
final ContentResolver contentResolver = context.getContentResolver();
final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
final boolean isSafeMode = pmHelper.isSafeMode();
final boolean isSdCardReady = Utilities.isBootCompleted();
final WidgetManagerHelper widgetHelper = new WidgetManagerHelper(context);
boolean clearDb = false;
if (!GridSizeMigrationTaskV2.migrateGridIfNeeded(context)) {
// Migration failed. Clear workspace.
clearDb = true;
}
if (clearDb) {
Log.d(TAG, "loadWorkspace: resetting launcher database");
LauncherSettings.Settings.call(contentResolver,
LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
}
//加载默认的数据,搜一下method哪里用到,很容易搜到下边的LauncherProvider,见2.1
LauncherSettings.Settings.call(contentResolver,
LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES);
//下边就是查询表里的数据了,代码太多不贴了。
2.LauncherProvider
2.1.call
arduino
public Bundle call(String method, final String arg, final Bundle extras) {
if (Binder.getCallingUid() != Process.myUid()) {
return null;
}
createDbIfNotExists();
>METHOD_LOAD_DEFAULT_FAVORITES
加载默认的favorite数据
csharp
case LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES: {
loadDefaultFavoritesIfNecessary();
return null;
}
>METHOD_SWITCH_DATABASE
切换数据库,这个好像是啥小型设备才用到
ini
case LauncherSettings.Settings.METHOD_SWITCH_DATABASE: {
//已打开的和传入的数据库名字一样,啥也不干
if (TextUtils.equals(arg, mOpenHelper.getDatabaseName())) return null;
final DatabaseHelper helper = mOpenHelper;
//是否有对应的authority数据
if (extras == null || !extras.containsKey(KEY_LAYOUT_PROVIDER_AUTHORITY)) {
mProviderAuthority = null;
} else {
mProviderAuthority = extras.getString(KEY_LAYOUT_PROVIDER_AUTHORITY);
}
//用新的数据库名字打开新的dbhelper
mOpenHelper = DatabaseHelper.createDatabaseHelper(
getContext(), arg, false /* forMigration */);
helper.close();
LauncherAppState app = LauncherAppState.getInstanceNoCreate();
if (app == null) return null;
app.getModel().forceReload();
return null;
}
2.2.loadDefaultFavoritesIfNecessary
- 获取默认的favorites数据,加载优先级如下边注解,依次查找
- 集成谷歌包以后,最终用的是GmsSampleIntegration里的partner_default_layout.xml文件
- 如果没有谷歌包,默认是launcher里的res/xml/default_workspace_(6x5根据实际使用的网格找对应文件)
scss
/**
* Loads the default workspace based on the following priority scheme:
* 1) From the app restrictions
* 2) From a package provided by play store
* 3) From a partner configuration APK, already in the system image
* 4) The default configuration for the particular device
*/
synchronized private void loadDefaultFavoritesIfNecessary() {
SharedPreferences sp = Utilities.getPrefs(getContext());
//默认数据库已创建
if (sp.getBoolean(mOpenHelper.getKey(EMPTY_DATABASE_CREATED), false)) {
AppWidgetHost widgetHost = mOpenHelper.newLauncherWidgetHost();
//见2.3
AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction(widgetHost);
if (loader == null) {
//见4.1,gms的包里没有,返回空
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();
//这里找的是partner_default_layout.xml文件
//在GmsSampleIntegration的res目录里能找到
int workspaceResId = partnerRes.getIdentifier(Partner.RES_DEFAULT_LAYOUT,
"xml", partner.getPackageName());
if (workspaceResId != 0) {
loader = new DefaultLayoutParser(getContext(), widgetHost,
mOpenHelper, partnerRes, workspaceResId);
}
}
}
//是否使用的是外部的layout
final boolean usingExternallyProvidedLayout = loader != null;
if (loader == null) {
//没有外部layout,则加载launcher默认的,见2.4
loader = getDefaultLayoutParser(widgetHost);
}
//见小节 2i.1,删除并创建新的favorites表
mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
//数据来源是外部layout,解析xml里的数据结果为空
if ((mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader) <= 0)
&& usingExternallyProvidedLayout) {
//外部layout没有数据,那么加载默认的layout数据
mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(),
getDefaultLayoutParser(widgetHost));
}
//清除if条件里的EMPTY_DATABASE_CREATED的值
clearFlagEmptyDbCreated();
}
}
2.3.createWorkspaceLoaderFromAppRestriction
根据提供的authority查找,
ini
private AutoInstallsLayout createWorkspaceLoaderFromAppRestriction(
LauncherWidgetHolder widgetHolder) {
Context ctx = getContext();
final String authority;
if (!TextUtils.isEmpty(mProviderAuthority)) {
authority = mProviderAuthority;
} else {
authority = Settings.Secure.getString(ctx.getContentResolver(),
"launcher3.layout.provider");
}
//公司设备上边2返回都是空
if (TextUtils.isEmpty(authority)) {
return null;
}
ProviderInfo pi = ctx.getPackageManager().resolveContentProvider(authority, 0);
if (pi == null) {
return null;
}
Uri uri = getLayoutUri(authority, ctx);
try (InputStream in = ctx.getContentResolver().openInputStream(uri)) {
// 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));
return new AutoInstallsLayout(ctx, widgetHolder, mOpenHelper,
ctx.getPackageManager().getResourcesForApplication(pi.applicationInfo),
() -> parser, AutoInstallsLayout.TAG_WORKSPACE);
} catch (Exception e) {
return null;
}
}
2.4.getDefaultLayoutParser
这个就是launcher里默认的配置
scss
private DefaultLayoutParser getDefaultLayoutParser(LauncherWidgetHolder widgetHolder) {
InvariantDeviceProfile idp = LauncherAppState.getIDP(getContext());
//mDefaultWorkspaceLayoutOverride是测试用的,正常用的是idp里的
int defaultLayout = mDefaultWorkspaceLayoutOverride > 0
? mDefaultWorkspaceLayoutOverride : idp.defaultLayoutId;
return new DefaultLayoutParser(getContext(), widgetHolder,
mOpenHelper, getContext().getResources(), defaultLayout);
}
idp里的数据来源是xml文件,位置:res/xml/device_profiles.xml,不同的网格数据不同,如下
ini
<grid-option
launcher:name="6_by_5"
launcher:devicePaddingId="@xml/paddings_6x5"
launcher:dbFile="launcher_6_by_5.db"
launcher:defaultLayoutId="@xml/default_workspace_6x5"
2i.DatabaseHelper
2i.1.createEmptyDB
清除favorites表和workspaceScreens表,并创建新的表
scss
/**
* Clears all the data for a fresh start.
*/
public void createEmptyDB(SQLiteDatabase db) {
try (SQLiteTransaction t = new SQLiteTransaction(db)) {
dropTable(db, Favorites.TABLE_NAME);
dropTable(db, "workspaceScreens");
onCreate(db);
t.commit();
}
}
2i.2.onCreate
scss
public void onCreate(SQLiteDatabase db) {
mMaxItemId = 1;
addFavoritesTable(db, false);//添加favorite表
// Fresh and clean launcher DB.
mMaxItemId = initializeMaxItemId(db);
if (!mForMigration) {
onEmptyDbCreated();
}
}
>onEmptyDbCreated
修改sp的值,表明默认的db已创建
scss
protected void onEmptyDbCreated() {
// Set the flag for empty DB
LauncherPrefs.getPrefs(mContext).edit().putBoolean(getKey(EMPTY_DATABASE_CREATED), true)
.commit();
}
2i.3.createDatabaseHelper
scss
static DatabaseHelper createDatabaseHelper(Context context, boolean forMigration) {
return createDatabaseHelper(context, null, forMigration);
}
static DatabaseHelper createDatabaseHelper(Context context, String dbName,
boolean forMigration) {
if (dbName == null) {
//默认的db名字,可以参考2.4里读取的配置文件
dbName = InvariantDeviceProfile.INSTANCE.get(context).dbFile;
}
DatabaseHelper databaseHelper = new DatabaseHelper(context, dbName, forMigration);
//如果favorites表没有创建,默认创建
if (!tableExists(databaseHelper.getReadableDatabase(), Favorites.TABLE_NAME)) {
databaseHelper.addFavoritesTable(databaseHelper.getWritableDatabase(), true);
}
//hotseat_restore_backup是否存在
databaseHelper.mHotseatRestoreTableExists = tableExists(
databaseHelper.getReadableDatabase(), Favorites.HYBRID_HOTSEAT_BACKUP_TABLE);
databaseHelper.initIds();//获取favorites表的最大id
return databaseHelper;
}
3.Partner
arduino
public static final String RES_PARTNER_DEFAULT_LAYOUT = "partner_default_layout";
3.1.get
typescript
private static final String
ACTION_PARTNER_CUSTOMIZATION = "com.android.launcher3.action.PARTNER_CUSTOMIZATION";
public static Partner get(PackageManager pm) {
return get(pm, ACTION_PARTNER_CUSTOMIZATION);
}
public static Partner get(PackageManager pm, String action) {
Pair<String, Resources> apkInfo = findSystemApk(action, pm);
return apkInfo != null ? new Partner(apkInfo.first, apkInfo.second) : null;
}
>findSystemApk
找到符合action的广播接收者
java
private static Pair<String, Resources> findSystemApk(String action, PackageManager pm) {
final Intent intent = new Intent(action);
for (ResolveInfo info : pm.queryBroadcastReceivers(intent, MATCH_SYSTEM_ONLY)) {
final String packageName = info.activityInfo.packageName;
try {
final Resources res = pm.getResourcesForApplication(packageName);
return Pair.create(packageName, res);
}
return null;
}
3.2.ACTION_PARTNER_CUSTOMIZATION
查找符合要求的receiver
>空实现的
vendor/partner_gms/apps/GmsSampleIntegration/AndroidManifest.xml
xml
<!-- This isn't a real receiver, it's only used as a marker interface. -->
<receiver android:name=".LauncherCustomizationReceiver"
android:exported="true">
<intent-filter>
<action android:name="com.android.launcher3.action.PARTNER_CUSTOMIZATION" />
</intent-filter>
</receiver>
3.3.getXmlResId
arduino
public int getXmlResId(String layoutName) {
return getResources().getIdentifier(layoutName, "xml", getPackageName());
}
4.AutoInstallsLayout
4.1.get
参看3.2对应的谷歌包,在里边没找到default_layout开头的xml文件,所以这个返回的是空
ini
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";
static AutoInstallsLayout get(Context context, LauncherWidgetHolder appWidgetHolder,
LayoutParserCallback callback) {
//这个参考小节 3.1
Partner partner = Partner.get(context.getPackageManager(), ACTION_LAUNCHER_CUSTOMIZATION);
if (partner == null) {
return null;
}
InvariantDeviceProfile grid = LauncherAppState.getIDP(context);
// 根据网格的行列以及hotseat的个数,获取布局名字,比如default_layout_6x5_h4
String layoutName = String.format(Locale.ENGLISH, FORMATTED_LAYOUT_RES_WITH_HOSTEAT,
grid.numColumns, grid.numRows, grid.numDatabaseHotseatIcons);
//看下有没有对应的文件
int layoutId = partner.getXmlResId(layoutName);
// Try with only grid size
if (layoutId == 0) {
//上边的 没找到,这次找default_layout_6x5
layoutName = String.format(Locale.ENGLISH, FORMATTED_LAYOUT_RES,
grid.numColumns, grid.numRows);
layoutId = partner.getXmlResId(layoutName);
}
// Try the default layout
if (layoutId == 0) {
//还是没找到,这次找default_layout.xml
layoutId = partner.getXmlResId(LAYOUT_RES);
}
if (layoutId == 0) {
//还没找到,那就是没有
return null;
}
return new AutoInstallsLayout(context, appWidgetHolder, callback, partner.getResources(),
layoutId, TAG_WORKSPACE);
}
5.Launcher
上篇里简单看了下launcher数据的获取流程,获取数据以后会有各种回调的,这里看下launcher里的回调
5.1.bindItems
ini
public void bindItems(
final List<ItemInfo> items,//这个就是获取到的数据集合
final boolean forceAnimateIcons,
final boolean focusFirstItemForAccessibility) {
// Get the list of added items and intersect them with the set of items here
final Collection<Animator> bounceAnims = new ArrayList<>();
boolean canAnimatePageChange = canAnimatePageChange();
Workspace<?> workspace = mWorkspace;
int newItemsScreenId = -1;
int end = items.size();
View newView = null;
for (int i = 0; i < end; i++) {
final ItemInfo item = items.get(i);
//..
final View view;//根据itemType创建不同类型的view
switch (item.itemType) {
//..
workspace.addInScreenFromBind(view, item);//添加到容器里,见6.1
if (newView == null) {
newView = view;
}
}
6.WorkspaceLayoutManager.java
Workspace实现了这个接口,所以上边的workspace调用的就是这个接口里的方法
6.1.addInScreenFromBind
ini
default void addInScreenFromBind(View child, ItemInfo info) {
int x = info.cellX;
int y = info.cellY;
if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
|| info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
int screenId = info.screenId;
x = getHotseat().getCellXFromOrder(screenId);
y = getHotseat().getCellYFromOrder(screenId);
}
addInScreen(child, info.container, info.screenId, x, y, info.spanX, info.spanY);
}
>addInScreen
ini
default void addInScreen(View child, int container, int screenId, int x, int y,
int spanX, int spanY) {
final CellLayout layout;
if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
|| container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
//容器类型是hotseat,获取对应容器
layout = getHotseat();
//hotseat只显示图标,不显示文字
if (child instanceof FolderIcon) {
((FolderIcon) child).setTextVisible(false);
}
} else {
// Show folder title if not in the hotseat
if (child instanceof FolderIcon) {
((FolderIcon) child).setTextVisible(true);
}
//这个是workspace了,可以有多个屏,根据当前id获取对应的容器
layout = getScreenWithId(screenId);
}
ViewGroup.LayoutParams genericLp = child.getLayoutParams();
CellLayoutLayoutParams lp;
//设置layoutParams
if (genericLp == null || !(genericLp instanceof CellLayoutLayoutParams)) {
lp = new CellLayoutLayoutParams(x, y, spanX, spanY, screenId);
} else {
lp = (CellLayoutLayoutParams) genericLp;
lp.cellX = x;
lp.cellY = y;
lp.cellHSpan = spanX;
lp.cellVSpan = spanY;
}
if (spanX < 0 && spanY < 0) {
lp.isLockedToGrid = false;
}
// Get the canonical child id to uniquely represent this view in this screen
ItemInfo info = (ItemInfo) child.getTag();
int childId = info.getViewId();
boolean markCellsAsOccupied = !(child instanceof Folder);
//添加到对应的容器里
if (!layout.addViewToCellLayout(child, -1, childId, lp, markCellsAsOccupied)) {
}
child.setHapticFeedbackEnabled(false);
child.setOnLongClickListener(getWorkspaceChildOnLongClickListener());
if (child instanceof DropTarget) {
onAddDropTarget((DropTarget) child);
}
}