Android 系统源码分析——Launcher3分析开篇

https://rainmonth.github.io/posts/A180702.html

Android Launcher3分析——开篇

简介

Launcher就是一个Activity,Launcher的源码中也是继承的Activity。直观体现就是手机的桌面,当我们打开手机的时候,手机的桌面就是Launcher,一个Activity,只是这个Activity做的事情比较多:

  • View方面,可以左右滑动,可以响应长按操作;
  • 逻辑方面,可以承载手机中所有应用的快捷方式,是其他程序的入口;

总的来说,Launcher就是一个包含了许多自定义控件的复杂Activity

整体上看Launcher3

Android四大组件一应俱全,可见Launcher3是一个综合性较强的项目

Activity(6个)

  • com.android.launcher3.Launcher(主要的Activity)
  • com.android.launcher3.ToggleWeightWatcher
  • com.android.launcher3.WallpaperPickerActivity
  • com.android.launcher3.WallpaperCropActivity
  • com.android.launcher3.SettingsActivity
  • com.android.launcher3.MemoryDumpActivity

Service(1个)

  • com.android.launcher3.MemoryTracker

BroadcastReceiver(4个)

  • com.android.launcher3.WallpaperChangedReceiver
  • com.android.launcher3.InstallShortcutReceiver
  • com.android.launcher3.AppWidgetsRestoredReceiver
  • com.android.launcher3.StartupReceiver

ContentProvider(1个)

  • com.android.launcher3.LauncherProvider

如何定义一个Launcher

Android定义一个Launcher很简单,要定义成Launcher的Activity代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<activity
android:name="com.android.launcher3.Launcher"
android:launchMode="singleTask"
android:clearTaskOnLaunch="true"
android:stateNotNeeded="true"
android:theme="@style/Theme"
android:windowSoftInputMode="adjustPan"
android:screenOrientation="nosensor"
android:resumeWhilePausing="true"
android:taskAffinity=""
android:enabled="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.MONKEY"/>
</intent-filter>
</activity>

注意,上面intent-filter的具体含义:

  • android.intent.action.MAIN决定应用程序最先启动的Activity;
  • android.intent.category.HOME决定设备启动后第一个启动的Activity(通常要更改framework层的设置才能使之生效,因为国内系统定制化严重,各家手机厂商的ROM都提供了自己的Launcher)
  • android.intent.category.LAUNCHER决定应用程序是否显示在程序列表里;
  • android.intent.category.DEFAULT,决定可以接受隐式intent的Activity在没有传递intent-filter时是否能匹配成功,添加了该项的能匹配成功,反之匹配失败(当然,如果Activity时应用最先启动的Activity就不需要这个了)
  • android.intent.category.MONKEY,决定Activity是否能被monkey或其他自动化测试工具进行访问测试

先看Launcher对应的布局文件launcher.xml,如下:

launcher.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2007 The Android Open Source Project

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->

<!-- Full screen view projects under the status bar and contains the background -->
<com.android.launcher3.LauncherRootView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:id="@+id/launcher"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">

<com.android.launcher3.DragLayer
android:id="@+id/drag_layer"
android:layout_width="match_parent"
android:layout_height="match_parent">

<com.android.launcher3.FocusIndicatorView
android:id="@+id/focus_indicator"
android:layout_width="52dp"
android:layout_height="52dp" />

<!-- The workspace contains 5 screens of cells -->
<!-- DO NOT CHANGE THE ID -->
<com.android.launcher3.Workspace
android:id="@+id/workspace"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
launcher:defaultScreen="@integer/config_workspaceDefaultScreen" />

<!-- DO NOT CHANGE THE ID -->
<include layout="@layout/hotseat"
android:id="@+id/hotseat"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="right" />

<include
android:id="@+id/search_drop_target_bar"
layout="@layout/search_drop_target_bar" />

<include layout="@layout/overview_panel"
android:id="@+id/overview_panel"
android:visibility="gone" />

<include layout="@layout/widgets_view"
android:id="@+id/widgets_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible" />

<include layout="@layout/all_apps"
android:id="@+id/apps_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible" />
</com.android.launcher3.DragLayer>

