一、launhcer3桌面布局
二、launcher3主要的类
LauncherModel:
跟数据有关系,保存了桌面运行时的状态信息,也提供了读写数据库的API,他有一个内部类LoaderTask,桌面启动从数据库中读取数据并把图标和小工具添加上去的时候用的就是他。
BubblTextView:
图标都是基于他,不过奇怪的是,他是继承自TextView
DragController:
DragLayer只是一个ViewGroup,具体的拖拽的处理都放到了DragController中。
LauncherAppState:
单例模式,主要在启动的时候用,他初始化了一些对象,并且注册了广播监听器和ContentObserver。
DragView:
在拖动图标的时候跟随手指移动的View就是他。
DragSource,DropTarget:
跟拖拽相关的接口,DragSource表示图标从哪里被拖出来,DropTarget表示图标可以被拖到哪里去。
Folder:
文件夹打开时候那个view。
FolderIcon:
文件夹图标。
LauncherProvider:
数据库类,Launcher3使用了SQLite,数据库文件保存在/data/data/包名/databases/launcher.db 下,有兴趣的同学可以把这个东西拷贝出来,用SQLite的工具看看里面都是怎么保存的。
ItemInfo:
运行时保存了桌面上每个项目的信息,包括图标在第几屏,第几行第几列,高度宽度等信息,每一个ItemInfo对象都对应着数据库中的一条记录。在Launcher3源码路径下,会有很多以Info结尾的类,这些类都是ItemInfo的子类,具体代表了桌面上的某个项目。比如说FolderIcon和FolderInfo是对应的,BubbleTextView和ShortcutInfo是对应的,AppWidgetHostView和LauncherAppWidgetInfo是对应的。有了对应关系,可以这样通过view获取ItemInfo对象:
ItemInfo info = (ItemInfo)bubbletextview.getTag();
这样这里的info其实就是ShortcutInfo对象了。
LauncherProvider:
桌面信息的ContentProvider。
LauncherSettings:
存了数据库相关的常量,字段名,字段常量等等。
DatabaseHelper:
LaucherProvider的内部类,继承自SQLiteOpenHelper,数据库表的创建就是在它的onCreate方法里完成的。
三、细说ItemInfo
分几类:
- 小工具:AppWidget
- 快捷方式:应用图标
- 文件夹
而ItemInfo就是抽象出来的东西(拥有的共同点),ItemInfo 对于不同的item,有不同的子类:
- 小工具对应的是LauncherAppWidgetInfo,增加了小工具的信息
- 快捷方式对应的是ShortcutInfo,增加了启动Activity的Intent信息
- 文件夹对应的是FolderInfo,增加了文件夹是否打开的标签,文件夹内图标的信息等等
ItemInfo的成员有几个值得说说:
- container:表明图标是放在哪里的,是放在Workspace还是Hotseat,还是文件夹里面的。如果是放在Workspace上的,那么值是LauncherSettings.Favorites.CONTAINER_DESKTOP,如果是放在文件夹里面的那么container的值就是文件夹FolderInfo的id。
- cellX,cellY:表明所在屏幕的哪个位置,cellY表明第几行,cellX表明第几列。如果是小工具占用多行多列的情况,就记录他左上角的位置。
- spanX,spanY:宽度和高度,快捷方式和文件夹宽高都是1,小工具的宽高就要看具体情况了。
- title:标题,显示应用的名字,文件夹的名字,小工具的话就不需要这个属性了。
- itemType: 数据库里保存的表明这个ItemInfo具体是哪种类型的ItemInfo,启动的时候好生成具体的ItemInfo子类对象。
四、Launcher3启动流程
LauncherAppState.java
private LauncherAppState(Context context) {
mInvariantDeviceProfile = new InvariantDeviceProfile(mContext);//设备的配置信息
mIconCache = new IconCache(mContext, mInvariantDeviceProfile);
mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache);
mModel = new LauncherModel(this, mIconCache, AppFilter.newInstance(mContext)); //重点
//应用变化的回调,由LauncherModel实现接口,及时的响应数据变化
LauncherAppsCompat.getInstance(mContext).addOnAppsChangedCallback(mModel);
// Register 应用变化的广播
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
}
Launcher.java
onCreate方法做一些准备工作
protected void onCreate(Bundle savedInstanceState) {
LauncherAppState app = LauncherAppState.getInstance(this);
mModel = app.setLauncher(this); //需要重点关注
initDeviceProfile(app.getInvariantDeviceProfile());
//第一次启动后的引导界面,通过sharedprefernce,keyvalue形式存储到xml文件中,初始化一些对象
mSharedPrefs = Utilities.getPrefs(this);
mIconCache = app.getIconCache();
mDragController = new DragController(this);
mAllAppsController = new AllAppsTransitionController(this);
mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
mAppWidgetHost = new LauncherAppWidgetHost(this);
mAppWidgetHost.startListening();
mLauncherView = LayoutInflater.from(this).inflate(R.layout.launcher, null);
//findviewbyid,通过setupViews()方法把view对象和之前初始化的DragController等东西结合起来
setupViews();
//重点关注mModel.startLoader
if (!mModel.startLoader(currentScreen)) {
if (!internalStateHandled) {
// If we are not binding synchronously, show a fade in animation when the first page bind completes.
mDragLayer.getAlphaProperty(ALPHA_INDEX_LAUNCHER_LOAD).setValue(0);
}
} else {
// Pages bound synchronously.
mWorkspace.setCurrentPage(currentScreen);
setWorkspaceLoading(true);
}
setContentView(mLauncherView);//设置布局
}
LauncherModel.java
public boolean startLoader(int synchronousBindPage) {
// Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_LOADER_RUNNING);
synchronized (mLock) {
// Don't bother to start the thread if we know it's not going to do anything
if (mCallbacks != null && mCallbacks.get() != null) {
final Callbacks oldCallbacks = mCallbacks.get();
// Clear any pending bind-runnables from the synchronized load process.
mUiExecutor.execute(oldCallbacks::clearPendingBinds);
// If there is already one running, tell it to stop.
stopLoader();
LoaderResults loaderResults = new LoaderResults(mApp, sBgDataModel,
mBgAllAppsList, synchronousBindPage, mCallbacks);
if (mModelLoaded && !mIsLoaderTaskRunning) {
// Divide the set of loaded items into those that we are binding synchronously,
// and everything else that is to be bound normally (asynchronously).
loaderResults.bindWorkspace();
// For now, continue posting the binding of AllApps as there are other
// issues that arise from that.
loaderResults.bindAllApps();
loaderResults.bindDeepShortcuts();
loaderResults.bindWidgets();
return true;
} else {
startLoaderForResults(loaderResults);
}
}
}
return false;
}
public void startLoaderForResults(LoaderResults results) {
synchronized (mLock) {
stopLoader();
mLoaderTask = new LoaderTask(mApp, mBgAllAppsList, sBgDataModel, results);
//把LoaderTask对象放到了工作线程中,耗时操作
runOnWorkerThread(mLoaderTask);
}
}
sWorker是一个Handler,可以传递一个Runnable对象,并在相关联的线程中执行。sWorker对应的线程是sWorkerThread,sWorkerThread在LauncherModel类加载的时候就已经开始运行了。
private static void runOnWorkerThread(Runnable r) {
if (sWorkerThread.getThreadId() == Process.myTid()) {
r.run();
} else {
// If we are not on the worker thread, then post to the worker handler
sWorker.post(r);
}
}
LoadTask.java
public void run() {
//step 1.1: loading workspace 数据层,从数据库中读取所有iteminfo生成对象
loadWorkspace();
//step 1.2: bind workspace workspace UI层,加载图标等显示到cellLayout上
mResults.bindWorkspace();
// step 1.3: send first screen broadcast
sendFirstScreenActiveInstallsBroadcast();
loadAllApps();
updateIconCache();
loadDeepShortcuts();
}
loadWorkspace():代码很多,做的事就是遍历数据库里的记录,判断类型,生成对应的itemInfo对象(ShortcutInfo,FolderInfo,LauncherAppWidgetInfo)
LoaderResults.java
public void bindWorkspace() {
//重要接口,Launcher.java实现了这个接口,用于更新UI
Callbacks callbacks = mCallbacks.get();
// Load items on the current page.
bindWorkspaceItems(currentWorkspaceItems, currentAppWidgets, mainExecutor);
// Load items on other page.
bindWorkspaceItems(otherWorkspaceItems, otherAppWidgets, deferredExecutor);
}
//加载图标,小工具,文件夹
private void bindWorkspaceItems(final ArrayList<ItemInfo> workspaceItems,
final ArrayList<LauncherAppWidgetInfo> appWidgets,
final Executor executor) {
// Bind the workspace items
int N = workspaceItems.size();
for (int i = 0; i < N; i += ITEMS_CHUNK) {
final Runnable r = new Runnable() {
@Override
public void run() {
Callbacks callbacks = mCallbacks.get();
if (callbacks != null) {
//在ui线程
callbacks.bindItems(workspaceItems.subList(start, start+chunkSize), false);
}
}
};
executor.execute(r);
}
// Bind the widgets, one at a time
N = appWidgets.size();
//....类似Bind the workspace
}
Launcher.java
/**
* Bind the items start-end from the list.
* Implementation of the method from LauncherModel.Callbacks.
*/
@Override
public void bindItems(final List<ItemInfo> items, final boolean forceAnimateIcons) {
Workspace workspace = mWorkspace;
long newItemsScreenId = -1;
int end = items.size();
for (int i = 0; i < end; i++) {
final ItemInfo item = items.get(i);
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); //产生View
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");
}
workspace.addInScreenFromBind(view, item); //产生view后显示在ui上
}
workspace.requestLayout();
}
加载图标调用了Callback.bindItems方法
文件夹加载调用了Callback.bindFolders
小工具的加载调用了Callback.bindAppWidgets。
Launcher.java
// Creates a view ,return A View inflated from layoutResId.
public View createShortcut(ViewGroup parent, ShortcutInfo info) {
BubbleTextView favorite = (BubbleTextView) LayoutInflater.from(parent.getContext())
.inflate(R.layout.app_icon, parent, false);
favorite.applyFromShortcutInfo(info); //设置图片和标题
favorite.setOnClickListener(ItemClickHandler.INSTANCE);
favorite.setOnFocusChangeListener(mFocusHandler);
return favorite;
}
WorkSpace.java
public void addInScreenFromBind(View child, ItemInfo info) {
int x = info.cellX;
int y = info.cellY;
if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
int screenId = (int) info.screenId;
x = mLauncher.getHotseat().getCellXFromOrder(screenId);
y = mLauncher.getHotseat().getCellYFromOrder(screenId);
}
//其实就是addViewToCellLayout,加到了CellLayout中,与布局图对应起来了
addInScreen(child, info.container, info.screenId, x, y, info.spanX, info.spanY);
}
private void addInScreen(View child, long container, long screenId, int x, int y,
int spanX, int spanY) {
final CellLayout layout;
if (!layout.addViewToCellLayout(child, -1, childId, lp, markCellsAsOccupied)) {
// TODO: This branch occurs when the workspace is adding views
// outside of the defined grid
// maybe we should be deleting these items from the LauncherModel?
Log.e(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout");
}
//给view添加长按监听
child.setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE);
}
参考文章:
https://fookwood.com/launcher-start-process-1
https://fookwood.com/launcher-start-process-2