Launcher3主页面加载显示流程分析

布局结构

抓取布局后,可以看到每个图标是一个DoubleShadowBubbleTextView,父布局是CellLayout、workspace。

我们可以在CellLayout添加子view打印出调用堆栈信息,可以整体上看页面加载显示流程。
在这里插入图片描述

主要类

  • Launcher.java:主界面,即MainActivity
  • launcher.xml:主界面布局文件
  • LauncherModel.java:管理Launcher状态,包括加载任务、状态回调等
  • LoaderTask.java:加载任务,是一个Runnable
  • LoaderResults.java:加载结果
  • BgDataModel#Callbacks:数据回调接口,Launcher实现该接口,加载任务通过该接口回调给Launcher
  • LauncherProvider:桌面数据提供者,采用db保存桌面图标数据(包括排列位置、类型等)
  • LauncherSettings:封装访问LauncherProvider时的uri、column等一些常量,通过ContentResolver来访问LauncherProvider,不直接操作db
  • LoaderCursor:封装cursor操作

流程图

在这里插入图片描述

创建Activity

主界面创建的时候,常规的setContentView和findView,创建LauncherModel并开始加载数据

// Launcher.java
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // 1.创建LauncherAppState和LauncherModel
    LauncherAppState app = LauncherAppState.getInstance(this);
    mModel = app.getModel();
    
    // 2.infalte布局文件,找到view
    setupViews();
    
    // 3. 设置loader监听,开始加载数据,加载完成后回调给主界面
    if (!mModel.addCallbacksAndLoad(this)) {
        if (!internalStateHandled) {
            // If we are not binding synchronously, pause drawing until initial bind complete,
            // so that the system could continue to show the device loading prompt
            mOnInitialBindListener = Boolean.FALSE::booleanValue;
        }
    }
   
    // 4. 设置view给Activity
    setContentView(getRootView());

加载数据

创建加载任务

mModel.addCallbacksAndLoad将Launcher设置给LauncherModel,然后创建了加载任务

// LauncherModel.java    
/**
 * Adds a callbacks to receive model updates
 * @return true if workspace load was performed synchronously
 */
public boolean addCallbacksAndLoad(@NonNull final Callbacks callbacks) {
    synchronized (mLock) {
        addCallbacks(callbacks);
        return startLoader(new Callbacks[] { callbacks });
    }
}

private boolean startLoader(@NonNull final Callbacks[] newCallbacks) {
    synchronized (mLock) {
        // 1.取消旧的加载任务
        boolean wasRunning = stopLoader(); // 之前没有loader任务,wasRunning为false
        boolean bindDirectly = mModelLoaded && !mIsLoaderTaskRunning;// 之前没有加载过,bindDirectly为false
        boolean bindAllCallbacks = wasRunning || !bindDirectly || newCallbacks.length == 0; // bindAllCallbacks为true, 上一步已经addCallbacks了,所以callbacksList里面包含从Launcher.java传进来的callbacks
        final Callbacks[] callbacksList = bindAllCallbacks ? getCallbacks() : newCallbacks;
        if (callbacksList.length > 0) {
            // 2.清空PendingBind
            for (Callbacks cb : callbacksList) {
                MAIN_EXECUTOR.execute(cb::clearPendingBinds);
            }
            // 3.创建LoaderResults和LoaderTask,开始加载数据
            LoaderResults loaderResults = new LoaderResults(
                    mApp, mBgDataModel, mBgAllAppsList, callbacksList);
            if (bindDirectly) {
                loaderResults.bindWorkspace(bindAllCallbacks);
                loaderResults.bindAllApps();
                loaderResults.bindDeepShortcuts();
                loaderResults.bindWidgets();
                return true;
            } else {
                stopLoader();
                mLoaderTask = new LoaderTask(
                        mApp, mBgAllAppsList, mBgDataModel, mModelDelegate, loaderResults);
                MODEL_EXECUTOR.post(mLoaderTask);
            }
        }
    }
}

整体加载步骤

