本文基于Android5.1.1版本。
Launcher、Launcher2(2.2(Froyo)之后) 和Launcher3(4.4(KitKat)之后)其实是一样的,只是Launcher2里面加入了3D,Launcher3桌面长按又可以直接呼叫小工具了,工具列配置不同,桌面数自动增减及无限化。
Launcher2源码位置在/package/apps/Launcher2。
如果要在Android Studio编译还需要导入三个jar包(/out/target/common/obj/JAVA_LIBRARIES/目录下):
另需要删掉AndroidManifest下的android:sharedUserId = “android.uid.shared”
此时如果还有错误,一般是由于SDK版本造成的,AndroidManifest下更改minSdkVersion、targetSdkVersion,更换为高版本的SDK可以解决问题。
下面认识下每一个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,效果如下:
9、id/workspace_cling当第一次运行Launcher2时,会显示的用于指导的动画,以后不再显示。
10、id/folder_cling,第一次使用Folder时,展示给用户的指导画面。
Launcher是由ActivityManagerService启动的,而ActivityManagerService和PackageManagerService一样,都是在开机时由SystemServer组件启动的,SystemServer组件首先是启动PackageManagerService,由它来负责安装系统的应用程序。
LauncherApplication:是整个程序的入口。启动了application,application再启动activity、service等。一些全局信息的初始化和保存工作在这里执行。包括屏幕大小,像素密度信息的获取,以及BroadcastReceiver和ContentObserver的注册都在整个程序的开始就完成。
启动过程需要先后初始化LauncherApplication和Launcher的对象。更加简洁的说,启动过程可以分成两步,第一步在LauncherApplication.onCreate()方法中,第二步在Launcher.onCreate()方法中。
LauncherApplication的工作结束之后,下面就开始初始化Launcher了。Launcher是一个Activity,有几个重要的回调方法。可以将Launcher.onCreate()所执行的操作大概分为七步:
Launcher在应用启动的时候,需要加载AppWidget(桌面小工具),shortcut(快捷方式)等内容项,通过调用LauncherModel.startLoader(),开始加载的工作。launcherModel中加载好的内容会通过
LauncherModel.Callbacks接口的回调函数将数据传给需要的组件,那先来看看Callbacks的方法:
简单的了解下每个方法的用途:
Launcher加载的工作由两部分组成,第一部分是为Workspace加载内容,第二部分则是为AllApps加载内容。每一部分的加载又可以分为两个步骤:1、由LauncherModel完成,主要工作是从数据库中读取信息,并且按类别将内容项分装到不同的数据结构中。2、由Launcher来完成,通过LauncherModel.Callbacks接口定义的回调方法,从LauncherModel中获取的数据,将其显示到桌面。
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()的调用。
回到mLoaderTask.run()方法中,当bindWorkspace()执行结束之后,并通过waitForIdle()确认加载完成之后,就会调用loadAndBindAllApps()来为AllApps页面加载内容。
AllApps中的加载过程和Workspace中的加载过程大致是相同的,只是AllApps的加载和绑定过程被放到同一个方法loadAllAppsByBatch()中执行(若已经加载了将执行onlyBindAllApps()方法)。
过程还是挺简单的,首先当然是查询所有的App了,通过向PackagedManager发送指定的Intent就能够获得安装好的应用的信息。
查询完毕之后,将数据封装到ArrayList< ApplicationInfo>对象中,然后通过Callbacks.bindAllApplication()或Callbacks.bindAppsAdded()将数据传给Launcher。Launcher中的操作也比加载Workspace时简单多,毕竟这里只需要加载Icon。
DragLayer层的主要任务是负责对图标和AppWidget进行拖拽,Workspace则主要负责左右滑动,CellLayout则用于容纳各种桌面的快捷方式。大概的分工如下:
滑动功能主要分两步:
总结一下onInterceptTouchEvent()的工作就是:切换PagedView的状态,如果处于滑动状态就拦截。
在onTouchEvent中,对接受到的不同的事件进行了分类的处理,大致可以将功能分类为:
到这里,Workspace滑动过程的基本流程就介绍完毕了。
在DragLayer中就负责对快捷图标和AppWidget等组件的拖拽工作。桌面的滑动和图标的拖拽是两项独立的工作,正常情况下我们用手指滑动桌面会触发滑动操作,而当长按一个图标时,则会触发图标的拖拽操作,此时再滑动则会拖拽图标移动而桌面不会滑动。那么这里就分两大部分来探讨:1、拖拽操作的启动。2、拖拽。
Launcher.onCreate()接着进入setupViews(),Workspace设置了OnLongClickListener,而Launcher又实现了这个接口。当用户在一个item上长按时,则itemUnderLongClick != null,再通过调用Workspace.startDrag()来激活item的拖拽。下面先通过时序图来看下拖拽状态激活所经历的过程:
拖拽过程的实现在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。