android framework13-launcher3【05数据获取】

1,129 阅读13分钟

1.简介

前边分析的都是ui功能相关的,核心的功能基本都了解了,现在就从头看下我们用到的数据都是如何获取的,比如allapps数据咋获取的,咋分发出去的?workspace里的数据都哪里来的? 那些默认的数据哪里来的?

1.1 shortcuts

android里的快捷方式是啥?如何添加,如何使用,参考下边这篇文章 juejin.cn/post/684490…

如下图,长按settings,弹出的红框里的那3个,就是快捷方式,点击长按还可以拖动出来放到workspace上, image.png

1.2.widgets

小部件,长按桌面空白处,弹框里选择widgets,会弹出一个页面,里边会显示个列表,所有带有小部件的app都在这里了,有的app可能有多个小部件。

2. InvariantDeviceProfile.java

  • 这个就是设备的不变的属性,也可以说是统一的属性吧,
  • 后边的DeviceProfile就是单独对应某个屏幕的属性了,比如横竖屏就不一样的数据。

2.1.getIDP

  • 调用的地方很多,单列模式,最早应该是TaskbarManager
    public static final MainThreadInitializedObject<LauncherAppState> INSTANCE =
            new MainThreadInitializedObject<>(LauncherAppState::new);//补充1

    public static InvariantDeviceProfile getIDP(Context context) {
        return InvariantDeviceProfile.INSTANCE.get(context);
    }

>1.构造方法

    private InvariantDeviceProfile(Context context) {
    //本地存储的grid name,补充2
        String gridName = getCurrentGridName(context);
        //参考2.2
        String newGridName = initGrid(context, gridName);
        if (!newGridName.equals(gridName)) {
            LauncherPrefs.getPrefs(context).edit().putString(KEY_IDP_GRID_NAME, newGridName)
                    .apply();
        }
        new DeviceGridState(this).writeToPrefs(context);

        DisplayController.INSTANCE.get(context).setPriorityListener(
                (displayContext, info, flags) -> {
                    if ((flags & (CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS
                            | CHANGE_NAVIGATION_MODE)) != 0) {
                        onConfigChanged(displayContext);
                    }
                });
    }

>2.getCurrentGridName

    public static String getCurrentGridName(Context context) {
        return LauncherPrefs.getPrefs(context).getString(KEY_IDP_GRID_NAME, null);
    }

2.2.initGrid

    private String initGrid(Context context, String gridName) {
    //见2.6
        Info displayInfo = DisplayController.INSTANCE.get(context).getInfo();
        //根据设备尺寸判断设备类型,平板,手机还是折叠屏
        @DeviceType int deviceType = getDeviceType(displayInfo);
    //获取预制的属性,读取xml文件
        ArrayList<DisplayOption> allOptions =//补充1
                getPredefinedDeviceProfiles(context, gridName, deviceType,
                        RestoreDbTask.isPending(context));
        //根据info和type找出allOptions里最合适的一个返回
        DisplayOption displayOption =
                invDistWeightedInterpolate(displayInfo, allOptions, deviceType);//补充5
        //        
        initGrid(context, displayInfo, displayOption, deviceType);
        return displayOption.grid.name;
    }

>1.getPredefinedDeviceProfiles

    private static ArrayList<DisplayOption> getPredefinedDeviceProfiles(Context context,
            String gridName, @DeviceType int deviceType, boolean allowDisabledGrid) {
        ArrayList<DisplayOption> profiles = new ArrayList<>();
    //读取xml文件,进行解析,参考补充2
        try (XmlResourceParser parser = context.getResources().getXml(R.xml.device_profiles)) {
            final int depth = parser.getDepth();
            int type;
            while (((type = parser.next()) != XmlPullParser.END_TAG ||
                    parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
                if ((type == XmlPullParser.START_TAG)
                        && GridOption.TAG_NAME.equals(parser.getName())) {
    //解析tag (grid-option)解析成gridOption
                    GridOption gridOption = new GridOption(context, Xml.asAttributeSet(parser),
                            deviceType);
                    //isEnable的判断条件见补充3
                    if (gridOption.isEnabled || allowDisabledGrid) {
                        final int displayDepth = parser.getDepth();
                        while (((type = parser.next()) != XmlPullParser.END_TAG
                                || parser.getDepth() > displayDepth)
                                && type != XmlPullParser.END_DOCUMENT) {
                            if ((type == XmlPullParser.START_TAG) && "display-option".equals(
                                    parser.getName())) {
                                    //display optionn可能有多个
                                profiles.add(new DisplayOption(gridOption, context,
                                        Xml.asAttributeSet(parser)));
                            }
                        }
                    }
                }
            }
        } catch (IOException | XmlPullParserException e) {
        }

        ArrayList<DisplayOption> filteredProfiles = new ArrayList<>();
        //gridname不为null的话,直接查找解析出来的数据
        if (!TextUtils.isEmpty(gridName)) {
            for (DisplayOption option : profiles) {
                if (gridName.equals(option.grid.name)
                        && (option.grid.isEnabled || allowDisabledGrid)) {
                    filteredProfiles.add(option);
                }
            }
        }
        //没找到对应的,把解析的都加进去
        if (filteredProfiles.isEmpty()) {
            for (DisplayOption option : profiles) {
                if (option.canBeDefault) {
                    filteredProfiles.add(option);
                }
            }
        }
        if (filteredProfiles.isEmpty()) {
            throw new RuntimeException("No display option with canBeDefault=true");
        }
        return filteredProfiles;
    }

>2.device_profiles.xml

    <grid-option
        launcher:name="6_by_5"
        launcher:numRows="5"
        launcher:numColumns="6"
        launcher:deviceCategory="tablet" >

<!--宽高完全匹配我们设备的时候会直接使用配置的属性,否则会动态计算,见补充5-->
        <display-option
            launcher:name="Tablet"
            launcher:minWidthDps="900" 
            launcher:minHeightDps="820"
            launcher:canBeDefault="true" />

    </grid-option>

</profiles>

>3.isEnable的判断

  • 上边的那个deviceCategory是tablet也就是2,下边的判断会返回true的

  • 就是判断配置里读取的category是否和当前设备的type匹配

              int deviceCategory = a.getInt(R.styleable.GridDisplayOption_deviceCategory,
                      DEVICE_CATEGORY_ALL);
              isEnabled = (deviceType == TYPE_PHONE
                          && ((deviceCategory & DEVICE_CATEGORY_PHONE) == DEVICE_CATEGORY_PHONE))
                      || (deviceType == TYPE_TABLET
                          && ((deviceCategory & DEVICE_CATEGORY_TABLET) == DEVICE_CATEGORY_TABLET))
                      || (deviceType == TYPE_MULTI_DISPLAY
                          && ((deviceCategory & DEVICE_CATEGORY_MULTI_DISPLAY)
                              == DEVICE_CATEGORY_MULTI_DISPLAY));
    

//自定义属性

        <attr name="deviceCategory" format="integer">
            <!-- Enable on phone only -->
            <flag name="phone" value="1" />
            <!-- Enable on tablets only -->
            <flag name="tablet" value="2" />
            <!-- Enable on multi display devices only -->
            <flag name="multi_display" value="4" />
        </attr>

>4.initGrid

        //窗口,这里横竖屏有3种
        for (WindowBounds bounds : displayInfo.supportedBounds) {
        //添加了3种DevicePorfile
            localSupportedProfiles.add(new DeviceProfile.Builder(context, this, displayInfo)
                    .setIsMultiDisplay(deviceType == TYPE_MULTI_DISPLAY)
                    .setWindowBounds(bounds)
                    .setDotRendererCache(dotRendererCache)
                    .build());
    //..
        }
        supportedProfiles = Collections.unmodifiableList(localSupportedProfiles);

>5.invDistWeightedInterpolate

  • 根据屏幕尺寸,从上边读取的预定义的配置里找到最合适的,如果没找到,根据所有的points算出一个新的出来,
  • 算法看不懂,暂不研究,具体看weight方法
  • minWidthDps是配置里读取的dp值,
  • 像素转dp算法,比如设备宽度是800px,density是200,那么dp值就是 800/(200/160),其中160是默认值
    private static DisplayOption invDistWeightedInterpolate(
            Info displayInfo, ArrayList<DisplayOption> points, @DeviceType int deviceType) {
        int minWidthPx = Integer.MAX_VALUE;
        int minHeightPx = Integer.MAX_VALUE;
        //从支持的bounds里找出最小的宽和高
        for (WindowBounds bounds : displayInfo.supportedBounds) {
            boolean isTablet = displayInfo.isTablet(bounds);
            if (isTablet && deviceType == TYPE_MULTI_DISPLAY) {
                // For split displays, take half width per page
                minWidthPx = Math.min(minWidthPx, bounds.availableSize.x / 2);
                minHeightPx = Math.min(minHeightPx, bounds.availableSize.y);

            } else if (!isTablet && bounds.isLandscape()) {
                // We will use transposed layout in this case
                //手机横屏
                minWidthPx = Math.min(minWidthPx, bounds.availableSize.y);
                minHeightPx = Math.min(minHeightPx, bounds.availableSize.x);
            } else {
            //平板或者手机竖屏
                minWidthPx = Math.min(minWidthPx, bounds.availableSize.x);
                minHeightPx = Math.min(minHeightPx, bounds.availableSize.y);
            }
        }
        //像素转换为DP值,见小节9
        float width = dpiFromPx(minWidthPx, displayInfo.getDensityDpi());
        float height = dpiFromPx(minHeightPx, displayInfo.getDensityDpi());

        // Sort the profiles based on the closeness to the device size
        //根据DisplayOption里的最小宽和高的DP值与上边算出的设备宽高值比较,按照差值排序
        Collections.sort(points, (a, b) ->
                Float.compare(dist(width, height, a.minWidthDps, a.minHeightDps),
                        dist(width, height, b.minWidthDps, b.minHeightDps)));
        //排序完取第一个,最接近的
        DisplayOption closestPoint = points.get(0);
        GridOption closestOption = closestPoint.grid;
        float weights = 0;
        //配置里的最小宽高和我们设备的宽高一模一样,直接返回,见补充5
        //注意是dp值额
        if (dist(width, height, closestPoint.minWidthDps, closestPoint.minHeightDps) == 0) {
        //这个除非专门配置,默认的一般应该不会一样的。
            return closestPoint;
        }
        //这里只复制了GridOption,具体逻辑见下边
        DisplayOption out = new DisplayOption(closestOption);
        for (int i = 0; i < points.size() && i < 3; ++i) {
            DisplayOption p = points.get(i);
            float w = weight(width, height, p.minWidthDps, p.minHeightDps, WEIGHT_POWER/*5*/);
            weights += w;
            out.add(new DisplayOption().add(p).multiply(w));
        }
        out.multiply(1.0f / weights);

        // Since the bitmaps are persisted, ensure that all bitmap sizes are not larger than
        // predefined size to avoid cache invalidation
        //iconSizes数组长度是4,存储了默认icon大小,横屏大小,多设备横屏以及竖屏大小,目前逻辑值都一样,都是默认值
        for (int i = INDEX_DEFAULT; i < COUNT_SIZES; i++) {
        //图片大小都设置成最小的那个值
            out.iconSizes[i] = Math.min(out.iconSizes[i], closestPoint.iconSizes[i]);
        }

        return out;
    }

>6.DisplayOption(grid)

就复制了gridOption的值,其他值都是空

        DisplayOption(GridOption grid) {
            this.grid = grid;
            minWidthDps = 0;
            minHeightDps = 0;
            canBeDefault = false;
            for (int i = 0; i < COUNT_SIZES; i++) {
                iconSizes[i] = 0;
                textSizes[i] = 0;
                borderSpaces[i] = new PointF();
                minCellSize[i] = new PointF();
                allAppsCellSize[i] = new PointF();
                allAppsIconSizes[i] = 0;
                allAppsIconTextSizes[i] = 0;
                allAppsBorderSpaces[i] = new PointF();
            }
        }

设置DisplayOption里的值

        private DisplayOption add(DisplayOption p) {
            for (int i = 0; i < COUNT_SIZES; i++) {
                iconSizes[i] += p.iconSizes[i];
                textSizes[i] += p.textSizes[i];
                borderSpaces[i].x += p.borderSpaces[i].x;
                borderSpaces[i].y += p.borderSpaces[i].y;
                minCellSize[i].x += p.minCellSize[i].x;
                minCellSize[i].y += p.minCellSize[i].y;
                horizontalMargin[i] += p.horizontalMargin[i];
                hotseatBarBottomSpace[i] += p.hotseatBarBottomSpace[i];
                hotseatQsbSpace[i] += p.hotseatQsbSpace[i];
                allAppsCellSize[i].x += p.allAppsCellSize[i].x;
                allAppsCellSize[i].y += p.allAppsCellSize[i].y;
                allAppsIconSizes[i] += p.allAppsIconSizes[i];
                allAppsIconTextSizes[i] += p.allAppsIconTextSizes[i];
                allAppsBorderSpaces[i].x += p.allAppsBorderSpaces[i].x;
                allAppsBorderSpaces[i].y += p.allAppsBorderSpaces[i].y;
            }

            return this;
        }

>7.dist

宽的差值和高的差值,算法是直角三角形的斜边

    private static float dist(float x0, float y0, float x1, float y1) {
        return (float) Math.hypot(x1 - x0, y1 - y0);
    }

>8.weight

10万除以(d的5次冥)

    private static float weight(float x0, float y0, float x1, float y1, float pow) {
        float d = dist(x0, y0, x1, y1);
        if (Float.compare(d, 0f) == 0) {
        //这里应该不可能,为0说明两者的值一样,走不到这里,上边就直接返回closestPoint了
            return Float.POSITIVE_INFINITY;
        }
        return (float) (WEIGHT_EFFICIENT / Math.pow(d, pow));
    }

>9.dpiFromPx

    public static float dpiFromPx(float size, int densityDpi) {
        float densityRatio = (float) densityDpi / DisplayMetrics.DENSITY_DEFAULT;//默认的是160dp
        return (size / densityRatio);
    }

2.3.writeToPrefs

默认配置数据处理好以后

    public void writeToPrefs(Context context) {
        LauncherPrefs.getPrefs(context).edit()
                .putString(KEY_WORKSPACE_SIZE, mGridSizeString)
                .putInt(KEY_HOTSEAT_COUNT, mNumHotseat)
                .putInt(KEY_DEVICE_TYPE, mDeviceType)
                .putString(KEY_DB_FILE, mDbFile)
                .apply();
    }

2.4.getDeviceProfile

    public DeviceProfile getDeviceProfile(Context context) {
        Resources res = context.getResources();
        Configuration config = context.getResources().getConfiguration();

        float screenWidth = config.screenWidthDp * res.getDisplayMetrics().density;
        float screenHeight = config.screenHeightDp * res.getDisplayMetrics().density;
        int rotation = WindowManagerProxy.INSTANCE.get(context).getRotation(context);

        return getBestMatch(screenWidth, screenHeight, rotation);
    }

2.5.getBestMatch

    public DeviceProfile getBestMatch(float screenWidth, float screenHeight, int rotation) {
        DeviceProfile bestMatch = supportedProfiles.get(0);
        float minDiff = Float.MAX_VALUE;

        for (DeviceProfile profile : supportedProfiles) {
            float diff = Math.abs(profile.widthPx - screenWidth)
                    + Math.abs(profile.heightPx - screenHeight);
            if (diff < minDiff) {
                minDiff = diff;
                bestMatch = profile;
            } else if (diff == minDiff && profile.rotationHint == rotation) {
                bestMatch = profile;
            }
        }
        return bestMatch;
    }

2.6.Info

>1.DisplayController.java

    private DisplayController(Context context) {
        //..

        WindowManagerProxy wmProxy = WindowManagerProxy.INSTANCE.get(context);
        Context displayInfoContext = getDisplayInfoContext(display);
        //构造方法里初始化一个Info
        mInfo = new Info(displayInfoContext, wmProxy,
                wmProxy.estimateInternalDisplayBounds(displayInfoContext));
    }

>2.estimateInternalDisplayBounds

    public ArrayMap<CachedDisplayInfo, WindowBounds[]> estimateInternalDisplayBounds(
            Context displayInfoContext) {
        CachedDisplayInfo info = getDisplayInfo(displayInfoContext).normalize();
        //...
        WindowBounds[] bounds = estimateWindowBounds(displayInfoContext, info);
        ArrayMap<CachedDisplayInfo, WindowBounds[]> result = new ArrayMap<>();
        result.put(info, bounds);
        return result;
    }

>3.estimateWindowBounds

返回可能的4个方向的窗口尺寸数组

    protected WindowBounds[] estimateWindowBounds(Context context, CachedDisplayInfo displayInfo) {
        //...

        WindowBounds[] result = new WindowBounds[4];
        Point tempSize = new Point();
        for (int i = 0; i < 4; i++) {
            int rotationChange = deltaRotation(rotation, i);
            tempSize.set(displayInfo.size.x, displayInfo.size.y);
            rotateSize(tempSize, rotationChange);
            Rect bounds = new Rect(0, 0, tempSize.x, tempSize.y);

            int navBarHeight, navbarWidth, statusBarHeight;
            if (tempSize.y > tempSize.x) {
                navBarHeight = navBarHeightPortrait;
                navbarWidth = 0;
                statusBarHeight = statusBarHeightPortrait;
            } else {
                navBarHeight = navBarHeightLandscape;
                navbarWidth = navbarWidthLandscape;
                statusBarHeight = statusBarHeightLandscape;
            }

            Rect insets = new Rect(safeCutout);
            rotateRect(insets, rotationChange);
            insets.top = Math.max(insets.top, statusBarHeight);
            insets.bottom = Math.max(insets.bottom, navBarHeight);

            if (i == Surface.ROTATION_270 || i == Surface.ROTATION_180) {
                // On reverse landscape (and in rare-case when the natural orientation of the
                // device is landscape), navigation bar is on the right.
                insets.left = Math.max(insets.left, navbarWidth);
            } else {
                insets.right = Math.max(insets.right, navbarWidth);
            }
            result[i] = new WindowBounds(bounds, insets, i);
        }
        return result;
    }

>4.Info构造方法

        public Info(Context displayInfoContext,
                WindowManagerProxy wmProxy,
                Map<CachedDisplayInfo, WindowBounds[]> perDisplayBoundsCache) {
            CachedDisplayInfo displayInfo = wmProxy.getDisplayInfo(displayInfoContext);
            normalizedDisplayInfo = displayInfo.normalize();
            rotation = displayInfo.rotation;
            currentSize = displayInfo.size;
            cutout = displayInfo.cutout;

            Configuration config = displayInfoContext.getResources().getConfiguration();
            fontScale = config.fontScale;
            densityDpi = config.densityDpi;
            mScreenSizeDp = new PortraitSize(config.screenHeightDp, config.screenWidthDp);
            navigationMode = wmProxy.getNavigationMode(displayInfoContext);
        //perDisplayBoundsCache的value数组里有4个值,前边有贴初始化的代码
            mPerDisplayBounds.putAll(perDisplayBoundsCache);
//...
            //这里supportedBounds的结果是3个,原因是竖向的两个bounds里边的值一样,具体见下边WindowBounds
            mPerDisplayBounds.values().forEach(
                    windowBounds -> Collections.addAll(supportedBounds, windowBounds));

        }

>5.WindowBounds.java

可以看到,判断对象是否相等,就看bounds值和insets值,和参数rotationHint无关

    public int hashCode() {
        return Objects.hash(bounds, insets);
    }

    @Override
    public boolean equals(@Nullable Object obj) {
        if (!(obj instanceof WindowBounds)) {
            return false;
        }
        WindowBounds other = (WindowBounds) obj;
        return other.bounds.equals(bounds) && other.insets.equals(insets);
    }

3.LauncherModel

    LauncherModel(@NonNull final Context context, @NonNull final LauncherAppState app,
            @NonNull final IconCache iconCache, @NonNull final AppFilter appFilter,
            final boolean isPrimaryInstance) {
        mApp = app;
        mBgAllAppsList = new AllAppsList(iconCache, appFilter);
        mModelDelegate = ModelDelegate.newInstance(context, app, mBgAllAppsList, mBgDataModel,
                isPrimaryInstance);
    }

3.1.getWriter

    public ModelWriter getWriter(final boolean hasVerticalHotseat, final boolean verifyChanges,
            @Nullable final Callbacks owner) {
        return new ModelWriter(mApp.getContext(), this, mBgDataModel,
                hasVerticalHotseat, verifyChanges, owner);
    }

3.2.addCallbacksAndLoad

    public boolean addCallbacksAndLoad(@NonNull final Callbacks callbacks) {
        synchronized (mLock) {
            addCallbacks(callbacks);
            return startLoader(new Callbacks[] { callbacks });

        }
    }

>2个地方用到

//launcher.java的onCreate方法

        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;
            }
        }