LoaderTask#run中根据类型加载步骤分为了5步,图标的加载主要看第一步

// LoaderTask.java
public void run() {
    try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
        // first step
        List<ShortcutInfo> allShortcuts = new ArrayList<>();
        loadWorkspace(allShortcuts, memoryLogger);
        // Sanitize data re-syncs widgets/shortcuts based on the workspace loaded from db.
        // sanitizeData should not be invoked if the workspace is loaded from a db different
        // from the main db as defined in the invariant device profile.
        // (e.g. both grid preview and minimal device mode uses a different db)
        if (mApp.getInvariantDeviceProfile().dbFile.equals(mDbName)) {
            sanitizeData();
        }
        mResults.bindWorkspace(true /* incrementBindId */);
        mModelDelegate.workspaceLoadComplete();
        // Notify the installer packages of packages with active installs on the first screen.
        sendFirstScreenActiveInstallsBroadcast();
        // Take a break
        waitForIdle();


        // second step
        List<LauncherActivityInfo> allActivityList;
         allActivityList = loadAllApps();
        mResults.bindAllApps();
        IconCacheUpdateHandler updateHandler = mIconCache.getUpdateHandler();
        setIgnorePackages(updateHandler);
        updateHandler.updateIcons(allActivityList,
                LauncherActivityCachingLogic.newInstance(mApp.getContext()),
                mApp.getModel()::onPackageIconsUpdated);
        updateHandler.updateIcons(allShortcuts, new ShortcutCachingLogic(),
                mApp.getModel()::onPackageIconsUpdated);
        waitForIdle();


        // third step
        List<ShortcutInfo> allDeepShortcuts = loadDeepShortcuts();
        mResults.bindDeepShortcuts();
        updateHandler.updateIcons(allDeepShortcuts,
                new ShortcutCachingLogic(), (pkgs, user) -> { });
        waitForIdle();
    

        // fourth step
        List<ComponentWithLabelAndIcon> allWidgetsList =
                mBgDataModel.widgetsModel.update(mApp, null);
        mResults.bindWidgets();
        updateHandler.updateIcons(allWidgetsList,
                new ComponentWithIconCachingLogic(mApp.getContext(), true),
                mApp.getModel()::onWidgetLabelsUpdated);
    

        // fifth step
        loadFolderNames();
        updateHandler.finish();
        mModelDelegate.modelLoadComplete();
        transaction.commit();
    } catch (CancellationException e) {
        ...
    }
}

图标解析

loadWorkspace()代码比较多,主要作用是通过ContentResolver查询LauncherProvider中保存的桌面图标信息,然后遍历cursor来解析数据。

在分析代码的时候,抓到数据流向,可以结合真实的db数据进行分析,其中一些判断容错处理可以跳过

主要流程如下:

  • 加载默认的数据(Provider会有判断,只有第一次才会加载)
  • 通过ContentResolver查询所有数据
  • 遍历cursor,根据不同图标类型进行解析,如ITEM_TYPE_APPLICATION代表普通的图标,ITEM_TYPE_FOLDER代表文件夹
  • 对数据进行一些校验,校验通过后将数据添加到mBgDataModel

数据流向:

Db(LauncherProvider) --> Cursor(LoaderCursor) --> List(BgDataModel)

