赞
踩
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的启动和加载数据的流程大致走了一遍。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。