壁紙服務的啓動過程

壁紙基礎

android中的壁紙分爲動態壁紙和靜態壁紙兩種,兩種類型的壁紙都以Service的類型運行在系統後臺。

  • 靜態壁紙:僅以圖片的形式進行展示
    對於靜態壁紙,可以使用WallpaperManager中的getDrawable()等接口獲取到當前的bitmap圖像。
  • 動態壁紙:顯示的內容爲動態的內容,同時可以對用戶的操作做出響應
    對於動態壁紙的實時圖像,是沒辦法通過android中原生的接口獲取到,需要獲取到動態壁紙的圖像得自己修改源碼。

壁紙實現時涉及的幾個主要的類:

  • WallpaperService及其內部類Engine:壁紙在WallpaperService這個服務中運行,當需要實現自己的壁紙時,繼承和實現這個類,是首先需要做的。Engine是WallpaperService中的一個內部類,實現了壁紙服務窗口的創建以及Surface的維護,同時Engine內部類還提供了onVisibilityChanged(),onCommand()等回調方法,用於可見狀態變化和用戶觸摸事件等。Engine類因此也是壁紙實現的核心類,實現和重寫其接口在開發中也相當重要。
  • WallpaperManagerService和WallpaperManager:WallpaperManagerService用於管理壁紙的運行與切換,並通過WallpaperManager對外界提供操作壁紙的接口。
  • WindowMangerService:該類用於計算壁紙窗口的Z序,可見性以及爲壁紙窗口應用動畫。

壁紙服務的兩種啓動場景

非首次重啓壁紙服務啓動流程

SystemService進程啓動時,會啓動各種系統服務。在該類的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()方法加載用戶設置過的壁紙信息,然後監聽用戶切換用戶switchUser(),切換用戶時,switchWallpaper()會調用bindWallpaperComponentLocked()方法拉起對應的壁紙服務。

手動切換時壁紙服務的啓動流程

手動切換壁紙服務時需要通過WallpaperManager.getIWallpaperManager().setWallpaperComponent()方法完成,我們在這個接口中傳入壁紙服務對應的ComponentName,getIWallpaperManager返回的是WallpaperManagerService的Bp(binder proxy binder代理)端,在WallpaperManagerService端,我們可以查看到setWallpaperComponent的具體實現,

private void setWallpaperComponent(ComponentName name, int userId) {
   
   
        ...
        /* 首先調用該方法的時候回去校驗權限,該權限定義在frameworks/base/core/res/AndroidManifest.xml,
            <permission android:name="android.permission.SET_WALLPAPER_COMPONENT"
        android:protectionLevel="signature|privileged" />
        查看protectionLevel,只有是特權應用或者系統簽名的應用才能獲取到這個系統權限,所以普通的應用是沒有辦法進行壁紙設置的
        */
        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" />
			  該權限也是系統級別的,防止三方應用切換壁紙,
			*/
            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信息中提供了縮略圖,開發者,簡單的描述等。會將這些信息轉換成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
			...
    }

上面的校驗可以看出一共校驗了三個條件:

  1. 啓動的時候首先會校驗這個壁紙服務是否聲明權限爲BIND_WALLPAPER權限, 該權限的定義在fwk/base/core/res/manifest.xml
    < permission android:name=“android.permission.BIND_WALLPAPER”
    android:protectionLevel=“signature|privileged” />
    該權限也是系統級別的,防止三方應用切換壁紙。


  2. 這個檢查來校驗服務是否聲明瞭android.service.wallpaper.WallpaperService這個action。如果這個服務沒有聲明這個action的話那麼,ris中就不會含有這個component信息。
  3. 獲取名爲android.service.wallpaper中的meta-data信息,該meta-data信息中提供了縮略圖,開發者,簡單的描述等。會將這些信息轉換成WallpaperInfo。

2.綁定壁紙服務