protected void loadWorkspace(
        List<ShortcutInfo> allDeepShortcuts,
        Uri contentUri,
        String selection,
        @Nullable LoaderMemoryLogger logger) {
    final Context context = mApp.getContext();
    final ContentResolver contentResolver = context.getContentResolver();
    // 1. 加载默认的数据(Provider会有判断,只有第一次才会加载)
    LauncherSettings.Settings.call(contentResolver,
            LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES);
    synchronized (mBgDataModel) {
        mBgDataModel.clear();
        mPendingPackages.clear();
        // 2. 查询所有数据
        final LoaderCursor c = new LoaderCursor(
                contentResolver.query(contentUri, null, selection, null, null), contentUri,
                mApp, mUserManagerState);
        final Bundle extras = c.getExtras();
        mDbName = extras == null
                ? null : extras.getString(LauncherSettings.Settings.EXTRA_DB_NAME);
        try {
            // 2.1 遍历cursor
            final int appWidgetIdIndex = c.getColumnIndexOrThrow(
                    LauncherSettings.Favorites.APPWIDGET_ID);
            final int appWidgetProviderIndex = c.getColumnIndexOrThrow(
                    LauncherSettings.Favorites.APPWIDGET_PROVIDER);
            final int spanXIndex = c.getColumnIndexOrThrow
                    (LauncherSettings.Favorites.SPANX);
            final int spanYIndex = c.getColumnIndexOrThrow(
                    LauncherSettings.Favorites.SPANY);
            final int rankIndex = c.getColumnIndexOrThrow(
                    LauncherSettings.Favorites.RANK);
            final int optionsIndex = c.getColumnIndexOrThrow(
                    LauncherSettings.Favorites.OPTIONS);
            final int sourceContainerIndex = c.getColumnIndexOrThrow(
                    LauncherSettings.Favorites.APPWIDGET_SOURCE);
            WorkspaceItemInfo info;
            LauncherAppWidgetInfo appWidgetInfo;
            LauncherAppWidgetProviderInfo widgetProviderInfo;
            Intent intent;
            String targetPkg;
            List<IconRequestInfo<WorkspaceItemInfo>> iconRequestInfos = new ArrayList<>();
            while (!mStopped && c.moveToNext()) {
                try {
                    // 2.2 根据不同图标类型进行解析,ITEM_TYPE_APPLICATION代表普通的图标,ITEM_TYPE_FOLDER代表文件夹
                    switch (c.itemType) {
                    case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                    case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                    case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
                        intent = c.parseIntent();
                      // 2.3 解析数据,进行一些校验判断
                        if (info != null) {
                            if (info.itemType
                                    != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
                                // Skip deep shortcuts; their title and icons have already been
                                // loaded above.
                                iconRequestInfos.add(
                                        c.createIconRequestInfo(info, useLowResIcon));
                            }
                            c.applyCommonProperties(info);
                            info.intent = intent;
                            info.rank = c.getInt(rankIndex);
                            info.spanX = 1;
                            info.spanY = 1;
                            info.runtimeStatusFlags |= disabledState;
                            if (isSafeMode && !isSystemApp(context, intent)) {
                                info.runtimeStatusFlags |= FLAG_DISABLED_SAFEMODE;
                            }
                                LauncherActivityInfo activityInfo = c.getLauncherActivityInfo();
                                if (activityInfo != null) {
                                    info.setProgressLevel(
                                            PackageManagerHelper
                                                .getLoadingProgress(activityInfo),
                                            PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING);
                                }
                            if (c.restoreFlag != 0 && !TextUtils.isEmpty(targetPkg)) {
                                tempPackageKey.update(targetPkg, c.user);
                                SessionInfo si = installingPkgs.get(tempPackageKey);
                                    if (si == null) {
                                        info.runtimeStatusFlags &=
                                            ~ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE;
                                    } else if (activityInfo == null) {
                                        int installProgress = (int) (si.getProgress() * 100);
                                        info.setProgressLevel(
                                                installProgress,
                                                PackageInstallInfo.STATUS_INSTALLING);
                                    }
                            }
                            // 3.将数据添加到mBgDataModel
                            c.checkAndAddItem(info, mBgDataModel, logger);
                        } else {
                            throw new RuntimeException("Unexpected null WorkspaceItemInfo");
                        }
                        break;
                    case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
                        FolderInfo folderInfo = mBgDataModel.findOrMakeFolder(c.id);
                        c.applyCommonProperties(folderInfo);
                        // Do not trim the folder label, as is was set by the user.
                        folderInfo.title = c.getString(c.titleIndex);
                        folderInfo.spanX = 1;
                        folderInfo.spanY = 1;
                        folderInfo.options = c.getInt(optionsIndex);
                        // no special handling required for restored folders
                        c.markRestored();
                        c.checkAndAddItem(folderInfo, mBgDataModel, logger);
                        break;
                    }
                } catch (Exception e) {
                    Log.e(TAG, "Desktop items loading interrupted", e);
                }
            }
        } finally {
            IOUtils.closeSilently(c);
        }
        // Load delegate items
        mModelDelegate.loadItems(mUserManagerState, shortcutKeyToPinnedShortcuts);
        // Load string cache
        mModelDelegate.loadStringCache(mBgDataModel.stringCache);
        // Remove dead items
        mItemsDeleted = c.commitDeleted();
        // Sort the folder items, update ranks, and make sure all preview items are high res.
        FolderGridOrganizer verifier =
                new FolderGridOrganizer(mApp.getInvariantDeviceProfile());
        for (FolderInfo folder : mBgDataModel.folders) {
            Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
            verifier.setFolderInfo(folder);
            int size = folder.contents.size();
            // Update ranks here to ensure there are no gaps caused by removed folder items.
            // Ranks are the source of truth for folder items, so cellX and cellY can be ignored
            // for now. Database will be updated once user manually modifies folder.
            for (int rank = 0; rank < size; ++rank) {
                WorkspaceItemInfo info = folder.contents.get(rank);
                info.rank = rank;
                if (info.usingLowResIcon()
                        && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
                        && verifier.isItemInPreview(info.rank)) {
                    mIconCache.getTitleAndIcon(info, false);
                }
            }
        }
        c.commitRestoredItems();
    }
}