<ViewStub
android:id="@+id/launcher_overlay_stub"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:inflatedId="@+id/launcher_overlay"
android:layout="@layout/launcher_overlay" />
</com.android.launcher3.LauncherRootView>

根布局层次结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
LauncherRootView extends InsettableFrameLayout
|---DragLayer
|---FocusIndicatorView
|---Workspace
|---hotset.xml(层级如下):
|---Hotseat extends FramLayout
|---CellLayout extends ViewGroup
|---search_drop_target_bar.xml
|---SearchDropTargetBar extends FrameLayout
|---LinearLayout(orientation:horizontal)
|---FrameLayout
|---DeleteDropTarget extends ButtonDropTarget(删除应用快捷方式)
FrameLayout
|---InfoDropTarget extends ButtonDropTarget(查看应用信息)
FrameLayout
|---UninstallDropTarget extends ButtonDropTarget(卸载应用)
|---overview_panel.xml(长按屏幕显示,默认隐藏)
|---LinearLayout
|---TextView(壁纸按钮):点击可设置壁纸
|---TextView(小部件按钮):点击可添加小部件(widget)
|---TextView(设置按钮):点击进入设置
|---widgets_view.xml(显示小部件的视图)
|---WidgetsContainerView extends BaseContainerView extends LinearLayout
|---FrameLayout
|---FrameLayout
|---WidgetsRecyclerView extends BaseRecyclerView extends RecyclerView
|---all_apps.xml(显示所有App的视图,里面的子控件都是通过AppAppsContainerView的实例获取的)
|---AllAppsContainerView extends BaseContainerView extends LinearLayout
|---FrameLayout(所有App时图顶部的搜索框)
|---FrameLayout
|---FrameLayout
|---all_apps_container.xml(承载所有App的视图(集列出所有安装的app的容器)
|---AllAppsRecyclerViewContainerView
|---AllAppsRecyclerView
|---ViewStub

Launcher中核心类的简单说明

具体说明请点击相应连接

  • Launcher:主界面Activity,最核心且唯一的Activity;
  • LauncherAppState:单例对象,主要有如下作用:
  1. 初始化InvariantDeviceProfileIconCacheWidgetPreviewLoaderLauncherModel等;
  2. 注册广播,用来处理本地配置变化、搜索数据库(global search provider)变化、应用的安装与卸载等;
  • InvariantDeviceProfile:一些不变的设备相关参数管理类,包含横竖屏的两种DeviceProfile;
    • IconCache:应用程序图标缓存类,里面用数据库存储了应用的icon及title等的缓存信息;
    • WidgetPreviewLoader:加载Widget信息数据库,里面用数据库存储了Widget的信息
    • LauncherModel:在内存中保存Launcher的状态,提供读写数据库的API,其内部类LoaderTask用来加载Launcher的内容(包括workspace icons、widgets和all apps icons)
      • LauncherAppsCompat:兼容抽象基类,用来获取已安装的App列表;
      • UserManagerCompat:兼容抽象基类,用来处理不通版本下的用户管理;
  • DragController:拖拽事件控制类,拖拽事件的处理逻辑在这里实现
  • LauncherStateTransitionAnimation:Launcher的动画导演,负责安排不同状态切换之间的动画处理
  • AppWidgetManagerCompat:兼容抽象基类,负责处理不通版本下应用和Widget管理
  • LauncherAppWidgetHost:继承子AppWidgetHost,顾名思义,AppWidgetHost是桌面app、widget等的宿主,之所以继承是为了LauncherAppWidgetHostView能更好的处理长按事件;
  • FocusIndicatorView:一个实现了View.OnFocusChangeListener的View(具体作用上不清楚)
  • DragLayer:一个用来协调子View拖拽事件的ViewGroup,实际上事件的分发拦截等是在DragController,因为DragLayer持有DragController的实例,并调用了setup方法初始化了它;
  • Workspace:一个包含了壁纸和有限数量的页面的较大空间

Launcher主流程

先分析生命周期函数

onCreate()

主要流程都在onCreate中,如下(采用代码中添加注释的方法说明,省略次要代码):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
@Override
protected void onCreate(Bundle savedInstanceState) {
//省略掉严格模式相关代码...
if (mLauncherCallbacks != null) {
mLauncherCallbacks.preOnCreate();
}
super.onCreate(savedInstanceState);

// 各种变量初始化
LauncherAppState.setApplicationContext(getApplicationContext());
// 初始化LauncherAppState对象
LauncherAppState app = LauncherAppState.getInstance();

// 根据配置的orientation来初始化DeviceProfile对象
mDeviceProfile = getResources().getConfiguration().orientation
== Configuration.ORIENTATION_LANDSCAPE ?
app.getInvariantDeviceProfile().landscapeProfile
: app.getInvariantDeviceProfile().portraitProfile;

// 初始化SharedPreference对象
mSharedPrefs = getSharedPreferences(LauncherAppState.getSharedPreferencesKey(),
Context.MODE_PRIVATE);
mIsSafeModeEnabled = getPackageManager().isSafeMode();
mModel = app.setLauncher(this);
mIconCache = app.getIconCache();

mDragController = new DragController(this);
mInflater = getLayoutInflater();
mStateTransitionAnimation = new LauncherStateTransitionAnimation(this);

mStats = new Stats(this);

mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);

mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
mAppWidgetHost.startListening();

// If we are getting an onCreate, we can actually preempt onResume and unset mPaused here,
// this also ensures that any synchronous binding below doesn't re-trigger another
// LauncherModel load.
mPaused = false;

if (PROFILE_STARTUP) {
android.os.Debug.startMethodTracing(
Environment.getExternalStorageDirectory() + "/launcher");
}
// 设置布局(注意,此时的布局是不包含布局参数的)
setContentView(R.layout.launcher);
// 初始化View,进行各种View的初始化,事件绑定
setupViews();
// 为布局中的View添加上布局参数
mDeviceProfile.layout(this);

lockAllApps();

// 恢复保存的状态
mSavedState = savedInstanceState;
restoreState(mSavedState);

if (PROFILE_STARTUP) {
android.os.Debug.stopMethodTracing();
}

// 数据加载核心部分,主要有LauncherModel的内部类LoaderTask来完成
if (!mRestoring) {
if (DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE) {
// If the user leaves launcher, then we should just load items asynchronously when
// they return.
mModel.startLoader(PagedView.INVALID_RESTORE_PAGE);
} else {
// 只有当launcher处于前台,且用户旋转屏幕活着触发方向配置上的改变时我们才同步加载数据
mModel.startLoader(mWorkspace.getRestorePage());
}
}

// For handling default keys
mDefaultKeySsb = new SpannableStringBuilder();
Selection.setSelection(mDefaultKeySsb, 0);

IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
registerReceiver(mCloseSystemDialogsReceiver, filter);

mRotationEnabled = Utilities.isRotationAllowedForDevice(getApplicationContext());
// In case we are on a device with locked rotation, we should look at preferences to check
// if the user has specifically allowed rotation.
if (!mRotationEnabled) {
mRotationEnabled = Utilities.isAllowRotationPrefEnabled(getApplicationContext(), false);
}

// On large interfaces, or on devices that a user has specifically enabled screen rotation,
// we want the screen to auto-rotate based on the current orientation
setOrientation();

if (mLauncherCallbacks != null) {
mLauncherCallbacks.onCreate(savedInstanceState);
if (mLauncherCallbacks.hasLauncherOverlay()) {
ViewStub stub = (ViewStub) findViewById(R.id.launcher_overlay_stub);
mLauncherOverlayContainer = (InsettableFrameLayout) stub.inflate();
mLauncherOverlay = mLauncherCallbacks.setLauncherOverlayView(
mLauncherOverlayContainer, mLauncherOverlayCallbacks);
mWorkspace.setLauncherOverlay(mLauncherOverlay);
}
}

// 是否显示欢迎说明
if (shouldShowIntroScreen()) {
showIntroScreen();
} else {
showFirstRunActivity();
showFirstRunClings();
}
}

