android13 launcher[06hotseat]

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);
        }
    }
相关推荐
千里马学框架3 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台3 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone3 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc3 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo3 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077003 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼3 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone3 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen3 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone3 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui