通过前两篇的分析,我们已经知道了LauncherModel的初始化及工作流程,如果您还不熟悉的话请看前两篇博文

android M Launcher之LauncherModel (一)

android M Launcher之LauncherModel (二)

了解了LauncherModel的工作过程后,我们继续来学习LauncherModel中提供的一些工具,从而了解Google工程师在自家系上怎么开发的。

1、线程等待waitForIdle

我们在这个系列第二篇开始讲LoaderTask的run方法时,在加载绑定桌面数据之后,加载与绑定应用程序之前,有一个等待动作即调用了waitForIdle方法,为什么要去调这个方法呢,大家都知道在绑定桌面数据时我们是去UI线程中处理的,接触过应用开发的都知道UI线程正常情况下是不能阻塞的,否则有可能产生ANR,这将严重影响用户体验。所有这里LoaderTask在将结果发送给UI线程之后,为了保证界面绑定任务可以高效的完成,往往会将自己的任务暂停下来,等待UI线程处理完成。不知道大家有没有这样设计过,反正我是没有。从这儿也可以看出Google工程师是比较严谨的。

那我们就来分析下waitForIdle函数是怎么实现的。

首先,创建一个UI线程闲时执行的任务,这个任务负责设置某些关键的控制标志,并将其通过PostIdle方法加入处理器的消息队列中。

 mHandler.postIdle(new Runnable() {
                    public void run() {
                        synchronized (LoaderTask.this) {
                            mLoadAndBindStepFinished = true;
                            if (DEBUG_LOADERS) {
                                Log.d(TAG, "done with previous binding step");
                            }
                            LoaderTask.this.notify();
                        }
                    }
                });

这样一来,只有UI线程闲置下来的时候,这里定义的任务才会得到执行,这也就说明了界面已经刷新完成。而一旦这个任务得到执行,就会将mLoadAndBindStepFinished 置为true,以控制即将来临的有条件的无限等待。

最后 设置一个有条件的无限等待,等待来自UI线程的指示。

  while (!mStopped && !mLoadAndBindStepFinished) {
                    try {
                        // Just in case mFlushingWorkerThread changes but we aren't woken up,
                        // wait no longer than 1sec at a time
                        this.wait(1000);
                    } catch (InterruptedException ex) {
                        // Ignore
                    }
                }

只要没有中断且加载没有完成这里将一直等待,知道完成,这样看还是很简单的逻辑呀,你有想到么。

2、停止加载工作stopLocked

对于一项任务,有时候我们需要停止它的工作,以保证数据或者流程的正确性。我们看下LoaderTask是怎么做的:

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

可以看出要停止工作只需将mStopped 置为true, 而LoaderTask在运行过程中会频繁的查询mStopped 标志,所有在开始设置之前需对LoaderTask上锁,以保证mStopped 得到正确的设定,有了上锁就必须有解锁,这就是this.notify();的作用。

3、获取通道 tryGetCallbacks

LoaderTask在执行一次加载任务的时候,都毫无意外的需要检验通往Launcher的通道是否存在,或者当前的通道是否经历过重建,如果这样的情况存在,那么LoaderTask久没有继续执行的必要了。

tryGetCallbacks工具的作用就是帮助完成通往Launcher通道的验证。

  Callbacks tryGetCallbacks(Callbacks oldCallbacks) {
            synchronized (mLock) {
                if (mStopped) {
                    return null;
                }

                if (mCallbacks == null) {
                    return null;
                }

                final Callbacks callbacks = mCallbacks.get();
                if (callbacks != oldCallbacks) {
                    return null;
                }
                if (callbacks == null) {
                    Log.w(TAG, "no mCallbacks");
                    return null;
                }

                return callbacks;
            }
        }

这里代码应该比较好明白吧。

4、桌面空间判断工具 checkItemPlacement

桌面上的每一个组件想要加载到桌面或者HotSeat,都需要确定当前的桌面或者HotSeat中是否还有足够的空间,checkItemPlacement方法用于完成这个任务。

