Android 7.0 Launcher3的启动和加载流程分析,Launcher的本质就是一个普通应用,它比普通应用多配置了Category的Android:name=”android.intent.category.HOME”属性,之后ActivityManagerService的startHomeActivityLocked方法将启动含有这个属性的Activity。
|
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
|
boolean startHomeActivityLocked(int userId) {
if(this.mHeadless) { this.ensureBootCompleted(); return false;
} else if(this.mFactoryTest == 1 && this.mTopAction == null) {
return false;
} else {
Intent intent = new Intent(this.mTopAction, this.mTopData != null?Uri.parse(this.mTopData):null);
intent.setComponent(this.mTopComponent); if(this.mFactoryTest != 1) { intent.addCategory("android.intent.category.HOME"); } ActivityInfo aInfo = intent.resolveActivityInfo(this.mContext.getPackageManager(), 1024); if(aInfo != null) { intent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name));
aInfo = new ActivityInfo(aInfo);
aInfo.applicationInfo = this.getAppInfoForUser(aInfo.applicationInfo, userId); ProcessRecord app = this.getProcessRecordLocked(aInfo.processName, aInfo.applicationInfo.uid); if(app == null || app.instrumentationClass == null) {
intent.setFlags(intent.getFlags() | 268435456); this.mMainStack.startActivityLocked((IApplicationThread)null, intent, (String)null, aInfo, (IBinder)null, (String)null, 0, 0, 0, 0, (Bundle)null, false, (ActivityRecord[])null); } } return true;
} } |
接下来看看Launcher界面的划分。Launcher3实质其实就是一个Activity包含N个自定义的View。
结合图和布局文件可能更好理解Launcher3的界面
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
<com.android.launcher3.launcherrootview android:id="@+id/launcher" xmlns:launcher="https://schemas.android.com/apk/res-auto" xmlns:android="https://schemas.android.com/apk/res/android" 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" android:cliptopadding="false" android:clipchildren="false">
<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" launcher:pageindicator="@+id/page_indicator" launcher:defaultscreen="@integer/config_workspaceDefaultScreen">
</com.android.launcher3.workspace> <!-- DO NOT CHANGE THE ID --> <include android:id="@+id/hotseat" android:layout_width="match_parent" android:layout_height="match_parent" layout="@layout/hotseat">
<include android:id="@+id/overview_panel" layout="@layout/overview_panel" android:visibility="gone">
<!-- Keep these behind the workspace so that they are not visible when we go into AllApps --> <include android:id="@+id/page_indicator" android:layout_width="wrap_content" android:layout_height="wrap_content" layout="@layout/page_indicator" android:layout_gravity="center_horizontal">
<include android:id="@+id/search_drop_target_bar" layout="@layout/search_drop_target_bar">
<include android:id="@+id/widgets_view" android:layout_width="match_parent" android:layout_height="match_parent" layout="@layout/widgets_view" android:visibility="invisible">
<include android:id="@+id/apps_view" android:layout_width="match_parent" android:layout_height="match_parent" layout="@layout/all_apps" android:visibility="invisible">
</include></include></include></include></include></include></com.android.launcher3.focusindicatorview></com.android.launcher3.draglayer></com.android.launcher3.launcherrootview> |
下面是Launcher3中一些类的大致含义:
Launcher:主界面Activity,最核心且唯一的Activity。
LauncherAppState:单例对象,构造方法中初始化对象、注册应用安装、卸载、更新,配置变化等广播。这些广播用来实时更新桌面图标等,其receiver的实现在LauncherModel类中,LauncherModel也在这里初始化。
LauncherModel:数据处理类,保存桌面状态,提供读写数据库的API,内部类LoaderTask用来初始化桌面。
InvariantDeviceProfile:一些不变的设备相关参数管理类,其内部包涵了横竖屏模式的DeviceProfile。
WidgetPreviewLoader:存储Widget信息的数据库,内部创建了数据库widgetpreviews.db。
LauncherAppsCompat:获取已安装App列表信息的兼容抽象基类,子类依据不同版本API进行兼容性处理。
AppWidgetManagerCompat:获取AppWidget列表的兼容抽象基类,子类依据不同版本API进行兼容性处理。
LauncherStateTransitionAnimation:各类动画总管处理执行类,负责各种情况下的各种动画效果处理。
IconCache:图标缓存类,应用程序icon和title的缓存,内部类创建了数据库app_icons.db。
LauncherProvider:核心数据库类,负责launcher.db的创建与维护。
LauncherAppWidgetHost:AppWidgetHost子类,是桌面插件宿主,为了方便托拽等才继承处理的。
LauncherAppWidgetHostView:AppWidgetHostView子类,配合LauncherAppWidgetHost得到HostView。
LauncherRootView:竖屏模式下根布局,继承了InsettableFrameLayout,控制是否显示在状态栏等下面。
DragLayer:一个用来负责分发事件的ViewGroup。
DragController:DragLayer只是一个ViewGroup,具体的拖拽的处理都放到了DragController中。
BubblTextView:图标都基于他,继承自TextView。
DragView:拖动图标时跟随手指移动的View。
Folder:打开文件夹展示的View。
FolderIcon:文件夹图标。
DragSource/DropTarget:拖拽接口,DragSource表示图标从哪开始拖,DropTarget表示图标被拖到哪去。
ItemInfo:桌面上每个Item的信息数据结构,包括在第几屏、第几行、第几列、宽高等信息;该对象与数据库中记录一一对应;该类有多个子类,譬如FolderIcon的FolderInfo、BubbleTextView的ShortcutInfo等。
了解上面这些类后,现在来看看Launcher3的启动流程:(小提示:若看不清图片可将网页放大至200%)
由于Launcher3也是一个Activity,其启动后首先会执行onCreate()方法,从流程图中可以看出在该方法里会调用LauncherAppState.getInstance()方法,Launcher3的各类数据的初始化和广播的注册都在这里被执行。随后执行LauncherModel mModel = app.setLauncher(this),将当前Launcher对象的引用传给LauncherProvider,在该方法里调用了LauncherModel的initialize(Callbacks callbacks)方法,因为Launcher也实现了LauncherModel.Callbacks接口,因此这里将Launcher和LauncherModel建立了联系,LauncherModel中的所有操作都会通过Callbacks接口中的方法传给Launcher。可以来看看LauncherModel.Callbacks接口。
|
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
|
public interface Callbacks {
//如果Launcher在加载完成之前被强制暂停,那么需要通过这个回调方法通知Launcher //在它再次显示的时候重新执行加载过程 public boolean setLoadOnResume();
//获取当前屏幕序号 public int getCurrentWorkspaceScreen();
//启动桌面数据绑定 public void startBinding();
//批量绑定桌面组件:快捷方式列表,列表的开始位置,列表结束的位置,是否使用动画 public void bindItems(ArrayList<iteminfo> shortcuts, int start, int end,
boolean forceAnimateIcons);
//批量绑定桌面页,orderedScreenIds 序列化后的桌面页列表 public void bindScreens(ArrayList<long> orderedScreenIds);
public void bindAddScreens(ArrayList<long> orderedScreenIds);
//批量绑定文件夹,folders 文件夹映射列表 public void bindFolders(LongArrayMap<folderinfo> folders);
//完成绑定 public void finishBindingItems();
//批量绑定小部件,info 需要绑定到桌面上的小部件信息 public void bindAppWidget(LauncherAppWidgetInfo info);
//绑定应用程序列表界面的应用程序信息,apps 需要绑定到应用程序列表中的应用程序列表 public void bindAllApplications(ArrayList apps);
//批量添加组件 public void bindAppsAdded(ArrayList<long> newScreens,
ArrayList<iteminfo> addNotAnimated, ArrayList<iteminfo> addAnimated, ArrayList addedApps); //批量更新应用程序相关的快捷方式或者入口 public void bindAppsUpdated(ArrayList apps);
public void bindShortcutsChanged(ArrayList<shortcutinfo> updated,
ArrayList<shortcutinfo> removed, UserHandleCompat user); //当Widget被重置的时候调用 public void bindWidgetsRestored(ArrayList<launcherappwidgetinfo> widgets);
public void bindRestoreItemsChange(HashSet<iteminfo> updates);
public void bindWorkspaceComponentsRemoved(
HashSet<string> packageNames, HashSet<componentname> components, UserHandleCompat user); public void bindAppInfosRemoved(ArrayList appInfos);
public void notifyWidgetProvidersChanged();
public void bindWidgetsModel(WidgetsModel model);
public void bindSearchProviderChanged();
public boolean isAllAppsButtonRank(int rank);
//指示正在绑定的页面 public void onPageBoundSynchronously(int page);
//输出当前Launcher信息到本地文件中 public void dumpLogsToLocalData();
}</appinfo></componentname></string></iteminfo></launcherappwidgetinfo></shortcutinfo></shortcutinfo></appinfo></appinfo></iteminfo></iteminfo></long></appinfo></folderinfo></long></long></iteminfo> |
前面说过Launcher也是一个Activity,所以它也需要执行setContentView()将布局文件显示出来。之后分别调用setupViews()、mDeviceProfile.layout(this)、restoreState(mSavedState)
我们先来看看setupViews()。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
private void setupViews() {
...... mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace); mWorkspace.setPageSwitchListener(this); mPageIndicators = mDragLayer.findViewById(R.id.page_indicator); ...... // Setup the hotseat mHotseat = (Hotseat) findViewById(R.id.hotseat); if (mHotseat != null) {
mHotseat.setOnLongClickListener(this); } // Setup the overview panel setupOverviewPanel(); ..... if (mLauncherCallbacks != null && mLauncherCallbacks.getAllAppsSearchBarController() != null) { mAppsView.setSearchBarController(mLauncherCallbacks.getAllAppsSearchBarController());
} else {
mAppsView.setSearchBarController(new DefaultAppSearchController());
}} |
可以看到setUpViews()的代码就是执行一系列的findViewById操作,并对控件设置各种监听和绑定。而mDeviceProfile.layout(this)所干的事大概就可以猜测是将这些控件进行布局。
看到layout里的代码也证实了我的猜想。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public void layout(Launcher launcher) {
FrameLayout.LayoutParams lp; boolean hasVerticalBarLayout = isVerticalBarLayout();
final boolean isLayoutRtl = Utilities.isRtl(launcher.getResources());
...... // Layout the page indicators View pageIndicator = launcher.findViewById(R.id.page_indicator); if (pageIndicator != null) {
if (hasVerticalBarLayout) {
// Hide the page indicators when we have vertical search/hotseat pageIndicator.setVisibility(View.GONE); } else {
// Put the page indicators above the hotseat lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams(); lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM; lp.width = LayoutParams.WRAP_CONTENT; lp.height = LayoutParams.WRAP_CONTENT; lp.bottomMargin = hotseatBarHeightPx; pageIndicator.setLayoutParams(lp); } } ......} |
执行上述方法之后,源码中还执行了一个restoreState ()方法,当onCreate()方法中的参数savedInstanceState不为空时才会进行相应的操作,该方法的作用就是恢复以前的状态。由于第一次启动Launcher时不会执行该方法,因此暂不进行分析。
随后就开始执行一个比较重要的方法,LauncherModel#startLoader()。
|
1
2
3
4
5
6
7
8
9
10
11
|
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 {
// We only load the page synchronously if the user rotates (or triggers a // configuration change) while launcher is in the foreground mModel.startLoader(mWorkspace.getRestorePage()); }} |
我们进入到LauncherModel的startLoader(),发现这是个重载的方法,最后都会执行public void startLoader(int synchronousBindPage, int loadFlags) {},该方法里最重要的就是创建了LoaderTask实例并执行其run()方法。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public void startLoader(int synchronousBindPage, int loadFlags) {
synchronized (mLock) {
...... if (mCallbacks != null && mCallbacks.get() != null) {
// If there is already one running, tell it to stop. stopLoaderLocked(); ...... mLoaderTask = new LoaderTask(mApp.getContext(), loadFlags);
..... if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
&& mAllAppsLoaded && mWorkspaceLoaded && !mIsLoaderTaskRunning) { mLoaderTask.runBindSynchronousPage(synchronousBindPage); } else {
//第一次启动会执行 sWorkerThread.setPriority(Thread.NORM_PRIORITY); sWorker.post(mLoaderTask); } } }} |
在LoadTask的run()方法里主要有以下几步操作:
loadAndBindWorkspace()->waitForIdle()->loadAndBindAllApps()
我们先来看loadAndBindWorkspace()
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
private void loadAndBindWorkspace() {
mIsLoadingAndBindingWorkspace = true; ...... if (!mWorkspaceLoaded) {
loadWorkspace(); synchronized (LoaderTask.this) {
if (mStopped) {
LauncherLog.d(TAG, "loadAndBindWorkspace returned by stop flag."); return; } mWorkspaceLoaded = true; } } // Bind the workspace bindWorkspace(-1);} |
mWorkspaceLoaded这个标识主要用来判断workspace是否已经加载过,如果没有,则先加载再进行绑定。我们先看看loadWorkspace(),其主要功能就是负责从数据库表中读取数据并转换为Launcher的数据结构。
|
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
|
private void loadWorkspace() {
if(){ ...... }else{ //加载默认值 LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary(); } synchronized (sBgLock) {
// 清空之前的内存数据(sBgWorkspaceItems,sBgAppWidgets等) clearSBgDataStructures(); // 存储无效数据的id,在后面统一从数据库中删掉 final ArrayList<long> itemsToRemove = new ArrayList<>();
// 查询ContentProvider,返回favorites表的结果集 final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
final Cursor c = contentResolver.query(contentUri, null, null, null, null);
try {
// 获取数据库每一列的索引值 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
final int intentIndex = c.getColumnIndexOrThrow
(LauncherSettings.Favorites.INTENT); ...... //查询ContentProvider while (!mStopped && c.moveToNext()) {
try {
//根据不同的itemType类型,将结果存储到相应的集合里 ...... switch (itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
...... break; case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
...... break; case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
...... break; } } catch (Exception e) {
...... } } } finally {
if (c != null) {
c.close(); } } // Break early if we've stopped loading if (mStopped) {
clearSBgDataStructures(); return; } // 对文件夹排序、contentResolver.update 、注册广播、移除空的屏幕 ...... }}</long> |
而bindWorkspace()则是将loadWorkspace()方法里获取到的数据显示在Launcher上。
|
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
|
/** * Binds all loaded data to actual views on the main thread. */private void bindWorkspace(int synchronizeBindPage) {
...... // Save a copy of all the bg-thread collections ArrayList<iteminfo> workspaceItems = new ArrayList<iteminfo>();
...... // Load all the items that are on the current page first (and in the process, unbind // all the existing workspace items before we call startBinding() below. unbindWorkspaceItemsOnMainThread(); ...... // Tell the workspace that we're about to start binding items r = new Runnable() {
public void run() {
Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null) {
callbacks.startBinding(); } } }; runOnMainThread(r); bindWorkspaceScreens(oldCallbacks, orderedScreenIds); // Load items on the current page bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets, currentFolders, null); if (isLoadingSynchronously) {
r = new Runnable() {
public void run() {
Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null && currentScreen != PagedView.INVALID_RESTORE_PAGE) {
callbacks.onPageBoundSynchronously(currentScreen); } } }; runOnMainThread(r); } // Load all the remaining pages (if we are loading synchronously, we want to defer this // work until after the first render) synchronized (mDeferredBindRunnables) {
mDeferredBindRunnables.clear(); } bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders, (isLoadingSynchronously ? mDeferredBindRunnables : null)); // Tell the workspace that we're done binding items r = new Runnable() {
public void run() {
Callbacks callbacks = tryGetCallbacks(oldCallbacks); if (callbacks != null) {
callbacks.finishBindingItems(); } mIsLoadingAndBindingWorkspace = false; // Run all the bind complete runnables after workspace is bound. if (!mBindCompleteRunnables.isEmpty()) {
synchronized (mBindCompleteRunnables) {
for (final Runnable r : mBindCompleteRunnables) {
runOnWorkerThread(r); } mBindCompleteRunnables.clear(); } } } }; if (isLoadingSynchronously) {
synchronized (mDeferredBindRunnables) {
mDeferredBindRunnables.add(r); } } else {
runOnMainThread(r); }}</iteminfo></iteminfo> |
bindWorkspace()的流程主要可以概括为:unbindWorkspaceItemsOnMainThread()->callbacks.startBinding()->bindWorkspaceScreens()-> bindWorkspaceItems()->callbacks.onPageBoundSynchronously(currentScreen)-> mDeferredBindRunnables.clear()->bindWorkspaceItems()->mBindCompleteRunnables.clear();
由于篇幅的限制这里就暂不做更深入的研究。
我们回到LoadTask的run()方法,执行完loadAndBindWorkspace()之后还执行了一个方法waitForIdle(),这个方法是用来干什么的呢?
这个方法里的注释告诉我们这个方法会等待其他线程执行完,直到workspace设置好了之后才开始执行loadAndBindAllApps(),相当于在这里阻塞线程。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
private void waitForIdle() {
// Wait until the either we're stopped or the other threads are done. // This way we don't start loading all apps until the workspace has settled down. synchronized (LoaderTask.this) {
...... while (!mStopped && !mLoadAndBindStepFinished) {
try {
// wait no longer than 1sec at a time this.wait(1000); } catch (InterruptedException ex) {
// Ignore } } }} |
至于loadAndBindAllApps(),就是加载主菜单的数据。现在很多国产的ROM在界面上已经看不到AllApps,猜测可能将这个方法屏蔽了。这个方法里的执行过程也很简单。首先会判断AllApps是否加载,如果没有加载则会先执行loadAllApps()、updateIconCache(),并赋值mAllAppsLoaded为true。若已经加载了则直接执行onlyBindAllApps()方法。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
private void loadAndBindAllApps() {
...... if (!mAllAppsLoaded) {
loadAllApps(); synchronized (LoaderTask.this) {
if (mStopped) {
return; } } updateIconCache(); synchronized (LoaderTask.this) {
if (mStopped) {
return; } mAllAppsLoaded = true; } } else {
onlyBindAllApps(); }} |
这整个过程和加载绑定workspace()类似。我们先看一下loadAllApps()方法。
|
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
|
private void loadAllApps() {
...... final List<userhandlecompat> profiles = mUserManager.getUserProfiles();
// Clear the list of apps mBgAllAppsList.clear(); //遍历账户列表 for (UserHandleCompat user : profiles) {
// Query for the set of apps final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
final List<launcheractivityinfocompat> apps = mLauncherApps.getActivityList(null, user);
...... boolean quietMode = mUserManager.isQuietModeEnabled(user);
// Create the ApplicationInfos,将应用加入到缓冲区 for (int i = 0; i < apps.size(); i++) {
LauncherActivityInfoCompat app = apps.get(i); // This builds the icon bitmaps. mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache, quietMode));
} //创建与该用户相关联的筛选器实例 final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user);
if (heuristic != null) {
final Runnable r = new Runnable() {
@Override public void run() {
//创建按账户分类应用程序的任务 heuristic.processUserApps(apps); } }; runOnMainThread(new Runnable() {
@Override public void run() {
// Check isLoadingWorkspace on the UI thread, as it is updated on // the UI thread. if (mIsLoadingAndBindingWorkspace) {
synchronized (mBindCompleteRunnables) {
mBindCompleteRunnables.add(r); } } else {
runOnWorkerThread(r); } } }); } } // Huh? Shouldn't this be inside the Runnable below? final ArrayList added = mBgAllAppsList.added;
mBgAllAppsList.added = new ArrayList();
// Post callback on main thread mHandler.post(new Runnable() {
public void run() {
...... if (callbacks != null) {
//绑定应用程序 callbacks.bindAllApplications(added); ...... } } }); // Cleanup any data stored for a deleted user. ......}</appinfo></appinfo></launcheractivityinfocompat></userhandlecompat> |
onlyBindAllApps()所执行的和loadAllApps()方法大同小异,这里就不做分析。
至此我已经把Launcher3的启动和加载数据的流程大致走了一遍。