Launcher2源码学习

本文基于Android5.1.1版本。

Launcher、Launcher2(2.2(Froyo)之后) 和Launcher3(4.4(KitKat)之后)其实是一样的,只是Launcher2里面加入了3D,Launcher3桌面长按又可以直接呼叫小工具了,工具列配置不同,桌面数自动增减及无限化。


Launcher2源码编译

Launcher2源码位置在/package/apps/Launcher2。
如果要在Android Studio编译还需要导入三个jar包(/out/target/common/obj/JAVA_LIBRARIES/目录下):

  1. framework_intermediates/classes.jar:android的框架类
  2. android_common_intermediates/classes.jar:包含com.android.common.Search这个类
  3. core_intermediates/classes.jar:包含dalvik.system.VMRuntime类

另需要删掉AndroidManifest下的android:sharedUserId = “android.uid.shared”

此时如果还有错误,一般是由于SDK版本造成的,AndroidManifest下更改minSdkVersion、targetSdkVersion,更换为高版本的SDK可以解决问题。


主布局介绍

Launcher2源码学习_第1张图片

下面认识下每一个View控件:

1、最外层的DragLayer,是一个继承自FramLayout的View控件,显示的就是整个桌面根容器。桌面的所有控件都是位于DragLayer中。

2、id/dock_divider,使用了布局workspace_divider,其实就是一个ImageView。是Workspace与Hotseat之间的分割线。
下面的分割线

3、id/paged_view_indicator,使用了布局scroll_indicator,显示效果是在id/dock_divider上显示一条淡蓝色的横线,来指示当分屏所处的位置。

4、id/workspace,工作空间拥有五个workspace_screen,即有五个分屏,每个分屏都可以放置shortcut和AppWidget。

5、id/cell1…cell5 ,分别代表五个分屏。

6、id/qsb_bar 搜索框/删除框,根据需要进行切换。

7、id/apps_customize_pane,效果如下:
Launcher2源码学习_第2张图片

8、id/hotseat 即主屏幕下方的五个快捷位置。
hotseat

9、id/workspace_cling当第一次运行Launcher2时,会显示的用于指导的动画,以后不再显示。
Launcher2源码学习_第3张图片 Launcher2源码学习_第4张图片

10、id/folder_cling,第一次使用Folder时,展示给用户的指导画面。
Launcher2源码学习_第5张图片


Launcher

Launcher是由ActivityManagerService启动的,而ActivityManagerService和PackageManagerService一样,都是在开机时由SystemServer组件启动的,SystemServer组件首先是启动PackageManagerService,由它来负责安装系统的应用程序。

LauncherApplication:是整个程序的入口。启动了application,application再启动activity、service等。一些全局信息的初始化和保存工作在这里执行。包括屏幕大小,像素密度信息的获取,以及BroadcastReceiver和ContentObserver的注册都在整个程序的开始就完成。

启动过程需要先后初始化LauncherApplication和Launcher的对象。更加简洁的说,启动过程可以分成两步,第一步在LauncherApplication.onCreate()方法中,第二步在Launcher.onCreate()方法中。

Launcher2源码学习_第6张图片

LauncherApplication的工作结束之后,下面就开始初始化Launcher了。Launcher是一个Activity,有几个重要的回调方法。可以将Launcher.onCreate()所执行的操作大概分为七步:

  1. LauncherAppliaction.setLauncher():实现Callbacks接口。
  2. AppWidgetHost.startListening():对widget事件进行监听,帮助Launcher管理AppWidget,并且能够捕获长按事件,使得应用可以正常的删除、添加AppWidget。
  3. checkForLocaleChange():检查更新本地保存的配置文件,先是检查了本地文件的配置与当前设备的配置是否一致,如果不一致,则更新配置,并且清空IconCache,因为配置的改变可能会改变语言环境,所以需要清空IconCache中的内容重新加载。
  4. setupViews():配置UI控件,简单的对所有的UI控件进行加载和配置。
  5. showFirstRunWorkspaceCling():第一次启动时显示的指导画面。
  6. registerContentObservers():设置内容监听器,注册对指定URI所指定的数据的监听,及时对数据变化做出反应。
  7. LauncherModel.startLoader():为Launcher加载WorkspaceAllApps中的内容。

