要了解Android桌面是如何启动的,我们有必要先从进程孵化器Zygote进程谈起,Zygote作为系统启动的第一个java进程,是所有其他java层进程的父进程,它依靠fork系统调用孵化出其它进程,可以大大减少各进程初始化环境的时间,而我们的SystemServer进程就是这样孵化出来的。下面是frameworks\base\core\java\com\android\internal\os\ZygoteInit.java的main函数方法。
public static void main(String argv[]) {
try {
SamplingProfilerIntegration.start();
boolean startSystemServer = false;
String socketName = "zygote";
String abiList = null;
for (int i = 1; i < argv.length; i++) {
if ("start-system-server".equals(argv[i])) {
startSystemServer = true;
} else if (argv[i].startsWith(ABI_LIST_ARG)) {
abiList = argv[i].substring(ABI_LIST_ARG.length());
} else if (argv[i].startsWith(SOCKET_NAME_ARG)) {
socketName = argv[i].substring(SOCKET_NAME_ARG.length());
} else {
throw new RuntimeException("Unknown command line argument: " + argv[i]);
}
}
if (abiList == null) {
throw new RuntimeException("No ABI list supplied.");
}
//此处创建了一个LocalServerSocket用于接收进程孵化请求
registerZygoteSocket(socketName);
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
SystemClock.uptimeMillis());
preload();
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
SystemClock.uptimeMillis());
SamplingProfilerIntegration.writeZygoteSnapshot();
gc();
Trace.setTracingEnabled(false);
if (startSystemServer) {//此处fork出SystemServer进程,SystemServer进程理当在接收其它进程fork之前先起来
startSystemServer(abiList, socketName);
}
//此处是一个while(true)循环,内部调用socket.accept来接收进程的fork请求并孵化下一个进程。
runSelectLoop(abiList);
//除非上面死循环关掉,不然这里不会走到
closeServerSocket();
} catch (MethodAndArgsCaller caller) {
caller.run();
} catch (RuntimeException ex) {
Log.e(TAG, "Zygote died with exception", ex);
closeServerSocket();
throw ex;
}
}
上面部分源码包含两个关键信息。
1.进程的fork是通过socket通信实现。
2.SystemServer进程在此处被fork出来,由于SystemServer内部会启动系统关键性的Service服务,它会在其他java进程fork之前调用。
/**
* Prepare the arguments and fork for the system server process.
*/
private static boolean startSystemServer(String abiList, String socketName)
throws MethodAndArgsCaller, RuntimeException {
...省略
pid = Zygote.forkSystemServer(
parsedArgs.uid, parsedArgs.gid,
parsedArgs.gids,
parsedArgs.debugFlags,
null,
parsedArgs.permittedCapabilities,
parsedArgs.effectiveCapabilities);
...省略
}
此后,SystemServer进程被创建出来,他作为系统核心进程,在run方法中启动了众多的系统service服务,其中就包括我们的AMS。在run方法内部调用了startBootstrapServices()创建了ActivityManagerService。并在run内部startOtherServices中调用ActivityManagerService的systemReady方法告诉AMS系统已经就绪。
private void run() {
//...省略
createSystemContext();
// Create the system service manager.
mSystemServiceManager = new SystemServiceManager(mSystemContext);
LocalServices.addService(SystemServiceManager.class, mSystemServiceManager);
// Start services.
try {
startBootstrapServices();
startCoreServices();
startOtherServices();
} catch (Throwable ex) {
Slog.e("System", "******************************************");
Slog.e("System", "************ Failure starting system services", ex);
throw ex;
}
//...省略
}
在AMS的systemReady中我们可以看到以下关键代码:
public void systemReady(final Runnable goingCallback) {
//...省略一长串
mBooting = true;
startHomeActivityLocked(mCurrentUserId);
//...省略一长串
}
根据名字我们就可以知道,此处将要开始启动桌面应用。它通过过滤IntentFilter中包含有Intent.CATEGORY_HOME字段的Activity来resolve谁是将要启动的Launcher。
Intent getHomeIntent() {
Intent intent = new Intent(mTopAction, mTopData != null ? Uri.parse(mTopData) : null);
intent.setComponent(mTopComponent);
if (mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
intent.addCategory(Intent.CATEGORY_HOME);
}
return intent;
}
boolean startHomeActivityLocked(int userId) {
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();
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);
mStackSupervisor.startHomeActivity(intent, aInfo);
}
}
return true;
}
就这样我们的Launcher总算启动了。在Launcher的清单文件中可以发现AMS要找的Home正是Launcher.java。
<activity
android:name="com.android.launcher2.Launcher"
android:launchMode="singleTask"
android:clearTaskOnLaunch="true"
android:stateNotNeeded="true"
android:resumeWhilePausing="true"
android:theme="@style/Theme"
android:windowSoftInputMode="adjustPan"
android:screenOrientation="nosensor">
<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"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
在知道了启动的Activity之后,接下来我们就要关注Launcher是如何启动以及如何将我们的应用排列到桌面的。首先我们先从Activity的onCreate开始看。
@Override
protected void onCreate(Bundle savedInstanceState) {
if (DEBUG_STRICT_MODE) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork() // or .detectAll() for all detectable problems
.penaltyLog()//将违规操作打印到日志中
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.penaltyLog()
.penaltyDeath()//触发违规条件直接crash当前程序
.build());
}
super.onCreate(savedInstanceState);
LauncherApplication app = ((LauncherApplication)getApplication());
mSharedPrefs = getSharedPreferences(LauncherApplication.getSharedPreferencesKey(),
Context.MODE_PRIVATE);
//通过application把launcher传递给LauncherModel类
mModel = app.setLauncher(this);
mIconCache = app.getIconCache();
mDragController = new DragController(this);
mInflater = getLayoutInflater();
mAppWidgetManager = AppWidgetManager.getInstance(this);
mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
mAppWidgetHost.startListening();
mPaused = false;
if (PROFILE_STARTUP) {
android.os.Debug.startMethodTracing(
Environment.getExternalStorageDirectory() + "/launcher");
}
//检查mcc mnc locale项
checkForLocaleChange();
setContentView(R.layout.launcher);
setupViews();
//判断是否首次打开应用,如果是那么就弹出cling提示layout
showFirstRunWorkspaceCling();
//appWidget监听
registerContentObservers();
//空实现
lockAllApps();
mSavedState = savedInstanceState;
restoreState(mSavedState);
// Update customization drawer _after_ restoring the states
if (mAppsCustomizeContent != null) {
mAppsCustomizeContent.onPackagesUpdated(
LauncherModel.getSortedWidgetsAndShortcuts(this));
}
if (PROFILE_STARTUP) {
android.os.Debug.stopMethodTracing();
}
if (!mRestoring) {
if (sPausedFromUserAction) {
// 调用Launcher的startLoader方法从LauncherProvider内部的数据库中获取应用排列信息
mModel.startLoader(true, -1);//会在model中加载好info,并回调到bindItems,bindAppWidget,bindFolders
} else {
// We only load the page synchronously if the user rotates (or triggers a
// configuration change) while launcher is in the foreground
mModel.startLoader(true, mWorkspace.getCurrentPage());
}
}
//如果app没有加载完成,那么显示加载弹框
if (!mModel.isAllAppsLoaded()) {
ViewGroup appsCustomizeContentParent = (ViewGroup) mAppsCustomizeContent.getParent();
mInflater.inflate(R.layout.apps_customize_progressbar, appsCustomizeContentParent);
}
// For handling default keys
mDefaultKeySsb = new SpannableStringBuilder();
Selection.setSelection(mDefaultKeySsb, 0);
//home按键广播
IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
registerReceiver(mCloseSystemDialogsReceiver, filter);
//根据Google Search 应用是否存在来更新顶部SeartDropTargetBar的显示状态,此判断在每次onResume时也会触发
updateGlobalIcons();
// On large interfaces, we want the screen to auto-rotate based on the current orientation
unlockScreenOrientation(true);
}
上文的mModel.startLoader就是进行异步加载应用排列信息并显示到WorkSpace。mModel即LauncherModel继承自BroadCastReceiver,它在LauncherApplication中初始化,并注册了诸如LOCALE_CHANGED、CONFIGURATION_CAHNGED等广播,整个应用可以通过app.getModel获取到LauncherModel的实例。Launcher应用遵循MVC模式,Launcher负责显示控件,Model负责从LauncherProvider(继承ContentProvider,内部有一个数据库Helper持久化保存所有桌面应用图标的坐标、图片、intent等信息)加载所有的应用排列信息。接下来我们就进入到mModel的startLoader方法中一探究竟。
public void startLoader(boolean isLaunching, int synchronousBindPage) {
synchronized (mLock) {
if (DEBUG_LOADERS) {
Log.d(TAG, "startLoader isLaunching=" + isLaunching);
}
// Clear any deferred bind-runnables from the synchronized load process
// We must do this before any loading/binding is scheduled below.
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.
// also, don't downgrade isLaunching if we're already running
isLaunching = isLaunching || stopLoaderLocked();
mLoaderTask = new LoaderTask(mApp, isLaunching);
//如果当前已经完成load操作,那么就进行bi同步bind
if (synchronousBindPage > -1 && mAllAppsLoaded && mWorkspaceLoaded) {
mLoaderTask.runBindSynchronousPage(synchronousBindPage);
} else {
sWorkerThread.setPriority(Thread.NORM_PRIORITY);
sWorker.post(mLoaderTask);
}
}
}
}
从上文可以看到方法内部是使用workThread线程post了一个异步任务LoaderTask。LoaderTask继承自Runnable,以内部类的方式存在于LauncherModel中。既然是Runnable的子类,那显然我们应该先关注其内部的run方法。
public void run() {
synchronized (mLock) {
mIsLoaderTaskRunning = true;
}
keep_running: {
// Elevate priority when Home launches for the first time to avoid
// starving at boot time. Staring at a blank home is not cool.
synchronized (mLock) {
if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to " +
(mIsLaunching ? "DEFAULT" : "BACKGROUND"));
Process.setThreadPriority(mIsLaunching
? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND);
}
// First step. Load workspace first, this is necessary since adding of apps from
// managed profile in all apps is deferred until onResume. See http://b/17336902.
if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
loadAndBindWorkspace();
if (mStopped) {
break keep_running;
}
// Whew! Hard work done. Slow us down, and wait until the UI thread has
// settled down.
synchronized (mLock) {
if (mIsLaunching) {
if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to BACKGROUND");
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
}
}
waitForIdle();
// Second step. Load all apps.
if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
/*这个方法是加载和绑定app到launcher的hotseat中部的全部app按钮中,国内一般都屏蔽掉*/
loadAndBindAllApps();
// Restore the default thread priority after we are done loading items
synchronized (mLock) {
Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
}
}
// Update the saved icons if necessary
if (DEBUG_LOADERS) Log.d(TAG, "Comparing loaded icons to database icons");
synchronized (sBgLock) {
for (Object key : sBgDbIconCache.keySet()) {
updateSavedIcon(mContext, (ShortcutInfo) key, sBgDbIconCache.get(key));
}
sBgDbIconCache.clear();
}
// Clear out this reference, otherwise we end up holding it until all of the
// callback runnables are done.
mContext = null;
synchronized (mLock) {
// If we are still the last one to be scheduled, remove ourselves.
if (mLoaderTask == this) {
mLoaderTask = null;
}
mIsLoaderTaskRunning = false;
}
}
run方法内部涉及的代码并不复杂,其中loadAndBindWorkspace和loadAndBindAllApps通过字面也很好理解,无非就是在其中从数据库加载出应用排列信息,并把这些信息绑定到桌面控件WorkSpace中。这里我们只看loadAndBindWorkspace(另一个原理是一样的)。
private void loadAndBindWorkspace() {
mIsLoadingAndBindingWorkspace = true;
// Load the workspace
if (DEBUG_LOADERS) {
Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
}
if (!mWorkspaceLoaded) {
loadWorkspace();
synchronized (LoaderTask.this) {
if (mStopped) {
return;
}
mWorkspaceLoaded = true;
}
}
// Bind the workspace
bindWorkspace(-1);
}
此方法在WorkThread线程(其实就是一个HandlerThread,对HandlerThread不了解的同学可以参见
HandlerThread使用)中执行,其中loadWorkspace即是使用ContentProvider来查询数据库。以下是loadWorkspace的代码片段。
private void loadWorkspace() {
final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
final Context context = mContext;
final ContentResolver contentResolver = context.getContentResolver();
final PackageManager manager = context.getPackageManager();
final AppWidgetManager widgets = AppWidgetManager.getInstance(context);
final boolean isSafeMode = manager.isSafeMode();
// Make sure the default workspace is loaded, if needed
mApp.getLauncherProvider().loadDefaultFavoritesIfNecessary(0, false);
synchronized (sBgLock) {
sBgWorkspaceItems.clear();//管理workspace或者hotseat
sBgAppWidgets.clear();//管理widget
sBgFolders.clear();//管理文件夹
sBgItemsIdMap.clear();//所有info管理
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)
final ItemInfo occupied[][][] =
new ItemInfo[Launcher.SCREEN_COUNT + 1][mCellCountX + 1][mCellCountY + 1];
try {
//以下是得知相应的字段在哪一列中
...此处有若干获取字段Index的代码,已省略
ShortcutInfo info;
String intentDescription;
LauncherAppWidgetInfo appWidgetInfo;
int container;
long id;
Intent intent;
UserHandle user;
while (!mStopped && c.moveToNext()) {
try {
//先判断当前行的type类型
int itemType = c.getInt(itemTypeIndex);
switch (itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
...省略
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
...省略
break;
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
...省略
break;
}
} catch (Exception e) {
Log.w(TAG, "Desktop items loading interrupted:", e);
}
}
} finally {
c.close();
}
...省略
}
在while循环中不断从Cursor中查找ItemInfo并根据itemType加入到不同的list集合中,方便下一步显示。查询完数据库并将其中内容都通过Cursor实例化出来之后,接着就是将这些应用排列数据通过CallBack回调给我们的Launcher.java中显示出来。
private void bindWorkspace(int synchronizeBindPage) {
final long t = SystemClock.uptimeMillis();
Runnable r;
// Don't use these two variables in any of the callback runnables.
// Otherwise we hold a reference to them.
final Callbacks oldCallbacks = mCallbacks.get();
if (oldCallbacks == null) {
// This launcher has exited and nobody bothered to tell us. Just bail.
Log.w(TAG, "LoaderTask running with no launcher");
return;
}
final boolean isLoadingSynchronously = (synchronizeBindPage > -1);
final int currentScreen = isLoadingSynchronously ? synchronizeBindPage :
oldCallbacks.getCurrentWorkspaceScreen();
// Load all the items that are on the current page first (and in the process, unbind
// all the existing workspace items before we call startBinding() below.
//主要是在info中释放资源的操作
unbindWorkspaceItemsOnMainThread();
ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>();
ArrayList<LauncherAppWidgetInfo> appWidgets =
new ArrayList<LauncherAppWidgetInfo>();
HashMap<Long, FolderInfo> folders = new HashMap<Long, FolderInfo>();
HashMap<Long, ItemInfo> itemsIdMap = new HashMap<Long, ItemInfo>();
synchronized (sBgLock) {
workspaceItems.addAll(sBgWorkspaceItems);
appWidgets.addAll(sBgAppWidgets);
folders.putAll(sBgFolders);
itemsIdMap.putAll(sBgItemsIdMap);
}
ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<ItemInfo>();
ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<ItemInfo>();
ArrayList<LauncherAppWidgetInfo> currentAppWidgets =
new ArrayList<LauncherAppWidgetInfo>();
ArrayList<LauncherAppWidgetInfo> otherAppWidgets =
new ArrayList<LauncherAppWidgetInfo>();
HashMap<Long, FolderInfo> currentFolders = new HashMap<Long, FolderInfo>();
HashMap<Long, FolderInfo> otherFolders = new HashMap<Long, FolderInfo>();
// Separate the items that are on the current screen, and all the other remaining items
filterCurrentWorkspaceItems(currentScreen, workspaceItems, currentWorkspaceItems,
otherWorkspaceItems);
filterCurrentAppWidgets(currentScreen, appWidgets, currentAppWidgets,
otherAppWidgets);
filterCurrentFolders(currentScreen, itemsIdMap, folders, currentFolders,
otherFolders);
//对WorkSpace中的itemInfo进行排序.
sortWorkspaceItemsSpatially(currentWorkspaceItems);
sortWorkspaceItemsSpatially(otherWorkspaceItems);
//当分离工作和排序工作都完成时,需要告诉Launcher开始绑定
// Tell the workspace that we're about to start binding items
r = new Runnable() {
public void run() {
Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
callbacks.startBinding();
}
}
};
//事件都是顺序执行的所以不必担心startBinding方法会出现在bindXXX之后
runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
//先绑定当前page,然后在考虑其他page
// Load items on the current page,加载当前页面到launcher
bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets,
currentFolders, null);
if (isLoadingSynchronously) {
r = new Runnable() {
public void run() {
Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
callbacks.onPageBoundSynchronously(currentScreen);
}
}
};
runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
}
// Load all the remaining pages (if we are loading synchronously, we want to defer this
// work until after the first render)
mDeferredBindRunnables.clear();
//绑定其他页面到launcher
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();
}
// 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");
}
mIsLoadingAndBindingWorkspace = false;
}
};
if (isLoadingSynchronously) {
mDeferredBindRunnables.add(r);
} else {
runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
}
}
通过startBinding告知Launcher当前将要开始绑定操作,通过finishBindingItems通知结束bind。具体的绑定分类型回调都在bindWorkspaceItems方法中。其中区分为bindItems、bindFodlers、bindWidgets。
private void bindWorkspaceItems(final Callbacks oldCallbacks,
final ArrayList<ItemInfo> workspaceItems,
final ArrayList<LauncherAppWidgetInfo> appWidgets,
final HashMap<Long, FolderInfo> folders,
ArrayList<Runnable> deferredBindRunnables) {
final boolean postOnMainThread = (deferredBindRunnables != null);
// Bind the workspace items
int N = workspaceItems.size();
for (int i = 0; i < N; i += ITEMS_CHUNK) {
final int start = i;
final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
final Runnable r = new Runnable() {
@Override
public void run() {
Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
callbacks.bindItems(workspaceItems, start, start+chunkSize);
}
}
};
if (postOnMainThread) {
deferredBindRunnables.add(r);
} else {
runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
}
}
// Bind the folders
if (!folders.isEmpty()) {
final Runnable r = new Runnable() {
public void run() {
Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
callbacks.bindFolders(folders);
}
}
};
if (postOnMainThread) {
deferredBindRunnables.add(r);
} else {
runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
}
}
// Bind the widgets, one at a time
N = appWidgets.size();
for (int i = 0; i < N; i++) {
final LauncherAppWidgetInfo widget = appWidgets.get(i);
final Runnable r = new Runnable() {
public void run() {
Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
callbacks.bindAppWidget(widget);
}
}
};
if (postOnMainThread) {
deferredBindRunnables.add(r);
} else {
runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
}
}
}
callback即是Launcher.java,因为这些回调都需要同步UI控件显示,所以都需要在主线程执行。绑定显示的过程大同小异,区分ItemInfo的属于Workspace还是HotSeat区域,然后再判断itemInfo是哪种显示类型来add到不同的控件中。
public void bindItems(final ArrayList<ItemInfo> shortcuts, final int start, final int end) {
if (waitUntilResume(new Runnable() {
public void run() {
bindItems(shortcuts, start, end);
}
})) {
return;
}
// Get the list of added shortcuts and intersect them with the set of shortcuts here
Set<String> newApps = new HashSet<String>();
newApps = mSharedPrefs.getStringSet(InstallShortcutReceiver.NEW_APPS_LIST_KEY, newApps);
Workspace workspace = mWorkspace;
for (int i = start; i < end; i++) {
final ItemInfo item = shortcuts.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;
}
switch (item.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
ShortcutInfo info = (ShortcutInfo) item;
String uri = info.intent.toUri(0).toString();
View shortcut = createShortcut(info);
//通过bindItems方法,创造一个个BubbleTextView,然后通过workspace加载到对应celllayout中
workspace.addInScreen(shortcut, item.container, item.screen, item.cellX,
item.cellY, 1, 1, false);
boolean animateIconUp = false;
synchronized (newApps) {
if (newApps.contains(uri)) {
animateIconUp = newApps.remove(uri);
}
}
//需要动画显示的app icon,统一加到AnimationSet中一起播放
if (animateIconUp) {
// Prepare the view to be animated up
shortcut.setAlpha(0f);
shortcut.setScaleX(0f);
shortcut.setScaleY(0f);
mNewShortcutAnimatePage = item.screen;
if (!mNewShortcutAnimateViews.contains(shortcut)) {
mNewShortcutAnimateViews.add(shortcut);
}
}
break;
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
(ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
(FolderInfo) item, mIconCache);
workspace.addInScreen(newFolder, item.container, item.screen, item.cellX,
item.cellY, 1, 1, false);
break;
}
}
workspace.requestLayout();
}
可以看到,最后通过workspace.addInScreen方法把这些ItemInfo都加载到了桌面,在addInScreen中通过传入的container(分为Workspace还是HotSeat),screen(哪个CellLayout页),cellX、cellY(在CellLayout中的位置)来添加到指定的控件中
void addInScreen(View child, long container, int screen, int x, int y, int spanX, int spanY,
boolean insert) {
if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
if (screen < 0 || screen >= getChildCount()) {
Log.e(TAG, "The screen must be >= 0 and < " + getChildCount()
+ " (was " + screen + "); skipping child");
return;
}
}
final CellLayout layout;
if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
layout = mLauncher.getHotseat().getLayout();
child.setOnKeyListener(null);
// Hide folder title in the hotseat
if (child instanceof FolderIcon) {
((FolderIcon) child).setTextVisible(false);
}
if (screen < 0) {
screen = mLauncher.getHotseat().getOrderInHotseat(x, y);
} else {
// Note: We do this to ensure that the hotseat is always laid out in the orientation
// of the hotseat in order regardless of which orientation they were added
x = mLauncher.getHotseat().getCellXFromOrder(screen);
y = mLauncher.getHotseat().getCellYFromOrder(screen);
}
} else {
// Show folder title if not in the hotseat
if (child instanceof FolderIcon) {
((FolderIcon) child).setTextVisible(true);
}
layout = (CellLayout) getChildAt(screen);
child.setOnKeyListener(new IconKeyEventListener());
}
LayoutParams genericLp = child.getLayoutParams();
CellLayout.LayoutParams lp;
if (genericLp == null || !(genericLp instanceof CellLayout.LayoutParams)) {
lp = new CellLayout.LayoutParams(x, y, spanX, spanY);
} else {
lp = (CellLayout.LayoutParams) genericLp;
lp.cellX = x;
lp.cellY = y;
lp.cellHSpan = spanX;
lp.cellVSpan = spanY;
}
if (spanX < 0 && spanY < 0) {
lp.isLockedToGrid = false;
}
// Get the canonical child id to uniquely represent this view in this screen
int childId = LauncherModel.getCellLayoutChildId(container, screen, x, y, spanX, spanY);
boolean markCellsAsOccupied = !(child instanceof Folder);
if (!layout.addViewToCellLayout(child, insert ? 0 : -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.w(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout");
}
if (!(child instanceof Folder)) {
child.setHapticFeedbackEnabled(false);
child.setOnLongClickListener(mLongClickListener);
}
if (child instanceof DropTarget) {
mDragController.addDropTarget((DropTarget) child);
}
}
上面方法在确定了CellLayout之后,调用其addViewToCellLayout添加到控件上,并且把child加入可拖动控制器中,用户就可以通过长按拖拽Cell了。
好了,通过以上的分析,应该对Launcher是如何加载启动有了大概的了解。