本文的分析基於MTK提供的Android 7.0源碼,並非Google官方提供的源碼,其中可能有一些小的差異,還望諒解。
Launcher的本質就是一個普通應用,它比普通應用多配置了Category的Android:name=”android.intent.category.HOME”屬性,之後ActivityManagerService的startHomeActivityLocked方法將啓動含有這個屬性的Activity。
boolean startHomeActivityLocked(int userId) {
if(this.mHeadless) {
this.ensureBootCompleted();
return false;
} else if(this.mFactoryTest == 1 && this.mTopAction == null) {
return false;
} else {
Intent intent = new Intent(this.mTopAction, this.mTopData != null?Uri.parse(this.mTopData):null);
intent.setComponent(this.mTopComponent);
if(this.mFactoryTest != 1) {
intent.addCategory("android.intent.category.HOME");
}
ActivityInfo aInfo = intent.resolveActivityInfo(this.mContext.getPackageManager(), 1024);
if(aInfo != null) {
intent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name));
aInfo = new ActivityInfo(aInfo);
aInfo.applicationInfo = this.getAppInfoForUser(aInfo.applicationInfo, userId);
ProcessRecord app = this.getProcessRecordLocked(aInfo.processName, aInfo.applicationInfo.uid);
if(app == null || app.instrumentationClass == null) {
intent.setFlags(intent.getFlags() | 268435456);
this.mMainStack.startActivityLocked((IApplicationThread)null, intent, (String)null, aInfo, (IBinder)null, (String)null, 0, 0, 0, 0, (Bundle)null, false, (ActivityRecord[])null);
}
}
return true;
}
}
這裏貼出一篇關於ActivityManagerService啓動Activity的博文:
http://blog.csdn.net/gaugamela/article/details/53183216
接下來看看Launcher界面的劃分。Launcher3實質其實就是一個Activity包含N個自定義的View。
結合圖和佈局文件可能更好理解Launcher3的界面
<com.android.launcher3.LauncherRootView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:id="@+id/launcher"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<com.android.launcher3.DragLayer
android:id="@+id/drag_layer"
android:clipChildren="false"
android:clipToPadding="false"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.android.launcher3.FocusIndicatorView
android:id="@+id/focus_indicator"
android:layout_width="52dp"
android:layout_height="52dp" />
<!-- The workspace contains 5 screens of cells -->
<!-- DO NOT CHANGE THE ID -->
<com.android.launcher3.Workspace
android:id="@+id/workspace"
android:layout_width="match_parent"
android:layout_height="match_parent"
launcher:defaultScreen="@integer/config_workspaceDefaultScreen"
launcher:pageIndicator="@+id/page_indicator">
</com.android.launcher3.Workspace>
<!-- DO NOT CHANGE THE ID -->
<include layout="@layout/hotseat"
android:id="@+id/hotseat"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<include layout="@layout/overview_panel"
android:id="@+id/overview_panel"
android:visibility="gone" />
<!-- Keep these behind the workspace so that they are not visible when
we go into AllApps -->
<include
android:id="@+id/page_indicator"
layout="@layout/page_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" />
<include
android:id="@+id/search_drop_target_bar"
layout="@layout/search_drop_target_bar" />
<include layout="@layout/widgets_view"
android:id="@+id/widgets_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible" />
<include layout="@layout/all_apps"
android:id="@+id/apps_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible" />
</com.android.launcher3.DragLayer>
</com.android.launcher3.LauncherRootView>
下面是Launcher3中一些類的大致含義:
Launcher:主界面Activity,最核心且唯一的Activity。
LauncherAppState:單例對象,構造方法中初始化對象、註冊應用安裝、卸載、更新,配置變化等廣播。這些廣播用來實時更新桌面圖標等,其receiver的實現在LauncherModel類中,LauncherModel也在這裏初始化。
LauncherModel:數據處理類,保存桌面狀態,提供讀寫數據庫的API,內部類LoaderTask用來初始化桌面。
InvariantDeviceProfile:一些不變的設備相關參數管理類,其內部包涵了橫豎屏模式的DeviceProfile。
WidgetPreviewLoader:存儲Widget信息的數據庫,內部創建了數據庫widgetpreviews.db。
LauncherAppsCompat:獲取已安裝App列表信息的兼容抽象基類,子類依據不同版本API進行兼容性處理。
AppWidgetManagerCompat:獲取AppWidget列表的兼容抽象基類,子類依據不同版本API進行兼容性處理。
LauncherStateTransitionAnimation:各類動畫總管處理執行類,負責各種情況下的各種動畫效果處理。
IconCache:圖標緩存類,應用程序icon和title的緩存,內部類創建了數據庫app_icons.db。
LauncherProvider:核心數據庫類,負責launcher.db的創建與維護。
LauncherAppWidgetHost:AppWidgetHost子類,是桌面插件宿主,爲了方便託拽等才繼承處理的。
LauncherAppWidgetHostView:AppWidgetHostView子類,配合LauncherAppWidgetHost得到HostView。
LauncherRootView:豎屏模式下根佈局,繼承了InsettableFrameLayout,控制是否顯示在狀態欄等下面。
DragLayer:一個用來負責分發事件的ViewGroup。
DragController:DragLayer只是一個ViewGroup,具體的拖拽的處理都放到了DragController中。
BubblTextView:圖標都基於他,繼承自TextView。
DragView:拖動圖標時跟隨手指移動的View。
Folder:打開文件夾展示的View。
FolderIcon:文件夾圖標。
DragSource/DropTarget:拖拽接口,DragSource表示圖標從哪開始拖,DropTarget表示圖標被拖到哪去。
ItemInfo:桌面上每個Item的信息數據結構,包括在第幾屏、第幾行、第幾列、寬高等信息;該對象與數據庫中記錄一一對應;該類有多個子類,譬如FolderIcon的FolderInfo、BubbleTextView的ShortcutInfo等。
瞭解上面這些類後,現在來看看Launcher3的啓動流程:(小提示:若看不清圖片可將網頁放大至200%)
由於Launcher3也是一個Activity,其啓動後首先會執行onCreate()方法,從流程圖中可以看出在該方法裏會調用LauncherAppState.getInstance()方法,Launcher3的各類數據的初始化和廣播的註冊都在這裏被執行。隨後執行LauncherModel mModel = app.setLauncher(this),將當前Launcher對象的引用傳給LauncherProvider,在該方法裏調用了LauncherModel的initialize(Callbacks callbacks)方法,因爲Launcher也實現了LauncherModel.Callbacks接口,因此這裏將Launcher和LauncherModel建立了聯繫,LauncherModel中的所有操作都會通過Callbacks接口中的方法傳給Launcher。可以來看看LauncherModel.Callbacks接口。
public interface Callbacks {
//如果Launcher在加載完成之前被強制暫停,那麼需要通過這個回調方法通知Launcher
//在它再次顯示的時候重新執行加載過程
public boolean setLoadOnResume();
//獲取當前屏幕序號
public int getCurrentWorkspaceScreen();
//啓動桌面數據綁定
public void startBinding();
//批量綁定桌面組件:快捷方式列表,列表的開始位置,列表結束的位置,是否使用動畫
public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end,
boolean forceAnimateIcons);
//批量綁定桌面頁,orderedScreenIds 序列化後的桌面頁列表
public void bindScreens(ArrayList<Long> orderedScreenIds);
public void bindAddScreens(ArrayList<Long> orderedScreenIds);
//批量綁定文件夾,folders 文件夾映射列表
public void bindFolders(LongArrayMap<FolderInfo> folders);
//完成綁定
public void finishBindingItems();
//批量綁定小部件,info 需要綁定到桌面上的小部件信息
public void bindAppWidget(LauncherAppWidgetInfo info);
//綁定應用程序列表界面的應用程序信息,apps 需要綁定到應用程序列表中的應用程序列表
public void bindAllApplications(ArrayList<AppInfo> apps);
//批量添加組件
public void bindAppsAdded(ArrayList<Long> newScreens,
ArrayList<ItemInfo> addNotAnimated,
ArrayList<ItemInfo> addAnimated,
ArrayList<AppInfo> addedApps);
//批量更新應用程序相關的快捷方式或者入口
public void bindAppsUpdated(ArrayList<AppInfo> apps);
public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated,
ArrayList<ShortcutInfo> removed, UserHandleCompat user);
//當Widget被重置的時候調用
public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
public void bindRestoreItemsChange(HashSet<ItemInfo> updates);
public void bindWorkspaceComponentsRemoved(
HashSet<String> packageNames, HashSet<ComponentName> components,
UserHandleCompat user);
public void bindAppInfosRemoved(ArrayList<AppInfo> appInfos);
public void notifyWidgetProvidersChanged();
public void bindWidgetsModel(WidgetsModel model);
public void bindSearchProviderChanged();
public boolean isAllAppsButtonRank(int rank);
//指示正在綁定的頁面
public void onPageBoundSynchronously(int page);
//輸出當前Launcher信息到本地文件中
public void dumpLogsToLocalData();
}
前面說過Launcher也是一個Activity,所以它也需要執行setContentView()將佈局文件顯示出來。之後分別調用setupViews()、mDeviceProfile.layout(this)、restoreState(mSavedState)
我們先來看看setupViews()。
private void setupViews() {
......
mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace);
mWorkspace.setPageSwitchListener(this);
mPageIndicators = mDragLayer.findViewById(R.id.page_indicator);
......
// Setup the hotseat
mHotseat = (Hotseat) findViewById(R.id.hotseat);
if (mHotseat != null) {
mHotseat.setOnLongClickListener(this);
}
// Setup the overview panel
setupOverviewPanel();
.....
if (mLauncherCallbacks != null && mLauncherCallbacks.getAllAppsSearchBarController() != null) { mAppsView.setSearchBarController(mLauncherCallbacks.getAllAppsSearchBarController());
} else {
mAppsView.setSearchBarController(new DefaultAppSearchController());
}
}
可以看到setUpViews()的代碼就是執行一系列的findViewById操作,並對控件設置各種監聽和綁定。而mDeviceProfile.layout(this)所幹的事大概就可以猜測是將這些控件進行佈局。
看到layout裏的代碼也證實了我的猜想。
public void layout(Launcher launcher) {
FrameLayout.LayoutParams lp;
boolean hasVerticalBarLayout = isVerticalBarLayout();
final boolean isLayoutRtl = Utilities.isRtl(launcher.getResources());
......
// Layout the page indicators
View pageIndicator = launcher.findViewById(R.id.page_indicator);
if (pageIndicator != null) {
if (hasVerticalBarLayout) {
// Hide the page indicators when we have vertical search/hotseat
pageIndicator.setVisibility(View.GONE);
} else {
// Put the page indicators above the hotseat
lp = (FrameLayout.LayoutParams) pageIndicator.getLayoutParams();
lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
lp.width = LayoutParams.WRAP_CONTENT;
lp.height = LayoutParams.WRAP_CONTENT;
lp.bottomMargin = hotseatBarHeightPx;
pageIndicator.setLayoutParams(lp);
}
}
......
}
執行上述方法之後,源碼中還執行了一個restoreState ()方法,當onCreate()方法中的參數savedInstanceState不爲空時纔會進行相應的操作,該方法的作用就是恢復以前的狀態。由於第一次啓動Launcher時不會執行該方法,因此暫不進行分析。
隨後就開始執行一個比較重要的方法,LauncherModel#startLoader()。
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());
}
}
我們進入到LauncherModel的startLoader(),發現這是個重載的方法,最後都會執行public void startLoader(int synchronousBindPage, int loadFlags) {},該方法裏最重要的就是創建了LoaderTask實例並執行其run()方法。
public void startLoader(int synchronousBindPage, int loadFlags) {
synchronized (mLock) {
......
if (mCallbacks != null && mCallbacks.get() != null) {
// If there is already one running, tell it to stop.
stopLoaderLocked();
......
mLoaderTask = new LoaderTask(mApp.getContext(), loadFlags);
.....
if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
&& mAllAppsLoaded && mWorkspaceLoaded && !mIsLoaderTaskRunning) {
mLoaderTask.runBindSynchronousPage(synchronousBindPage);
} else {
//第一次啓動會執行
sWorkerThread.setPriority(Thread.NORM_PRIORITY);
sWorker.post(mLoaderTask);
}
}
}
}
在LoadTask的run()方法裏主要有以下幾步操作:
loadAndBindWorkspace()->waitForIdle()->loadAndBindAllApps()
我們先來看loadAndBindWorkspace()
private void loadAndBindWorkspace() {
mIsLoadingAndBindingWorkspace = true;
......
if (!mWorkspaceLoaded) {
loadWorkspace();
synchronized (LoaderTask.this) {
if (mStopped) {
LauncherLog.d(TAG, "loadAndBindWorkspace returned by stop flag.");
return;
}
mWorkspaceLoaded = true;
}
}
// Bind the workspace
bindWorkspace(-1);
}
mWorkspaceLoaded這個標識主要用來判斷workspace是否已經加載過,如果沒有,則先加載再進行綁定。我們先看看loadWorkspace(),其主要功能就是負責從數據庫表中讀取數據並轉換爲Launcher的數據結構。
private void loadWorkspace() {
if(){
......
}else{
//加載默認值 LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary();
}
synchronized (sBgLock) {
// 清空之前的內存數據(sBgWorkspaceItems,sBgAppWidgets等)
clearSBgDataStructures();
// 存儲無效數據的id,在後面統一從數據庫中刪掉
final ArrayList<Long> itemsToRemove = new ArrayList<>();
// 查詢ContentProvider,返回favorites表的結果集
final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
final Cursor c = contentResolver.query(contentUri, null, null, null, null);
try {
// 獲取數據庫每一列的索引值
final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
final int intentIndex = c.getColumnIndexOrThrow
(LauncherSettings.Favorites.INTENT);
......
//查詢ContentProvider
while (!mStopped && c.moveToNext()) {
try {
//根據不同的itemType類型,將結果存儲到相應的集合裏
......
switch (itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
......
break;
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
......
break;
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
......
break;
}
} catch (Exception e) {
......
}
}
} finally {
if (c != null) {
c.close();
}
}
// Break early if we've stopped loading
if (mStopped) {
clearSBgDataStructures();
return;
}
// 對文件夾排序、contentResolver.update 、註冊廣播、移除空的屏幕
......
}
}
而bindWorkspace()則是將loadWorkspace()方法裏獲取到的數據顯示在Launcher上。
/**
* Binds all loaded data to actual views on the main thread.
*/
private void bindWorkspace(int synchronizeBindPage) {
......
// Save a copy of all the bg-thread collections
ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>();
......
// 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.
unbindWorkspaceItemsOnMainThread();
......
// 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();
}
}
};
runOnMainThread(r);
bindWorkspaceScreens(oldCallbacks, orderedScreenIds);
// Load items on the current page
bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets,
currentFolders, null);
if (isLoadingSynchronously) {
r = new Runnable() {
public void run() {
Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null && currentScreen != PagedView.INVALID_RESTORE_PAGE) {
callbacks.onPageBoundSynchronously(currentScreen);
}
}
};
runOnMainThread(r);
}
// Load all the remaining pages (if we are loading synchronously, we want to defer this
// work until after the first render)
synchronized (mDeferredBindRunnables) {
mDeferredBindRunnables.clear();
}
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 (isLoadingSynchronously) {
synchronized (mDeferredBindRunnables) {
mDeferredBindRunnables.add(r);
}
} else {
runOnMainThread(r);
}
}
bindWorkspace()的流程主要可以概括爲:unbindWorkspaceItemsOnMainThread()->callbacks.startBinding()->bindWorkspaceScreens()-> bindWorkspaceItems()->callbacks.onPageBoundSynchronously(currentScreen)-> mDeferredBindRunnables.clear()->bindWorkspaceItems()->mBindCompleteRunnables.clear();
由於篇幅的限制這裏就暫不做更深入的研究。
我們回到LoadTask的run()方法,執行完loadAndBindWorkspace()之後還執行了一個方法waitForIdle(),這個方法是用來幹什麼的呢?
這個方法裏的註釋告訴我們這個方法會等待其他線程執行完,直到workspace設置好了之後纔開始執行loadAndBindAllApps(),相當於在這裏阻塞線程。
private void waitForIdle() {
// Wait until the either we're stopped or the other threads are done.
// This way we don't start loading all apps until the workspace has settled down.
synchronized (LoaderTask.this) {
......
while (!mStopped && !mLoadAndBindStepFinished) {
try {
// wait no longer than 1sec at a time
this.wait(1000);
} catch (InterruptedException ex) {
// Ignore
}
}
}
}
至於loadAndBindAllApps(),就是加載主菜單的數據。現在很多國產的ROM在界面上已經看不到AllApps,猜測可能將這個方法屏蔽了。這個方法裏的執行過程也很簡單。首先會判斷AllApps是否加載,如果沒有加載則會先執行loadAllApps()、updateIconCache(),並賦值mAllAppsLoaded爲true。若已經加載了則直接執行onlyBindAllApps()方法。
private void loadAndBindAllApps() {
......
if (!mAllAppsLoaded) {
loadAllApps();
synchronized (LoaderTask.this) {
if (mStopped) {
return;
}
}
updateIconCache();
synchronized (LoaderTask.this) {
if (mStopped) {
return;
}
mAllAppsLoaded = true;
}
} else {
onlyBindAllApps();
}
}
這整個過程和加載綁定workspace()類似。我們先看一下loadAllApps()方法。
private void loadAllApps() {
......
final List<UserHandleCompat> profiles = mUserManager.getUserProfiles();
// Clear the list of apps
mBgAllAppsList.clear();
//遍歷賬戶列表
for (UserHandleCompat user : profiles) {
// Query for the set of apps
final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
final List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);
......
boolean quietMode = mUserManager.isQuietModeEnabled(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, quietMode));
}
//創建與該用戶相關聯的篩選器實例
final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user);
if (heuristic != null) {
final Runnable r = new Runnable() {
@Override
public void run() {
//創建按賬戶分類應用程序的任務
heuristic.processUserApps(apps);
}
};
runOnMainThread(new Runnable() {
@Override
public void run() {
// Check isLoadingWorkspace on the UI thread, as it is updated on
// the UI thread.
if (mIsLoadingAndBindingWorkspace) {
synchronized (mBindCompleteRunnables) {
mBindCompleteRunnables.add(r);
}
} else {
runOnWorkerThread(r);
}
}
});
}
}
// Huh? Shouldn't this be inside the Runnable below?
final ArrayList<AppInfo> added = mBgAllAppsList.added;
mBgAllAppsList.added = new ArrayList<AppInfo>();
// Post callback on main thread
mHandler.post(new Runnable() {
public void run() {
......
if (callbacks != null) {
//綁定應用程序,因爲Launcher這個Activity實現了
//LauncherModel.CallBacks接口,因此這句相當於調用LauncherModel
//的bindAllApplications方法
callbacks.bindAllApplications(added);
......
}
}
});
// Cleanup any data stored for a deleted user.
......
}
onlyBindAllApps()所執行的和loadAllApps()方法大同小異,這裏就不做分析。
至此我已經把Launcher3的啓動和加載數據的流程大致走了一遍。其中還有很多細節沒有考慮到,希望以後再看的時候能夠及時查漏補缺。