Launcher在应用启动的时候,需要加载AppWidget(桌面小工具),shortcut(快捷方式)等内容项,通过调用LauncherModel.startLoader(),开始加载的工作。launcherModel中加载好的内容会通过
LauncherModel.Callbacks接口的回调函数将数据传给需要的组件,那先来看看Callbacks的方法:
简单的了解下每个方法的用途:

  1. setLoadOnResume():由于Launcher继承自Activity,因此Launcher可能会处于paused状态(onPause()被调用),则有可能在这段时间内资源可能发生了改变,如应用被删除或新应用安装,因此需要在onResume()中调用此方法进行重新加载。
  2. getCurrentWorkspace():获取当前屏幕的序号。
  3. startBinding():通知Launcher加载开始,并更新Workspace上的shortcuts。
  4. bindItems(ArrayList< ItemInfo> shortcuts, int start, int end):加载一批内容项到Workspace,加载的内容项包括,Application、shortcut、folder。
  5. bindFolders(HashMap< Long, FolderInfo> folders) :加载folder的内容。
  6. finishBindingItems():通知Launcher加载结束。
  7. bindAppWidget(LauncherAppWidgetInfo item):加载AppWidget到Workspace。
  8. bindAllApplications(final ArrayList< ApplicationInfo> apps):在All Apps页加载所有应用的Icon。
  9. bindAppsAdded(ArrayList< ApplicationInfo> apps):通知Launcher一个新的应用被安装,并加载这个应用。
  10. bindAppsUpdated(ArrayList< ApplicationInfo> apps):通知Launcher一个应用发生了更新。
  11. bindAppsRemoved(ArrayList< ApplicationInfo> apps, boolean permanent):通知Launcher一个应用被删除了。
  12. bindPackagesUpdated():通知Launcher多个应用发生了更新。
  13. isAllAppsVisible():用于在加载的过程中记录当前Launcher的状态,返回true则当前显示的AllApps。
  14. bindSearchablesChanged():当搜索/删除框状态发生改变时调用。

Launcher加载的工作由两部分组成,第一部分是为Workspace加载内容,第二部分则是为AllApps加载内容。每一部分的加载又可以分为两个步骤:1、由LauncherModel完成,主要工作是从数据库中读取信息,并且按类别将内容项分装到不同的数据结构中。2、由Launcher来完成,通过LauncherModel.Callbacks接口定义的回调方法,从LauncherModel中获取的数据,将其显示到桌面。

Workspace内容加载

1.mLoaderTask.run()中首先会调用loadAndBindWorkspace()方法开始Workspace的加载工作。

loadWorkspace的工作就是从ContentProvider获取指定URI中的数据,并将它们分类存放到指定的数据结构中。分类的标准有两条:1、item的类型。包括ITEM_TYPE_APPLICATION,ITEM_TYPE_SHORTCUT,ITEM_TYPE_FOLDER,ITEM_TYPE_APPWIDGET四类。2、item所属的容器。包括CONTAINER_DESKTOP,
CONTAINER_HOTSEAT以及其它(主要指文件夹)。

2.LauncherModel在读取完数据之后,通过LauncherModel.bindWorkspace()将数据传给到Launcher。

Launcher的内容绑定分为五步:分别对应着startBinding()、bindItems()、bindFolders()、bindAppWidgets()、finishBindingItems()的调用。


AllApps的内容加载

回到mLoaderTask.run()方法中,当bindWorkspace()执行结束之后,并通过waitForIdle()确认加载完成之后,就会调用loadAndBindAllApps()来为AllApps页面加载内容。