//TaskbarViewController.java 的init方法

        if (mActivity.isUserSetupComplete()) {
            // Only load the callbacks if user setup is completed
            LauncherAppState.getInstance(mActivity).getModel().addCallbacksAndLoad(mModelCallbacks);
        }

3.3removeCallbacks

    public void removeCallbacks(@NonNull final Callbacks callbacks) {
    synchronized (mCallbacksList) {
        Preconditions.assertUIThread();
        if (mCallbacksList.remove(callbacks)) {
            if (stopLoader()) {
                // Rebind existing callbacks
                startLoader();
            }
        }
    }
}

>调用的地方

//多屏幕的时候也会调用,我们这里不考虑这种,

//一个是launcher的onDestroy

//一个是TaskbarViewController.java 的onDestroy方法,原因好像是TouchInteractionService.java这个服务会启动两次,所以第一次的会调用onDestroy方法

    public void onDestroy() {
        LauncherAppState.getInstance(mActivity).getModel().removeCallbacks(mModelCallbacks);
      
    }

3.4.rebindCallbacks

    public void rebindCallbacks() {
        if (hasCallbacks()) {
            startLoader();
        }
    }

3.5.forceReload

    public void forceReload() {
        synchronized (mLock) {
            // Stop any existing loaders first, so they don't set mModelLoaded to true later
            stopLoader();
            mModelLoaded = false;
        }

        // Start the loader if launcher is already running, otherwise the loader will run,
        // the next time launcher starts
        if (hasCallbacks()) {
            startLoader();
        }
    }

