LauncherModel继承BroadcastReceiver,显然是一个广播接收者。在上一篇Launcher的启动中讲到桌面数据的加载工作是在LauncherModel中执行的,那么它是如何加载数据的呢?下面将分析Launcher和LauncherModel的通讯方式以及LauncherModel加载桌面数据的过程。

首先分析的是Launcher和LauncherModel的通讯方式:

(1)LauncherModel.Callback回调接口:LauncherModel通过接口输送已经加载完成的数据给Launcher。

//在Launcher.java中持有LauncherModel对象
mModel = app.setLauncher(this);

继续分析setLauncher(Launcher launcher)方法

//返回LauncherModel
LauncherModel setLauncher(Launcher launcher) {getLauncherProvider().setLauncherProviderChangeListener(launcher);mModel.initialize(launcher);mAccessibilityDelegate = ((launcher != null) && Utilities.isLmpOrAbove()) ?new LauncherAccessibilityDelegate(launcher) : null;return mModel;}

以上方法引用了此LauncherModel中的initialize(Callbacks callbacks);方法。且到LauncherModel中分析

public void initialize(Callbacks callbacks) {synchronized (mLock) {// Disconnect any of the callbacks and drawables associated with ItemInfos on the// workspace to prevent leaking Launcher activities on orientation change.unbindItemInfosAndClearQueuedBindRunnables();mCallbacks = new WeakReference<Callbacks>(callbacks);}}

发现传入的参数是Callbacks对象,而Launcher实现了LauncherModel.Callbacks接口,显然LauncherModel和Launcher就建立了联系。

从而完成了Launcher在LauncherModel中注册接口,将接口实现的引用注册到LauncherModel中,并且返回已经完成初始化的LauncherModel的引用。LauncherModel的所有操作结果都会通过callbacks中定义的各个回调接口中的方法通知给Launcher,并由Launcher分发给不同的桌面组件或者Launcher自身。

LauncherModel提供的Callbacks接口:

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<AppInfo> apps);// Add folders in all app list.public void bindAllApplications2Folder(ArrayList<AppInfo> apps, ArrayList<ItemInfo> items);//批量添加组件public void bindAppsAdded(ArrayList<Long> newScreens,ArrayList<ItemInfo> addNotAnimated,ArrayList<ItemInfo> addAnimated,ArrayList<AppInfo> addedApps);//批量更新应用程序相关的快捷方式或者入口public void bindAppsUpdated(ArrayList<AppInfo> apps);public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated,ArrayList<ShortcutInfo> removed, UserHandleCompat user);public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);public void bindRestoreItemsChange(HashSet<ItemInfo> updates);// 从桌面移除一些组件,当应用程序被移除或者禁用的时候调用public void bindComponentsRemoved(ArrayList<String> packageNames,ArrayList<AppInfo> appInfos, UserHandleCompat user, int reason);public void bindAllPackages(WidgetsModel model);//全局搜索或者搜索属性更新public void bindSearchablesChanged();public boolean isAllAppsButtonRank(int rank);/*** 指示正在绑定的页面* @param page  桌面页序号*/public void onPageBoundSynchronously(int page);//输出当前Launcher信息到本地文件中public void dumpLogsToLocalData();
}

(2)线程处理

@Thunk DeferredHandler mHandler = new DeferredHandler();
@Thunk LoaderTask mLoaderTask;
@Thunk static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
@Thunk static final Handler sWorker = new Handler(sWorkerThread.getLooper());

Launcher和LauncherModel运行在Launcher这个应用程序的主线程中, sWorkerThread只是Launcher应用程序主线程下的一个子线程,对于线程和线程之间的消息交互,一个比较好的方案是将任务抛到目标线程的处理器中,为此,LauncherModel为sWorkerThread在主线程中创建了一个处理器,以实现sWorkerThread和Launcher所在进程之间的信息交互。

(3)广播接口:

LauncherModel继承了BroadcastReceiver,因此可以接收来自Launcher或其他地方的广播,在onReceive(Context context, Intent intent)方法中做出相应的处理。


来个分割线,分析上面加载绑定数据过程的几个方法:

LauncherModel负责桌面数据的加载,即调用startLoader方法启动线程,加载数据。