可以结合db数据进行分析:

  • container: 表示显示的布局,像普通、文件夹、dock栏等
  • screen: 表示现在第几屏
  • itemType:表示类型,像应用图标、文件夹、快捷方式等
  • 在这里插入图片描述

图标显示

到这里,我们已经获取到桌面数据了,下面就是要设置到view显示出来。在上面LoaderTask#run()中,loadWorkspace()获取到了数据,而ui显示的触发在mResults.bindWorkspace(true)

bindWorkspace()先是将数据复制一份,然后遍历callback进行bind。上一步mBgDataModel保存了数据库中数据,mBgDataModel是LauncherModel的一个成员变量,在activity#onCreate时创建的,而activity就是Callbacks,所以到这里都关联了起来。
Launcher (Callbacks)—> LoaderTask —> LoaderResults --> BgDataModel --> Callbacks


// LoaderResults.java
/**
 * Binds all loaded data to actual views on the main thread.
 */
public void bindWorkspace(boolean incrementBindId) {
    // Save a copy of all the bg-thread collections
    ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
    ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
    final IntArray orderedScreenIds = new IntArray();
    ArrayList<FixedContainerItems> extraItems = new ArrayList<>();
    synchronized (mBgDataModel) {
        workspaceItems.addAll(mBgDataModel.workspaceItems);
        appWidgets.addAll(mBgDataModel.appWidgets);
        orderedScreenIds.addAll(mBgDataModel.collectWorkspaceScreens());
        mBgDataModel.extraItems.forEach(extraItems::add);
        if (incrementBindId) {
            mBgDataModel.lastBindId++;
        }
        mMyBindingId = mBgDataModel.lastBindId;
    }
    for (Callbacks cb : mCallbacksList) {
        new WorkspaceBinder(cb, mUiExecutor, mApp, mBgDataModel, mMyBindingId,
                workspaceItems, appWidgets, extraItems, orderedScreenIds).bind();
    }
}

WorkspaceBinder中bind方法会执行callback回调,回调到activity后,创建BubbleTextView添加到CellLayout。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/948158.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

开发培训:慧集通(DataLinkX)iPaaS集成平台-基于接口的连接器开发(不需要认证机制)

一、开发一个简单的应用0源&#xff0c;本实例中对接的应用不需要接口认证 1、【连接管理-自建】新建应用源&#xff0c;保存并发布 代码示例 return {$$ - >//日志打印$$.$Log.info(日志打印) } 二、使用应用&#xff0c;建立应用连接 1、实例创建&#xff0c;【连接管理…