AllApps中的加载过程和Workspace中的加载过程大致是相同的,只是AllApps的加载和绑定过程被放到同一个方法loadAllAppsByBatch()中执行(若已经加载了将执行onlyBindAllApps()方法)。

过程还是挺简单的,首先当然是查询所有的App了,通过向PackagedManager发送指定的Intent就能够获得安装好的应用的信息。

查询完毕之后,将数据封装到ArrayList< ApplicationInfo>对象中,然后通过Callbacks.bindAllApplication()或Callbacks.bindAppsAdded()将数据传给Launcher。Launcher中的操作也比加载Workspace时简单多,毕竟这里只需要加载Icon。


Workspace滑动

DragLayer层的主要任务是负责对图标和AppWidget进行拖拽,Workspace则主要负责左右滑动,CellLayout则用于容纳各种桌面的快捷方式。大概的分工如下:

Launcher2源码学习_第7张图片

滑动功能主要分两步:

  1. 在onInterceptTouchEvent中进行拦截。
  2. 在onTouchEvent中进行滑动。

总结一下onInterceptTouchEvent()的工作就是:切换PagedView的状态,如果处于滑动状态就拦截。

在onTouchEvent中,对接受到的不同的事件进行了分类的处理,大致可以将功能分类为:

  • 当接受到ACTION_DOWN时,若滑动正在进行,则停止。
  • 当接受到ACTION_MOVE时,根据当前的状态调用scrollBy进行滑动或则调用determineScrollingStart准备开始滑动。
  • 当接受到ACTION_UP时,根据当前所滑动的位移和速度,判断松手后进入到哪一个分屏。

到这里,Workspace滑动过程的基本流程就介绍完毕了。


桌面快捷图标的拖拽

在DragLayer中就负责对快捷图标和AppWidget等组件的拖拽工作。桌面的滑动和图标的拖拽是两项独立的工作,正常情况下我们用手指滑动桌面会触发滑动操作,而当长按一个图标时,则会触发图标的拖拽操作,此时再滑动则会拖拽图标移动而桌面不会滑动。那么这里就分两大部分来探讨:1、拖拽操作的启动。2、拖拽。

拖拽操作的启动

Launcher.onCreate()接着进入setupViews(),Workspace设置了OnLongClickListener,而Launcher又实现了这个接口。当用户在一个item上长按时,则itemUnderLongClick != null,再通过调用Workspace.startDrag()来激活item的拖拽。下面先通过时序图来看下拖拽状态激活所经历的过程:

Launcher2源码学习_第8张图片

拖拽(DragController.Java)

拖拽过程的实现在DragLayer中,当进行图标的拖拽时,DragLayer.onInterceptTouchEvent()就会对MotionEvent进行拦截。并且在自身的onTouchEvent()方法中进行操作,从而实现图标的移动。由于onInterceptTouchEvent()拦截了MotionEvent,因此Workspace等UI控件不会接收到事件,从而不会产生干扰。


开启应用

怎么开启的应用呢?捕捉图标点击事件,然后startActivity()发送对应的Intent请求。

在桌面上的图标,使用的是BubbleTextView对象,这个对象在TextView的基础之上,添加了一些特效,比如你长按移动图标的时候,图标位置会出现一个背景(不同版本的效果不同),所以我们找到BubbleTextView对象的点击事件,就可以找到Launcher如何开启一个App了。BubbleTextView的点击事件在哪里呢?在Launcher.onClick(View v)里面。不管从哪里点击图标,最终调用的都是Launcher.startActivitySafely()。

除了在桌面上有图标之外,在程序列表中点击图标,也可以开启对应的程序。这里的图标使用的不是BubbleTextView对象,而是PagedViewIcon对象,我们如果找到它的点击事件,就也可以找到Launcher如何开启一个App。

你可能感兴趣的:(Android源码)