可以看到,在设置布局之后,进行了View的初始化、View的事件绑定等,然后根据DeviceProfile(设备描述类,定义了Launcher在不同设备、不公状态下的以下常量等)的layout方法为初始化的View添加上布局参数。参数设置完成后,就进入了数据加载阶段,数据加载是通过LauncherModel的内部类LoaderTask来完成的(根据当前的配置,来选择时同步加载数据还是异步加载数据)。接下来就是控制Launcher Intro Screen的显示与否了,显示的话,就显示Intro Screen,不显示就进入else部分,显示Launcher Clings(其实就是首次运行Launcher时的一些关于Launcher用途的说明)

Launcher 代码中关于mLauncherCallbacks部分,由于mLauncherCallbacks的赋值操作必须调用setLauncherCallbacks来完成,但该函数只在LauncherExtension中才调用,所以如果要对Launcher做扩展,需要了解这部分代码,否则,可以忽略。

onResume()

onResume()中主要进行的是视图显示状态的恢复、依次执行Runnable任务(包括BindAllApplicationRunnable、BindPackagesUpdatesRunnable以及UpdateOrientationRunnable)、恢复App或App Shortcut的状态,必要的时候还会重新生成workspace上的Widget和QSB等。

Launcher中有一个waitUntilResume函数,字面意思“直到onResume执行才。。。。”,先看看其具体代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Thunk
boolean waitUntilResume(Runnable run, boolean deletePreviousRunnables) {
if (mPaused) {
if (LOGD) Log.d(TAG, "Deferring update until onResume");
if (deletePreviousRunnables) {
while (mBindOnResumeCallbacks.remove(run)) {
}
}
mBindOnResumeCallbacks.add(run);
return true;
} else {
return false;
}
}