3.6.startLoader

    private boolean startLoader(@NonNull final Callbacks[] newCallbacks) {
        // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
        ItemInstallQueue.INSTANCE.get(mApp.getContext())
                .pauseModelPush(ItemInstallQueue.FLAG_LOADER_RUNNING);
        synchronized (mLock) {
            // If there is already one running, tell it to stop.
            boolean wasRunning = stopLoader();
            //数据已经加载完成过并且当前没有加载数据
            boolean bindDirectly = mModelLoaded && !mIsLoaderTaskRunning;
            boolean bindAllCallbacks = wasRunning || !bindDirectly || newCallbacks.length == 0;
            final Callbacks[] callbacksList = bindAllCallbacks ? getCallbacks() : newCallbacks;

            if (callbacksList.length > 0) {
                // Clear any pending bind-runnables from the synchronized load process.
                for (Callbacks cb : callbacksList) {
                //先调用callback的clear方法清除未执行的binds
                    MAIN_EXECUTOR.execute(cb::clearPendingBinds);
                }

                LoaderResults loaderResults = new LoaderResults(
                        mApp, mBgDataModel, mBgAllAppsList, callbacksList);
                //刚开始走的是else
                if (bindDirectly) {
                    //走这里说明数据已经加载完事了,这里直接同步返回数据
                    loaderResults.bindWorkspace(bindAllCallbacks);
                    // For now, continue posting the binding of AllApps as there are other
                    // issues that arise from that.
                    loaderResults.bindAllApps();
                    loaderResults.bindDeepShortcuts();
                    loaderResults.bindWidgets();
                    return true;
                } else {
                //刚启动的时候走的是这里,异步加载数据
                    stopLoader();//先停掉已有的task
                    //开启新的task,就是个runnable
                    mLoaderTask = new LoaderTask(
                            mApp, mBgAllAppsList, mBgDataModel, mModelDelegate, loaderResults);
                //异步执行,防止阻塞线程
                    MODEL_EXECUTOR.post(mLoaderTask);
                }
            }
        }
        return false;
    }