这个方法分为几步,

  • 获取Launcher属性及item属性

    要检查需要处理的项是否有足够的空间,首先要知道当前Launcher有多少空间可以被占用,以及当前的项所处的空间情况,

    LauncherAppState app = LauncherAppState.getInstance();
            InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
            final int countX = profile.numColumns;
            final int countY = profile.numRows;
            //被处理项的桌面索引
            long containerIndex = item.screenId;
  • 处理被处理项在所处容器的占用情况。

    主要处理两种情况,一种是HotSeat容器,一种是桌面容器。

    如果需要处理的是HotSeat容器 ,它需要一些判断,如下:
  if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
                // Return early if we detect that an item is under the hotseat button
                if (mCallbacks == null ||
                        mCallbacks.get().isAllAppsButtonRank((int) item.screenId)) {
                    Log.e(TAG, "Error loading shortcut into hotseat " + item
                            + " into position (" + item.screenId + ":" + item.cellX + ","
                            + item.cellY + ") occupied by all apps");
                    return false;
                }
                //获取HotSeat容器中空间的占用情况,
                final ItemInfo[][] hotseatItems =
                        occupied.get((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT);
                //如果被处理项要求的位置超过了HotSeat的容量,返回false
                if (item.screenId >= profile.numHotseatIcons) {
                    Log.e(TAG, "Error loading shortcut " + item
                            + " into hotseat position " + item.screenId
                            + ", position out of bounds: (0 to " + (profile.numHotseatIcons - 1)
                            + ")");
                    return false;
                }
                //如果HotSeat空间已经被Launcher分配
                if (hotseatItems != null) {
                    //如果被处理的项所要求的位置已经被占用,返回分配失败
                    if (hotseatItems[(int) item.screenId][0] != null) {
                        Log.e(TAG, "Error loading shortcut into hotseat " + item
                                + " into position (" + item.screenId + ":" + item.cellX + ","
                                + item.cellY + ") occupied by "
                                + occupied.get(LauncherSettings.Favorites.CONTAINER_HOTSEAT)
                                [(int) item.screenId][0]);
                        return false;
                        //如果该空间空闲,则将此空间分配给此项,返回分配成功
                    } else {
                        hotseatItems[(int) item.screenId][0] = item;
                        return true;
                    }
                    //如果HotSeat空间还没有被分配,分配空间后返回分配成功
                } else {
                    //开辟HotSeat空间
                    final ItemInfo[][] items = new ItemInfo[(int) profile.numHotseatIcons][1];
                    items[(int) item.screenId][0] = item;
                    //将分配的空间加入占用列表中维护
                    occupied.put((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT, items);
                    return true;
                }
            } 

如果是桌面容器

if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
                if (!workspaceScreens.contains((Long) item.screenId)) {
                    // The item has an invalid screen id.
                    return false;
                }
            } 

其他情况一律返回分配成功。

  • 添加占用列表

如果对应的桌面索引存在,但Launcher并没有在这个桌面页上分配空间,那么Launcher需要为此分配足够的空间:

if (!occupied.containsKey(item.screenId)) {
                ItemInfo[][] items = new ItemInfo[countX + 1][countY + 1];
                occupied.put(item.screenId, items);
            }
  • 判断快捷方式是否有足够的空间

在这里需要判断当前处理的项是否有足够的空间容纳该项,因为该项有可能是一个桌面小部件,可能占用比较大的空间。

 //获取屏幕上被占用情况
            final ItemInfo[][] screens = occupied.get(item.screenId);
            //如果被处理的项超过范围,则返回分配失败
            if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
                    item.cellX < 0 || item.cellY < 0 ||
                    item.cellX + item.spanX > countX || item.cellY + item.spanY > countY) {
                Log.e(TAG, "Error loading shortcut " + item
                        + " into cell (" + containerIndex + "-" + item.screenId + ":"
                        + item.cellX + "," + item.cellY
                        + ") out of screen bounds ( " + countX + "x" + countY + ")");
                return false;
            }
  • 检查空间及屏幕占用

