android M Launcher之LauncherModel (一)
眾所周知 LauncherModel在Launcher中所佔的位置,它相當於Launcher的資料中心,Launcher的桌面以及應用程式選單中所需的資料像 桌面小部件的資訊、快捷方式資訊、檔案資訊、以及一些比較特殊的桌面頁的資訊等都是由它提供,因此我們這裡來分析下LauncherModel的工作流程。
一、LauncherModel的建立
要了解LauncherModel,我們要從LauncherModel的例項化開始。
LauncherModel是在Launcher應用程式被建立的時候,由LauncherApplication通過呼叫LauncherAppState的初始化方法實現的,即由LauncherAppState的getInstance方法呼叫LauncherAppState
的構造方法來實現,因此我們需要去看下LauncherAppState的構造方法
1、建立LauncherModel的準備工作
LauncherModel作為中心樞紐,不可避免的要與大量的圖片打交道。這些圖片包括Launcher所管理的醫用程式圖示,以及一些桌面小部件的預覽圖示,Launcher為他們準備了一個圖片快取區(IconCache),因此LauncherModel首先需要持有IconCache的例項,另外,如果Launcher並不希望那麼多的應用程式展示在其中,所以它還需要一個應用程式的篩選器例項(AppFilter)。於是,LauncherModel的準備工作實際上就是IconCache和AppFilter的建立過程。
mIconCache = new IconCache(sContext, mInvariantDeviceProfile);
...
mAppFilter = AppFilter.loadByName(sContext.getString(R.string.app_filter_class));
...
中間的程式碼都省略一下只看和Launchermodel有關的。
2、建立LauncherModel的例項
在完成了以上準備工作後,就可以進行LauncherModel的例項建立了
mModel = new LauncherModel(this, mIconCache, mAppFilter);
3、LauncherModel的一些設定
LauncherModel本身是一個廣播接收器的子類,LauncherAppState在建立的時候會為它設定一些廣播,以便讓LauncherModel的例項能夠處理自己的廣播:
// 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);
從上面程式碼可以看出,LauncherModel本身需要關注裝置的語種,裝置配置變化以及搜尋相關的變化,這是因為Launcher需要根據裝置配置資訊的改變來改變載入在桌面的圖示、標題的資訊,例如語言改變後 Launcher會馬上生效。
到這裡 LauncherModel的建立過程就完成了,接下來看下LauncherModel的建構函式。
二 、LauncherModel的建構函式
上圖展示了LauncherModel的建立過程,可以看出LauncherModel的執行需要依賴一些條件。
如下
1、有些應用程式會通過其配置把自己安裝到外部儲存器上,因此Launcher在管理應用程式的時候就需要額外關注這個儲存器是否是一個可以被移除的裝置
mAppsCanBeOnRemoveableStorage = Environment.isExternalStorageRemovable();
isExternalStorageRemovable這個方法將返回這個外部儲存器的性質。
2、Launcher3是在Android4.4以後才提供的全新桌面應用程式,因此我們需要考慮從Launcher2的升級,其中一個重要的部分就是將Launcher2的資料庫資料遷移並升級到Launcher3中,並適配Launcher3的資料庫結構。在Launcher3中通過字串的配置將Launcher2資料庫的URL保持起來後,LauncherModel在建立的時候通過此字串的配置獲取訪問Launcher2資料庫的URL,如下
String oldProvider = context.getString(R.string.old_launcher_provider_uri);
3、有了訪問Launcher2資料庫的URL,接下來要做的是判斷Launcher2的資料庫是否還存在。
// This may be the same as MIGRATE_AUTHORITY, or it may be replaced by a different
// resource string.
//獲取Launcher2資料庫的Author資訊
String redirectAuthority = Uri.parse(oldProvider).getAuthority();
//通過特定的Author獲取特定的資料庫元件資訊
ProviderInfo providerInfo =
context.getPackageManager().resolveContentProvider(MIGRATE_AUTHORITY, 0);
ProviderInfo redirectProvider =
context.getPackageManager().resolveContentProvider(redirectAuthority, 0);
Log.d(TAG, "Old launcher provider: " + oldProvider);
//判斷Launcher2的資料庫元件是否存在
mOldContentProviderExists = (providerInfo != null) && (redirectProvider != null);
這裡主要的目的是通過mOldContentProviderExists 保持當前是否存在Launcher2資料庫元件,因為只有這些元件存在,才可以保證Launcher2資料庫存在。
4、最後LauncherModel還需要保持一些對於自身工作有著深遠影響的資訊,如圖片快取區、使用者管理器等資訊
//儲存Launcher的執行狀態
mApp = app;
//支援後臺載入資料的列表
mBgAllAppsList = new AllAppsList(iconCache, appFilter);
//圖片快取
mIconCache = iconCache;
//應用程式相容管理器
mLauncherApps = LauncherAppsCompat.getInstance(context);
//儲存當前使用者管理器
mUserManager = UserManagerCompat.getInstance(context);
到這裡LauncherModel就例項化完成了,當LauncherAppState例項化流程完成後,LauncherModel就可以投入執行,等待來自其他元件的排程。
三、與LauncherModel的溝通方式
LauncherModel作為Launcher的中心樞紐,它給Launcher的各個元件提供了三類藉口,以便更新Launcher的介面等資訊,這些藉口包括LauncherModel操作的回撥藉口,廣播介面和應用程式內部介面。
1、LauncherModel操作的回撥介面
LauncherModel操作的回撥介面主要使用在一下場景中。
Launcher在啟動時,需要桌面的資料,這些資料的形成需要由LauncherModel完成,在資料不斷被完善的過程中,LauncherModel需要通過這些介面輸送已經完成的資料
在Launcher使用的過程中,我們可能會改變桌面上的元素配置,比如手動往桌面上新增快捷方式,每一次操作也無一例外的需要通過LauncherModel來完成。操作完成後LauncherModel也需要通過這些介面通知介面更新。
第三方應用程式通過Launcher提供的介面往桌面上新增或者從桌面上刪除摸個快捷方式,這個操作由Launcher提供的廣播接收器將請求轉發到LauncherModel來處理,LauncherModel完成相關資料處理後,將會通過這些介面通知介面更新。
以上就是這些回撥介面的作用以及使用場景,需要注意的是,這些介面並非由LauncherModel實現,而是由Launcher的元件實現並註冊到LauncherModel中,從而實現這樣的溝通機制。
下面分析Launcher怎麼註冊到LauncherModel中
① 註冊回撥介面
在Launcher中,很多元件都需要與LauncherModel建立聯絡,而這些聯絡多數是通過Launcher的主Activity(Launcher.java)來實現的,因此Launcher需要實現LauncherModel的回撥介面,LauncherModel在執行某個操作的時候,會將執行的過程和結果通過回撥的方式告訴Launcher,下面看下Launcher是如何將介面註冊到LauncherModel中。
Launcher的入口Activity在建立的時候,通過LauncherAppState的setLauncher方法開始註冊介面的過程,如下
mModel = app.setLauncher(this);
LauncherAppState的setLauncher方法除了完成註冊以外,還將LauncherModel例項的引用返回到Launcher。
在LauncherAppState的setLauncher方法中呼叫LauncherModel的initialize方法,將介面實現的引用註冊到LauncherModel中,並返回已經完成初始化的LauncherModel的引用。比較繞口 看程式碼就明白
LauncherModel setLauncher(Launcher launcher) {
getLauncherProvider().setLauncherProviderChangeListener(launcher);
mModel.initialize(launcher);
mAccessibilityDelegate = ((launcher != null) && Utilities.ATLEAST_LOLLIPOP) ?
new LauncherAccessibilityDelegate(launcher) : null;
return mModel;
}
在LauncherModel的initialize方法中,實際上只是使用Launcher的介面實現部分,並將介面實現的引用放在一個弱引用中,從而完成初始化過程
如下
public void initialize(Callbacks callbacks) {
synchronized (mLock) {
// Disconnect any of the callbacks and drawables associated with ItemInfos on the
// workspace to prevent leaking Launcher activities on orientation change.
unbindItemInfosAndClearQueuedBindRunnables();
mCallbacks = new WeakReference<Callbacks>(callbacks);
}
}
這樣Launcher和LauncherModel中的回撥方法就建立起了聯絡,LauncherModel的所有操作結果都會通過callbacks中定義的各個回撥介面中的方法通知給Launcher,並由Launcher分發給不同的桌面元件或者Launcher自身。
② 回撥介面的定義
上面瞭解了介面的註冊過程,接下來我們看下介面的具體內容。
在LauncherModel中定義了一個名叫callbacks的介面,它體現了Launchermodel在操作的過程中可能產生的中間過程以及動作,方便LauncherModel獲取實現方的執行狀態以決定其操作的過程。每個以介面的含義請參考這篇部落格
2、廣播介面
系統也可以通過傳送廣播來驅動LauncherModel工作,當LauncherModel接收到一些廣播的時候,會啟動介面重新整理。LauncherModel本身就是一個廣播接收器,根據Android的管理,LauncherModel通過自身實現的OnReceive方法來接收來自不同地方的廣播
public void onReceive(Context context, Intent intent) {
if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent);
final String action = intent.getAction();
if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
// If we have changed locale we need to clear out the labels in all apps/workspace.
forceReload();
} else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action)) {
Callbacks callbacks = getCallback();
if (callbacks != null) {
callbacks.bindSearchProviderChanged();
}
} else if (LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED.equals(action)
|| LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
UserManagerCompat.getInstance(context).enableAndResetCache();
forceReload();
}
}
對於LauncherModel來說,系統的語言配置屬性變化或者裝置中的mcc碼配置發生了改變,需要對桌面上的元件產生影響,例如,當語言配置屬性發生改變的時候,裝置上的顯示資訊需要更新成相應的語言,因此我們需要對Launcher管理的應用程式的快捷方式等桌面元件進行一次整體更新。
3、LauncherModel的應用程式級別介面
LauncherModel作為Launcher的樞紐,絕大多數時間是在處理Launcher應用程式通過呼叫LauncherModel的介面發來的請求,比如當第三方應用程式安裝通過介面往桌面上新增快捷方式的時候,當用戶安裝或者解除安裝某個應用程式的時候,以及當Launcher啟動初期時,都需要LauncherModel為Launcher應用程式提供必要的介面以及正確的資料。
下面按介面來看下他們的實現
① 啟動載入任務startLoader
這是一個用於指示LauncherModel進行桌面以及應用程式選單資料載入的介面。Launcher在啟動或者被恢復的時候,Launcher都會通過這個介面來進行資料載入。他的宣告如下
public void startLoader(int synchronousBindPage, int loadFlags) {};
它沒有返回值,因為在載入的過程中,會通過Launcher註冊的回撥介面得到資料處理的中間過程
輸入引數的含義
- synchronousBindPage 這是一個整型的輸入引數,呼叫方通過定製這個引數的值來指示LauncherModel載入對應的桌面頁資料,當輸入-1001時(在pageview中定義)將會對當前Launcher上維護的所有桌面頁上的資料進行載入。
loadFlags 這是一個整型的輸入引數,呼叫者通過這個引數來指示LauncherModel的載入任務應該執行的動作,LauncherModel提供了3中不同的選項
- LOADER_FLAG_CLEAR_WORKSPACE 用於指示LauncherModel對synchronousBindPage 引數指定的桌面資料進行清理
- LOADER_FLAG_MIGRATE_SHORTCUTS 用於指示LauncherModel對Launcher2的快捷方式資料進行遷移。
- LOADER_FLAG_NONE 用於指示LauncherModel對Launcher3的桌面資料進行一次載入或者重新整理。
public void startLoader(int synchronousBindPage, int loadFlags) {
// Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
InstallShortcutReceiver.enableInstallQueue();
synchronized (mLock) {
// 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();
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);
}
}
}
}
具體實現如上。
在Launcher內部,所有的桌面資料的操作請求都通過這個方法來處理,因此startLoader需要保證在多個請求併發的情況。正是基於這個原因LauncherModel的startLoader方法的方法體都被包含在一個同步塊中。
Launcher和LauncherModel之間的互動式是以類似訊息驅動的方式進行的。LauncherModel產生的訊息通過Launcher註冊到LauncherModel中的回撥介面,並得到通知。而當LauncherModel完成一次載入的時候,通過finishBindingItems介面通知Launcher載入已經完成,如果當前只是對桌面的某一個頁面進行資料重新整理,那麼這個介面的呼叫會被封裝成為一個任務載入到一個訊息佇列中,等待後續所有的任務完成後才統一執行,如果這個任務還沒有得到執行,而新的重新整理頁面的請求已經到來,那麼LauncherModel在啟動載入之前會將訊息佇列清空,以確保所在任務都執行完成後。Launcher才會得到通知具體實現如下:
synchronized (mDeferredBindRunnables) {
mDeferredBindRunnables.clear();
}
mDeferredBindRunnables是一個Runable的列表,當LauncherModel的載入任務完成後,這裡將會儲存發往Launcher的通知,封裝在一個Runable中加入該列表。
接下來LauncherModel將決策載入任務(LoaderTask)的執行方式是即時執行環視執行緒排程執行。
如果LauncherModel接收到的是對指定頁面的重新整理任務,LauncherModel還需要對應用程式選單及桌面資料的載入完成情況做判斷,如果他們的資料都已經載入完成則當前載入任務將會即時執行。
mLoaderTask.runBindSynchronousPage(synchronousBindPage);
如果當前選擇的是對所有桌面頁資料進行載入這類大量資料的請求,或者桌面以及應用程式選單的資料的載入工作並未完成,那麼這個任務將會被放置在lauModel建立的執行緒的執行緒佇列中等待執行
sWorkerThread.setPriority(Thread.NORM_PRIORITY);
sWorker.post(mLoaderTask);
② 停止載入
LauncherModel在需要停止載入任務的時候通過呼叫stopLoaderLocked方法來完成。
private void stopLoaderLocked() {
LoaderTask oldTask = mLoaderTask;
if (oldTask != null) {
oldTask.stopLocked();
}
}
它是LauncherModel的一個工具不對外公開,不需要任何引數,只負責將正在執行的任務停掉。
③ LauncherModel的工作執行緒
在LauncherModel中定義了一個靜態執行緒變數sWorkerThread,所有需要等待載入的任務都被拋到該執行緒的佇列中等待處理
@Thunk static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
static {
sWorkerThread.start();
}
這樣設計能確保sWorkerThread 在LauncherModel類被載入的時候就已經存在,並處於執行狀態。
另外HandlerThread是Thread的一個子類,這種型別的執行緒能確保訊息佇列中的每個請求按照先進先出的順序執行。
在啟動LauncherModel的工作執行緒後,緊接著LauncherModel還接管了工作執行緒的訊息佇列 如下:
@Thunk static final Handler sWorker = new Handler(sWorkerThread.getLooper());
當完成以上一系列動作後,LauncherModel就完成了對工作執行緒的初始化,這個執行緒將會一直處於訊息等待狀態,等待每一個重新整理請求,支援LauncherModel的工作執行緒建立並且啟動完成。
④ LauncherModel的主執行緒處理器
LauncherModel的sWorkerThread是主執行緒下的一個子執行緒,它負責接收來自Launcher應用程式元件的重新整理請求,對於sWorkerThread而言,在處理任務的同時也需要與Launcher有所互動,比如sWorkerThread需要將處理程序訊息通過Launcher註冊到LauncherModel中的介面通知Launcher,以便Launcher做出必要的響應。
那麼現在的問題就是Launcher和LauncherModel執行在Launcher這個應用程式的主執行緒中(這個執行緒由Android框架維護) sWorkerThread只是Launcher應用程式主執行緒下的一個子執行緒,對於執行緒和執行緒之間的訊息互動,一個比較好的方案是將任務拋到目標執行緒的處理器中,為此,LauncherModel為sWorkerThread在主執行緒中建立了一個處理器,以實現sWorkerThread和Launcher所在程序之間的資訊互動。程式碼如下
DeferredHandler mHandler = new DeferredHandler();
這裡引入了一個定義在LauncherModel外面而在LauncherModel中使用的執行緒處理器工具DeferredHandler。 接下來看下它的實現原理
DeferredHandler被當做主執行緒的處理器例項化在LauncherModel中,作為處理器,它需要滿足通用處理器的一些定製化特性,因此它定義了一些巢狀類來滿足這些特性。
Impl類
它首先是被定義在DeferredHandler的一個巢狀類。
class Impl extends Handler implements MessageQueue.IdleHandler {
作為handler的子類,它主要被用於處理髮往主執行緒的訊息。
IdleRunnable類
IdleRunnable是DeferredHandler內部的另外一個巢狀類,它是一個實現了Run那邊了介面的類
private class IdleRunnable implements Runnable {
Runnable mRunnable;
IdleRunnable(Runnable r) {
mRunnable = r;
}
public void run() {
mRunnable.run();
}
}
由它的建構函式以及實現的run介面可知,它實際上只是Runnable的簡單實現。
這些類的具體使用我們後續繼續分析。
好了 今天主要看了LauncherModel的建立,例項化(建構函式)和與LauncherModel的通訊方式。
終於可以歇口氣了。