詳解Android壁紙服務的啟動過程
壁紙基礎
android中的壁紙分為動態(tài)壁紙和靜態(tài)壁紙兩種,兩種類型的壁紙都以Service的類型運行在系統(tǒng)后臺。
- 靜態(tài)壁紙:僅以圖片的形式進行展示對于靜態(tài)壁紙,可以使用WallpaperManager中的getDrawable()等接口獲取到當前的bitmap圖像。
- 動態(tài)壁紙:顯示的內(nèi)容為動態(tài)的內(nèi)容,同時可以對用戶的操作做出響應對于動態(tài)壁紙的實時圖像,是沒辦法通過android中原生的接口獲取到,需要獲取到動態(tài)壁紙的圖像得自己修改源碼。
壁紙實現(xiàn)時涉及的幾個主要的類:
- WallpaperService及其內(nèi)部類Engine:壁紙在WallpaperService這個服務中運行,當需要實現(xiàn)自己的壁紙時,繼承和實現(xiàn)這個類,是首先需要做的。Engine是WallpaperService中的一個內(nèi)部類,實現(xiàn)了壁紙服務窗口的創(chuàng)建以及Surface的維護,同時Engine內(nèi)部類還提供了onVisibilityChanged(),onCommand()等回調(diào)方法,用于可見狀態(tài)變化和用戶觸摸事件等。Engine類因此也是壁紙實現(xiàn)的核心類,實現(xiàn)和重寫其接口在開發(fā)中也相當重要。
- WallpaperManagerService和WallpaperManager:WallpaperManagerService用于管理壁紙的運行與切換,并通過WallpaperManager對外界提供操作壁紙的接口。
- WindowMangerService:該類用于計算壁紙窗口的Z序,可見性以及為壁紙窗口應用動畫。
壁紙服務的兩種啟動場景
非首次重啟壁紙服務啟動流程
SystemService進程啟動時,會啟動各種系統(tǒng)服務。在該類的startOtherServices()方法中會首先拉起
WallpaperManagerService,通過該類,WallpaperService后面才得以啟動。
if (context.getResources().getBoolean(R.bool.config_enableWallpaperService)) { t.traceBegin("StartWallpaperManagerService"); mSystemServiceManager.startService(WALLPAPER_SERVICE_CLASS); t.traceEnd(); } else { Slog.i(TAG, "Wallpaper service disabled by config"); }
WallpaperManagerService啟動之后systemReady()方法中會通過loadSettingsLocked()方法加載用戶設置過的壁紙信息,然后監(jiān)聽用戶切換用戶switchUser(),切換用戶時,switchWallpaper()會調(diào)用bindWallpaperComponentLocked()方法拉起對應的壁紙服務。
手動切換時壁紙服務的啟動流程
手動切換壁紙服務時需要通過WallpaperManager.getIWallpaperManager().setWallpaperComponent()方法完成,我們在這個接口中傳入壁紙服務對應的ComponentName,getIWallpaperManager返回的是WallpaperManagerService的Bp(binder proxy binder代理)端,在WallpaperManagerService端,我們可以查看到setWallpaperComponent的具體實現(xiàn),
private void setWallpaperComponent(ComponentName name, int userId) { ... /* 首先調(diào)用該方法的時候回去校驗權限,該權限定義在frameworks/base/core/res/AndroidManifest.xml, <permission android:name="android.permission.SET_WALLPAPER_COMPONENT" android:protectionLevel="signature|privileged" /> 查看protectionLevel,只有是特權應用或者系統(tǒng)簽名的應用才能獲取到這個系統(tǒng)權限,所以普通的應用是沒有辦法進行壁紙設置的 */ checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT); int which = FLAG_SYSTEM; boolean shouldNotifyColors = false; WallpaperData wallpaper; synchronized (mLock) { Slog.v(TAG, "setWallpaperComponent name=" + name); /* 此處會先通過當前的用戶ID獲取到與該用戶相關的壁紙信息,WallpaperManagerService支持多用戶機制,用戶的信息在mWallpaperMap中存儲,每一個用戶對應一個WallpaperData,WallpaperData存儲壁紙相關信息 */ wallpaper = mWallpaperMap.get(userId); if (wallpaper == null) { throw new IllegalStateException("Wallpaper not yet initialized for user " + userId); } ... // 在這里真正會去拉起對應的WallPaperService if (bindWallpaperComponentLocked(name, false, true, wallpaper, null)) { ... }
setWallpaperComponent最終也是通過bindWallpaperComponentLocked拉起壁紙服務
壁紙服務啟動過程
1.校驗是否是壁紙服務
bindWallpaperComponentLocked()方法將會啟動該ComponentName所指定的WallpaperService,在啟動的時候首先會進行校驗,以確定待拉起的服務是一個壁紙服務,
private boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force, boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) { ... int serviceUserId = wallpaper.userId; ServiceInfo si = mIPackageManager.getServiceInfo(componentName, PackageManager.GET_META_DATA | PackageManager.GET_PERMISSIONS, serviceUserId); if (si == null) { // The wallpaper component we're trying to use doesn't exist Slog.w(TAG, "Attempted wallpaper " + componentName + " is unavailable"); return false; } /* 第一個校驗: 啟動的時候首先會校驗這個壁紙服務是否聲明權限為BIND_WALLPAPER權限, 該權限的定義同樣也在fwk/base/core/res/manifest.xml <permission android:name="android.permission.BIND_WALLPAPER" android:protectionLevel="signature|privileged" /> 該權限也是系統(tǒng)級別的,防止三方應用切換壁紙, */ if (!android.Manifest.permission.BIND_WALLPAPER.equals(si.permission)) { String msg = "Selected service does not have " + android.Manifest.permission.BIND_WALLPAPER + ": " + componentName; if (fromUser) { throw new SecurityException(msg); } Slog.w(TAG, msg); return false; } WallpaperInfo wi = null; Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE); if (componentName != null && !componentName.equals(mImageWallpaper)) { // Make sure the selected service is actually a wallpaper service. /* 第二個校驗: 這個檢查來校驗服務是否聲明了android.service.wallpaper.WallpaperService這個action。如果這個服務沒有聲明這個action的話那么,ris中就不會含有這個component信息, */ List<ResolveInfo> ris = mIPackageManager.queryIntentServices(intent, intent.resolveTypeIfNeeded(mContext.getContentResolver()), PackageManager.GET_META_DATA, serviceUserId).getList(); for (int i=0; i<ris.size(); i++) { ServiceInfo rsi = ris.get(i).serviceInfo; if (rsi.name.equals(si.name) && rsi.packageName.equals(si.packageName)) { try { /* 第三個檢查: 獲取名為android.service.wallpaper中的meta-data信息,該meta-data信息中提供了縮略圖,開發(fā)者,簡單的描述等。會將這些信息轉(zhuǎn)換成WallpaperInfo */ wi = new WallpaperInfo(mContext, ris.get(i)); } catch (XmlPullParserException e) { if (fromUser) { throw new IllegalArgumentException(e); } Slog.w(TAG, e); return false; } catch (IOException e) { if (fromUser) { throw new IllegalArgumentException(e); } Slog.w(TAG, e); return false; } break; } } if (wi == null) { String msg = "Selected service is not a wallpaper: " + componentName; if (fromUser) { throw new SecurityException(msg); } Slog.w(TAG, msg); return false; } } // 當壁紙服務支持在ambient模式下進行繪制的時候,需要檢查是否有AMBIENT_WALLPAPER權限, if (wi != null && wi.supportsAmbientMode()) { final int hasPrivilege = mIPackageManager.checkPermission( android.Manifest.permission.AMBIENT_WALLPAPER, wi.getPackageName(), serviceUserId); if (hasPrivilege != PackageManager.PERMISSION_GRANTED) { String msg = "Selected service does not have " + android.Manifest.permission.AMBIENT_WALLPAPER + ": " + componentName; if (fromUser) { throw new SecurityException(msg); } Slog.w(TAG, msg); return false; } } // 檢驗完畢,這里才會開始bind 壁紙服務,如果校驗失敗的話,會返回false ... }
上面的校驗可以看出一共校驗了三個條件:
- 啟動的時候首先會校驗這個壁紙服務是否聲明權限為BIND_WALLPAPER權限, 該權限的定義在fwk/base/core/res/manifest.xml中,< permission android:name=“android.permission.BIND_WALLPAPER”
android:protectionLevel=“signature|privileged” />
該權限也是系統(tǒng)級別的,防止三方應用切換壁紙。 - 這個檢查來校驗服務是否聲明了android.service.wallpaper.WallpaperService這個action。如果這個服務沒有聲明這個action的話那么,ris中就不會含有這個component信息。
- 獲取名為android.service.wallpaper中的meta-data信息,該meta-data信息中提供了縮略圖,開發(fā)者,簡單的描述等。會將這些信息轉(zhuǎn)換成WallpaperInfo。
2.綁定壁紙服務
壁紙服務的校驗滿足后,開始啟動和綁定目標服務:
private boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force, boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) { // 校驗服務是否符合要求結(jié)束后,開始著手啟動服務 ... //1. 創(chuàng)建一個WallpaperConnection,該對象可以監(jiān)聽和WallpaperService之間的連接狀態(tài),同時歸對象繼承了IWallpaperConnection.Stub,這樣該對象有擁有了跨進程通信的能力,當服務綁定成功后,onServiceConnected()方法調(diào)用中,WallpaperConnection實力會被發(fā)送到WallpaperService,該實例可以用于WallpaperService想WallpaperManagerService進行通信的橋梁。 intent.setComponent(componentName); intent.putExtra(Intent.EXTRA_CLIENT_LABEL, com.android.internal.R.string.wallpaper_binding_label); ... /* 2. 這里啟動指定的壁紙服務,服務啟動后,壁紙還沒有辦法進行顯示,還需要WallpaperConnection.onServiceConnected中進行相應的處理*/ if (!mContext.bindServiceAsUser(intent, newConn,Context.BIND_AUTO_CREATE | Context.BIND_SHOWING_UI| Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE | Context.BIND_INCLUDE_CAPABILITIES,new UserHandle(serviceUserId))) { } /*3. 新的壁紙服務啟動之后,就開始銷毀舊服務*/ if (wallpaper.userId == mCurrentUserId && mLastWallpaper != null && !wallpaper.equals(mFallbackWallpaper)) { detachWallpaperLocked(mLastWallpaper); } /* 4.將新的壁紙服務信息進行保存*/ wallpaper.wallpaperComponent = componentName; wallpaper.connection = newConn; ...
bindWallpaperComponentLocked函數(shù)在拉起壁紙服務的時候主要做了下面幾件事情:
- 創(chuàng)建了WallpaperConnection對象,由于實現(xiàn)了ServiceConnection接口,所以WallpaperConnection可以用來監(jiān)聽和壁紙服務的連接狀態(tài),另外由于繼承了IWallpoaperConnection.Stub接口,所以WallpaperConnection具有了跨進程通信的能力。
- 啟動壁紙服務:這里僅僅是拉起服務,和拉起普通服務的方式基本一致,拉起方式上則使用了bindServiceAsUser,查看官方注解,該接口增加了校驗該用戶是否能拉起該服務,其余的行為和bindService相同。
- 保存當前WallpaperConnection實例,ConponentName,到WallpaperData中
bindWallpaperComponentLocked()函數(shù)將壁紙服務拉了起來,但是僅僅將壁紙服務拉起來是沒有辦法顯示圖像的,因為啟動的服務并沒有窗口令牌,這樣就沒辦法添加窗口。剩下的這部分顯示的工作在WallpaperConnection的onServiceConnected()方法中進行,在該回調(diào)中同樣也能拿到壁紙服務端服務端提供的Binder對象。
WallpaperService在被bind的時候返回了一個IWallpaperServiceWrapper對象,從代碼中可以看到,該對象中保存了WallpaperService實例,看了代碼后再去理解這個對象的命名(包裝WallpaperService),果然名副其實。
class IWallpaperServiceWrapper extends IWallpaperService.Stub { private final WallpaperService mTarget; private IWallpaperEngineWrapper mEngineWrapper; public IWallpaperServiceWrapper(WallpaperService context) { mTarget = context; } @Override public void attach(IWallpaperConnection conn, IBinder windowToken, int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding,int displayId) { ... } @Override public void detach() { ... } }
該接口中一共有兩個接口,attach和detach,attach接口在創(chuàng)建的時候可以將相關信息傳遞到壁紙服務中,對應的,detach接口在服務銷毀的時候調(diào)用。
3.引擎的創(chuàng)建和初始化
引擎的創(chuàng)建準備工作開始于onServiceConnected()回調(diào)處,該回調(diào)會傳遞壁紙服務需要的窗口令牌和ServiceConnection對象等。
WallpaperManagerService.java
@Override public void onServiceConnected(ComponentName name, IBinder service) { synchronized (mLock) { if (mWallpaper.connection == this) { mService = IWallpaperService.Stub.asInterface(service); attachServiceLocked(this, mWallpaper); // XXX should probably do saveSettingsLocked() later // when we have an engine, but I'm not sure about // locking there and anyway we always need to be able to // recover if there is something wrong. if (!mWallpaper.equals(mFallbackWallpaper)) { // 保存當前的壁紙信息到文件系統(tǒng)中,這樣重啟的時候就可以加載之前用戶設置過的壁紙 saveSettingsLocked(mWallpaper.userId); } FgThread.getHandler().removeCallbacks(mResetRunnable); mContext.getMainThreadHandler().removeCallbacks(mTryToRebindRunnable); } } }
onServiceConnected()函數(shù)中,首先將返回的binder對象進行了保存,然后在attachServiceLocked()方法中會調(diào)用connectLocked()方法,connectLocked()接口中調(diào)用了attach方法傳遞了壁紙服務所需要的信息。
void connectLocked(WallpaperConnection connection, WallpaperData wallpaper) { ... mWindowManagerInternal.addWindowToken(mToken, TYPE_WALLPAPER, mDisplayId); final DisplayData wpdData = getDisplayDataOrCreate(mDisplayId); try { connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false, wpdData.mWidth, wpdData.mHeight, wpdData.mPadding, mDisplayId); } ... }
attach接口回傳了許多信息,其中
- connection為WallpaperConnection的實例。WallpaperConnection之所以具有跨進程通信的能力是因為繼承了IWallpaperConnection.Stub類,該Stub對象中比較重要的一個接口就是attachEngine(),因為Engine實現(xiàn)才是動態(tài)壁紙的核心,WallpaperService會將創(chuàng)建好的Engine引用通過attachEngine()回傳給WallpaperManagerService進行管理。
- mToken是向WMS注冊過的窗口令牌,只有擁有了這個令牌,WallpaperService才有權添加壁紙窗口。
傳遞了WallpaperService需要的信息之后,WallPaperService開始進行引擎的創(chuàng)建。查看WallpaperService中attach()方法的實現(xiàn),
class IWallpaperServiceWrapper extends IWallpaperService.Stub { ... @Override public void attach(IWallpaperConnection conn, IBinder windowToken, int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding, int displayId) { mEngineWrapper = new IWallpaperEngineWrapper(mTarget, conn, windowToken, windowType, isPreview, reqWidth, reqHeight, padding, displayId); } ... }
attach方法創(chuàng)建了一個IWallpaperEngineWrapper,顧名思義,該對象有壁紙服務創(chuàng)建的引擎的引用,在創(chuàng)建IWallpaperEngineWrapper對象的時候,會發(fā)送DO_ATTACH消息,該消息用于壁紙服務引擎的創(chuàng)建,
IWallpaperEngineWrapper.java IWallpaperEngineWrapper(WallpaperService context, IWallpaperConnection conn, IBinder windowToken, int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding, int displayId) { mCaller = new HandlerCaller(context, context.getMainLooper(), this, true); ... Message msg = mCaller.obtainMessage(DO_ATTACH); mCaller.sendMessage(msg); } ... @Override public void executeMessage(Message message) { switch (message.what) { case DO_ATTACH: { try { // 將IWallpaperEngineWapper對象傳遞給WallpaperConnection進行保存,通過這個引用,WallpaperManagerService也可以通過它與engine進行通信 mConnection.attachEngine(this, mDisplayId); } catch (RemoteException e) { Log.w(TAG, "Wallpaper host disappeared", e); return; } // 創(chuàng)建一個引擎,該方法為抽象方法,需要子類根據(jù)自身實現(xiàn)具體的引擎 Engine engine = onCreateEngine(); mEngine = engine; mActiveEngines.add(engine); // 該方法中會完成窗口的創(chuàng)建,surface創(chuàng)建等工作。 engine.attach(this); return; }
由于mConnection.attachEngine()方法將IWallpaperEngineWrapper傳遞給了WallpaperManagerService,因此WallpaperManagerService可以轉(zhuǎn)發(fā)相關的請求和設置到Engine對象中,實現(xiàn)WallpaperManagerService到壁紙的通信。
onCreateEngine方法執(zhí)行后,引擎創(chuàng)建完成,之后通過engine.attach()方法進行引擎相關的初始化:
void attach(IWallpaperEngineWrapper wrapper) { ... mIWallpaperEngine = wrapper; mCaller = wrapper.mCaller; mConnection = wrapper.mConnection; mWindowToken = wrapper.mWindowToken; mSurfaceHolder.setSizeFromLayout(); mInitializing = true; // 這個session用于和WMS進行通信 mSession = WindowManagerGlobal.getWindowSession(); // mWindow是一個IWindow對象,用于接收從WMS發(fā)送過來的消息 mWindow.setSession(mSession); mLayout.packageName = getPackageName(); mIWallpaperEngine.mDisplayManager.registerDisplayListener(mDisplayListener, mCaller.getHandler()); mDisplay = mIWallpaperEngine.mDisplay; mDisplayContext = createDisplayContext(mDisplay); mDisplayState = mDisplay.getState(); if (DEBUG) Log.v(TAG, "onCreate(): " + this); // 子類可以重寫該接口,在該接口中可以修改mSurfaceHolder相關的屬性,這個時候 // 窗口尚未創(chuàng)建。設置的相關屬性將在updateSurface中創(chuàng)建窗口時使用 onCreate(mSurfaceHolder); mInitializing = false; mReportedVisible = false; // updateSurface會進行窗口以及Surface的創(chuàng)建。 updateSurface(false, false, false); }
attach方法執(zhí)行的完成,標志著壁紙啟動的完成,之后可以調(diào)用壁紙的surface顯示圖像。
壁紙服務的啟動流程總結(jié)
壁紙服務的啟動相比于普通服務的啟動較為復雜,接下來用下面的示意圖對整體的流程進行梳理:
壁紙服務在啟動的時候,大體可以分為兩個階段,首先就要是拉起對應的服務,拉起服務后然后將WindowToken等參數(shù)傳遞給引擎進行窗口的創(chuàng)建,surface的創(chuàng)建。在WallpaperManagerService和WallpaperService交互的過程中,主要有下面三個跨進程通信的Binder對象:
- WallpaperConnection:實現(xiàn)在WallpaperManagerService中,并通過IWallpaperService.attach回調(diào)傳遞給了IWallpaperEngineWrapper,通過WallpaperConnection.attachEngine()方法,WallpaperService將IWallpaperEngineWrapper回傳給了WallpaperManagerService,實現(xiàn)了雙向的通信。
- IWallpaperService:實現(xiàn)在WallpaperService中,該對象提供了attach方法,用于從WallpaperManagerService獲取引擎創(chuàng)建時需要的WindowToken等信息。
- IWallpaperEngineWrapper:實現(xiàn)在壁紙服務進程中,同時引用交給了WallpaperManagerService,該對象封裝了Engine類,WallpaperManagerService對引擎相關的控制需要通過該對象提供的接口實現(xiàn)。
自己最近因為需要定位一個開機壁紙服務啟動慢的問題,所以熟悉了下壁紙服務的啟動過程,在此記錄啟動流程。定位該問題梳理從bind到onServiceConnected和引擎相關初始化,對比每一個階段的時間,最終確定問題的原因。
參考:
- 《深入理解Android卷3》第八章:深入理解android壁紙服務
- framework-base源碼
到此這篇關于詳解Android壁紙服務的啟動過程的文章就介紹到這了,更多相關Android壁紙內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Android ViewDragHelper仿淘寶拖動加載效果
這篇文章主要為大家詳細介紹了Android ViewDragHelper仿淘寶拖動加載效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-08-08Android中FoldingLayout折疊布局的用法及實戰(zhàn)全攻略
這篇文章主要介紹了Android中FoldingLayout折疊布局的用法及實例,通過FoldingLayout我們可以制作出炫酷的菜單折疊效果,文中的例子講解得非常詳細,需要的朋友可以參考下2016-02-02Android使用Intent啟動其他非系統(tǒng)應用程序的方法
這篇文章主要介紹了Android使用Intent啟動其他非系統(tǒng)應用程序的方法,實例分析了Intent調(diào)用系統(tǒng)應用程序的相關技巧,需要的朋友可以參考下2015-12-12Android開發(fā)中TextView各種常見使用方法小結(jié)
這篇文章主要介紹了Android開發(fā)中TextView各種常見使用方法,結(jié)合實例形式總結(jié)分析了Android開發(fā)中TextView各種常見布局與功能實現(xiàn)技巧,需要的朋友可以參考下2019-04-04一文了解Android?ViewModelScope?如何自動取消協(xié)程
這篇文章主要介紹了一文了解Android?ViewModelScope?如何自動取消協(xié)程,文章圍繞主題站展開詳細的內(nèi)容介紹,具有一定參考價值,感興趣的小伙伴可以參考一下2022-07-07詳談Android動畫效果translate、scale、alpha、rotate
下面小編就為大家?guī)硪黄斦凙ndroid動畫效果translate、scale、alpha、rotate。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-01-01Android實現(xiàn)在TextView文字過長時省略部分或滾動顯示的方法
這篇文章主要介紹了Android實現(xiàn)在TextView文字過長時省略部分或滾動顯示的方法,結(jié)合實例形式分析了Android中TextView控件文字顯示及滾動效果相關操作技巧,需要的朋友可以參考下2016-10-10