public void startLoader(int synchronousBindPage) {startLoader(synchronousBindPage, LOADER_FLAG_NONE);
}
//启动LoaderTask线程里面的run方法,sWorker是一个Handler对象,用于启动线程的run方法
public void startLoader(int synchronousBindPage, int loadFlags) {// Enable queue before starting loader. It will get disabled in Launcher#finishBindingItemsInstallShortcutReceiver.enableInstallQueue();synchronized (mLock) {// Clear any deferred bind-runnables from the synchronized load process// We must do this before any loading/binding is scheduled below.////如果当前只是对桌面的第一个页面进行数据刷新,那么这个接口的调用会被封装成为一个任务加载到一个消息队列中//等待后续所有任务完成后才统一执行,如果这个任务还没有得到执行,而新的刷新页面的请求已经来到,那么LauncherModel//在启动加载之前会将消息队列清空,以确保所在任务都执行完成后,Launcher才会得到通知。synchronized (mDeferredBindRunnables) {//mDeferredBindRunnables是一个Runnable的列表(ArrayList),当LauncherModel的加载任务完成后,这里将会保存发往Launcher的通知,封装在一个Runnable中,加入该表mDeferredBindRunnables.clear();}// Don't bother to start the thread if we know it's not going to do anythingif (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);}}}
}

接下来看LoaderTask的run方法。

public void run() {synchronized (mLock) {if (mStopped) {return;}mIsLoaderTaskRunning = true;}// Optimize for end-user experience: if the Launcher is up and // running with the// All Apps interface in the foreground, load All Apps first. Otherwise, load the// workspace first (default).keep_running: {//加载和绑定workspace数据if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");loadAndBindWorkspace();if (mStopped) {break keep_running;}//等待线程空闲的时候再继续加载,避免ANRwaitForIdle();// second step//加载和绑定all apps数据if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");loadAndBindAllApps();}// Clear out this reference, otherwise we end up holding it until all of the// callback runnables are done.mContext = null;synchronized (mLock) {// If we are still the last one to be scheduled, remove ourselves.if (mLoaderTask == this) {mLoaderTask = null;}mIsLoaderTaskRunning = false;mHasLoaderCompletedOnce = true;}
}

分析该方法,可见加载Launcher数据主要执行两个步骤:

(1)loadAndBindWorkspace();

(2)loadAndBindAllApps();

那就按着这两个步骤,继续往下看:

loadAndBindWorkspace();

private void loadAndBindWorkspace() {//加载绑定桌面数据任务开始truemIsLoadingAndBindingWorkspace = true;// Load the workspaceif (DEBUG_LOADERS) {Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);}//接下来先判断桌面数据是否完全加载if (!mWorkspaceLoaded) {//false,加载桌面数据loadWorkspace();synchronized (LoaderTask.this) {if (mStopped) {//任务已经被停止,返回return;}//任务未停止mWorkspaceLoaded = true;}}//桌面数据加载完成,将数据绑定到桌面// Bind the workspacebindWorkspace(-1);
}

根据mWorkspaceLoader这个标识判断桌面数据是否加载完成,如果为true则执行bindWorkspace(-1)绑定桌面数据,如果为false,则先执行桌面数据的加载loadWorkspace(),再执行数据的绑定。

