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界面
结合图和布局文件可能更好理解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启动流程
由于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()方法。
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的启动和加载数据的流程大致走了一遍。

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值