有了足够的空间,还需要判断该项需要占用的空间内是否还被其他桌面项(快捷方式 桌面小部件) 占用,如果该项需要的空间范围内只有一块被占用也属于分配失败。

 // Check if any workspace icons overlap with each other
            for (int x = item.cellX; x < (item.cellX + item.spanX); x++) {
                for (int y = item.cellY; y < (item.cellY + item.spanY); y++) {
                    if (screens[x][y] != null) {
                        Log.e(TAG, "Error loading shortcut " + item
                                + " into cell (" + containerIndex + "-" + item.screenId + ":"
                                + x + "," + y
                                + ") occupied by "
                                + screens[x][y]);
                        return false;
                    }
                }
            }
  • 让被处理的项占用它需要的空间
for (int x = item.cellX; x < (item.cellX + item.spanX); x++) {
                for (int y = item.cellY; y < (item.cellY + item.spanY); y++) {
                    screens[x][y] = item;
                }
            }

6、桌面组件的排序工具sortWorkspaceItemsSpatially

在Launcher中,在绑定桌面组件之前都需要对桌面组件进行一次排序,原则上是Y轴方向从上到下,X轴从左到右

 final LauncherAppState app = LauncherAppState.getInstance();
            final InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
            // XXX: review this
            Collections.sort(workspaceItems, new Comparator<ItemInfo>() {
                @Override
                public int compare(ItemInfo lhs, ItemInfo rhs) {
                    int cellCountX = (int) profile.numColumns;
                    int cellCountY = (int) profile.numRows;
                    int screenOffset = cellCountX * cellCountY;
                    int containerOffset = screenOffset * (Launcher.SCREEN_COUNT + 1); // +1 hotseat
                    long lr = (lhs.container * containerOffset + lhs.screenId * screenOffset +
                            lhs.cellY * cellCountX + lhs.cellX);
                    long rr = (rhs.container * containerOffset + rhs.screenId * screenOffset +
                            rhs.cellY * cellCountX + rhs.cellX);
                    return (int) (lr - rr);
                }
            });

这个是java对一个数组的排序方式。

以上就是LoaderTask提供的一些重要工具,通过学习Google的源码我们也可以把它应用到实际的开发中去,提高自己代码的健壮性。

好了,从第一篇开始到这篇结束,我们终于把LauncherModel的整个工作流程走了一遍。以下是整个LauncherModel系列的索引

一 、LauncherModel的的实例化

二 、 LauncherModel数据的加载流程

三 、LauncherModel中一些工具的介绍