//Launcher桌面的数据主要包括来自Launcher数据库的各种表,
//loadWorkspace方法负责从这些数据库表中读取数据并转译为Launcher桌面项的数据结构
private void loadWorkspace() {final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;final Context context = mContext;final ContentResolver contentResolver = context.getContentResolver();//获取包管理服务final PackageManager manager = context.getPackageManager();final boolean isSafeMode = manager.isSafeMode();//获取Launcher定制的应用程序管理接口final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);final boolean isSdCardReady = context.registerReceiver(null,new IntentFilter(StartupReceiver.SYSTEM_READY)) != null;//获取桌面的行列数LauncherAppState app = LauncherAppState.getInstance();InvariantDeviceProfile profile = app.getInvariantDeviceProfile();int countX = (int) profile.numColumns;int countY = (int) profile.numRows;//根据输入的标识对数据进行预处理//判断标识是否为清空桌面if ((mFlags & LOADER_FLAG_CLEAR_WORKSPACE) != 0) {Launcher.addDumpLog(TAG, "loadWorkspace: resetting launcher database", true);//删除数据LauncherAppState.getLauncherProvider().deleteDatabase();}//判断是否为迁移Launcher2的快捷方式if ((mFlags & LOADER_FLAG_MIGRATE_SHORTCUTS) != 0) {// append the user's Launcher2 shortcutsLauncher.addDumpLog(TAG, "loadWorkspace: migrating from launcher2", true);//迁移Launcher2的快捷方式LauncherAppState.getLauncherProvider().migrateLauncher2Shortcuts();} else {// Make sure the default workspace is loaded//确保默认数据得以加载Launcher.addDumpLog(TAG, "loadWorkspace: loading default favorites", false);//根据需要加载默认快捷方式LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary();}synchronized (sBgLock) {clearSBgDataStructures();//更新并获取缓存final HashMap<String, Integer> installingPkgs = PackageInstallerCompat.getInstance(mContext).updateAndGetActiveSessionCache();final ArrayList<Long> itemsToRemove = new ArrayList<Long>();final ArrayList<Long> restoredRows = new ArrayList<Long>();//获取快捷方式的urifinal Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri);//根据获取的uri查询Favorites表数据final Cursor c = contentResolver.query(contentUri, null, null, null, null);// +1 for the hotseat (it can be larger than the workspace)// Load workspace in reverse order to ensure that latest items are loaded first (and// before any earlier duplicates)//准备桌面占用情况标识映射final LongArrayMap<ItemInfo[][]> occupied = new LongArrayMap<>();……// Break early if we've stopped loading//加载任务已经被停止if (mStopped) {//从数据库中清理需要删除的数据项clearSBgDataStructures();return;}//从数据库中清理需要删除的数据项if (itemsToRemove.size() > 0) {// Remove dead itemscontentResolver.delete(LauncherSettings.Favorites.CONTENT_URI,Utilities.createDbSelectionQuery(LauncherSettings.Favorites._ID, itemsToRemove), null);if (DEBUG_LOADERS) {Log.d(TAG, "Removed = " + Utilities.createDbSelectionQuery(LauncherSettings.Favorites._ID, itemsToRemove));}// Remove any empty folderfor (long folderId : LauncherAppState.getLauncherProvider().deleteEmptyFolders()) {sBgWorkspaceItems.remove(sBgFolders.get(folderId));sBgFolders.remove(folderId);sBgItemsIdMap.remove(folderId);}}//设置恢复状态为已恢复if (restoredRows.size() > 0) {// Update restored items that no longer require special handlingContentValues values = new ContentValues();values.put(LauncherSettings.Favorites.RESTORED, 0);contentResolver.update(LauncherSettings.Favorites.CONTENT_URI, values,Utilities.createDbSelectionQuery(LauncherSettings.Favorites._ID, restoredRows), null);}//处理在装在sdcard上的应用程序if (!isSdCardReady && !sPendingPackages.isEmpty()) {context.registerReceiver(new AppsAvailabilityCheck(),new IntentFilter(StartupReceiver.SYSTEM_READY),null, sWorker);}//处理桌面数据sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext));// Remove any empty screensArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens);for (ItemInfo item: sBgItemsIdMap) {long screenId = item.screenId;if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&unusedScreens.contains(screenId)) {unusedScreens.remove(screenId);}}// If there are any empty screens remove them, and update.if (unusedScreens.size() != 0) {sBgWorkspaceScreens.removeAll(unusedScreens);updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);}if (DEBUG_LOADERS) {Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms");Log.d(TAG, "workspace layout: ");int nScreens = occupied.size();for (int y = 0; y < countY; y++) {String line = "";for (int i = 0; i < nScreens; i++) {long screenId = occupied.keyAt(i);if (screenId > 0) {line += " | ";}ItemInfo[][] screen = occupied.valueAt(i);for (int x = 0; x < countX; x++) {if (x < screen.length && y < screen[x].length) {line += (screen[x][y] != null) ? "#" : ".";} else {line += "!";}}}Log.d(TAG, "[ " + line + " ]");}}}
}

加载数据完成就可以绑定数据到桌面了。

