当前位置: 首页 > article >正文

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。


http://www.kler.cn/a/467842.html

相关文章:

  • springboot适配mybatis+guassdb与Mysql兼容性问题处理
  • GitLab集成Runner详细版--及注意事项汇总【最佳实践】
  • 计算机毕业设计Python+CNN卷积神经网络高考推荐系统 高考分数线预测 高考爬虫 协同过滤推荐算法 Vue.js Django Hadoop 大数据毕设
  • SRS 服务器入门:实时流媒体传输的理想选择
  • Spark是什么?Flink和Spark区别
  • 使用 OpenAI 进行结构化标签提取的 Python 实现
  • ROS节点架构设计:提高模块化与可扩展性
  • 算法解析-经典150(区间、栈)
  • 【通识安全】应急救护常识23则
  • C++软件设计模式之访问者模式
  • Linux 异步 I/O 框架 io_uring:基本原理、程序示例与性能压测
  • Android SPRD 工模测试修改
  • boot-126网易邮件发送
  • CSS系列(47)-- Animation Timeline详解
  • 车载软件架构 ---互联网人才怎么转变成汽车软件专家?
  • 【网络协议】开放式最短路径优先协议OSPF详解(三)
  • OSError: [WinError 126] 找不到指定的模块 backend_with_compiler.dll
  • 文件I/O - 文件读写操作
  • 计算机网络 —— 网络编程实操(1)(UDP)
  • C#利用Attribute实现面向切面编程(AOP)
  • LangChain4j 框架探索
  • 前端-计算机网络篇
  • 【Unity功能集】TextureShop纹理工坊(八)修剪工具
  • 基于Spring Boot的前后端分离的外卖点餐系统
  • 前端异常处理合集
  • python pandas 对mysql 一些常见操作