Launcher就是一个Activity,Launcher的源码中也是继承的Activity。直观体现就是手机的桌面,当我们打开手机的时候,手机的桌面就是Launcher,一个Activity,只是这个Activity做的事情比较多:
- View方面,可以左右滑动,可以响应长按操作;
- 逻辑方面,可以承载手机中所有应用的快捷方式,是其他程序的入口;
总的来说,Launcher就是一个包含了许多自定义控件的复杂Activity。
Android四大组件一应俱全,可见Launcher3是一个综合性较强的项目
Android定义一个Launcher很简单,要定义成Launcher的Activity代码如下:
<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的具体含义:
先看Launcher对应的布局文件launcher.xml,如下:
launcher.xml
<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" />
<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" />
<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>
根布局层次结构:
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
具体说明请点击相应连接
- 初始化InvariantDeviceProfile、IconCache、WidgetPreviewLoader、LauncherModel等;
- 注册广播,用来处理本地配置变化、搜索数据库(global search provider)变化、应用的安装与卸载等;
主要流程都在onCreate中,如下(采用代码中添加注释的方法说明,省略次要代码):
@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()中主要进行的是视图显示状态的恢复、依次执行Runnable任务(包括BindAllApplicationRunnable、BindPackagesUpdatesRunnable以及UpdateOrientationRunnable)、恢复App或App Shortcut的状态,必要的时候还会重新生成workspace上的Widget和QSB等。
Launcher中有一个waitUntilResume函数,字面意思“直到onResume执行才。。。。”,先看看其具体代码:
@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;
}
}
还有一个重载方法:
private boolean waitUntilResume(Runnable run) {
return waitUntilResume(run, false);
}
在如下几个地方调用了:
可见,与绑定有关的runnable都是在onResume的时候执行的,那么在这些runnable到底都做了什么,有什么功能,如何实现这些功能的呢?这里先提如下几个问题,我们带着问题读代码,马上就能得到答案:
现在依次解答上面的个问题,其实在onResume之前就已经被LauncherModel(数据处理的核心类)安排好了,现在来答上面的问题:
在这些runnable执行之前,做了些什么?
onCreate里面调用了Launcher的startLoader方法,开方法会开启其内部类LoaderTask的run方法来进行如下操作:
- loadAndBindWorkspace,即加载并绑定workspace;
- loadWrokspace
- bindWorkspace
- loadAndBindAllApps,即加载并绑定All apps;
我们都要绑定写什么对象?
想想Launcher上都有什么,显然App、AppShortcut、widget、Folder等都可能有,所以要绑定的当然是这些对象。
我们要绑定的这些对象怎么得到的?
通过LauncherModel的内部类LoaderTask来得到这些对象
我们要把这些对象绑定到哪去?
该放哪儿去放哪儿去。由于Launcher中的对象(App、AppShortcut、Widget、Folder等)无论是在Desktop(即桌面)或Hotseat(即底部的图标栏)都有相应的坐标点(cellX,cellY)及占地面积(spanX、spanY),我们只要将其按对应坐标对号入座即可。当然特需情况(如图标遮挡什么的)需要特殊处理。
其实,2、3、4里面要做的任务都是在1里面完成的,可见数据处理核心还是LauncherModel里面。接下来的文章会对LauncherModel做详细分析。