android M Launcher之LauncherModel (三)的更多相关文章

  1. android M Launcher之LauncherModel (二)

    上一篇我们通过LauncherModel的创建 ,实例化,以及与LauncherModel之间的沟通方式.初步了解了LauncherModel一些功能及用法,如果对LauncherModel一系列初始 ...

  2. android M Launcher之LauncherModel (一)

    众所周知 LauncherModel在Launcher中所占的位置,它相当于Launcher的数据中心,Launcher的桌面以及应用程序菜单中所需的数据像 桌面小部件的信息.快捷方式信息.文件信息. ...

  3. Android实现全屏的三种方式

    一.通过代码 requestWindowFeature(Window.FEATURE_NO_TITLE);// 隐藏标题栏 getWindow().setFlags(WindowManager.Lay ...

  4. Android系列之网络(三)----使用HttpClient发送HTTP请求(分别通过GET和POST方法发送数据)

    ​[声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/ ...

  5. Android系列之Fragment(三)----Fragment和Activity之间的通信(含接口回调)

    ​[声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/ ...

  6. Android应用之《宋词三百首》(二)

    接上回,上回我们讲到MainActivity里面将所有的宋词标题和作者显示到界面的ListView中去,我们接下来的工作是通过点击ListView的Item跳转到ContentActivity里面去显 ...

  7. android中解析文件的三种方式

    android中解析文件的三种方式     好久没有动手写点东西了,最近在研究android的相关技术,现在就android中解析文件的三种方式做以下总结.其主要有:SAX(Simple API fo ...

  8. Android 仿PhotoShop调色板应用(三) 主体界面绘制

    版权声明:本文为博主原创文章,未经博主允许不得转载. Android 仿PhotoShop调色板应用(三) 主体界面绘制    关于PhotoShop调色板应用的实现我总结了两个最核心的部分:   1 ...

  9. Android艺术开发探索第三章————View的事件体系(下)

    Android艺术开发探索第三章----View的事件体系(下) 在这里就能学习到很多,主要还是对View的事件分发做一个体系的了解 一.View的事件分发 上篇大致的说了一下View的基础知识和滑动 ...

随机推荐

  1. 爆炸,解体,入侵,你想得到的你想不到的大BUG们

    郑昀 创建于2017/9/29 最后更新于2017/10/6 提纲: 阿丽亚娜火箭的解体 阿波罗飞船的P01模式 德勤的Google+ 麻省理工的500英里邮件 又到了扶额兴叹的节气.(前文回顾:5年 ...

  2. CentOS安装node.js-8.11.1+替换淘宝NPM镜像

    注:以下所有操作均在CentOS 6.8 x86_64位系统下完成. #准备工作# 由于node.js-8.11.1在源码编译安装的时候需要gcc 4.9.4或clang++ 3.4.2以上版本的支持 ...

  3. 列表(list)之三 -如何较为均匀的将任意字符串按指定组数分组,方差最少

    当字符串的长度不是份数的整数倍时如何均匀地分割,例如:长度为11的字符串要分割成4份,有很多种分法,比如3, 3, 3, 2(前3个字符一份,中间3个一份,再中间3个一份,最后2个一份)这种是比较均匀 ...

  4. java四种访问控制权限:public ,default,protected,private

    四种访问权限的控制 范围 private default protected  public 同一个类中 √ √ √ √ 相同包不同类 × √ √ √ 不同包的子类中 × × √ √ 不同包非子类 × ...

  5. ASP.NET Core 如何在运行Docker容器时指定容器外部端口

    前面我写了一系列关于持续集成的文章,最终构建出来的镜像运行之后,应该会发现每次构建运行之后端口都变了,这对于我们来说是十分不方便的,所以我们可以通过修改docker compose的配置文件来完成我们 ...

  6. [C#]使用 Jenkins 为 .Net Core 实现持续集成/部署

    在前后端分离开发的项目当中为了避免重复构建发布,我们需要部署一个持续发布环境,而目前的开发环境服务器都是基于 CentOS 的,因此每次在本地发布之后还需要打包,上传,部署,十分繁琐.故这里采用了比较 ...

  7. [LeetCode] Predict the Winner 预测赢家

    Given an array of scores that are non-negative integers. Player 1 picks one of the numbers from eith ...

  8. 【django小练习之主机管理界面】

    需求: 利用django,js,bootstrap等实现登录,主机管理等操作. 实现截图 登录界面 主机界面,添加及编辑 部门管理界面 代码实现 目录层级 settings.py "&quo ...

  9. HTTP你真的懂了吗?

    最近面试踩了些坑,自己看书看过的内容,即使能记得差不多,回答起来就是很混乱(绝望脸).比如HTTP的这几个问题,现在整理一下,一个点一个点的说! 1.    聊一聊你理解的HTTP 1)   Http ...

  10. [测试题]無名(noname)

    Description 因为是蒯的题所以没想好名字,为什么要用繁体呢?去看<唐诗三百首>吧! 题意很简单,给你一个串,求他有多少个不同的子串,满足前缀为A,后缀为B. 需要注意的是,串中所 ...