>stopLoader

这个方法会修改task的状态为stop,方便跳出task,task里的任务每一步都会判断stop状态的

    private boolean stopLoader() {
        synchronized (mLock) {
            LoaderTask oldTask = mLoaderTask;
            mLoaderTask = null;
            if (oldTask != null) {
                oldTask.stopLocked();
                return true;
            }
            return false;
        }
    }

>beginLoader

    public LoaderTransaction beginLoader(@NonNull final LoaderTask task)
            throws CancellationException {
        return new LoaderTransaction(task);
    }

>LoaderTransaction

        private LoaderTransaction(@NonNull final LoaderTask task) throws CancellationException {
            synchronized (mLock) {
                if (mLoaderTask != task) {
                    throw new CancellationException("Loader already stopped");
                }
                mTask = task;
                mIsLoaderTaskRunning = true;
                mModelLoaded = false;
            }
        }

        public void commit() {
            synchronized (mLock) {
                // Everything loaded bind the data.
                mModelLoaded = true;
            }
        }

        @Override
        public void close() {
            synchronized (mLock) {
                // If we are still the last one to be scheduled, remove ourselves.
                if (mLoaderTask == mTask) {
                    mLoaderTask = null;
                }
                mIsLoaderTaskRunning = false;
            }
        }
    }

