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 "@+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">     "@+id/drag_layer" android:layout_width="match_parent" android:layout_height="match_parent" android:cliptopadding="false" android:clipchildren="false">         "@+id/focus_indicator" android:layout_width="52dp" android:layout_height="52dp">                           "@+id/workspace" android:layout_width="match_parent" android:layout_height="match_parent" launcher:pageindicator="@+id/page_indicator" launcher:defaultscreen="@integer/config_workspaceDefaultScreen">                             "@+id/hotseat" android:layout_width="match_parent" android:layout_height="match_parent" layout="@layout/hotseat">         "@+id/overview_panel" layout="@layout/overview_panel" android:visibility="gone">                  "@+id/page_indicator" android:layout_width="wrap_content" android:layout_height="wrap_content" layout="@layout/page_indicator" android:layout_gravity="center_horizontal">         "@+id/search_drop_target_bar" layout="@layout/search_drop_target_bar">         "@+id/widgets_view" android:layout_width="match_parent" android:layout_height="match_parent" layout="@layout/widgets_view" android:visibility="invisible">         "@+id/apps_view" android:layout_width="match_parent" android:layout_height="match_parent" layout="@layout/all_apps" android:visibility="invisible">     

下面是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 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 folders);         //完成绑定         public void finishBindingItems();         //批量绑定小部件,info 需要绑定到桌面上的小部件信息         public void bindAppWidget(LauncherAppWidgetInfo info);         //绑定应用程序列表界面的应用程序信息,apps 需要绑定到应用程序列表中的应用程序列表         public void bindAllApplications(ArrayList apps);         //批量添加组件         public void bindAppsAdded(ArrayList<long> newScreens,                                   ArrayList addNotAnimated,                                   ArrayList addAnimated,                                   ArrayList addedApps);         //批量更新应用程序相关的快捷方式或者入口         public void bindAppsUpdated(ArrayList apps);         public void bindShortcutsChanged(ArrayList updated,                 ArrayList removed, UserHandleCompat user);         //当Widget被重置的时候调用         public void bindWidgetsRestored(ArrayList widgets);         public void bindRestoreItemsChange(HashSet updates);         public void bindWorkspaceComponentsRemoved(                 HashSet packageNames, HashSet 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(); }long>long>long>

前面说过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 workspaceItems = new ArrayList();     ......     // 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);     } }

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 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 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.     ...... }

onlyBindAllApps()所执行的和loadAllApps()方法大同小异,这里就不做分析。

至此我已经把Launcher3的启动和加载数据的流程大致走了一遍。

更多相关文章

  1. Android(安卓)如何连真机测试
  2. Android之Service组件
  3. Android(安卓)Parcel和Parcelable类
  4. 判断应用或Activity是否存在
  5. Android设置Dialog透明度、黑暗度方法
  6. RecyclerView 中 item 点击事件的优化
  7. android获取各种路径的方法
  8. Android设置透明效果的三种方法
  9. Android使用SimpleAdapter

随机推荐

  1. android hook getdeceiveid
  2. Android中创建对话框
  3. Android(安卓)Stduio出现“required plug
  4. android ExpandableListView ExpandableL
  5. pandaboard ES学习之旅——4 Android源代
  6. Android 中文 API (29) ―― CompoundButto
  7. 跟着做 Android NDK学习入门如此简单(二)
  8. android数据存取的四种方式
  9. 在activity中调用Application 出现androi
  10. Android——编译release版签名系统