上面一篇文章说了Launcher是如何被启动的,Launcher启动的过程主要是加载界面数据然后显示出来,

界面数据都是系统APP有关的数据,都是从Launcher的数据库读取,下面我们详细分析Launcher如何加载数据。

在Launcher.java的onCreate()方法里面,调用了开始加载数据接口:

//Edited by mythou
//http://www.cnblogs.com/mythou/
//加载启动数据
if (!mRestoring)
{
mModel.startLoader(this, true);
}

mModel是LauncherModel的对象,由此可见,数据加载主要是在LauncherModel类里面实现的。

1、Callbacks接口

LauncherModel里面,需要先分析一个Callbacks接口。


//Edited by mythou
//http://www.cnblogs.com/mythou/
 public interface Callbacks {
public boolean setLoadOnResume();
public int getCurrentWorkspaceScreen();
public void startBinding();
public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end);
public void bindFolders(HashMap<Long,FolderInfo> folders);
public void finishBindingItems();
public void bindAppWidget(LauncherAppWidgetInfo info);
public void bindAllApplications(ArrayList<ApplicationInfo> apps);
public void bindAppsAdded(ArrayList<ApplicationInfo> apps);
public void bindAppsUpdated(ArrayList<ApplicationInfo> apps);
public void bindAppsRemoved(ArrayList<ApplicationInfo> apps, boolean permanent);
public void bindPackagesUpdated();
public boolean isAllAppsVisible();
public void bindSearchablesChanged();
}

Callbacks接口提供了很多接口,用于返回相关的数据给Launcher模块,下面我们对每个接口作用做个阐释。

setLoadOnResume() :当Launcher.java类的Activity处于onPause的时候,如果重新恢复,需要调用onResume,此时需要在onResume调用这个接口,恢复Launcher数据。

getCurrentWorkspace():获取屏幕序号(0~4)

startBinding():通知Launcher开始加载数据。清空容器数据,重新加载

bindItems(ArrayList<ItemInfo> shortcuts, int start, int end):加载App shortcut、Live Folder、widget到Launcher相关容器。

bindFolders(HashMap<Long, FolderInfo> folders):加载folder的内容

finishBindingItems():数据加载完成。

bindAppWidget(LauncherAppWidgetInfo item):workspace加载APP 快捷方式

bindAllApplications(final ArrayList<ApplicationInfo> apps):所有应用列表接着APP图标数据

bindAppsAdded(ArrayList<ApplicationInfo> apps):通知Launcher新安装了一个APP,更新数据。

bindAppsUpdated(ArrayList<ApplicationInfo> apps):通知Launcher一个APP更新了。(覆盖安装)

bindAppsRemoved(ArrayList<ApplicationInfo> apps, boolean permanent):通知Launcher,应用被删除

bindPackagesUpdated():多个应用更新。

isAllAppsVisible():返回所有应用列表是否可见状态。

bindSearchablesChanged():Google搜索栏或者删除区域发生变化时通知Launcher

2、数据加载流程

Launcher.java类继承了Callbacks接口,并实现了该接口。LauncherModel里面会调用这些接口,反馈数据和状态给Launcher。数据加载总体分为两部分,一部分是加载workspace的数据,另一部分是加载All APP界面的数据。

下面是一个加载数据流程图:

3、startLoader()

下面我们先分析startLoader()接口,startLoader主要是启动了一个线程,用于加载数据。

//Edited by mythou
//http://www.cnblogs.com/mythou/
public void startLoader(Context context, boolean isLaunching) {
synchronized (mLock)
{
//...............
if (mCallbacks != null && mCallbacks.get() != null) {
isLaunching = isLaunching || stopLoaderLocked();
mLoaderTask = new LoaderTask(context, isLaunching);
sWorkerThread.setPriority(Thread.NORM_PRIORITY);
sWorker.post(mLoaderTask);
}
}
}

startLoader主要是启动LoaderTask线程里面的run方法。sWorker是一个Handle对象,用于启动线程的run方法。

 4、LoaderTask的run()方法