3.7.LoaderTask

    public synchronized void stopLocked() {
        mStopped = true;
        this.notify();
    }

//run方法里每一步都会判断stop状态,如果已经stop了,就直接抛出异常。

    private synchronized void verifyNotStopped() throws CancellationException {
        if (mStopped) {
            throw new CancellationException("Loader stopped");
        }
    }

>run方法

这里会加载我们需要的各种数据,看名字大概就知道是啥数据了,具体的数据加载逻辑就不看了。

    public void run() {
        synchronized (this) {
            // Skip fast if we are already stopped.
            if (mStopped) {
                return;
            }
        }
        //实例化一个loader事务,控制task的状态
        try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
        //这个是显示在桌面的shortcuts,就是长按app,选择一个shortcuts长按拖动到桌面
            List<ShortcutInfo> allShortcuts = new ArrayList<>();
       //#### 加载workspace页面需要的数据
            try {
                loadWorkspace(allShortcuts, memoryLogger);
            } finally {
            }
        
            if (mApp.getInvariantDeviceProfile().dbFile.equals(mDbName)) {
                verifyNotStopped();
                sanitizeData();
            }
            verifyNotStopped();
            //绑定数据,比如launcher页里给LauncherModel添加了callback,最终会调用对应的callback
            mResults.bindWorkspace(true /* incrementBindId */);
          
            mModelDelegate.workspaceLoadComplete();
            sendFirstScreenActiveInstallsBroadcast();
           

            // Take a break
            waitForIdle();
            verifyNotStopped();

     // second step #### allapps页面的数据
            List<LauncherActivityInfo> allActivityList;
            try {
               allActivityList = loadAllApps();
            } finally {
                
            }
           
            verifyNotStopped();
            mResults.bindAllApps();
         

            verifyNotStopped();
            IconCacheUpdateHandler updateHandler = mIconCache.getUpdateHandler();
            setIgnorePackages(updateHandler);
            //更新allapps图标
            updateHandler.updateIcons(allActivityList,
                    LauncherActivityCachingLogic.newInstance(mApp.getContext()),
                    mApp.getModel()::onPackageIconsUpdated);
         

            verifyNotStopped();
           //更新shortcuts图标
            updateHandler.updateIcons(allShortcuts, new ShortcutCachingLogic(),
                    mApp.getModel()::onPackageIconsUpdated);

            // Take a break
            waitForIdle();
            verifyNotStopped();

     // third step ****这里是所有的shortcuts,包括没显示在workspace上的
            List<ShortcutInfo> allDeepShortcuts = loadDeepShortcuts();
            
            verifyNotStopped();
            mResults.bindDeepShortcuts();
           

            verifyNotStopped();
            updateHandler.updateIcons(allDeepShortcuts,
                    new ShortcutCachingLogic(), (pkgs, user) -> { });

            // Take a break
            waitForIdle();
           
            verifyNotStopped();

      // fourth step ****所有的小部件,一个app可能有多个
            List<ComponentWithLabelAndIcon> allWidgetsList =
                    mBgDataModel.widgetsModel.update(mApp, null);
        

            verifyNotStopped();
            mResults.bindWidgets();
           
            verifyNotStopped();

            updateHandler.updateIcons(allWidgetsList,
                    new ComponentWithIconCachingLogic(mApp.getContext(), true),
                    mApp.getModel()::onWidgetLabelsUpdated);
            

            // fifth step 文件夹
            loadFolderNames();

            verifyNotStopped();
            updateHandler.finish();
            

            mModelDelegate.modelLoadComplete();
            transaction.commit();//这里表示一次task完成了,修改状态。
            memoryLogger.clearLogs();
        } catch (CancellationException e) {
          //上边有很多verifyNotStopped,如果中途stop的话会抛出异常,走这里,任务结束
        } catch (Exception e) {
            memoryLogger.printLogs();
            throw e;
        } finally {
          
        }
      
    }

>loadWorkspace

    private void loadWorkspace(List<ShortcutInfo> allDeepShortcuts, LoaderMemoryLogger logger) {
        loadWorkspace(allDeepShortcuts, LauncherSettings.Favorites.CONTENT_URI,
                null /* selection */, logger);
    }

>CONTENT_URI

可以看到,查找的是favorites表的数据

        /**
         * The content:// style URL for "favorites" table
         */
        public static final Uri CONTENT_URI = Uri.parse("content://"
                + LauncherProvider.AUTHORITY + "/" + TABLE_NAME);

3.8.shortcuts种类

    //这种包括动态添加的,清单文件里静态的,以及固定在桌面的
    public static final int ALL = ShortcutQuery.FLAG_MATCH_DYNAMIC
            | ShortcutQuery.FLAG_MATCH_MANIFEST | ShortcutQuery.FLAG_MATCH_PINNED;
     //动态的以及静态的,长按某个app,会查找这个app支持的shortcuts,会用到这个       
    public static final int PUBLISHED = ShortcutQuery.FLAG_MATCH_DYNAMIC
            | ShortcutQuery.FLAG_MATCH_MANIFEST;
    //这种是固定在workspace上的
    public static final int PINNED = ShortcutQuery.FLAG_MATCH_PINNED;

