Android Launcher3分析——LauncherModel
接上篇Android Launcher3分析——开篇,上篇提到LauncherModel是Launcher3处理数据的核心,这当然不是瞎掰的,我们用具体的代码分析来予以佐证。
LauncherModel本身继承自BroadcastReceiver,实现了OnAppsChangedCallbackCompat接口,该接口在LauncherAppsCompat.java中定义,由LauncherModel中实现,并在LauncherAppsCompat子类的内部类PackageMonitor(API16以上)(继承BroadcastReceiver类型)或WrapperCallback(API15以下)调用,源码中关于LauncherModel的说明,大致含义如下:
保持Launcher的内存状态。在内存静态区应该只能有一个LauncherModel的实例,同时它还为Launcher提供了一系列的更新数据库的Api。
内部类或接口
Callbacks(接口)
该接口定义了一些绑定数据时的具体方法,由Launcher实现,在LauncherModel中调用。
ItemInfoFilter(接口)
ItemInfo过滤器,过滤掉不符合条件的ItemInfo
LoaderTask(实现了Runnable接口)
核心内部类,主要任务负责加载数据(workspace、all apps)和绑定数据(workspace、all apps)
核心方法run
1 | public void run() { |
先加载并绑定workspace,然后调用waitForIdle方法等待资源锁的释放,从而开始加载并绑定All Apps。在手动置空mContext(不然的话LoaderTask将持有mContext直至所有的runnable对象都被执行完毕,容易引起内存泄漏),接着指控LoaderTask本身,更新相关标识位。那么两个run里面的两个load函数到底有什么功能呢?
PackageUpdatedTask()
核心内部类, 实现了Runnable接口,主要负责更新Package信息(如package的增删改查等)
AppsAvailabilityCheck
BroadcastReceiver类型,根据包名检查对应app的有效性。
核心方法
loadAndBindWorkspace
加载并绑定workspace,先加载后绑定;
loadWorkspace
loadWorkspace的代码实现较复杂,这里就不贴代码了,只根据代码做个简单的梳理:
- 初始化必要的变量值,判断是否需要执行MigrateFromRestoreTask任务,根据mFlags判断是否需要对LauncherProvider中定义的数据库做初始化操作;
- 进入同步代码块:
- 清空sBg的数据,使其恢复到初始化状态,定义用于接受清洗过后的数据的数据结构(itemsToRemove、restoreRows和occupied,这里的occupied指的就是Launcher桌面已经被Item占据的单元列表);
- 利用ContentResolver查询Favorites表,获取所有列的索引,以便后面根据对应索引去的对应列的值;
- 遍历Favorites表,根据ItemType对数据进行清洗:
- Application和Shortcut类型Item:
- 将需要删除的item,需要移除的item,加入到对应的存储数据结构中,并将需要更新的item进行跟新操作
- 数据清洗完后,根据提供的条件,得到ShortcutInfo(最终展示在Launcher桌面上的那些图标),设置其属性(其中主要属性有intent(点击图标会对应的跳转操作),坐标(cellX,cellY)、占地面积(spanX,spanY),容纳图标的容器,所属的是哪一个屏幕等),这里还会根据container决定将当前item放入到什么位置(container为Workspace和Hotseat时,放入workspace中,其他情况放入Folder中
- 文件夹类型Item:
- 设置folderInfo的属性,对于FolderInfo类型的Item,其本身位于容器Workspace之中,但又可以作为容器来承载(Application和Shortcut类Item)
- 设置完属性后,将其加入到对应的数据存储结构中,以便后面处理
- Widget类型(自定义的或App提供的)
- 根据对应的id机appWidgetId判断其有效性(将无效的加入到itemsToRemoved列表中,有效的则获取其appWidgetInfo信息);
- 移除不知道归属容器的Widget(就是不知道放哪儿的Widget)
- 更新providerName不一致的非自定义widget,然后将其加入到相应的数据结构;
- Application和Shortcut类型Item:
- 处理清洗后的数据(itemsToRemoved、restoredRows等),这一步实质上是根据列表生成相应的sql语句,进行数据库操作;
- 移除空的屏幕,并更新。
以上就是loadWorkspace的全部过程,接下来看bindWorkspace时如何实现的。
bindWorkspace
由于bindWorkspace涉及到的都是UI操作,所以毫无疑问这些操作是在UI线程(即主线程)中完成的,具体实现方式是由LauncherModel调用Launcher实现的Callbacks接口里面的相应方法。bindWorkspace具体分析
- 绑定前的准备工作(待使用的数据结构初始化,变量的初始化);
- 在主线程中解除当前已存在的items的绑定;
- 过滤数据,将要绑定的内容分为当前的和非当前的;
- 通知Launcher开始绑定
- 先绑定screen,在绑定item
- 通知Launcher所有绑定已完成
- 善后处理
loadAndBindAllApps
加载并绑定all apps,先加载后绑定,具体实现的分析与loadAndBindWorkspace类似,不做赘述。
更新数据库的操作
主要包括对(App、App Shortcut、Widget、Folder的增删改查,具体体现在以下方法。
- addItemToDatabase,向数据库添加数据;
- deletePackageFromDatabase,从数据库删除数据Package
- deleteItemFromDatabase,从数据库删除Item
- deleteItemsFromDatabase,从数据库批量删除item
- deleteFolderContentsFromDatabase,从数据库删除文件夹里面的内容信息
- moveItemInDatabase,更新Item的位置信息
- moveItemsIndatabase,批量更新Item的位置信息
- modifyItemInDatabase,更新Item的信息
- updateItemInDatabase,更新Item的信息
总结
以上就是本人根据源码结合自己对Launcher3中的数据处理核心LauncherModel的粗陋分析,个人觉得值得借鉴的地方在于Callbacks的设计以及开始绑定之前的数据区分处理,当然个人觉得这里还是有不足之处的,那就是LauncherModel的loadWorkspace方法似乎太庞大了,如果自定义Launcher需要在这方面上进行优化。接下来会对Launcher3中的一些展示核心(自定义控件进行分析)。