//Edited by mythou
//http://www.cnblogs.com/mythou/
public void run() {
       //............ keep_running: {
         //...............          //加载当前页面的数据,先把一页的数据加载完成,
//主要是为了增加程序流畅性,提高用户体验
if (loadWorkspaceFirst) {
if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
loadAndBindWorkspace();
} else {
if (DEBUG_LOADERS) Log.d(TAG, "step 1: special: loading all apps");
loadAndBindAllApps();
} if (mStopped) {
break keep_running;
} // THREAD_PRIORITY_BACKGROUND设置线程优先级为后台,
         //这样当多个线程并发后很多无关紧要的线程分配的CPU时间将会减少,有利于主线程的处理

synchronized (mLock) {
if (mIsLaunching) {
if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to BACKGROUND");
android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
}
}
         //等待线程空闲的时候,继续加载其他页面数据
waitForIdle(); //加载剩余页面的数据,包含workspace和all app页面
if (loadWorkspaceFirst) {
if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
loadAndBindAllApps();
} else {
if (DEBUG_LOADERS) Log.d(TAG, "step 2: special: loading workspace");
loadAndBindWorkspace();
} // Restore the default thread priority after we are done loading items
synchronized (mLock) {
android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
}
}
}

上面是经过简化的LoaderTask的run方法代码,其实主要就两部分操作,第一部分操作,加载当前页面的数据

(当前workspace页面或者当前All APP页面的数据)然后等待线程空闲的时候,再加载剩余的页面数据。

代码上面加了关键注释,可以结合代码分析。这样做主要目的是增加Launcher启动的速度,让用户觉得系统初始化速度

较快,有较好的用户体验。先把用户看见的界面初始化完毕,然后再开一个后台线程慢慢加载其他的数据。

下面我们分别分析workspace和All APP加载和绑定。

5、workspace加载数据

loadAndBindWorkspace()方法主要就是执行loadWorkspace()和 bindWorkspace()方法。
下面分别对这两个方法进行分析。
//Edited by mythou
//http://www.cnblogs.com/mythou/
private void loadWorkspace() {
       //..........        //清空容器,存放界面不同的元素,App快捷方式、widget、folder
synchronized (sBgLock) {
sBgWorkspaceItems.clear();
sBgAppWidgets.clear();
sBgFolders.clear();
sBgItemsIdMap.clear();
sBgDbIconCache.clear(); final ArrayList<Long> itemsToRemove = new ArrayList<Long>(); final Cursor c = contentResolver.query(
LauncherSettings.Favorites.CONTENT_URI, 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)
          //表示屏幕上的位置,
         //第一维表示分屏的序号,其中最后一个代表Hotseat             
         //第二维表示x方向方格的序号         
         //第三维表示y方向方格的序号
                final ItemInfo occupied[][][] =
new ItemInfo[Launcher.SCREEN_COUNT + ][mCellCountX + ][mCellCountY + ];
          //读取数据库响应键值列序号
try {
final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
final int intentIndex = c.getColumnIndexOrThrow
(LauncherSettings.Favorites.INTENT);
final int titleIndex = c.getColumnIndexOrThrow
(LauncherSettings.Favorites.TITLE);
final int iconTypeIndex = c.getColumnIndexOrThrow(
LauncherSettings.Favorites.ICON_TYPE);
            //...........
          while (!mStopped && c.moveToNext()) {
try {
int itemType = c.getInt(itemTypeIndex); switch (itemType) {
                 //item类型为ITEM_TYPE_APPLICATION或者ITEM_TYPE_SHORTCUT  
                //container为CONTAINER_DESKTOP或者CONTAINER_HOTSEAT
                //把当前的item添加到sWorkspaceItems中
                case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                      emType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
info = getShortcutInfo(manager, intent, context, c, iconIndex,
titleIndex, mLabelCache);
} else {
info = getShortcutInfo(c, context, iconTypeIndex,
iconPackageIndex, iconResourceIndex, iconIndex,
titleIndex);
                    
switch (container) {
case LauncherSettings.Favorites.CONTAINER_DESKTOP:
case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
                        //添加数据
sBgWorkspaceItems.add(info);
break;
default:
//如果item的属性是folder,添加到folder,创建forder
FolderInfo folderInfo =
findOrMakeFolder(sBgFolders, container);
folderInfo.add(info);
break;
}
sBgItemsIdMap.put(info.id, info); } else {
}
break;
                //item类型为文件夹,添加
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
id = c.getLong(idIndex);
FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
                   //.........
sBgItemsIdMap.put(folderInfo.id, folderInfo);
sBgFolders.put(folderInfo.id, folderInfo);
break;
                 //Widget添加
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
// Read all Launcher-specific widget details
int appWidgetId = c.getInt(appWidgetIdIndex);
id = c.getLong(idIndex);
                   //...........
sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
sBgAppWidgets.add(appWidgetInfo);
break;
}
} catch (Exception e) {
Log.w(TAG, "Desktop items loading interrupted:", e);
}
}
} finally {
c.close();
}
}
}