private void bindWorkspace(int synchronizeBindPage) {final long t = SystemClock.uptimeMillis();Runnable r;// Don't use these two variables in any of the callback runnables.// Otherwise we hold a reference to them.//获取Launcher的接口final Callbacks oldCallbacks = mCallbacks.get();//判断Launcher的接口是否存在if (oldCallbacks == null) {// This launcher has exited and nobody bothered to tell us.  Just bail.Log.w(TAG, "LoaderTask running with no launcher");return;}// Save a copy of all the bg-thread collections//桌面数据项列表ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>();//桌面小部件项数据列表ArrayList<LauncherAppWidgetInfo> appWidgets =new ArrayList<LauncherAppWidgetInfo>();//经过排序的桌面索引列表ArrayList<Long> orderedScreenIds = new ArrayList<Long>();final LongArrayMap<FolderInfo> folders;final LongArrayMap<ItemInfo> itemsIdMap;//或冲区数据复制synchronized (sBgLock) {workspaceItems.addAll(sBgWorkspaceItems);appWidgets.addAll(sBgAppWidgets);orderedScreenIds.addAll(sBgWorkspaceScreens);folders = sBgFolders.clone();itemsIdMap = sBgItemsIdMap.clone();}//获取launcher当前所处的页面索引final boolean isLoadingSynchronously =synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE;int currScreen = isLoadingSynchronously ? synchronizeBindPage :oldCallbacks.getCurrentWorkspaceScreen();if (currScreen >= orderedScreenIds.size()) {// There may be no workspace screens (just hotseat items and an empty page).currScreen = PagedView.INVALID_RESTORE_PAGE;}final int currentScreen = currScreen;final long currentScreenId = currentScreen < 0? INVALID_SCREEN_ID : orderedScreenIds.get(currentScreen);// 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();//分离当前页面与其他页面的数据并通知Launcher开始加载// Separate the items that are on the current screen, and all the other remaining items//分类数据区定义ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<ItemInfo>();ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<ItemInfo>();ArrayList<LauncherAppWidgetInfo> currentAppWidgets =new ArrayList<LauncherAppWidgetInfo>();ArrayList<LauncherAppWidgetInfo> otherAppWidgets =new ArrayList<LauncherAppWidgetInfo>();LongArrayMap<FolderInfo> currentFolders = new LongArrayMap<>();LongArrayMap<FolderInfo> otherFolders = new LongArrayMap<>();//分类桌面项、小部件、文件夹数据filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,otherWorkspaceItems);filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets,otherAppWidgets);filterCurrentFolders(currentScreenId, itemsIdMap, folders, currentFolders,otherFolders);//对数据进行排序sortWorkspaceItemsSpatially(currentWorkspaceItems);sortWorkspaceItemsSpatially(otherWorkspaceItems);// Tell the workspace that we're about to start binding items//在主线程上执行通知Launcher绑定开始任务r = new Runnable() {public void run() {Callbacks callbacks = tryGetCallbacks(oldCallbacks);if (callbacks != null) {//绑定数据到launcher,launcher回调callbacks.startBinding();}}};runOnMainThread(r);bindWorkspaceScreens(oldCallbacks, orderedScreenIds);// Load items on the current pagebindWorkspaceItems(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();}// If we're profiling, ensure this is the last thing in the queue.if (DEBUG_LOADERS) {Log.d(TAG, "bound workspace in "+ (SystemClock.uptimeMillis()-t) + "ms");}//设置加载以及绑定结束标识mIsLoadingAndBindingWorkspace = false;}};if (isLoadingSynchronously) {synchronized (mDeferredBindRunnables) {mDeferredBindRunnables.add(r);}} else {//在主线程中执行该任务runOnMainThread(r);}
}

加载和绑定好桌面数据就可以加载和绑定AllApps数据了

//通过调用两个方法来完成应用程序的数据加载即绑定
private void loadAndBindAllApps() {if (DEBUG_LOADERS) {Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);}//判断应用程序菜单是否已加载if (!mAllAppsLoaded) {//未加载,执行加载绑定过程loadAllApps();synchronized (LoaderTask.this) {if (mStopped) {return;}}updateIconCache();synchronized (LoaderTask.this) {if (mStopped) {return;}//设置已经加载标识mAllAppsLoaded = true;}} else {//已加载数据,只执行绑定过程onlyBindAllApps();}
}