pikachu靶场--目录遍历和敏感信息泄露

pikachu靶场—目录遍历和敏感信息泄露 目录遍历 概述 在web功能设计中,很多时候我们会要将需要访问的文件定义成变量&#xff0c;从而让前端的功能便的更加灵活。 当用户发起一个前端的请求时&#xff0c;便会将请求的这个文件的值(比如文件名称)传递到后台&#xff0c;后台再…

机器学习详解(13):CNN图像数据增强(解决过拟合问题)

在之前的文章卷积神经网络CNN之手语识别代码详解中&#xff0c;我们发现最后的训练和验证损失的曲线的波动非常大&#xff0c;而且验证集的准确率仍然落后于训练集的准确率&#xff0c;这表明模型出现了过拟合现象&#xff1a;在验证数据集测试时&#xff0c;模型对未见过的数据…

Word2Vec解读

Word2Vec: 一种词向量的训练方法 简单地讲&#xff0c;Word2Vec是建模了一个单词预测的任务&#xff0c;通过这个任务来学习词向量。假设有这样一句话Pineapples are spiked and yellow&#xff0c;现在假设spiked这个单词被删掉了&#xff0c;现在要预测这个位置原本的单词是…

#渗透测试#漏洞挖掘#WAF分类及绕过思路

免责声明 本教程仅为合法的教学目的而准备&#xff0c;严禁用于任何形式的违法犯罪活动及其他商业行为&#xff0c;在使用本教程前&#xff0c;您应确保该行为符合当地的法律法规&#xff0c;继续阅读即表示您需自行承担所有操作的后果&#xff0c;如有异议&#xff0c;请立即停…

电子应用设计方案85:智能 AI门前柜系统设计

智能 AI 门前柜系统设计 一、引言 智能 AI 门前柜系统旨在提供便捷、安全和智能的物品存储与管理解决方案&#xff0c;适用于家庭、公寓或办公场所的入口区域。 二、系统概述 1. 系统目标 - 实现无接触式物品存取&#xff0c;减少交叉感染风险。 - 具备智能识别和分类功能&am…

如何在不丢失数据的情况下从 IOS 14 回滚到 IOS 13

您是否后悔在 iPhone、iPad 或 iPod touch 上安装 iOS 14&#xff1f;如果你这样做&#xff0c;你并不孤单。许多升级到 iOS 14 beta 的 iPhone、iPad 和 iPod touch 用户不再适应它。 如果您在正式发布日期之前升级到 iOS 14 以享受其功能&#xff0c;但您不再适应 iOS 14&am…

线性代数考研笔记

行列式 背景 分子行列式&#xff1a;求哪个未知数&#xff0c;就把b1&#xff0c;b2放在对应的位置 分母行列式&#xff1a;系数对应写即可 全排列与逆序数 1 3 2&#xff1a;逆序数为1 奇排列 1 2 3&#xff1a;逆序数为0 偶排列 将 1 3 2 只需将3 2交换1次就可以还原原…

设计心得——流程图和数据流图绘制

一、流程图和数据流图 在软件开发中&#xff0c;画流程图和数据流图可以说是几乎每个人都会遇到。 1、数据流&#xff08;程&#xff09;图 Data Flow Diagram&#xff0c;DFG。它可以称为数据流图或数据流程图。其主要用来描述系统中数据流程的一种图形工具&#xff0c;可以将…

SpringBoot框架开发中常用的注解

文章目录 接收HTTP请求。RestController全局异常处理器Component依赖注入LombokDataBuildersneakyThrowsRequiredArgsConstructor 读取yml文件配置类注解 接收HTTP请求。 RequestMapping 接收HTTP请求。具体一点是 GetMapping PostMapping PutMapping DeleteMapping 一共…

ELK日志平台搭建 (最新版)