workspace的数据加载总的来说也是按照元素属性来区分加载,分为App快捷方式、Widget、Folder元素。

这几个元素分别加载到不同的容器里面。其中sItemsIdMap保存所有元素的id和ItemInfo组成的映射。其他

元素分别加载到3个不同的容器里面,用于后面绑定数据用。这里只给出了loadWorkspace的流程代码,详细代码,

需要看源码,还有很多细节。不过刚开始分析Launcher,我的原则是先把握整体流程和知道改动代码,需要在哪里查找。

6、workspace绑定数据

Launcher的内容绑定分为五步:分别对应着startBinding()、bindItems()、bindFolders()、 bindAppWidgets()、

finishBindingItems()的调用。下面针对bindWorkspace做个简单的流程分析。


//Edited by mythou
//http://www.cnblogs.com/mythou/

private void bindWorkspace() {        //通知Launcher开始绑定数据
mHandler.post(new Runnable() {
public void run() {
Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
//绑定数据到launcher,Launcher回调,清空相关容器 OWL
callbacks.startBinding();
}
}
});
//添加元素到workspace,主要是添加APP快捷方式

N = workspaceItems.size();
for (int i=; i<N; i+=ITEMS_CHUNK) {
final int start = i;
final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
mHandler.post(new Runnable() {
public void run() {
Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
callbacks.bindItems(workspaceItems, start, start+chunkSize);
}
}
});
}
//文件夹绑定
final HashMap<Long, FolderInfo> folders = new HashMap<Long, FolderInfo>(sFolders);
mHandler.post(new Runnable() {
public void run() {
Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
callbacks.bindFolders(folders);
}
}
});
//分两次加载widget ,当前界面和其他界面,增强用户体验OWL
     //其他页面widget会在后台线程再次加载
final int currentScreen = oldCallbacks.getCurrentWorkspaceScreen();
N = sAppWidgets.size();
// once for the current screen
for (int i=; i<N; i++) {
final LauncherAppWidgetInfo widget = sAppWidgets.get(i);
if (widget.screen == currentScreen) {
mHandler.post(new Runnable() {
public void run() {
Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
callbacks.bindAppWidget(widget);
}
}
});
}
}
//加载其他看不见的屏幕widget
for (int i=; i<N; i++) {
final LauncherAppWidgetInfo widget = sAppWidgets.get(i);
if (widget.screen != currentScreen) {
mHandler.post(new Runnable() {
public void run() {
Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
callbacks.bindAppWidget(widget);
}
}
});
}
}
//加载完成,通知Launcher,已经完成数据加载
mHandler.post(new Runnable() {
public void run() {
Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
callbacks.finishBindingItems();
}
}
});
}

上面就是Launcher的workspace绑定数据的过程,跟加载数据过程很相似,也是区分3中类型的元素进行加载。

下面我们总结一下,workspace的加载和绑定数据的过程。我们现在回头看,可以发现,其实workspace里面就是

存放了3中数据ItemInfo、FolderInfo、LauncherAppWidgetInfo。分别对应我们的APP快捷方式、文件夹、Widget

数据。其中FolderInfo、LauncherAppWidgetInfo都是继承了ItemInfo。数据加载过程,就是从Launcher的数据库

读取数据然后按元素属性分别放到3个ArrayList里面。绑定数据过程就是把3个ArrayList的队列关联到Launcher界面里面。

 7、ALL APP数据加载绑定


//Edited by mythou
//http://www.cnblogs.com/mythou/