壁紙服務的校驗滿足後,開始啓動和綁定目標服務:

    private boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force,
            boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) {
   
   
	// 校驗服務是否符合要求結束後,開始着手啓動服務
	...
	//1. 創建一個WallpaperConnection,該對象可以監聽和WallpaperService之間的連接狀態,同時歸對象繼承了IWallpaperConnection.Stub,這樣該對象有擁有了跨進程通信的能力,當服務綁定成功後,onServiceConnected()方法調用中,WallpaperConnection實力會被髮送到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函數在拉起壁紙服務的時候主要做了下面幾件事情:

  • 創建了WallpaperConnection對象,由於實現了ServiceConnection接口,所以WallpaperConnection可以用來監聽和壁紙服務的連接狀態,另外由於繼承了IWallpoaperConnection.Stub接口,所以WallpaperConnection具有了跨進程通信的能力。
  • 啓動壁紙服務:這裏僅僅是拉起服務,和拉起普通服務的方式基本一致,拉起方式上則使用了bindServiceAsUser,查看官方註解,該接口增加了校驗該用戶是否能拉起該服務,其餘的行爲和bindService相同。
  • 保存當前WallpaperConnection實例,ConponentName,到WallpaperData中

bindWallpaperComponentLocked()函數將壁紙服務拉了起來,但是僅僅將壁紙服務拉起來是沒有辦法顯示圖像的,因爲啓動的服務並沒有窗口令牌,這樣就沒辦法添加窗口。剩下的這部分顯示的工作在WallpaperConnection的onServiceConnected()方法中進行,在該回調中同樣也能拿到壁紙服務端服務端提供的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接口在創建的時候可以將相關信息傳遞到壁紙服務中,對應的,detach接口在服務銷燬的時候調用。

3.引擎的創建和初始化

引擎的創建準備工作開始於onServiceConnected()回調處,該回調會傳遞壁紙服務需要的窗口令牌和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)) {
   
   
                    	// 保存當前的壁紙信息到文件系統中,這樣重啓的時候就可以加載之前用戶設置過的壁紙
                        saveSettingsLocked(mWallpaper.userId);
                    }
                    FgThread.getHandler().removeCallbacks(mResetRunnable);
                    mContext.getMainThreadHandler().removeCallbacks(mTryToRebindRunnable);
                }
            }
        }

onServiceConnected()函數中,首先將返回的binder對象進行了保存,然後在attachServiceLocked()方法中會調用connectLocked()方法,connectLocked()接口中調用了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實現纔是動態壁紙的核心,WallpaperService會將創建好的Engine引用通過attachEngine()回傳給WallpaperManagerService進行管理。
  • mToken是向WMS註冊過的窗口令牌,只有擁有了這個令牌,WallpaperService纔有權添加壁紙窗口。

傳遞了WallpaperService需要的信息之後,WallPaperService開始進行引擎的創建。查看WallpaperService中attach()方法的實現,

    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方法創建了一個IWallpaperEngineWrapper,顧名思義,該對象有壁紙服務創建的引擎的引用,在創建IWallpaperEngineWrapper對象的時候,會發送DO_ATTACH消息,該消息用於壁紙服務引擎的創建,

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;
                    }
                    // 創建一個引擎,該方法爲抽象方法,需要子類根據自身實現具體的引擎
                    Engine engine = onCreateEngine();
                    mEngine = engine;
                    mActiveEngines.add(engine);
					// 該方法中會完成窗口的創建,surface創建等工作。
                    engine.attach(this);
                    return;
                }

由於mConnection.attachEngine()方法將IWallpaperEngineWrapper傳遞給了WallpaperManagerService,因此WallpaperManagerService可以轉發相關的請求和設置到Engine對象中,實現WallpaperManagerService到壁紙的通信。
onCreateEngine方法執行後,引擎創建完成,之後通過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發送過來的消息
            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相關的屬性,這個時候
            // 窗口尚未創建。設置的相關屬性將在updateSurface中創建窗口時使用
            onCreate(mSurfaceHolder);

            mInitializing = false;

            mReportedVisible = false;
			
			// updateSurface會進行窗口以及Surface的創建。
            updateSurface(false, false, false);
        }

attach方法執行的完成,標誌着壁紙啓動的完成,之後可以調用壁紙的surface顯示圖像。

壁紙服務的啓動流程總結

壁紙服務的啓動相比於普通服務的啓動較爲複雜,接下來用下面的示意圖對整體的流程進行梳理:
在這裏插入圖片描述
壁紙服務在啓動的時候,大體可以分爲兩個階段,首先就要是拉起對應的服務,拉起服務後然後將WindowToken等參數傳遞給引擎進行窗口的創建,surface的創建。在WallpaperManagerService和WallpaperService交互的過程中,主要有下面三個跨進程通信的Binder對象:

  • WallpaperConnection:實現在WallpaperManagerService中,並通過IWallpaperService.attach回調傳遞給了IWallpaperEngineWrapper,通過WallpaperConnection.attachEngine()方法,WallpaperService將IWallpaperEngineWrapper回傳給了WallpaperManagerService,實現了雙向的通信。
  • IWallpaperService:實現在WallpaperService中,該對象提供了attach方法,用於從WallpaperManagerService獲取引擎創建時需要的WindowToken等信息。
  • IWallpaperEngineWrapper:實現在壁紙服務進程中,同時引用交給了WallpaperManagerService,該對象封裝了Engine類,WallpaperManagerService對引擎相關的控制需要通過該對象提供的接口實現。

自己最近因爲需要定位一個開機壁紙服務啓動慢的問題,所以熟悉了下壁紙服務的啓動過程,在此記錄啓動流程。定位該問題梳理從bind到onServiceConnected和引擎相關初始化,對比每一個階段的時間,最終確定問題的原因。
參考:《深入理解Android卷3》第八章:深入理解android壁紙服務

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章