3.9LoaderResults

还记得launcherModel里startTask的时候,里边初始化了一个laoderResults

                LoaderResults loaderResults = new LoaderResults(
                        mApp, mBgDataModel, mBgAllAppsList, callbacksList);

然后在LoaderTask里会调用各种方法绑定数据

>bindWorkspace

    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();
        }
    }

>bindAllApps

    public void bindAllApps() {
        // shallow copy
        AppInfo[] apps = mBgAllAppsList.copyData();
        int flags = mBgAllAppsList.getFlags();
        executeCallbacksTask(c -> c.bindAllApplications(apps, flags), mUiExecutor);
    }

3.10.WorkspaceBinder

        WorkspaceBinder(Callbacks callbacks,
                Executor uiExecutor,
                LauncherAppState app,
                BgDataModel bgDataModel,
                int myBindingId,
                ArrayList<ItemInfo> workspaceItems,
                ArrayList<LauncherAppWidgetInfo> appWidgets,
                ArrayList<FixedContainerItems> extraItems,
                IntArray orderedScreenIds) {
            mCallbacks = callbacks;
            mUiExecutor = uiExecutor;
            mApp = app;
            mBgDataModel = bgDataModel;
            mMyBindingId = myBindingId;
            mWorkspaceItems = workspaceItems;
            mAppWidgets = appWidgets;
            mExtraItems = extraItems;
            mOrderedScreenIds = orderedScreenIds;
        }

>bind

        private void bind() {
            final IntSet currentScreenIds =
                    mCallbacks.getPagesToBindSynchronously(mOrderedScreenIds);

            //当前页和其他页面数据分离,各自包含2种数据类型
            ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
            ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
            ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
            ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();

        //根据容器id以及类型进行分类
            filterCurrentWorkspaceItems(currentScreenIds, mWorkspaceItems, currentWorkspaceItems,
                    otherWorkspaceItems);
         //同上,这里分离的是小部件
            filterCurrentWorkspaceItems(currentScreenIds, mAppWidgets, currentAppWidgets,
                    otherAppWidgets);
             //排序
            final InvariantDeviceProfile idp = mApp.getInvariantDeviceProfile();
            sortWorkspaceItemsSpatially(idp, currentWorkspaceItems);
            sortWorkspaceItemsSpatially(idp, otherWorkspaceItems);

            //即将开始进行绑定数据,回调告诉一下
            executeCallbacksTask(c -> {
                c.clearPendingBinds();
                c.startBinding();
            }, mUiExecutor);

            // Bind workspace screens ,绑定页面,可以理解成创建容器
            executeCallbacksTask(c -> c.bindScreens(mOrderedScreenIds), mUiExecutor);

            // Load items on the current page.加载数据到当前页面
            bindWorkspaceItems(currentWorkspaceItems, mUiExecutor);
            bindAppWidgets(currentAppWidgets, mUiExecutor);
            mExtraItems.forEach(item ->
                    executeCallbacksTask(c -> c.bindExtraContainerItems(item), mUiExecutor));

            RunnableList pendingTasks = new RunnableList();
            Executor pendingExecutor = pendingTasks::add;
            //绑定其他页面数据
            bindWorkspaceItems(otherWorkspaceItems, pendingExecutor);
            bindAppWidgets(otherAppWidgets, pendingExecutor);
            //绑定数据完成的回调
            executeCallbacksTask(c -> c.finishBindingItems(currentScreenIds), pendingExecutor);
            //其他一些相关的回调
            pendingExecutor.execute(
                    () -> {
                        MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
                        ItemInstallQueue.INSTANCE.get(mApp.getContext())
                                .resumeModelPush(FLAG_LOADER_RUNNING);
                    });

            executeCallbacksTask(
                    c -> {
                        MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                        c.onInitialBindComplete(currentScreenIds, pendingTasks);
                    }, mUiExecutor);

            mCallbacks.bindStringCache(mBgDataModel.stringCache.clone());
        }

4.LauncherAppState.java

    public LauncherAppState(Context context, @Nullable String iconCacheFileName) {
        mContext = context;

        mInvariantDeviceProfile = InvariantDeviceProfile.INSTANCE.get(context);
        mIconProvider = new LauncherIconProvider(context);
        mIconCache = new IconCache(mContext, mInvariantDeviceProfile,
                iconCacheFileName, mIconProvider);
        mModel = new LauncherModel(context, this, mIconCache, new AppFilter(mContext),
                iconCacheFileName != null);
        mOnTerminateCallback.add(mIconCache::close);
    }

