Android 7.0 Launcher3的启动和加载流程分析----转载
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的启动和加载数据的流程大致走了一遍。