Launcher3源码浅析(5.1)--LauncherModel

时间:2021-07-18 04:31:53

目录

前言

  LauncherModel是一个数据模型类,从数据模型上去完整维护Launcher的数据。其主要处理的是加载数据到模型,监听数据的变化,更新数据模型和数据库。另外它有一套丰富的回调函数,当数据变更时,这些回调会被调用,主要用于更新视图。

数据加载

  首先在Launcher.java的onCreate里调用LauncherModel的startLoader方法:
mModel.startLoader
  startLoader在线程里处理数据的加载:

 ......
mLoaderTask = new LoaderTask(mApp.getContext(), isLaunching, loadFlags);
if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
&& mAllAppsLoaded && mWorkspaceLoaded) {
mLoaderTask.runBindSynchronousPage(synchronousBindPage);
} else {
sWorkerThread.setPriority(Thread.NORM_PRIORITY);
sWorker.post(mLoaderTask);
}
......

  sWorkerThread是一个HandlerThread,在一开始就初始化并启动了;sWorker是一个Handler对象,调用post方法,则实际处理加载是在LoaderTask这个Runnable里。

......
private static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
static {
sWorkerThread.start();
}
private static final Handler sWorker = new Handler(sWorkerThread.getLooper());
......

  LoaderTask的run里调用loadAndBindWorkspace()来加载Workspace的数据,调用loadAndBindAllApps()来加载抽屉里的数据,即Android设备上显示的所有应用的图标信息。接下来分别看看loadAndBindWorkspace和loadAndBindAllApps是如何加载数据的。

1.loadAndBindWorkspace
  loadAndBindWorkspace里调用loadWorkspace,最终就是在loadWorkspace()里加载数据库的数据。那么下面再来看看loadWorkspace()是怎么加载数据的。

.....
final ContentResolver contentResolver = context.getContentResolver();
.....
final Cursor c = contentResolver.query(contentUri, null, null, null, null);
.....
while (!mStopped && c.moveToNext()) {
try {
int itemType = c.getInt(itemTypeIndex);
boolean restored = 0 != c.getInt(restoredIndex);
boolean allowMissingTarget = false;

switch (itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
id = c.getLong(idIndex);
.....
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
id = c.getLong(idIndex);
FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
.....
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
// Read all Launcher-specific widget details
int appWidgetId = c.getInt(appWidgetIdIndex);
.....
.....
break;
}
} catch (Exception e) {
Launcher.addDumpLog(TAG, "Desktop items loading interrupted", e, true);
}
}

  上面截取的loadWorkspace中的部分代码,可以看到是通过ContentResolver根据URI来查询ContentProvider中提供的数据的,while循环获取Cursor里的数据,根据itemType,是快捷方式图标,还是文件夹,还是桌面插件,这三种不同的类型数据来分别处理,放到各自的全局变量里去。

2.loadAndBindAllApps
  类似loadAndBindWorkspace的处理,loadAndBindAllApps是调用loadAllApps()来最终实现数据加载。接着再来看看loadAllApps的实现。

.....
// Clear the list of apps
mBgAllAppsList.clear();
SharedPreferences prefs = mContext.getSharedPreferences(
LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE);
for (UserHandleCompat user : profiles) {
// Query for the set of apps
final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, 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, mLabelCache));
}
.....
}
.....
// Post callback on main thread
mHandler.post(new Runnable() {
public void run() {
final long bindTime = SystemClock.uptimeMillis();
final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
callbacks.bindAllApplications(added);
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");
}
}
});
.....

  从上面截取的loadAllApps的部分代码可以看到,应用图标信息的加载是通过LauncherAppsCompat的getActivityList方法实现的,跟进去可以看到最终是由LauncherApps(5.1还是5.0以前的版本没有这个对象)这个系统服务调用getActivityList来获取的。

  获取到数据之后,把数据存到全局列表mBgAllAppsList里,然后通过回调bindAllApplications把这些数据放到AppsCustomizePagedView容器里,bindAllApplications接口在Launcher.java里实现。

数据变化

  首先在LauncherApplication的onCreate方法里,初始化了LauncherAppState对象,如下:

LauncherAppState.setApplicationContext(this);
LauncherAppState.getInstance();

在LauncherAppState的构造函数里则注册了一些广播,并设置数据改变的监听,

 ......
// Register intent receivers
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_LOCALE_CHANGED);
filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
sContext.registerReceiver(mModel, filter);
filter = new IntentFilter();
filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
sContext.registerReceiver(mModel, filter);
filter = new IntentFilter();
filter.addAction(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED);
sContext.registerReceiver(mModel, filter);
// Register for changes to the favorites
ContentResolver resolver = sContext.getContentResolver();
resolver.registerContentObserver(LauncherSettings.Favorites.CONTENT_URI, true, mFavoritesObserver);
......

  LaucherModel继承自BroadcastReceiver,所以当收到上面注册的广播时,就会进行响应处理。
   再看一下监听到数据库改变时的处理:

......
/**
* Receives notifications whenever the user favorites have changed.
*/
private final ContentObserver mFavoritesObserver = new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
// If the database has ever changed, then we really need to force a reload of the
// workspace on the next load
mModel.resetLoadedState(false, true);
mModel.startLoaderFromBackground();
}
};
......

  上述代码可以看到,监听到数据改变时,会强制重新加载数据。

更新数据

  LaucherModel实现了LauncherAppsCompat.OnAppsChangedCallbackCompat接口,

public interface OnAppsChangedCallbackCompat {
void onPackageRemoved(String packageName, UserHandleCompat user);
void onPackageAdded(String packageName, UserHandleCompat user);
void onPackageChanged(String packageName, UserHandleCompat user);
void onPackagesAvailable(String[] packageNames, UserHandleCompat user, boolean replacing);
void onPackagesUnavailable(String[] packageNames, UserHandleCompat user, boolean replacing);
}

  当应用改变的时候,通过回调会调用到LaucherModel里实现的上面的接口,这些实现都调用了enqueuePackageUpdated方法:

void enqueuePackageUpdated(PackageUpdatedTask task) {
sWorker.post(task);
}

  可以看到实际的实现在PackageUpdatedTask里,PackageUpdatedTask里会相应的去更新数据库及相关数据模型。因为我也没改过这里边的东西,没有详细的了解,所以具体就不深入分析了,有兴趣的可以看看。

博客搬家:http://www.mosiliang.top/?p=158
以后更新都在自个博客上了,有兴趣的可以关注看看,谢谢!