还有一个重载方法:

1
2
3
private boolean waitUntilResume(Runnable run) {
return waitUntilResume(run, false);
}

在如下几个地方调用了:

  • Launcher(直接调用的地方)
    • bindAllApplications(final ArrayList apps)
    • bindAllPackages(final WidgetsModel model)
    • onSettingsChanged(String settings, boolean value)
  • Launcher(间接调用的地方,调用的是重载的方法)
    • bindAppsAdded
    • bindAppsUpdated
    • bindAppWidget
    • bindComponentsRemoved
    • bindFolders
    • bindItems
    • bindRestoreItemsChange
    • bindShortcutsChanged
    • bindWidgetsRestored
    • finishBindingItems

可见,与绑定有关的runnable都是在onResume的时候执行的,那么在这些runnable到底都做了什么,有什么功能,如何实现这些功能的呢?这里先提如下几个问题,我们带着问题读代码,马上就能得到答案:

  1. 在这些runnable执行之前,又做了什么了?
  2. 我们都要绑定写什么对象?
  3. 我们要绑定的这些对象怎么得到的?
  4. 我们要把这些对象绑定到哪去?

现在依次解答上面的个问题,其实在onResume之前就已经被LauncherModel(数据处理的核心类)安排好了,现在来答上面的问题:

  1. 在这些runnable执行之前,做了些什么?

    onCreate里面调用了Launcher的startLoader方法,开方法会开启其内部类LoaderTask的run方法来进行如下操作:

    • loadAndBindWorkspace,即加载并绑定workspace;
      • loadWrokspace
      • bindWorkspace
    • loadAndBindAllApps,即加载并绑定All apps;
  2. 我们都要绑定写什么对象?

    想想Launcher上都有什么,显然App、AppShortcut、widget、Folder等都可能有,所以要绑定的当然是这些对象。

  3. 我们要绑定的这些对象怎么得到的?

    通过LauncherModel的内部类LoaderTask来得到这些对象

  4. 我们要把这些对象绑定到哪去?

    该放哪儿去放哪儿去。由于Launcher中的对象(App、AppShortcut、Widget、Folder等)无论是在Desktop(即桌面)或Hotseat(即底部的图标栏)都有相应的坐标点(cellX,cellY)及占地面积(spanX、spanY),我们只要将其按对应坐标对号入座即可。当然特需情况(如图标遮挡什么的)需要特殊处理。

其实,2、3、4里面要做的任务都是在1里面完成的,可见数据处理核心还是LauncherModel里面。接下来的文章会对LauncherModel做详细分析。