Aosp launcher3源码随着Android大版本的迭代,也在不断的更新,大体功能变化不大,但却在不断的重构和优化代码,代码的封装和扩展性变得越来越好,作为launcher开发者,也要紧跟步伐去学习,把好的实现及时运用到实际开发中去。本文将基于最新的Android P版本的launcher3,分析下这个流程。
这几个类可以算是launcher主要的框架类,熟悉了它们,对launcher就基本有个大概了解了:
- Launcher:launcher主界面,是一个Activity,开机后由系统自动启动。
- LauncherAppState:这是一个单例类,其中初始化了基本所有涉及的重要对象,包括LauncherModel
- LauncherModel:这是workspace数据加载最核心的一个类,负责workspace数据的加载和更新,并维护了所有缓存,还负责监听package的各种变化。
- LoaderResults:该类是Launcher3 P版本新增的,把之前绑定view的操作都封装了起来。
- LauncherProvider:封装了workspace相关的数据库操作,外部通过该provider调用操作
- BgDataModel:该类是Launcher3 O版本新增的,对memory缓存作了封装,缓存了所有app、shortcut、folder、widget、screen等数据
- Workspace:这就是桌面上网格显示应用图标的View,可以左右滑动,显示多页。
private LauncherModel mModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
......
LauncherAppState app = LauncherAppState.getInstance(this);
mModel = app.setLauncher(this);
mModel.startLoader(currentScreen);//开始加载数据
......
}
onCreate中调用LauncherAppState.getInstance会创建LauncherAppState单例,在LauncherAppState构造函数中,做了很多初始化操作,其中一个就是实例化LauncherModel,并且会持有它的引用。然后使用mModel对象调用startLoader开始加载workspace和all apps数据,本文主要讨论workspace的数据加载。
LauncherAppState构造函数如下:
private final LauncherModel mModel;
private LauncherAppState(Context context) {
......
mModel = new LauncherModel(this, mIconCache, AppFilter.newInstance(mContext));
}
LauncherModel只在此处实例化了一次,它实际上也是一个单实例的存在。
public boolean startLoader(int synchronousBindPage) {
......
LoaderResults loaderResults = new LoaderResults(mApp, sBgDataModel,
mBgAllAppsList, synchronousBindPage, mCallbacks);
startLoaderForResults(loaderResults);
}
public void startLoaderForResults(LoaderResults results) {
synchronized (mLock) {
stopLoader();
mLoaderTask = new LoaderTask(mApp, mBgAllAppsList, sBgDataModel, results);
runOnWorkerThread(mLoaderTask);//在子线程加载数据
}
}
LoaderTask实现了Runnable接口,封装了加载数据的操作,作为一个任务被加入线程队列。
run方法实现如下:
public void run() {
......
try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
TraceHelper.partitionSection(TAG, "step 1.1: loading workspace");
loadWorkspace();//加载workspace
verifyNotStopped();
TraceHelper.partitionSection(TAG, "step 1.2: bind workspace workspace");
mResults.bindWorkspace();//绑定workspace
// Notify the installer packages of packages with active installs on the first screen.
TraceHelper.partitionSection(TAG, "step 1.3: send first screen broadcast");
sendFirstScreenActiveInstallsBroadcast();
// Take a break
TraceHelper.partitionSection(TAG, "step 1 completed, wait for idle");
waitForIdle();
verifyNotStopped();
// second step
TraceHelper.partitionSection(TAG, "step 2.1: loading all apps");
loadAllApps();//加载all apps
TraceHelper.partitionSection(TAG, "step 2.2: Binding all apps");
verifyNotStopped();
mResults.bindAllApps();//绑定app apps数据到界面
verifyNotStopped();
TraceHelper.partitionSection(TAG, "step 2.3: Update icon cache");
updateIconCache();
// Take a break
TraceHelper.partitionSection(TAG, "step 2 completed, wait for idle");
waitForIdle();
verifyNotStopped();
// third step
TraceHelper.partitionSection(TAG, "step 3.1: loading deep shortcuts");
loadDeepShortcuts();
verifyNotStopped();
TraceHelper.partitionSection(TAG, "step 3.2: bind deep shortcuts");
mResults.bindDeepShortcuts();
// Take a break
TraceHelper.partitionSection(TAG, "step 3 completed, wait for idle");
waitForIdle();
verifyNotStopped();
// fourth step
TraceHelper.partitionSection(TAG, "step 4.1: loading widgets");
mBgDataModel.widgetsModel.update(mApp, null);
verifyNotStopped();
TraceHelper.partitionSection(TAG, "step 4.2: Binding widgets");
mResults.bindWidgets();
transaction.commit();
} catch (CancellationException e) {
// Loader stopped, ignore
TraceHelper.partitionSection(TAG, "Cancelled");
}
......
}
run方法中依次loadWorkspace、bindWorkspace、loadAllApps、bindAllApps等直至所有数据加载完毕。
这里主要分析下Workspace的加载过程:
private void loadWorkspace() {
......
Log.d(TAG, "loadWorkspace: loading default favorites");
LauncherSettings.Settings.call(contentResolver,
LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES);
synchronized (mBgDataModel) {
mBgDataModel.clear();
......
// 这边LoaderCursor对Cursor操作做了封装
final LoaderCursor c = new LoaderCursor(contentResolver.query(
LauncherSettings.Favorites.CONTENT_URI, null, null, null, null), mApp);
}
}
loadWorkspace方法主要做了两件事情,一件是加载默认配置xml文件中的items,并把默认数据存入数据库,另一件是加载数据库中的数据到缓存,当然还会做各种有效性校验,这里不做具体分析。
此处加载默认配置到数据库是调用LauncherProvider中call方法实现的,代码如下:
@Override
public Bundle call(String method, final String arg, final Bundle extras) {
......
switch (method) {
......
case LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES: {
loadDefaultFavoritesIfNecessary();
return null;
}
}
}
LauncherProvide的call方法,又调用了loadDefaultFavoritesIfNecessary去加载默认配置,代码如下:
/**
* Loads the default workspace based on the following priority scheme:
* 1) From the app restrictions
* 2) From a package provided by play store
* 3) From a partner configuration APK, already in the system image
* 4) The default configuration for the particular device
*/
synchronized private void loadDefaultFavoritesIfNecessary() {
SharedPreferences sp = Utilities.getPrefs(getContext());
if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) {
Log.d(TAG, "loading default workspace");
AppWidgetHost widgetHost = mOpenHelper.newLauncherWidgetHost();
AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction(widgetHost);
if (loader == null) {
loader = AutoInstallsLayout.get(getContext(),widgetHost, mOpenHelper);
}
if (loader == null) {
final Partner partner = Partner.get(getContext().getPackageManager());
if (partner != null && partner.hasDefaultLayout()) {
final Resources partnerRes = partner.getResources();
int workspaceResId = partnerRes.getIdentifier(Partner.RES_DEFAULT_LAYOUT,
"xml", partner.getPackageName());
if (workspaceResId != 0) {
loader = new DefaultLayoutParser(getContext(), widgetHost,
mOpenHelper, partnerRes, workspaceResId);
}
}
}
final boolean usingExternallyProvidedLayout = loader != null;
if (loader == null) {
loader = getDefaultLayoutParser(widgetHost);
}
// There might be some partially restored DB items, due to buggy restore logic in
// previous versions of launcher.
mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
// Populate favorites table with initial favorites
if ((mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader) <= 0)
&& usingExternallyProvidedLayout) {
// Unable to load external layout. Cleanup and load the internal layout.
mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(),
getDefaultLayoutParser(widgetHost));
}
clearFlagEmptyDbCreated();
}
}
如上面方法注释,此处会按照优先级顺序,检查4种配置是否存在。默认配置xml文件,如果是定制ROM,可以配置在系统里面,以apk的形式,或者固定路径的形式提供配置;如果是第三方Launcher,需要放在在apk里面。
android P上新增了LoaderResults类,封装了数据绑定到界面的操作,如下面的bindWorkspace,使代码结构更加合理和清晰。
public void bindWorkspace() {
Runnable r;
......
// Tell the workspace that we're about to start binding items
r = new Runnable() {
public void run() {
Callbacks callbacks = mCallbacks.get();
if (callbacks != null) {
callbacks.clearPendingBinds();
callbacks.startBinding();
}
}
};
mUiExecutor.execute(r);
// Bind workspace screens
mUiExecutor.execute(new Runnable() {
@Override
public void run() {
Callbacks callbacks = mCallbacks.get();
if (callbacks != null) {
callbacks.bindScreens(orderedScreenIds);
}
}
});
Executor mainExecutor = mUiExecutor;
// Load items on the current page.
bindWorkspaceItems(currentWorkspaceItems, currentAppWidgets, mainExecutor);
// In case of validFirstPage, only bind the first screen, and defer binding the
// remaining screens after first onDraw (and an optional the fade animation whichever
// happens later).
// This ensures that the first screen is immediately visible (eg. during rotation)
// In case of !validFirstPage, bind all pages one after other.
final Executor deferredExecutor =
validFirstPage ? new ViewOnDrawExecutor() : mainExecutor;
mainExecutor.execute(new Runnable() {
@Override
public void run() {
Callbacks callbacks = mCallbacks.get();
if (callbacks != null) {
callbacks.finishFirstPageBind(
validFirstPage ? (ViewOnDrawExecutor) deferredExecutor : null);
}
}
});
bindWorkspaceItems(otherWorkspaceItems, otherAppWidgets, deferredExecutor);
// Tell the workspace that we're done binding items
r = new Runnable() {
public void run() {
Callbacks callbacks = mCallbacks.get();
if (callbacks != null) {
callbacks.finishBindingItems();
}
}
};
deferredExecutor.execute(r);
}
Launcher实现了LauncherModel.Callbacks接口,用于回调界面显示更新,代码如下:
public interface Callbacks {
......
//这边只列出了这边分析涉及的接口,便于查看
public void bindItems(List shortcuts, boolean forceAnimateIcons);//绑定items
public void bindScreens(ArrayList orderedScreenIds);//绑定screen
public void bindAllApplications(ArrayList apps);//绑定所有应用
}
Launcher中的实现,绑定屏幕的代码如下:
@Override
public void bindScreens(ArrayList orderedScreenIds) {
// Make sure the first screen is always at the start.
if (FeatureFlags.QSB_ON_FIRST_SCREEN &&
orderedScreenIds.indexOf(Workspace.FIRST_SCREEN_ID) != 0) {
orderedScreenIds.remove(Workspace.FIRST_SCREEN_ID);
orderedScreenIds.add(0, Workspace.FIRST_SCREEN_ID);
LauncherModel.updateWorkspaceScreenOrder(this, orderedScreenIds);
} else if (!FeatureFlags.QSB_ON_FIRST_SCREEN && orderedScreenIds.isEmpty()) {
// If there are no screens, we need to have an empty screen
mWorkspace.addExtraEmptyScreen();
}
bindAddScreens(orderedScreenIds);
// After we have added all the screens, if the wallpaper was locked to the default state,
// then notify to indicate that it can be released and a proper wallpaper offset can be
// computed before the next layout
mWorkspace.unlockWallpaperFromDefaultPageOnNextLayout();
}
bindAddScreens会根据实际计算所得的屏幕个数,创建屏幕的View,launcher中每一屏对应的View是一个CellLayout。
绑定Item的代码如下:
@Override
public void bindItems(final List items, final boolean forceAnimateIcons) {
......
int end = items.size();
for (int i = 0; i < end; i++) {
final ItemInfo item = items.get(i);
// Short circuit if we are loading dock items for a configuration which has no dock
if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
mHotseat == null) {
continue;
}
final View view;
switch (item.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
ShortcutInfo info = (ShortcutInfo) item;
view = createShortcut(info);
break;
}
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: {
view = FolderIcon.fromXml(R.layout.folder_icon, this,
(ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
(FolderInfo) item);
break;
}
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: {
view = inflateAppWidget((LauncherAppWidgetInfo) item);
if (view == null) {
continue;
}
break;
}
default:
throw new RuntimeException("Invalid Item Type");
}
/*
* Remove colliding items.
*/
if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
CellLayout cl = mWorkspace.getScreenWithId(item.screenId);
if (cl != null && cl.isOccupied(item.cellX, item.cellY)) {
View v = cl.getChildAt(item.cellX, item.cellY);
Object tag = v.getTag();
String desc = "Collision while binding workspace item: " + item
+ ". Collides with " + tag;
if (FeatureFlags.IS_DOGFOOD_BUILD) {
throw (new RuntimeException(desc));
} else {
Log.d(TAG, desc);
getModelWriter().deleteItemFromDatabase(item);
continue;
}
}
}
workspace.addInScreenFromBind(view, item);
}
.......
workspace.requestLayout();
}
bindItems会循环创建对于类型的View,刷新显示到桌面上,这边创建的View就是我们最终看到的应用图标、文件夹、小部件。
以上即本次分析的所有内容,主要讨论了androd P版本launcher3 workspace的加载和绑定过程,谈不上深度解析,欢迎指正。后续会继续深入,具体到一些关键细节的实现,以及开发过程中常涉及的需求修改点。