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);
        }
    }
相关推荐
丘狸尾2 小时前
[cisco 模拟器] ftp服务器配置
android·运维·服务器
van叶~4 小时前
探索未来编程:仓颉语言的优雅设计与无限可能
android·java·数据库·仓颉
Crossoads8 小时前
【汇编语言】端口 —— 「从端口到时间:一文了解CMOS RAM与汇编指令的交汇」
android·java·汇编·深度学习·网络协议·机器学习·汇编语言
li_liuliu9 小时前
Android4.4 在系统中添加自己的System Service
android
C4rpeDime11 小时前
自建MD5解密平台-续
android
鲤籽鲲13 小时前
C# Random 随机数 全面解析
android·java·c#
m0_5485147716 小时前
2024.12.10——攻防世界Web_php_include
android·前端·php
凤邪摩羯17 小时前
Android-性能优化-03-启动优化-启动耗时
android
凤邪摩羯17 小时前
Android-性能优化-02-内存优化-LeakCanary原理解析
android
喀什酱豆腐17 小时前
Handle
android