private void loadAllAppsByBatch() {//只有这两个标记才需要显示在所有程序列表 OWL
final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); final PackageManager packageManager = mContext.getPackageManager();
List<ResolveInfo> apps = null; int N = Integer.MAX_VALUE; int startIndex;
int i=;
int batchSize = -;
while (i < N && !mStopped) {
if (i == ) {
mAllAppsList.clear();
final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : ;
//过滤需要显示的app
apps = packageManager.queryIntentActivities(mainIntent, );
if (DEBUG_LOADERS) {
Log.d(TAG, "queryIntentActivities took "
+ (SystemClock.uptimeMillis()-qiaTime) + "ms");
}
if (apps == null) {
return;
}
N = apps.size();
if (DEBUG_LOADERS) {
Log.d(TAG, "queryIntentActivities got " + N + " apps");
}
if (N == ) {
// There are no apps?!?
return;
}
//mBatchSize==0表示一次性加载所有的应用
if (mBatchSize == ) {
batchSize = N;
} else {
batchSize = mBatchSize;
}
} final boolean first = i <= batchSize;
final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
final ArrayList<ApplicationInfo> added = mAllAppsList.added;
mAllAppsList.added = new ArrayList<ApplicationInfo>();
         //绑定加载所有的APP数据
mHandler.post(new Runnable() {
public void run() {
final long t = SystemClock.uptimeMillis();
if (callbacks != null) {
if (first) {
//一次性加载所以app,返回数据到launcher
callbacks.bindAllApplications(added);
} else {
callbacks.bindAppsAdded(added);
}
if (DEBUG_LOADERS) {
Log.d(TAG, "bound " + added.size() + " apps in "
+ (SystemClock.uptimeMillis() - t) + "ms");
}
} else {
Log.i(TAG, "not binding apps: no Launcher activity");
}
}
});
}

AllAPP的数据加载和绑定跟workspace的差不多,也是先加载数据然后绑定数据,通知Launcher。加载数据的时候

从PackageManager获取所有已经安装的APK包信息,然后过滤只包含需要显示在所有应用列表的应用,需要包含

ACTION_MAIN和CATEGORY_LAUNCHER两个属性。这个我们在编写应用程序的时候都应该知道。

AllAPP加载跟workspace不同的地方是加载的同时,完成数据绑定的操作,也就是说第一次加载AllAPP页面的数据,

会同时绑定数据到Launcher。第二次需要加载的时候,只会把数据直接绑定到Launcher,而不会重新搜索加载数据。

Launcher启动加载和绑定数据就是这样完成。绑定完数据,Launcher就可以运行。

系列文章:

Android Launcher分析和修改1——Launcher默认界面配置(default_workspace)

Android Launcher分析和修改2——Icon修改、界面布局调整、壁纸设置

Android Launcher分析和修改3——Launcher启动和初始化

Edited by mythou

原创博文,转载请标明出处:http://www.cnblogs.com/mythou/p/3165259.html 