一、安装 JDK 1. 下载 JDK 21 RPM 包 wget https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.rpm2. 安装 JDK 21,使用 rpm 命令安装下载的 RPM 包&#xff1a; sudo rpm -ivh jdk-21_linux-x64_bin.rpm3. 配置环境变量 编辑 /etc/profile 文件以配置 JAVA_HO…

使用 Jupyter Notebook:安装与应用指南

文章目录 安装 Jupyter Notebook1. 准备环境2. 安装 Jupyter Notebook3. 启动 Jupyter Notebook4. 选择安装方式&#xff08;可选&#xff09; 二、Jupyter Notebook 的基本功能1. 单元格的类型与运行2. 可视化支持3. 内置魔法命令 三、Jupyter Notebook 的实际应用场景1. 数据…

AcWing-164.可达性统计(拓扑排序 + 位运算)

原题链接&#xff1a;164. 可达性统计 - AcWing题库 题目描述&#xff1a; 题目 输入格式 输出格式 数据范围 输入样例&#xff1a; 输出样例&#xff1a; 思路 AC代码&#xff1a; 题目描述&#xff1a; 题目 给定一张 &#x1d441; 个点 &#x1d440; 条边的有向无…

Windows安装了pnpm后无法在Vscode中使用

Windows安装了pnpm后无法在Vscode中使用 解决方法&#xff1a; 以管理员身份打开 PowerShell 并执行以下命令后输入Y回车即可。 Set-ExecutionPolicy RemoteSigned -Scope CurrentUser之后就可以正常使用了

python学opencv|读取图像(二十五)使用cv2.putText()绘制文字进阶-垂直镜像文字

【1】引言 前序学习进程找那个&#xff0c;已经掌握了使用pythonopencv绘制常规文字和倾斜文字的基本技巧。相关链接如下&#xff1a; python学opencv|读取图像&#xff08;二十三&#xff09;使用cv2.putText()绘制文字-CSDN博客 python学opencv|读取图像&#xff08;二十四…

6.充放电相关实验(过压、欠压、过流、短路、过温、低温)演示

1.充放电演示 (1)一定要按照操作步骤来,先将电池板上的充放电开关一定要处于断开状态(字母O一边按下是断开,字母I一边按下是接通),然后夹上充电器的电源夹子到BMS控制板的PACK-、PACK+两端,然后给充电器插上电源(如果使用自己的充电器一定要注意不要大于21V),然后拨动…

解决HBuilderX报错:未安装内置终端插件,是否下载?或使用外部命令行打开。

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl 错误描述 在HBuilderX中执行npm run build总是提醒下载插件&#xff1b;图示如下&#xff1a; 但是&#xff0c;下载总是失败。运行项目时候依然弹出上述提醒。 解决方案 …

【小程序开发】- 小程序版本迭代指南(版本发布教程)

一&#xff0c;版本号 版本号是小程序版本的标识&#xff0c;通常由一系列数字组成&#xff0c;如 1.0.0、1.1.0 等。版本号的格式通常是 主版本号.次版本号.修订号 主版本号&#xff1a;当小程序有重大更新或不兼容的更改时&#xff0c;主版本号会增加。 次版本号&#xff1a…

基于微信小程序投票评选系统的设计与实现ssm+论文源码调试讲解

第4章 系统设计 4.1 系统设计的原则 在系统设计过程中&#xff0c;也需要遵循相应的设计原则&#xff0c;这些设计原则可以帮助设计者在短时间内设计出符合设计规范的设计方案。设计原则主要有可靠性&#xff0c;安全性&#xff0c;可定制化&#xff0c;可扩展性&#xff0c;可…

库伦值自动化功耗测试工具

1. 功能介绍 PlatformPower工具可以自动化测试不同场景的功耗电流&#xff0c;并可导出为excel文件便于测试结果分析查看。测试同时便于后续根据需求拓展其他自动化测试用例。 主要原理&#xff1a;基于文件节点 coulomb_count 实现&#xff0c;计算公式&#xff1a;电流&…