最近开始接手Launcher模块,为了更好的技术积累,也看到很多大神在CSDN上发的博文,就有了在CSDN写博客的想法,这篇博文是我在研究了一段时间Launcher3后写的,可能有不对的,望大家拍砖。首先我们可以先参考这篇http://blog.csdn.net/yanbober/article/details/50525559博文,这篇博文介绍了launcher的代码主流程框架,这里我直接贴代码分析。
Launcher首次启动是通过ActivityManagerService.systemReady方法启动的,在Android系统启动中会启动system_server进程,在SystemServer.java中可以找到调用ActivityManagerService.systemReady的地方,来看一下ActivityManagerService中的systemReady方法
public void systemReady(final Runnable goingCallback) {
...//省略
startHomeActivityLocked(currentUserId, "systemReady");
...
}
继续看startHomeActivityLocked()方法
boolean startHomeActivityLocked(int userId, String reason) {
if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL
&& mTopAction == null) {
// We are running in factory test mode, but unable to find
// the factory test app, so just sit around displaying the
// error message and don't try to start anything.
return false;
}
Intent intent = getHomeIntent();//这里得到Launcher的Intent
ActivityInfo aInfo = resolveActivityInfo(intent, STOCK_PM_FLAGS, userId);
if (aInfo != null) {
intent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name));
// Don't do this if the home app is currently being
// instrumented.
aInfo = new ActivityInfo(aInfo);
aInfo.applicationInfo = getAppInfoForUser(aInfo.applicationInfo, userId);
ProcessRecord app = getProcessRecordLocked(aInfo.processName,
aInfo.applicationInfo.uid, true);
if (app == null || app.instrumentationClass == null) {
intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
//这里就会启动
mActivityStarter.startHomeActivityLocked(intent, aInfo, reason);
}
/// M: PerfService for recording the last pause activity information. @{
if (mStackSupervisor.mLastResumedActivity.packageName == null ||
mStackSupervisor.isUpdatedLastActivityWhenStartHome(aInfo.packageName,
aInfo.name)) {
mStackSupervisor.mLastResumedActivity.packageName = aInfo.packageName;
mStackSupervisor.mLastResumedActivity.activityName = aInfo.name;
mStackSupervisor.mLastResumedActivity.activityType =
ActivityRecord.HOME_ACTIVITY_TYPE;
}
/// @}
} else {
Slog.wtf(TAG, "No home screen found for " + intent, new Throwable());
}
return true;
}
来看下getHomeIntent()方法
Intent getHomeIntent() {
Intent intent = new Intent(mTopAction, mTopData != null ? Uri.parse(mTopData) : null);
intent.setComponent(mTopComponent);
intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
if (mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
intent.addCategory(Intent.CATEGORY_HOME);//CATEGORY_HOME
}
return intent;
}
在Launcher3中的AndroidManifest.xml中有注册这个Category的activity
<activity
android:name="com.android.launcher3.Launcher"
android:launchMode="singleTask"
android:clearTaskOnLaunch="true"
android:stateNotNeeded="true"
android:theme="@style/Theme"
android:configChanges="mcc|mnc"
android:windowSoftInputMode="adjustPan"
android:screenOrientation="portrait"
android:resumeWhilePausing="true"
android:taskAffinity=""
android:enabled="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.MONKEY"/>
intent-filter>
activity>
简单分析完了Launcher是怎么被启动的,现在就来看Launcher本身的启动和加载流程,现在来看Launcher.java的onCreate方法,我在一些地方加了注释
@Override
protected void onCreate(Bundle savedInstanceState) {
/*这两行初始化LauncherAppState,这个单例对象,会注册应用安装、卸载、更新,配置变化等广播,同时会
初始化LauncherModel,里面有一个内部类LoaderTask用来获取数据,初始化桌面*/
LauncherAppState.setApplicationContext(getApplicationContext());
LauncherAppState app = LauncherAppState.getInstance();
/*new InvariantDeviceProfile对象,从名字看意思是不变的设备相关参数存储类,里面会初始化管理横
竖屏的两个DeviceProfile对象*/
app.RenewInvariantDeviceProfile();
//获得横竖屏的DeviceProfile对象
mDeviceProfile = getResources().getConfiguration().orientation
== Configuration.ORIENTATION_LANDSCAPE ?
app.getInvariantDeviceProfile().landscapeProfile
: app.getInvariantDeviceProfile().portraitProfile;
mModel = app.setLauncher(this);//获取在LauncherAppState中new LauncherModel对象
mIconCache = app.getIconCache();//桌面图标缓存类
mDragController = new DragController(this);//拖拽控制类
mInflater = getLayoutInflater();
mStateTransitionAnimation = new LauncherStateTransitionAnimation(this);//动画管理类
mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);//widget管理类
mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
mAppWidgetHost.startListening();
setupViews();//初始化布局控件
mDeviceProfile.layout(this);//根据设备配置调整布局
//调用mModel.startLoader方法开始加载异步加载桌面的数据,如app,folder,widget等
if (!mRestoring) {
if (DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE) {
// If the user leaves launcher, then we should just load items asynchronously when
// they return.
mModel.startLoader(PagedView.INVALID_RESTORE_PAGE);
} else {
// We only load the page synchronously if the user rotates (or triggers a
// configuration change) while launcher is in the foreground
mModel.startLoader(mWorkspace.getRestorePage());
}
}
//是否启动向导界面
if (shouldShowIntroScreen()) {
showIntroScreen();
} else {
showFirstRunActivity();
showFirstRunClings();
}
以上就是Launcher.java中onCreate方法中的部分代码,以上很多地方都用到了LauncherAppState这个单例对象,现在主要看这个类的构造方法,这个构造方法会初始化很多关键对象
private LauncherAppState() {
if (sContext == null) {
throw new IllegalStateException("LauncherAppState inited before app context set");
}
Log.v(Launcher.TAG, "LauncherAppState inited");
if (sContext.getResources().getBoolean(R.bool.debug_memory_enabled)) {
MemoryTracker.startTrackingMe(sContext, "L");
}
mInvariantDeviceProfile = new InvariantDeviceProfile(sContext);
mIconCache = new IconCache(sContext, mInvariantDeviceProfile);//存放Icon的对象
mWidgetCache = new WidgetPreviewLoader(sContext, mIconCache);
mAppFilter = AppFilter.loadByName(sContext.getString(R.string.app_filter_class));
mBuildInfo = BuildInfo.loadByName(sContext.getString(R.string.build_info_class));
mModel = new LauncherModel(this, mIconCache, mAppFilter);//LauncherModel加载桌面的类
/*下面这行就是注册APP安装,更新,卸载的广播,LauncherAppsCompat.getInstance(sContext)
会根据Sdk的版本得到不同的实例,当前分析的是M版本的所以得到的是LauncherAppsCompatV16这个
单例对象,调用这个addOnAppsChangedCallback方法,调用registerForPackageIntents();
这个方法就是注册APP安装,更新,卸载的广播的*/
LauncherAppsCompat.getInstance(sContext).addOnAppsChangedCallback(mModel);
// Register intent receivers
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_LOCALE_CHANGED);
filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
// For handling managed profiles
filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED);
filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED);
if (LauncherLog.DEBUG) {
LauncherLog.d(TAG, "LauncherAppState: mIconCache = " + mIconCache + ", mModel = "
+ mModel + ", this = " + this);
}
sContext.registerReceiver(mModel, filter);
UserManagerCompat.getInstance(sContext).enableAndResetCache();
mPowerManager = (PowerManager) sContext.getSystemService(Context.POWER_SERVICE);
}
桌面其实就是手机中的APP的入口,放的就是APP的快捷方式,widget等,所有首先我们要获取这些信息,然后把这些信息用UI的方法显示在桌面上,但是获取这些信息是一个耗时的操作,不可能在主线程里面去操作,所以需要用异步线程的方式获取,在LauncherModel类中就有一个异步线程sWorkerThread,这个异步线程中有一个Handler sWorker = new Handler(sWorkerThread.getLooper());还有一个主线程的Handler—mHandler = new DeferredHandler();这个Handler是封装好的,上文说到onCreate中mModle.startLoader代码就是来加载桌面的,现在我们进入LauncherModel这个类去分析startLoader方法,
public void startLoader(int synchronousBindPage, int loadFlags) {
// Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
InstallShortcutReceiver.enableInstallQueue();
synchronized (mLock) {
if (DEBUG_LOADERS) {
LauncherLog.d(TAG, "startLoader: mCallbacks = " + mCallbacks);
}
// Clear any deferred bind-runnables from the synchronized load process
// We must do this before any loading/binding is scheduled below.
synchronized (mDeferredBindRunnables) {
mDeferredBindRunnables.clear();
}
// Don't bother to start the thread if we know it's not going to do anything
if (mCallbacks != null && mCallbacks.get() != null) {
// If there is already one running, tell it to stop.
stopLoaderLocked();
/// M: op01, 0p02, added for top package feature, load top packages from a xml file.
AllAppsListPluginEx.loadTopPackage(mApp.getContext());
mLoaderTask = new LoaderTask(mApp.getContext(), loadFlags);
if (LauncherLog.DEBUG) {
LauncherLog.d(TAG, "startLoader: mAllAppsLoaded = " + mAllAppsLoaded
+ ",mWorkspaceLoaded = " + mWorkspaceLoaded + ",synchronousBindPage = "
+ synchronousBindPage + ",mIsLoaderTaskRunning = "
+ mIsLoaderTaskRunning + ",mLoaderTask = " + mLoaderTask);
}
if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
&& mAllAppsLoaded && mWorkspaceLoaded && !mIsLoaderTaskRunning) {
mLoaderTask.runBindSynchronousPage(synchronousBindPage);
} else {
sWorkerThread.setPriority(Thread.NORM_PRIORITY);
/*通过分析,第一次加载最后会走到这里,sWorker上面已经说过异步线程的Handler,
执行mLoaderTask这个Runnable,继续看mLoaderTask这个线程的run()方法*/
sWorker.post(mLoaderTask);
}
}
}
}
public void run() {
synchronized (mLock) {
if (DEBUG_LOADERS) {
LauncherLog.d(TAG, "Set load task running flag >>>>, mIsLaunching = " +
",this = " + this);
}
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: {
if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
loadAndBindWorkspace();//看名字,应该就知道这个就是来加载和bind
if (mStopped) {
LauncherLog.i(TAG, "LoadTask break in the middle, this = " + this);
break keep_running;
}
waitForIdle();
// second step
if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
/*这个是加载所有APP,桌面最下面那一排中间的全部app按钮,国内Launcher基本上
都会屏蔽这个*/
loadAndBindAllApps();
}
继续分析loadAndBindWorkspace()这个方法;
private void loadAndBindWorkspace() {
mIsLoadingAndBindingWorkspace = true;//更新状态
// Load the workspace
if (DEBUG_LOADERS) {
Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
}
if (!mWorkspaceLoaded) {
loadWorkspace();//这个方法就是去加载app等信息
synchronized (LoaderTask.this) {
if (mStopped) {
LauncherLog.d(TAG, "loadAndBindWorkspace returned by stop flag.");
return;
}
mWorkspaceLoaded = true;
}
}
// Bind the workspace
bindWorkspace(-1);//bind,就是创建视图view,更新UI
}
先看loadWorkspace()这个方法,这个方法代码很多,就不全部贴出来,只分析里面的一些关键性代码
private void loadWorkspace() {
...
if ((mFlags & LOADER_FLAG_MIGRATE_SHORTCUTS) != 0) {
// append the user's Launcher2 shortcuts
Launcher.addDumpLog(TAG, "loadWorkspace: migrating from launcher2", true);
LauncherAppState.getLauncherProvider().migrateLauncher2Shortcuts();
} else {
// Make sure the default workspace is loaded
Launcher.addDumpLog(TAG, "loadWorkspace: loading default favorites", false);
/*如果数据库中没有数据,就从默认的defaultxxx.xml中解析出布局文件,并存入数据库
favorites表,存的就是所有的app,widget,folder(文件夹)信息,如位置,icon
title等,还有workspacescreens表,存的就是桌面每屏的id和顺序
下面的操作都会去查询这些数据库*/
LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary();
}
synchronized (sBgLock) {
clearSBgDataStructures();
final HashMap installingPkgs = PackageInstallerCompat
.getInstance(mContext).updateAndGetActiveSessionCache();
//从数据库获取屏幕的信息,比如屏幕的个数
sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
//查询Favorites表
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;
container = c.getInt(containerIndex);
switch (itemType) {
//app的类型,就是桌面的每一个APP图标
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
id = c.getLong(idIndex);
intentDescription = c.getString(intentIndex);
serialNumber = c.getInt(profileIdIndex);
user = allUsers.get(serialNumber);
int promiseType = c.getInt(restoredIndex);
int disabledState = 0;
boolean itemReplaced = false;
if (user == null) {
// User has been deleted remove the item.
itemsToRemove.add(id);
continue;
}
try {
intent = Intent.parseUri(intentDescription, 0);
ComponentName cn = intent.getComponent();
if (cn != null && cn.getPackageName() != null) {
//判断APP是否可用
boolean validPkg = launcherApps.isPackageEnabledForProfile(
cn.getPackageName(), user);
boolean validComponent = validPkg &&
launcherApps.isActivityEnabledForProfile(cn, user);
if (validComponent) {
if (restored) {
// no special handling necessary for this item
restoredRows.add(id);
restored = false;
...
} else if (itemType ==
LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
//得到app的信息,对应一个ShortcutInfo
info = getAppShortcutInfo(manager, intent, user, context, c,
cursorIconInfo.iconIndex, titleIndex,
allowMissingTarget, useLowResIcon);
} else {
info = getShortcutInfo(c, context, titleIndex, cursorIconInfo);
// App shortcuts that used to be automatically added to Launcher
// didn't always have the correct intent flags set, so do that
// here
if (intent.getAction() != null &&
intent.getCategories() != null &&
intent.getAction().equals(Intent.ACTION_MAIN) &&
intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
intent.addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
}
}
if (info != null) {
info.id = id;
info.intent = intent;
info.container = container;
info.screenId = c.getInt(screenIndex);
info.cellX = c.getInt(cellXIndex);
info.cellY = c.getInt(cellYIndex);
info.rank = c.getInt(rankIndex);
info.spanX = 1;
info.spanY = 1;
info.intent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber);
if (info.promisedIntent != null) {
info.promisedIntent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber);
}
info.isDisabled = disabledState;
if (isSafeMode && !Utilities.isSystemApp(context, intent)) {
info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SAFEMODE;
}
// check & update map of what's occupied
if (!checkItemPlacement(occupied, info, sBgWorkspaceScreens)) {
itemsToRemove.add(id);
break;
}
if (restored) {
ComponentName cn = info.getTargetComponent();
if (cn != null) {
Integer progress = installingPkgs.get(cn.getPackageName());
if (progress != null) {
info.setInstallProgress(progress);
} else {
info.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
}
}
}
switch (container) {
//DESKTOP就是桌面中可滑动的部分
case LauncherSettings.Favorites.CONTAINER_DESKTOP:
//HOTSEAT就是桌面底部放4个常用app的地方
case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
sBgWorkspaceItems.add(info);//放入这个集合里面
break;
default:
// Item is in a user folder
FolderInfo folderInfo =
findOrMakeFolder(sBgFolders, container);
folderInfo.add(info);
break;
}
sBgItemsIdMap.put(info.id, info);//文件夹中的app
} else {
throw new RuntimeException("Unexpected null ShortcutInfo");
}
break;
//文件夹类型,放在sBgFolders这个集合中
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
id = c.getLong(idIndex);
FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
....
//widget类型
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
// Read all Launcher-specific widget details
boolean customWidget = itemType ==
LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
}
以上这些都是从数据库中将各种类型的信息读取出来,如app,folder,widget等,然后放在不同的集合里面,
sBgWorkspaceItems—–这里面放的所有桌面的item
sBgFolders—–这里面放的folder
sBgAppWidgets—–这里面放的widget
分析完了,加载信息的过程,那这些app,folder,widget是怎么显示到桌面上的呢,现在我们就来分析,在上面的loadAndBindWorkspace()方法中,执行完上面的loadworkspace后,就会执行bindworkspace方法,这个方法就把这些显示到桌面的的,继续分析代码
private void bindWorkspace(int synchronizeBindPage) {
...
// Save a copy of all the bg-thread collections
ArrayList workspaceItems = new ArrayList();
ArrayList appWidgets =
new ArrayList();
ArrayList orderedScreenIds = new ArrayList();
final LongArrayMap folders;
final LongArrayMap itemsIdMap;
//复制一份上面获取到的各种信息集合
synchronized (sBgLock) {
workspaceItems.addAll(sBgWorkspaceItems);
appWidgets.addAll(sBgAppWidgets);
orderedScreenIds.addAll(sBgWorkspaceScreens);
folders = sBgFolders.clone();
itemsIdMap = sBgItemsIdMap.clone();
}
...
ArrayList currentWorkspaceItems = new ArrayList();
ArrayList otherWorkspaceItems = new ArrayList();
ArrayList currentAppWidgets =
new ArrayList();
ArrayList otherAppWidgets =
new ArrayList();
LongArrayMap currentFolders = new LongArrayMap<>();
LongArrayMap otherFolders = new LongArrayMap<>();
...
//过滤当前页面的item和其他页面的
filterCurrentWorkspaceItems(tempCurrentScreen, workspaceItems, currentWorkspaceItems,
otherWorkspaceItems);
filterCurrentAppWidgets(tempCurrentScreen, appWidgets, currentAppWidgets,
otherAppWidgets);
filterCurrentFolders(tempCurrentScreen, itemsIdMap, folders, currentFolders,
otherFolders);
...
// Tell the workspace that we're about to start binding items
r = new Runnable() {
public void run() {
//这个回调一直会用到,Luancher.java实现了这个回调接口,
Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
//实际调用Launcher.java中的startbinding方法,做一些准备操作
callbacks.startBinding();
}
}
};
runOnMainThread(r);//在主线程中运行这个runnable
//创建桌面屏的个数
bindWorkspaceScreens(oldCallbacks, orderedScreenIds);
// Load items on the current page
//创建当前桌面的图标
bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets,
currentFolders, null);
...
//绑定其他桌面的图标
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();
}
mIsLoadingAndBindingWorkspace = false;
// Run all the bind complete runnables after workspace is bound.
if (!mBindCompleteRunnables.isEmpty()) {
synchronized (mBindCompleteRunnables) {
for (final Runnable r : mBindCompleteRunnables) {
runOnWorkerThread(r);
}
mBindCompleteRunnables.clear();
}
}
// 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");
}
}
};
if (isLoadingSynchronously) {
synchronized (mDeferredBindRunnables) {
mDeferredBindRunnables.add(r);
}
} else {
runOnMainThread(r);//在主线程运行
}
}
上面这个流程基本上是很条理清晰的,我们来看一下runOnMainThread这个方法,在Activiy中内部会自带这个方法,但是在这个LauncherModel内是怎么实现的呢,其实就是用我们上面提到的主线程的mHander的post方法
@Thunk void runOnMainThread(Runnable r) {
if (sWorkerThread.getThreadId() == Process.myTid()) {
// If we are on the worker thread, post onto the main handler
mHandler.post(r);
} else {
r.run();
}
}
上面说过,Luancher.java实现了Callback这个回调接口,那我们现在就来分析Luancher是这么实现这些回调的,看Luancher.java的代码
public void startBinding() {
setWorkspaceLoading(true);
if (LauncherLog.DEBUG) {
LauncherLog.d(TAG, "startBinding: this = " + this);
}
// If we're starting binding all over again, clear any bind calls we'd postponed in
// the past (see waitUntilResume) -- we don't need them since we're starting binding
// from scratch again
//开始bind前,做一些清除操作
mBindOnResumeCallbacks.clear();
// Clear the workspace because it's going to be rebound
mWorkspace.clearDropTargets();
mWorkspace.removeAllWorkspaceScreens();
mWidgetsToAdvance.clear();
if (mHotseat != null) {
mHotseat.resetLayout();
}
if (LauncherLog.DEBUG) {
LauncherLog.d(TAG, "startBinding: mIsLoadingWorkspace = " + mIsLoadingWorkspace);
}
}
先看bindscreens()方法
@Override
public void bindScreens(ArrayList orderedScreenIds) {
//调用这个方法,继续看这个方法
bindAddScreens(orderedScreenIds);
// If there are no screens, we need to have an empty screen
if (orderedScreenIds.size() == 0) {
mWorkspace.addExtraEmptyScreen();
}
// Create the custom content page (this call updates mDefaultScreen which calls
// setCurrentPage() so ensure that all pages are added before calling this).
if (hasCustomContentToLeft()) {
mWorkspace.createCustomContentContainer();
populateCustomContentContainer();
}
}
先来看一下桌面的界面布局,用一张图来展示,图片截取于http://blog.csdn.net/yanbober/article/details/50525559,如有侵权请告知
Workspace继承于PagedView,是一个自定义的ViewGroup,用来滑动,每一个屏幕就是一个CellLayout
理解了这些,下面的代码就很容易看懂了
@Override
public void bindAddScreens(ArrayList orderedScreenIds) {
int count = orderedScreenIds.size();
//for循环,有多少屏就add多少CellLayout,
for (int i = 0; i < count; i++) {
mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(orderedScreenIds.get(i));
}
}
继续来看binditems()方法
public void bindItems(final ArrayList shortcuts, final int start, final int end,
final boolean forceAnimateIcons) {
/*下面的这两段代码会经常用到,它的作用就是保证桌面可见即执行onresume之后才执行这些方法
如果桌面不可见,这个waitUntilResume(r)会把这个runnable放入集合中,等到执行onresume
的时候去执行这个线程*/
Runnable r = new Runnable() {
public void run() {
bindItems(shortcuts, start, end, forceAnimateIcons);
}
};
if (waitUntilResume(r)) {
return;
}
// Get the list of added shortcuts and intersect them with the set of shortcuts here
final AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
final Collection bounceAnims = new ArrayList();
final boolean animateIcons = forceAnimateIcons && canRunNewAppsAnimation();
Workspace workspace = mWorkspace;
long newShortcutsScreenId = -1;
for (int i = start; i < end; i++) {
final ItemInfo item = shortcuts.get(i);
if (LauncherLog.DEBUG) {
LauncherLog.d(TAG, "bindItems: start = " + start + ", end = " + end
+ "item = " + item + ", this = " + this);
}
// 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:
ShortcutInfo info = (ShortcutInfo) item;
view = createShortcut(info);//创建app快捷方式的view
/*
* TODO: FIX collision case
*/
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 (LauncherAppState.isDogfoodBuild()) {
throw (new RuntimeException(desc));
} else {
Log.d(TAG, desc);
}
}
}
break;
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
//创建文件夹的view
view = FolderIcon.fromXml(R.layout.folder_icon, this,
(ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
(FolderInfo) item, mIconCache);
break;
default:
throw new RuntimeException("Invalid Item Type");
}
//将view添加进workspace中
workspace.addInScreenFromBind(view, item.container, item.screenId, item.cellX,
item.cellY, 1, 1);
if (animateIcons) {
// Animate all the applications up now
view.setAlpha(0f);
view.setScaleX(0f);
view.setScaleY(0f);
bounceAnims.add(createNewAppBounceAnimation(view, i));
newShortcutsScreenId = item.screenId;
}
}
...
workspace.requestLayout();
}
bindfolders()方法
public void bindFolders(final LongArrayMap folders) {
if (LauncherLog.DEBUG) {
LauncherLog.d(TAG, "bindFolders: this = " + this);
}
Runnable r = new Runnable() {
public void run() {
bindFolders(folders);
}
};
if (waitUntilResume(r)) {
return;
}
/*并没有做什么操作,只是clone文件夹集合,因为在上面的binditems中的创建文件夹view
FolderIcon.fromXml(R.layout.folder_icon, this, (ViewGroup)已经把
打开folder的布局初始化好了,具体的大家可以去看仔细的代码*/
sFolders = folders.clone();
}
还有一个bindWidget()这个方法略微复杂,这里就不说了,代码流程差不多,具体的大家可以去看详细的代码,最后还一个收尾的finishBindingItems方法
public void finishBindingItems() {
if (LauncherLog.DEBUG) {
LauncherLog.d(TAG, "finishBindingItems: mSavedState = " + mSavedState
+ ", mSavedInstanceState = " + mSavedInstanceState + ", this = " + this);
}
Runnable r = new Runnable() {
public void run() {
finishBindingItems();
}
};
if (waitUntilResume(r)) {
return;
}
if (mSavedState != null) {
if (!mWorkspace.hasFocus()) {
View view = mWorkspace.getChildAt(mWorkspace.getCurrentPage());
if (view != null) {
view.requestFocus();
}
}
mSavedState = null;
}
mWorkspace.restoreInstanceStateForRemainingPages();
setWorkspaceLoading(false);//设置状态
sendLoadingCompleteBroadcastIfNecessary();//launcher是否首次启动,保存状态
// If we received the result of any pending adds while the loader was running (e.g. the
// widget configuration forced an orientation change), process them now.
if (sPendingAddItem != null) {
final long screenId = completeAdd(sPendingAddItem);
// TODO: this moves the user to the page where the pending item was added. Ideally,
// the screen would be guaranteed to exist after bind, and the page would be set through
// the workspace restore process.
mWorkspace.post(new Runnable() {
@Override
public void run() {
mWorkspace.snapToScreenId(screenId);
}
});
sPendingAddItem = null;
}
InstallShortcutReceiver.disableAndFlushInstallQueue(this);
if (mLauncherCallbacks != null) {
mLauncherCallbacks.finishBindingItems(false);
}
}
以上这些就是Launcher界面的主要加载流程,里面还有很多的细节都没有讲,比如workspace,celllayout怎么添加这些view,还有桌面的滑动snap,拖拽drag流程,还有Launcher的各种模式,文件夹等。后面有时间会继续分析。
这是我第一次发博客,里面难免会有些错误或没考虑的问题,希望大家指出