上一篇墨香帶你學(xué)Launcher之-概述,我已經(jīng)介紹了Launcher的布局以及相關(guān)的界面跳轉(zhuǎn),今天我們繼續(xù)學(xué)習(xí),按照計劃,我們開始學(xué)習(xí)Launcher啟動之?dāng)?shù)據(jù)加載,主要是圖標(biāo)章办、Widget和文件夾的加載.
1.基礎(chǔ)知識
在介紹加載之前我先介紹一點需要用的相關(guān)知識:
Launcher:繼承Activity,是桌面的主界面,因此可知,桌面其實就是一個activity,只是和平常的應(yīng)用不同,他用來顯示圖標(biāo)哮内、Widget和文件夾等;
LauncherModel:繼承BroadcastReceiver,由此可知他是一個廣播接收器,用來接收廣播,另外,LauncherModel還主要加載數(shù)據(jù);
LauncherProvider:繼承ContentProvider,主要是處理數(shù)據(jù)庫操作;
LauncherAppState:單例模式的全局管理類,主要是初始化一些對象,注冊廣播等.
Compat:兼容包,帶有這個后綴的都是做兼容處理的類.
2.默認(rèn)圖標(biāo)配置
我們在買回新的手機(jī)或者第一次安裝新的Launcher后,會發(fā)現(xiàn)手機(jī)的第一頁已經(jīng)有了一些應(yīng)用的圖標(biāo)和時鐘或者天氣插件,那么這個是怎么實現(xiàn)的呢?其實,手機(jī)在出廠的時候或者Launcher發(fā)到市場的時候已經(jīng)默認(rèn)排布了一些應(yīng)用,在第一啟動時就會加載并且判斷手機(jī)中是否有這些圖標(biāo),如果有則顯示到固定位置,這個位置其實是已經(jīng)寫好的.下面我們看看這個位置到底在哪里寫好的.
下面是Launcher的資源文件,我們看這個比我們平時的多一個xml文件夾,里面有很多xml文件,那么這些是做什么用的,我來解釋一下,有三個文件,分別為default_workspace_4x4.xml,default_workspace_5x5.xml和default_workspace_5x6.xml,這三個文件就是我們默認(rèn)的布局文件,后面的跟著的4x4、5x5和5x6表示桌面圖標(biāo)的列數(shù)和行數(shù),也就是4行4列,5行5列,5行6列,這個怎么用我們后面再說.
我們先看一下default_workspace_4x4.xml這個文件中的代碼:
第20行是一個include的文件,在xml文件夾中的名字dw_phone_hotseat文件,我們后面在看,接著看上圖的下面的代碼,下面是三個resolve文件,里面包含一些信息,screen表示第幾屏,x表示橫向的位置,y表示縱向的位置,那么這個位置怎定的呢,我來畫一個4x4的圖你就明白了:
先看上半部分,就是我們說的4x4部分,沒一格表示一格圖標(biāo),在我們繪制圖標(biāo)的時候已經(jīng)分好了格,每格的大小,只要知道知道他的位置即可繪制圖標(biāo)到相應(yīng)的位置,那么代碼中的x,y就是這個圖標(biāo)的位置.上面resolve中還有兩個favorite,在第一個中最后面有個"APP_",這個我們一看就知道是應(yīng)用的屬性,其實這就表示我們配置了那個app在這個位置,我們再看一下上面介紹的hotseat那個xml文件:
這個圖我只截圖了一部分,想看全部的可以下載我github上的源碼查看,其實只是重復(fù),我介紹一個就知道了,上一章我介紹過hotseat這個概念,其實就是我們手機(jī)最下面的那個四個或者五個最常用的app圖標(biāo),這個就是在這里面配置的,我以第一個為例來介紹這個Hotseat配置,我們先看第21行,這個比我們前面介紹的多個屬性就是這個container,之前的是沒有的,這個就表示容器,-101就是hotseat,也就是這個圖標(biāo)放置到Hotseat中,Hotseat只有一行,所以只有x在變,而y不變.
到此基本的桌面默認(rèn)圖標(biāo)顯示配置就介紹完了,如果你需要默認(rèn)顯示哪個只需要配置這個文件即可.
3.Launcher啟動過程
下面我們開始介紹Launcher的啟動過程.分析Launcher的啟動過程要從源碼開始分析.在源碼中是通過startHomeActivityLocked這個方法調(diào)用的啟動Launcher,我們先看一下哪里開始調(diào)用的這個函數(shù),
從上面的調(diào)用圖可知有三個地方調(diào)用了啟動Launcher的方法,這三個方法中首次啟動應(yīng)該是中間的那個systemReady方法,系統(tǒng)準(zhǔn)備過程中調(diào)用啟動Launcher,我們看一下systemReady方法是哪里調(diào)用的來驗證一下:
從上代碼靜態(tài)分析圖來看最開始是在System.main方法開始的,正好這個方法就是啟動系統(tǒng)的一個入口,也就是在這個過程中啟動了Launcher,找到調(diào)用的地方后,我們來看一下startHomeActivityLocked是怎么啟動Launcher的,首先看一下源碼:
我們看上面的3473行,獲取Intent,再看3451行,如果不為空,則啟動HomeActivity,我們看一下這個Intent是什么的Intent:
上面的3424行,有個Intent.CATEGORY_HOME,我們在Intent中找到這個屬性的代碼:
這個就是我們上一章講的設(shè)置app為launcher的屬性值.
通過上面這些分析可以看到系統(tǒng)是怎么啟動launcher的.下面我們看是介紹Launcher內(nèi)部是如何啟動的.
4.Launcher初始化
我們知道App的啟動是從Application開始的,但是我們最新的Launcher3中,谷歌工程師把這個類移除,再次之前的版本都是有這個類的,我在這提一下就是因為開發(fā)以前l(fā)auncher的時候遇到一個問題,就是在Application和ContentProvider同時存在時,ContentProvider的onCreate方法要比Application的onCreate方法先啟動,下面我們通過源碼分析來驗證這個問題.
啟動Application是從ActivityManagerService中的attachApplication方法開始的,代碼:
public final void attachApplication(IApplicationThread thread) {
synchronized (this) {
int callingPid = Binder.getCallingPid();
final long origId = Binder.clearCallingIdentity();
attachApplicationLocked(thread, callingPid);
Binder.restoreCallingIdentity(origId);
}
}
接著調(diào)用attachApplicationLocked方法,代碼如下:
private final boolean attachApplicationLocked(IApplicationThread thread, int pid) {
app.makeActive(thread, mProcessStats);
app.curAdj = app.setAdj = -100;
app.curSchedGroup = app.setSchedGroup = Process.THREAD_GROUP_DEFAULT;
app.forcingToForeground = null;
updateProcessForegroundLocked(app, false, false);
app.hasShownUi = false;
app.debugging = false;
app.cached = false;
app.killedByAm = false;
mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
List<ProviderInfo> providers = normalMode ? generateApplicationProvidersLocked(app) : null;
if (!normalMode) {
Slog.i(TAG, "Launching preboot mode app: " + app);
}
if (DEBUG_ALL) Slog.v(
TAG, "New app record " + app
+ " thread=" + thread.asBinder() + " pid=" + pid);
try {
...
ProfilerInfo profilerInfo = profileFile == null ? null
: new ProfilerInfo(profileFile, profileFd, samplingInterval, profileAutoStop);
thread.bindApplication(processName, appInfo, providers, app.instrumentationClass,
profilerInfo, app.instrumentationArguments, app.instrumentationWatcher,
app.instrumentationUiAutomationConnection, testMode, enableOpenGlTrace,
isRestrictedBackupMode || !normalMode, app.persistent,
new Configuration(mConfiguration), app.compat,
getCommonServicesLocked(app.isolated),
mCoreSettingsObserver.getCoreSettingsLocked());
updateLruProcessLocked(app, false, null);
app.lastRequestedGc = app.lastLowMemory = SystemClock.uptimeMillis();
} catch (Exception e) {
app.resetPackageList(mProcessStats);
app.unlinkDeathRecipient();
startProcessLocked(app, "bind fail", processName);
return false;
}
...
return true;
}
上面代碼中主要有一個thread.bindApplication方法來綁定application,接著看bindApplication代碼:
public final void bindApplication(String processName, ApplicationInfo appInfo,
List<ProviderInfo> providers, ComponentName instrumentationName,
ProfilerInfo profilerInfo, Bundle instrumentationArgs,
IInstrumentationWatcher instrumentationWatcher,
IUiAutomationConnection instrumentationUiConnection, int debugMode,
boolean enableOpenGlTrace, boolean isRestrictedBackupMode, boolean persistent,
Configuration config, CompatibilityInfo compatInfo, Map<String, IBinder> services,
Bundle coreSettings) {
...
AppBindData data = new AppBindData();
data.processName = processName;
data.appInfo = appInfo;
data.providers = providers;
data.instrumentationName = instrumentationName;
data.instrumentationArgs = instrumentationArgs;
data.instrumentationWatcher = instrumentationWatcher;
data.instrumentationUiAutomationConnection = instrumentationUiConnection;
data.debugMode = debugMode;
data.enableOpenGlTrace = enableOpenGlTrace;
data.restrictedBackupMode = isRestrictedBackupMode;
data.persistent = persistent;
data.config = config;
data.compatInfo = compatInfo;
data.initProfilerInfo = profilerInfo;
sendMessage(H.BIND_APPLICATION, data);
}
準(zhǔn)備data數(shù)據(jù)偏序,然后發(fā)送消息到Handler记舆,Handler中處理消息的代碼如下:
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case BIND_APPLICATION:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
AppBindData data = (AppBindData)msg.obj;
handleBindApplication(data);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
}
if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
}
根據(jù)消息類型BIND_APPLICATION來判斷調(diào)用handleBindApplication方法院尔,
private void handleBindApplication(AppBindData data) {
...
try {
// If the app is being launched for full backup or restore, bring it up in
// a restricted environment with the base application class.
Application app = data.info.makeApplication(data.restrictedBackupMode, null);
mInitialApplication = app;
// don't bring up providers in restricted mode; they may depend on the
// app's custom Application class
if (!data.restrictedBackupMode) {
List<ProviderInfo> providers = data.providers;
if (providers != null) {
//安裝ContentProviders
installContentProviders(app, providers);
// For process that contains content providers, we want to
// ensure that the JIT is enabled "at some point".
mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);
}
}
...
try {
//啟動Application的onCreate方法
mInstrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
if (!mInstrumentation.onException(app, e)) {
throw new RuntimeException(
"Unable to create application " + app.getClass().getName()
+ ": " + e.toString(), e);
}
}
} finally {
StrictMode.setThreadPolicy(savedPolicy);
}
}
在上面函數(shù)中調(diào)用installContentProviders方法來安裝ContentProvider御板,代碼如下:
private void installContentProviders(
Context context, List<ProviderInfo> providers) {
final ArrayList<IActivityManager.ContentProviderHolder> results =
new ArrayList<IActivityManager.ContentProviderHolder>();
for (ProviderInfo cpi : providers) {
if (DEBUG_PROVIDER) {
StringBuilder buf = new StringBuilder(128);
buf.append("Pub ");
buf.append(cpi.authority);
buf.append(": ");
buf.append(cpi.name);
Log.i(TAG, buf.toString());
}
IActivityManager.ContentProviderHolder cph = installProvider(context, null, cpi,
...
}
...
}
調(diào)用installProvider返回一個IActivityManager.ContentProviderHolder對象说榆,我們看這個方法里面做了哪些處理虚吟,
private IActivityManager.ContentProviderHolder installProvider(Context context,
IActivityManager.ContentProviderHolder holder, ProviderInfo info,
boolean noisy, boolean noReleaseNeeded, boolean stable) {
ContentProvider localProvider = null;
IContentProvider provider; if (holder == null || holder.provider == null) {
if (DEBUG_PROVIDER || noisy) {
Slog.d(TAG, "Loading provider " + info.authority + ": "
+ info.name);
}
Context c = null;
ApplicationInfo ai = info.applicationInfo;
if (context.getPackageName().equals(ai.packageName)) {
c = context;
} else if (mInitialApplication != null &&
mInitialApplication.getPackageName().equals(ai.packageName)) {
c = mInitialApplication;
} else {
try {
c = context.createPackageContext(ai.packageName,
Context.CONTEXT_INCLUDE_CODE);
} catch (PackageManager.NameNotFoundException e) {
// Ignore
}
}
if (c == null) {
Slog.w(TAG, "Unable to get context for package " +
ai.packageName +
" while loading content provider " +
info.name);
return null;
}
try {
final java.lang.ClassLoader cl = c.getClassLoader();
localProvider = (ContentProvider)cl.
loadClass(info.name).newInstance();
//獲取ContentProvider
provider = localProvider.getIContentProvider();
if (provider == null) {
Slog.e(TAG, "Failed to instantiate class " +
info.name + " from sourceDir " +
info.applicationInfo.sourceDir);
return null;
}
if (DEBUG_PROVIDER) Slog.v(
TAG, "Instantiating local provider " + info.name);
// XXX Need to create the correct context for this provider.
localProvider.attachInfo(c, info);
} catch (java.lang.Exception e) {
if (!mInstrumentation.onException(null, e)) {
throw new RuntimeException(
"Unable to get provider " + info.name
+ ": " + e.toString(), e);
}
return null;
}
} else {
provider = holder.provider;
if (DEBUG_PROVIDER) Slog.v(TAG, "Installing external provider " + info.authority + ": "
+ info.name);
}
...
return retHolder;
}
上面代碼中有個關(guān)鍵方法:localProvider.attachInfo(c, info),這個方法就是添加Provider的签财,代碼如下:
private void attachInfo(Context context, ProviderInfo info, boolean testing) {
/*
* Only allow it to be set once, so after the content service gives
* this to us clients can't change it.
*/
if (mContext == null) {
mContext = context;
if (context != null) {
mTransport.mAppOpsManager = (AppOpsManager) context.getSystemService(
Context.APP_OPS_SERVICE);
}
mMyUid = Process.myUid();
if (info != null) {
setReadPermission(info.readPermission);
setWritePermission(info.writePermission);
setPathPermissions(info.pathPermissions);
mExported = info.exported;
}
ContentProvider.this.onCreate();
}
}
我們看到在最后調(diào)用了ContentProvider.this.onCreate()這個方法串慰,然后會返回到handleBindApplication方法中執(zhí)行mInstrumentation.callApplicationOnCreate(app)方法,代碼如下:
public void callApplicationOnCreate(Application app) {
app.onCreate();
}
因此我們看到ContentProvider的onCreate方法比Application的onCreate方法調(diào)用早荠卷。這里只是簡單介紹詳細(xì)過程去看源碼模庐。
我現(xiàn)在講解的是基于最新的Launcher3代碼,因此我們這個Launcher中沒有Application油宜,所以程序啟動最開始的是ContentProvider的onCreate方法掂碱,代碼如下:
public boolean onCreate() {
final Context context = getContext();
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
mOpenHelper = new DatabaseHelper(context);
StrictMode.setThreadPolicy(oldPolicy);
LauncherAppState.setLauncherProvider(this);
return true;
}
代碼中處理的事情不多怜姿,主要是啟動嚴(yán)苛模式和創(chuàng)建數(shù)據(jù)庫,關(guān)于嚴(yán)苛模式的具體信息看官方文檔或者博客疼燥,都有很詳細(xì)的講解沧卢,然后將ContentProvider放置到整個Launcher的管理類LauncherAppState中,以方便獲取醉者。
接下來就是啟動Launcher,我么看一下Launcher中的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()
.build());
}
if (mLauncherCallbacks != null) {
mLauncherCallbacks.preOnCreate();
}
super.onCreate(savedInstanceState);
LauncherAppState.setApplicationContext(getApplicationContext());
LauncherAppState app = LauncherAppState.getInstance();
// Load configuration-specific DeviceProfile
mDeviceProfile = getResources().getConfiguration().orientation
== Configuration.ORIENTATION_LANDSCAPE ?
app.getInvariantDeviceProfile().landscapeProfile
: app.getInvariantDeviceProfile().portraitProfile;
mSharedPrefs = getSharedPreferences(LauncherAppState.getSharedPreferencesKey(),
Context.MODE_PRIVATE);
mIsSafeModeEnabled = getPackageManager().isSafeMode();
mModel = app.setLauncher(this);
mIconCache = app.getIconCache();
mDragController = new DragController(this);
mInflater = getLayoutInflater();
mStateTransitionAnimation = new LauncherStateTransitionAnimation(this);
mStats = new Stats(this);
mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
mAppWidgetHost.startListening();
// If we are getting an onCreate, we can actually preempt onResume and unset mPaused here,
// this also ensures that any synchronous binding below doesn't re-trigger another
// LauncherModel load.
mPaused = false;
if (PROFILE_STARTUP) {
android.os.Debug.startMethodTracing(
Environment.getExternalStorageDirectory() + "/launcher");
}
setContentView(R.layout.launcher);
registerHomeKey();
setupViews();
//動態(tài)設(shè)置各布局的參數(shù)
mDeviceProfile.layout(this);
mSavedState = savedInstanceState;
restoreState(mSavedState);
if (PROFILE_STARTUP) {
android.os.Debug.stopMethodTracing();
}
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());
}
}
// For handling default keys
mDefaultKeySsb = new SpannableStringBuilder();
Selection.setSelection(mDefaultKeySsb, 0);
IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
registerReceiver(mCloseSystemDialogsReceiver, filter);
mRotationEnabled = Utilities.isRotationAllowedForDevice(getApplicationContext());
// In case we are on a device with locked rotation, we should look at preferences to check
// if the user has specifically allowed rotation.
if (!mRotationEnabled) {
mRotationEnabled = Utilities.isAllowRotationPrefEnabled(getApplicationContext(), false);
}
// On large interfaces, or on devices that a user has specifically enabled screen rotation,
// we want the screen to auto-rotate based on the current orientation
setOrientation();
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onCreate(savedInstanceState);
if (mLauncherCallbacks.hasLauncherOverlay()) {
ViewStub stub = (ViewStub) findViewById(R.id.launcher_overlay_stub);
mLauncherOverlayContainer = (InsettableFrameLayout) stub.inflate();
mLauncherOverlay = mLauncherCallbacks.setLauncherOverlayView(
mLauncherOverlayContainer, mLauncherOverlayCallbacks);
mWorkspace.setLauncherOverlay(mLauncherOverlay);
}
}
if (shouldShowIntroScreen()) {
showIntroScreen();
} else {
showFirstRunActivity();
showFirstRunClings();
}
}
代碼比較多我們看一下執(zhí)行過程圖:
首先是啟動嚴(yán)苛模式但狭,準(zhǔn)備回調(diào)接口,初始化LauncherAppState:
private LauncherAppState() {
if (sContext == null) {
throw new IllegalStateException("LauncherAppState inited before app context set");
}
if (sContext.getResources().getBoolean(R.bool.debug_memory_enabled)) {
MemoryTracker.startTrackingMe(sContext, "L");
}
//初始化固定的設(shè)備配置
mInvariantDeviceProfile = new InvariantDeviceProfile(sContext);
//初始化圖標(biāo)管理工具
mIconCache = new IconCache(sContext, mInvariantDeviceProfile);
//初始化Widget加載混存工具
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);
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);
//注冊廣播
sContext.registerReceiver(mModel, filter);
UserManagerCompat.getInstance(sContext).enableAndResetCache();
}
然后初始化手機(jī)固件信息對象DeviceProfile撬即,初始化拖拽管理器DragController立磁,然后初始化小部件管理器,加載布局剥槐,初始化桌面各個控件唱歧,并且設(shè)置各個控件的位置:
public void layout(Launcher launcher) {
FrameLayout.LayoutParams lp;
boolean hasVerticalBarLayout = isVerticalBarLayout();
final boolean isLayoutRtl = Utilities.isRtl(launcher.getResources());
// Layout the search bar space
View searchBar = launcher.getSearchDropTargetBar();
lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
if (hasVerticalBarLayout) {
// Vertical search bar space -- The search bar is fixed in the layout to be on the left
// of the screen regardless of RTL
lp.gravity = Gravity.LEFT;
lp.width = searchBarSpaceHeightPx;
LinearLayout targets = (LinearLayout) searchBar.findViewById(R.id.drag_target_bar);
targets.setOrientation(LinearLayout.VERTICAL);
FrameLayout.LayoutParams targetsLp = (FrameLayout.LayoutParams) targets.getLayoutParams();
targetsLp.gravity = Gravity.TOP;
targetsLp.height = LayoutParams.WRAP_CONTENT;
} else {
// Horizontal search bar space
lp.gravity = Gravity.TOP;
lp.height = searchBarSpaceHeightPx;
LinearLayout targets = (LinearLayout) searchBar.findViewById(R.id.drag_target_bar);
targets.getLayoutParams().width = searchBarSpaceWidthPx;
}
searchBar.setLayoutParams(lp);
//其他省略
...
}
這里就是動態(tài)設(shè)置桌面各個控件的位置及寬高等屬性。當(dāng)所有信息初始化完成后粒竖,就開始調(diào)用mModel.startLoader方法來加載應(yīng)用數(shù)據(jù)颅崩。下面我們詳細(xì)來講數(shù)據(jù)加載流程。
5.Launcher數(shù)據(jù)加載
數(shù)據(jù)加載主要是從LauncherModel中的startLoader方法開始蕊苗,先看一下這個方法做的事情:
這里的事情不多沿后,主要是調(diào)用LoaderTask這個任務(wù),LoaderTask實現(xiàn)了Runnable這個接口朽砰,因此首先執(zhí)行潤run方法尖滚,我么看一下這個run方法里面做了哪些事情,
public void run() {
...
keep_running:
{
loadAndBindWorkspace();
if (mStopped) {
break keep_running;
}
waitForIdle();
...
loadAndBindAllApps();
}
...
}
在這個方法中主要是三件事锅移,我們用時序圖表一下:
首先是執(zhí)行l(wèi)oadAndBindWorkspace方法:
private void loadAndBindWorkspace() {
...
//判斷workspace是否已經(jīng)加載
if (!mWorkspaceLoaded) {
loadWorkspace();
synchronized (LoaderTask.this) {
if (mStopped) {
return;
}
mWorkspaceLoaded = true;
}
}
// Bind the workspace
bindWorkspace(-1);
}
這里面主要是執(zhí)行l(wèi)oadWorkspace和bindWorkspace熔掺,也就是加載workspace的應(yīng)用并且進(jìn)行綁定。先看loadWorkspace方法非剃,代碼很多置逻,我們只貼關(guān)鍵部分:
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);
LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary();
}
synchronized (sBgLock) {
//初始化一些值
...
try {
//從數(shù)據(jù)庫查詢解析出來的所有應(yīng)用信息
...
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) {
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
...
try {
intent = Intent.parseUri(intentDescription, 0);
ComponentName cn = intent.getComponent();
if (cn != null && cn.getPackageName() != null) {
//檢測數(shù)據(jù)庫(從xml文件解析出來存入數(shù)據(jù)庫的)中取出來的app包是否存在
boolean validPkg = launcherApps.isPackageEnabledForProfile(
cn.getPackageName(), user);
//檢測數(shù)據(jù)庫(從xml文件解析出來存入數(shù)據(jù)庫的)中取出來的app組件是否存在
boolean validComponent = validPkg &&
launcherApps.isActivityEnabledForProfile(cn, user);
if (validComponent) {
...
} else if (validPkg) {
...
} else if (restored) {
...
} else if (launcherApps.isAppEnabled(
manager, cn.getPackageName(),
PackageManager.GET_UNINSTALLED_PACKAGES)) {
...
} else if (!isSdCardReady) {
...
} else {
...
}
} else if (cn == null) {
// For shortcuts with no component, keep them as they are
restoredRows.add(id);
restored = false;
}
} catch (URISyntaxException e) {
Launcher.addDumpLog(TAG,
"Invalid uri: " + intentDescription, true);
itemsToRemove.add(id);
continue;
}
...
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);
...
switch (container) {
case LauncherSettings.Favorites.CONTAINER_DESKTOP:
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);
} else {
throw new RuntimeException("Unexpected null ShortcutInfo");
}
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) {
Launcher.addDumpLog(TAG, "Desktop items loading interrupted", e, true);
}
}
} finally {
...
}
...
// Sort all the folder items and make sure the first 3 items are high resolution.
for (FolderInfo folder : sBgFolders) {
Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
int pos = 0;
for (ShortcutInfo info : folder.contents) {
if (info.usingLowResIcon) {
info.updateIcon(mIconCache, false);
}
pos++;
if (pos >= FolderIcon.NUM_ITEMS_IN_PREVIEW) {
break;
}
}
}
if (restoredRows.size() > 0) {
// Update restored items that no longer require special handling
ContentValues values = new ContentValues();
values.put(LauncherSettings.Favorites.RESTORED, 0);
contentResolver.update(LauncherSettings.Favorites.CONTENT_URI, values,
Utilities.createDbSelectionQuery(
LauncherSettings.Favorites._ID, restoredRows), null);
}
if (!isSdCardReady && !sPendingPackages.isEmpty()) {
context.registerReceiver(new AppsAvailabilityCheck(),
new IntentFilter(StartupReceiver.SYSTEM_READY),
null, sWorker);
}
// Remove any empty screens
...
// If there are any empty screens remove them, and update.
if (unusedScreens.size() != 0) {
sBgWorkspaceScreens.removeAll(unusedScreens);
updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
}
...
}
}
首先是調(diào)用loadDefaultFavoritesIfNecessary這個方法,來解析我們上面講的配置默認(rèn)的桌面圖標(biāo)的xml文件备绽,流程就是:初始化AutoInstallsLayout券坞,然后調(diào)用LauncherProvider中的loadFavorites方法,在這個方法中調(diào)用AutoInstallsLayout中的loadLayout方法來解析配置的xml文件肺素,在AutoInstallsLayout中通過對小部件恨锚,圖標(biāo),文件夾等分類進(jìn)行分辨解析倍靡,解析過程中如果有include標(biāo)簽猴伶,則對相應(yīng)的xml文件進(jìn)行解析,解析過程相對簡單,不在做詳細(xì)講解他挎,解析過程中將解析的各種信息存儲到數(shù)據(jù)庫中筝尾,以方便后面使用,當(dāng)xml文件解析完成后办桨,開始讀取解析xml配置文件存儲到數(shù)據(jù)庫的數(shù)據(jù)筹淫,讀取出來后,根據(jù)相應(yīng)的類型(圖標(biāo)呢撞,小部件损姜,文件夾等)進(jìn)行判斷,判斷系統(tǒng)中這個應(yīng)用是否存在殊霞,是否可用摧阅,如果可用則生成相應(yīng)對象并存儲到想定的map中,如果不存在則刪除數(shù)據(jù)庫中的數(shù)據(jù)脓鹃,這樣整個判斷完成后數(shù)據(jù)庫中的數(shù)據(jù)就只剩下系統(tǒng)中存在的配置應(yīng)用過了逸尖。
加載完配置應(yīng)用圖標(biāo)后,開始執(zhí)行bindWorkspace方法綁定應(yīng)用圖標(biāo)到桌面瘸右,代碼略過,我們看一下UML圖:
通過上面的時序圖岩齿,我們看到太颤,首先執(zhí)行過濾工作,比如這個圖標(biāo)是在workspace中還是在Hotseat中盹沈,不同的位置放置不同的分類龄章,然后進(jìn)行排序處理,然后執(zhí)行bindWorkspaceScreens方法來綁定手機(jī)有幾個屏幕乞封,接著調(diào)用bindWorkspaceItems方法綁定當(dāng)前屏幕的圖標(biāo)做裙、文件夾和小插件信息,最后調(diào)用綁定其他屏幕的應(yīng)用圖標(biāo)肃晚、文件夾和小插件锚贱,關(guān)于綁定我們下一章再講。
接著執(zhí)行LoadTask中的waitForIdle方法关串,改方法主要是等待加載數(shù)據(jù)結(jié)束拧廊。
最后執(zhí)行l(wèi)oadAndBindAllApps方法來加載第二層的多有圖標(biāo)信息,看代碼:
private void loadAndBindAllApps() {
if (DEBUG_LOADERS) {
Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
}
if (!mAllAppsLoaded) {
loadAllApps();
synchronized (LoaderTask.this) {
if (mStopped) {
return;
}
}
updateIconCache();
synchronized (LoaderTask.this) {
if (mStopped) {
return;
}
mAllAppsLoaded = true;
}
} else {
onlyBindAllApps();
}
}
主要是如果已經(jīng)加載了所有應(yīng)用這只是執(zhí)行綁定應(yīng)用晋修,如果沒有加載則執(zhí)行加載操作吧碾。下面看加載操作:
private void loadAllApps() {
...
final List<UserHandleCompat> profiles = mUserManager.getUserProfiles();
// Clear the list of apps
mBgAllAppsList.clear();
for (UserHandleCompat user : profiles) {
...
final List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);
// Fail if we don't have any apps
// TODO: Fix this. Only fail for the current user.
if (apps == null || apps.isEmpty()) {
return;
}
// 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));
}
...
// 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() {
final long bindTime = SystemClock.uptimeMillis();
final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
callbacks.bindAllApplications(added);
if (DEBUG_LOADERS) {
Log.d(TAG, "bound " + added.size() + " apps in "
+ (SystemClock.uptimeMillis() - bindTime) + "ms");
}
} else {
Log.i(TAG, "not binding apps: no Launcher activity");
}
}
});
...
loadAndBindWidgetsAndShortcuts(tryGetCallbacks(oldCallbacks), true /* refresh */);
...
}
上面代碼中通過mLauncherApps.getActivityList方法獲取所有應(yīng)用啟動界面的一個對象列表,然后根據(jù)LauncherActivityInfoCompat來初始化對應(yīng)的app對象墓卦,這樣就可以獲取手機(jī)中所有的應(yīng)用列表倦春。獲取完成后就執(zhí)行綁定操作,最后調(diào)用loadAndBindWidgetsAndShortcuts方法加載綁定小部件和快捷方式到小部件界面。
public void loadAndBindWidgetsAndShortcuts(final Callbacks callbacks, final boolean refresh) {
runOnWorkerThread(new Runnable() {
@Override
public void run() {
updateWidgetsModel(refresh);
final WidgetsModel model = mBgWidgetsModel.clone();
mHandler.post(new Runnable() {
@Override
public void run() {
Callbacks cb = getCallback();
if (callbacks == cb && cb != null) {
callbacks.bindAllPackages(model);
}
}
});
// update the Widget entries inside DB on the worker thread.
LauncherAppState.getInstance().getWidgetCache().removeObsoletePreviews(
model.getRawList());
}
});
}
在這個方法中首先調(diào)用updateWidgetsModel方法睁本,代碼如下:
void updateWidgetsModel(boolean refresh) {
PackageManager packageManager = mApp.getContext().getPackageManager();
final ArrayList<Object> widgetsAndShortcuts = new ArrayList<Object>();
widgetsAndShortcuts.addAll(getWidgetProviders(mApp.getContext(), refresh));
Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
widgetsAndShortcuts.addAll(packageManager.queryIntentActivities(shortcutsIntent, 0));
mBgWidgetsModel.setWidgetsAndShortcuts(widgetsAndShortcuts);
}
上面代碼中通過調(diào)用getWidgetProviders來獲取所有小部件山叮,通過shortcutsIntent來獲取所有的跨界方式,最后通過mBgWidgetsModel.setWidgetsAndShortcuts方法把小部件和快捷方式放到WidgetsModel對象中添履,在后期加載中可以從這個里面獲取小部件和快捷方式屁倔。
到這整個launcher的數(shù)據(jù)加載基本就完成了,還有很多細(xì)節(jié)沒有講暮胧,xml解析等锐借,這個谷歌工程師設(shè)計都是非常好的,有興趣的可以看看源碼往衷。
參考
Android系統(tǒng)默認(rèn)Home應(yīng)用程序(Launcher)的啟動過程源代碼分析
Android應(yīng)用程序組件Content Provider的啟動過程源代碼分析
注
本文的源碼是基于Android 6.0系統(tǒng)钞翔;
Launcher源碼:Launcher3_mx
首發(fā)地址:墨香博客
公眾賬號:Code-MX
本文原創(chuàng),轉(zhuǎn)載請注明出處席舍。