Android Launcher分析和修改4——初始化加载数据的更多相关文章

  1. Android Launcher分析和修改13——实现Launcher编辑模式(1) 壁纸更换

    已经很久没更新Launcher系列文章,今天不分析源码,讲讲如何在Launcher里面添加桌面设置的功能.目前很多第三方Launcher或者定制Rom都有简单易用的桌面设置功能.例如小米MIUI的La ...

  2. Android Launcher分析和修改9——Launcher启动APP流程

    本来想分析AppsCustomizePagedView类,不过今天突然接到一个临时任务.客户反馈说机器界面的图标很难点击启动程序,经常点击了没有反应,Boss说要优先解决这问题.没办法,只能看看是怎么 ...

  3. Android Launcher分析和修改10——HotSeat深入进阶

    前面已经写过Hotseat分析的文章,主要是讲解如何在Launcher里面配置以及修改Hotseat的参数.今天主要是讲解一下如何在Hotseat里面的Item显示名称.这个小问题昨天折腾了半天,最后 ...

  4. Android Launcher分析和修改5——HotSeat分析

    今天主要是分析一下Launcher里面的快捷方式导航条——HotSeat,一般我们使用手机底下都会有这个导航条,但是如果4.0的Launcher放到平板电脑里面运行,默认是没有HotSeat的,刚好我 ...

  5. Android Launcher分析和修改6——页面滑动(PagedView)

    本来打算分析CellLayout的源码,不过因为它们之间是容器包含关系,所以打算先把PagedView分析.PagedView代码很多,今天主要是分析跟核心功能相关的代码.PagedView主要实现一 ...

  6. Android Launcher分析和修改7——AllApp全部应用列表(AppsCustomizeTabHost)

    今天主要是分析一下Launcher里面的所有应用列表.Android4.0 Launcher的所有应用列表跟2.X比较大的区别就是多了Widget的显示.下面会详细分析Launcher里面有关所有应用 ...

  7. Android Launcher分析和修改8——AllAPP界面拖拽元素(PagedViewWithDraggableItems)

    接着上一篇文章,继续分析AllAPP列表界面.上一篇文章分析了所有应用列表的界面构成以及如何通过配置文件修改属性.今天主要是分析PagedViewWithDraggableItems类,因为在我们分析 ...

  8. Android Launcher分析和修改11——自定义分页指示器(paged_view_indicator)

    Android4.0的Launcher自带了一个简单的分页指示器,就是Hotseat上面那个线段,这个本质上是一个ImageView利用.9.png图片做,效果实在是不太美观,用测试人员的话,太丑了. ...

  9. Android Launcher分析和修改12——Widget列表信息收集

    很久没写Launcher分析的文章,最近实在太忙.今天七夕本来是想陪女朋友逛街 ,碰巧打台风呆在家里,就继续写一篇文章.今天主要是讲一下Launcher里面的Widget列表,这方面信息比较多,今天重 ...

随机推荐

  1. 从字符集发展史看Unicode和UTF-8的区别

    从字符集发展史看Unicode和UTF-8的区别 版权声明 本文并非本人原创,其内容来源于网络,本文根据其演绎而来,具体出出已经无法考证,在这里只好给出我所参考的连接. 知乎 https://www. ...

  2. 洛谷.3374.[模板]树状数组1(CDQ分治)

    题目链接 简易CDQ分治教程 //每个操作分解为一个有序数对(t,p),即(时间,操作位置),时间默认有序,用CDQ分治处理第二维 //对于位置相同的操作 修改优先于查询 //时间是默认有序的 所以可 ...

  3. 洛谷.4008.[NOI2003]editor文本编辑器(块状链表)

    题目链接 st(n)表示sqrt(n) 为使块状链表不会退化,通常将每块的大小S维持在[st(n)/2,2st(n)]中,这样块数C也一定[st(n)/2,2st(n)]中 在此使用另一种方法(方便) ...

  4. 2016年3月12日Android学习笔记

    1. //此句不能忘,否则onFling左右滑动不起作用 mLlExamView.setLongClickable(true); mLlExamView.setOnTouchListener(new ...

  5. java中线程安全的map是ConcurrentHashMap

    原理:http://www.cnblogs.com/ITtangtang/p/3948786.html 与hashtable的区别:  http://blog.csdn.net/songfeihu08 ...

  6. 随机查出满足条件的5条数据(tp5)

    随机查出满足条件的5条数据 public function showQuestion() { $data[; $data[ $data['level'] = (int)$data['level']; ...

  7. spring源码分析系列 (8) FactoryBean工厂类机制

    更多文章点击--spring源码分析系列 1.FactoryBean设计目的以及使用 2.FactoryBean工厂类机制运行机制分析 1.FactoryBean设计目的以及使用 FactoryBea ...

  8. Android 创建单独的服务运行在后台(无界面)

    转自:https://blog.csdn.net/a704225995/article/details/56481934 今天项目有个需求是,开启一个服务单独运行在后台,而且还不能有界面,在度娘搜索了 ...

  9. Queue depth

    Queue depth - It is the number of I/O requests that can be kept waiting to be serviced in a port que ...

  10. 微信小程序 多个视频播放器

    大致思路就是,wx:for="{{ list }}"下两个view,一个视频video,另一个封面image(客户需求,要可以自定义封面).主要控制变量是playIndex,当点击 ...