跟桌面的数据加载绑定过程类似,也是根据mAllAppsLoaded这个标识来判断执行的是加载还是绑定。

private void loadAllApps() {final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;final Callbacks oldCallbacks = mCallbacks.get();if (oldCallbacks == null) {// This launcher has exited and nobody bothered to tell us.  Just bail.Log.w(TAG, "LoaderTask running with no launcher (loadAllApps)");return;}//Android系统提供了多账户的概念,不同的账户下可以使用的应用程序是不同的,因此Launcher需要注意这个细节,//在不同账户下处理不同的应用程序列表信息,所有在加载应用程序列表的时候需要获取当前设备上的所有账户。final List<UserHandleCompat> profiles = mUserManager.getUserProfiles();// Clear the list of apps//清除列表mBgAllAppsList.clear();for (UserHandleCompat user : profiles) {// Query for the set of appsfinal long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;//获取应用程序入口信息final List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);if (DEBUG_LOADERS) {Log.d(TAG, "getActivityList took "+ (SystemClock.uptimeMillis()-qiaTime) + "ms for user " + user);Log.d(TAG, "getActivityList got " + apps.size() + " apps for user " + user);}// Fail if we don't have any apps// TODO: Fix this. Only fail for the current user.if (apps == null || apps.isEmpty()) {return;}//将应用程序信息加入缓存区// Create the ApplicationInfosfor (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));}//按账户保存查询到的应用列表,ManagedProfileHeuristic工具将查询得到的数据分类保存到共享文件final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user);if (heuristic != null) {runAfterBindCompletes(new Runnable() {@Overridepublic void run() {heuristic.processUserApps(apps);}});}}……//将需要加载的应用程序菜单中的数据完成分类后,接着将数据发送到Launcher中处理// Post callback on main threadmHandler.post(new Runnable() {public void run() {final long bindTime = SystemClock.uptimeMillis();final Callbacks callbacks = tryGetCallbacks(oldCallbacks);if (callbacks != null) {if (!FolderPlugins.getInstance().isEnable()) {//(arrayForBind == null || arrayForBind.isEmpty()) && ){callbacks.bindAllApplications(added);} else {callbacks.bindAllApplications2Folder(added, arrayForBind);}if (DEBUG_LOADERS) {Log.d(TAG, "bound " + added.size() + " apps in "+ (SystemClock.uptimeMillis() - bindTime) + "ms");}} else {Log.i(TAG, "not binding apps: no Launcher activity");}}});// Cleanup any data stored for a deleted user.ManagedProfileHeuristic.processAllUsers(profiles, mContext);loadAndBindWidgetsAndShortcuts(mApp.getContext(), tryGetCallbacks(oldCallbacks),true /* refresh */);if (DEBUG_LOADERS) {Log.d(TAG, "Icons processed in "+ (SystemClock.uptimeMillis() - loadTime) + "ms");}
}
private void onlyBindAllApps() {final Callbacks oldCallbacks = mCallbacks.get();if (oldCallbacks == null) {// This launcher has exited and nobody bothered to tell us.  Just bail.Log.w(TAG, "LoaderTask running with no launcher (onlyBindAllApps)");return;}// shallow copy@SuppressWarnings("unchecked")final ArrayList<AppInfo> list= (ArrayList<AppInfo>) mBgAllAppsList.data.clone();final WidgetsModel widgetList = mBgWidgetsModel.clone();Runnable r = new Runnable() {public void run() {final long t = SystemClock.uptimeMillis();final Callbacks callbacks = tryGetCallbacks(oldCallbacks);if (callbacks != null) {callbacks.bindAllApplications(list);callbacks.bindAllPackages(widgetList);}if (DEBUG_LOADERS) {Log.d(TAG, "bound all " + list.size() + " apps from cache in "+ (SystemClock.uptimeMillis()-t) + "ms");}}};boolean isRunningOnMainThread = !(sWorkerThread.getThreadId() == Process.myTid());if (isRunningOnMainThread) {r.run();} else {mHandler.post(r);}
}

Launcher3源码分析(LauncherModel加载数据)相关推荐

  1. Mybatis3源码分析(05)-加载Configuration-加载MappedStatement

    2019独角兽企业重金招聘Python工程师标准>>> Mybatis3源码分析(05)-加载Configuration-加载MappedStatement 博客分类: java m ...

  2. springboot集成mybatis源码分析-启动加载mybatis过程(二)

    springboot集成mybatis源码分析-启动加载mybatis过程(二) 1.springboot项目最核心的就是自动加载配置,该功能则依赖的是一个注解@SpringBootApplicati ...

  3. Spring源码分析4---IOC加载过程补充总结

    原文出自:http://cmsblogs.com IOC 之 获取验证模型 DTD 与 XSD 的区别 DTD(Document Type Definition),即文档类型定义,为 XML 文件的验 ...

  4. Launcher3源码分析 — 将Workspace的数据与界面绑定

    在数据从数据库加载到内存之后,接下来的工作就是把这些数据与launcher的UI视图绑定.绑定的过程在LauncherModel.bindWorkspace()方法中完成,在这个方法中会调用回调接口C ...

  5. 红橙Darren视频笔记setContentView源码分析 xml加载的过程

    setContentView过程分析 从继承Activity的类开始进行分析 MainActivity setContentView(R.layout.activity_main); Activity ...

  6. Spark 源码分析之ShuffleMapTask内存数据Spill和合并

    Spark 源码分析之ShuffleMapTask内存数据Spill和合并 更多资源分享 SPARK 源码分析技术分享(视频汇总套装视频): https://www.bilibili.com/vide ...

  7. 基于Android7.0的Launcher3源码分析(1)——框架设计分析

    从事Android rom一年多,一直在负责 Launcher 相关的工作.最近打算写些文章记录下自己对这个模块的理解和源码实现的一些解析. 这些文章将会基于 Android 7.0 的 Launch ...

  8. Glide 4.9源码解析-图片加载流程

    本文Glide源码基于4.9,版本下载地址如下:Glide 4.9 前言 由于Glide源码真的很复杂,因此本文只分析和贴出与图片加载流程相关的功能以及代码.另外本文Glide源码基于4.9,与3.x ...

  9. android 图片缩放算法,Android大图加载,缩放,滑动浏览--SubsamplingScaleImageView 源码分析大图加载...

    **************这个开源项目有点大的,也不知道几篇能写完,先根据功能点分析解读********************* 1.写在前面 图片浏览的坑不少,大图加载导致内存溢出的情况相信每个 ...

最新文章

  1. Thread系列——Thread.Sleep(0)
  2. 神经网络训练集两张图片之间的相互作用
  3. easy mysql_GitHub - aleafs/easymysql: mysql client in cluster, based on node-mysql
  4. 轻松的java,轻松进行Java Portlets
  5. 《代码敲不队》第八次团队作业:Alpha冲刺 第五天
  6. 使用Attribute简单地扩展WebForm
  7. asp.net怎么生成json数据_[随笔]利用 VS2019 + NET5.0 生成镜像部署
  8. dig命令的介绍与使用
  9. c语言 队列方法的编写
  10. Matlab求一个数组的平均值、方差
  11. 明天全国哀悼日,小程序只需三行代码秒变黑白
  12. 离散数学 06.05 同构及同态
  13. C++实现简单数独游戏
  14. 微信小程序 - 获取汉字拼音首字母(汉字英文首字母)
  15. 08、Flutter FFI 内存管理
  16. 全球与中国太阳能并网逆变器市场深度研究分析报告
  17. alphapose的使用
  18. 苹果大战泄密者内幕曝光:从中国工厂到美国总部
  19. Gym 101653T Runes (模拟)
  20. SQL+Tableau化妆品数据分析

热门文章

  1. 韦氏紫血三国——WESB之赤壁
  2. Oracle的 nvl 函数及 nvl2 函数的应用
  3. 企业省钱之道:是使用IDC的二手服务器还是云主机?
  4. 数据库基本操作及增删改查示例
  5. SAP License:看看你在哪个阶段
  6. 虽然计算机应用的范围越来越广,《高考语文一本通》最新版本(全国卷适用) 第一部分 语言文字运用...
  7. perf的基本使用方法
  8. 速卖通72小时上网率什么意思?解读考核标准和达成权益
  9. 蚂蚁3期JAVA互联网高级架构师视频教程
  10. chouti的布局 ,chouti的主页面,ContentFragment界面