//LauncherAppState

    public LauncherAppState(Context context) {
        this(context, LauncherFiles.APP_ICONS_DB);
        //监听配置的改变,重新加载launcher
        mInvariantDeviceProfile.addOnChangeListener(modelPropertiesChanged -> {
            if (modelPropertiesChanged) {
                refreshAndReloadLauncher();
            }
        });

        mContext.getSystemService(LauncherApps.class).registerCallback(mModel);

        SimpleBroadcastReceiver modelChangeReceiver =
                new SimpleBroadcastReceiver(mModel::onBroadcastIntent);
        modelChangeReceiver.register(mContext, Intent.ACTION_LOCALE_CHANGED,
                Intent.ACTION_MANAGED_PROFILE_AVAILABLE,
                Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE,
                Intent.ACTION_MANAGED_PROFILE_UNLOCKED,
                ACTION_DEVICE_POLICY_RESOURCE_UPDATED);
        if (FeatureFlags.IS_STUDIO_BUILD) {
            modelChangeReceiver.register(mContext, Context.RECEIVER_EXPORTED, ACTION_FORCE_ROLOAD);
        }
        mOnTerminateCallback.add(() -> mContext.unregisterReceiver(modelChangeReceiver));

        CustomWidgetManager.INSTANCE.get(mContext)
                .setWidgetRefreshCallback(mModel::refreshAndBindWidgetsAndShortcuts);

        SafeCloseable userChangeListener = UserCache.INSTANCE.get(mContext)
                .addUserChangeListener(mModel::forceReload);
        mOnTerminateCallback.add(userChangeListener::close);

        IconObserver observer = new IconObserver();
        SafeCloseable iconChangeTracker = mIconProvider.registerIconChangeListener(
                observer, MODEL_EXECUTOR.getHandler());
        mOnTerminateCallback.add(iconChangeTracker::close);
        MODEL_EXECUTOR.execute(observer::verifyIconChanged);
        SharedPreferences prefs = LauncherPrefs.getPrefs(mContext);
        prefs.registerOnSharedPreferenceChangeListener(observer);
        mOnTerminateCallback.add(
                () -> prefs.unregisterOnSharedPreferenceChangeListener(observer));

        InstallSessionTracker installSessionTracker =
                InstallSessionHelper.INSTANCE.get(context).registerInstallTracker(mModel);
        mOnTerminateCallback.add(installSessionTracker::unregister);

        // Register an observer to rebind the notification listener when dots are re-enabled.
        SettingsCache settingsCache = SettingsCache.INSTANCE.get(mContext);
        SettingsCache.OnChangeListener notificationLister = this::onNotificationSettingsChanged;
        settingsCache.register(NOTIFICATION_BADGING_URI, notificationLister);
        onNotificationSettingsChanged(settingsCache.getValue(NOTIFICATION_BADGING_URI));
        mOnTerminateCallback.add(() ->
                settingsCache.unregister(NOTIFICATION_BADGING_URI, notificationLister));
    }

4.1.refreshAndReloadLauncher

    private void refreshAndReloadLauncher() {
        LauncherIcons.clearPool();
        mIconCache.updateIconParams(
                mInvariantDeviceProfile.fillResIconDpi, mInvariantDeviceProfile.iconBitmapSize);
        mModel.forceReload();
    }

5.Launcher.java

5.1.onCreate

        super.onCreate(savedInstanceState);

        LauncherAppState app = LauncherAppState.getInstance(this);
        mModel = app.getModel();

        mRotationHelper = new RotationHelper(this);
        InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
        initDeviceProfile(idp);
        idp.addOnChangeListener(this);
        mSharedPrefs = LauncherPrefs.getPrefs(this);
        mIconCache = app.getIconCache();
        mAccessibilityDelegate = createAccessibilityDelegate();

        initDragController();
        mAllAppsController = new AllAppsTransitionController(this);
        mStateManager = new StateManager<>(this, NORMAL);

        mOnboardingPrefs = createOnboardingPrefs(mSharedPrefs);

        // TODO: move the SearchConfig to SearchState when new LauncherState is created.
        mBaseSearchConfig = new BaseSearchConfig();

        mAppWidgetManager = new WidgetManagerHelper(this);
        mAppWidgetHolder = createAppWidgetHolder();
        mAppWidgetHolder.startListening();

        setupViews();
        crossFadeWithPreviousAppearance();
        mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);

        //..
        mStateManager.reapplyState();
        //..
        //这个会启动loadtask加载数据
        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;
            }
        }

        // For handling default keys
        setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);

        setContentView(getRootView());
        if (mOnInitialBindListener != null) {
            getRootView().getViewTreeObserver().addOnPreDrawListener(mOnInitialBindListener);
        }
        getRootView().dispatchInsets();

        // Listen for broadcasts
        registerReceiver(mScreenOffReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));

        getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW,
                Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText));

        if (mLauncherCallbacks != null) {
            mLauncherCallbacks.onCreate(savedInstanceState);
        }
        mOverlayManager = getDefaultOverlay();
        PluginManagerWrapper.INSTANCE.get(this).addPluginListener(this,
                LauncherOverlayPlugin.class, false /* allowedMultiple */);

        mRotationHelper.initialize();
        TraceHelper.INSTANCE.endSection(traceToken);

        mUserChangedCallbackCloseable = UserCache.INSTANCE.get(this).addUserChangeListener(
                () -> getStateManager().goToState(NORMAL));

        if (Utilities.ATLEAST_R) {
            getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
        }
        setTitle(R.string.home_screen);
    }

6.idp数据相关

6.1.

LauncherAppState.getIDP(mContext)

7.总结

  • InvariantDeviceProfile 通用配置文件数据的获取,之后是屏幕相关的DeviceProfile
  • 数据的加载是通过LauncherModel的,在launcher和其他页面添加了callback以后,就会调用对应的方法,开启一个LoaderTask获取数据,之后通过callback把数据分发出去