1. 程式人生 > >Android桌布管理(Android N)

Android桌布管理(Android N)

    boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force,
            boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) {
        if (DEBUG) Slog.v(TAG, "bindWallpaperComponentLocked: componentName=" + componentName);
        // Has the component changed?
        if (!force) {
            if (wallpaper.connection != null) {
                if (wallpaper.wallpaperComponent == null) {
                    if (componentName == null) {
                        if (DEBUG) Slog.v(TAG, "bindWallpaperComponentLocked: still using default");
                        // Still using default wallpaper.
                        return true;
                    }
                } else if (wallpaper.wallpaperComponent.equals(componentName)) {
                    // Changing to same wallpaper.
                    if (DEBUG) Slog.v(TAG, "same wallpaper");
                    return true;
                }
            }
        }

        try {
            if (componentName == null) {//當componentName為null時,使用預設桌布。
                // 這裡將componentName改為預設桌布的componentName
                componentName = WallpaperManager.getDefaultWallpaperComponent(mContext);
                if (componentName == null) {//如果沒有找到預設桌布,則使用ImageWallpaper(靜態桌布)代替預設桌布
                    // Fall back to static image wallpaper
                    componentName = mImageWallpaper;
                    //clearWallpaperComponentLocked();
                    //return;
                    if (DEBUG) Slog.v(TAG, "Using image wallpaper");
                }
            }
            // WallpaperManagerService從PackageManager中獲取ComponentName指定的Service資訊,
            // 獲取此資訊的目的在於確認該Service是一個服務要求的桌布服務.
            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;
            }
            // 首先,要求這個service必須宣告其訪問許可權BIND_WALLPAPER,用於防止桌布服務被第三方應用程式啟動而產生混亂
            if (!android.Manifest.permission.BIND_WALLPAPER.equals(si.permission)) {
                String msg = "Selected service does not require "
                        + android.Manifest.permission.BIND_WALLPAPER
                        + ": " + componentName;
                if (fromUser) {
                    throw new SecurityException(msg);
                }
                Slog.w(TAG, msg);
                return false;
            }

            WallpaperInfo wi = null;

            // 其次,要求這個service必須可以處理android.service.wallpaper.WallpaperService這個Action。
            Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);
            if (componentName != null && !componentName.equals(mImageWallpaper)) {
                // Make sure the selected service is actually a wallpaper service.
                // 獲取所有可以處理andriod.service.wallpaper.WallpaperService的服務資訊
                List<ResolveInfo> ris =
                        mIPackageManager.queryIntentServices(intent,
                                intent.resolveTypeIfNeeded(mContext.getContentResolver()),
                                PackageManager.GET_META_DATA, serviceUserId).getList();
                // 再次,要求這個service必須在其meta-data中提供關於桌布的描述資訊。
                // 如果即將啟動的服務位於查詢結果中,便可以確定這是一個桌布服務。此時會建立一個WallpaperInfo物件,以解析並存儲此桌布服務的描述資訊。
                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 {
                            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;
                    }
                }
                // WallpaperInfo為null,表示即將啟動的服務沒有位於查詢結果中,或者沒有提供必要的meta-data,繫結失敗。
                if (wi == null) {
                    String msg = "Selected service is not a wallpaper: "
                            + componentName;
                    if (fromUser) {
                        throw new SecurityException(msg);
                    }
                    Slog.w(TAG, msg);
                    return false;
                }
            }

            // Bind the service!
            if (DEBUG) Slog.v(TAG, "Binding to:" + componentName);
            // 建立一個WallpaperConnection。
            // WallpaperConnection不僅實現ServiceConnection的介面,用於監聽和WallpaperService的連線狀態,
            // 同時還實現IWallpaperConnection.Stub,也就是支援跨程序通訊。
            // 在服務繫結成功後的WallpaperConnection.onServiceConnected()方法呼叫中,
            // WallpaperConnection的例項會被髮送給WallpaperService,使其作為WallpaperService
            // 向WallpaperManagerService進行通訊的橋樑.
            WallpaperConnection newConn = new WallpaperConnection(wi, wallpaper);
            // 為啟動桌布服務準備intent
            intent.setComponent(componentName);
            intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
                    com.android.internal.R.string.wallpaper_binding_label);
            intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivityAsUser(
                    mContext, 0,
                    Intent.createChooser(new Intent(Intent.ACTION_SET_WALLPAPER),
                            mContext.getText(com.android.internal.R.string.chooser_wallpaper)),
                    0, null, new UserHandle(serviceUserId)));
            // 啟動指定的桌布服務。當服務啟動完成後,剩下的啟動流程會在WallpaperConnection.onServiceConnected()中繼續
            if (!mContext.bindServiceAsUser(intent, newConn,
                    Context.BIND_AUTO_CREATE | Context.BIND_SHOWING_UI
                            | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
                    new UserHandle(serviceUserId))) {
                String msg = "Unable to bind service: "
                        + componentName;
                if (fromUser) {
                    throw new IllegalArgumentException(msg);
                }
                Slog.w(TAG, msg);
                return false;
            }
            // 新的桌布服務啟動成功後,通過detachWallpaperLocked()方法銷燬舊的桌布服務
            if (wallpaper.userId == mCurrentUserId && mLastWallpaper != null) {
                detachWallpaperLocked(mLastWallpaper);
            }
            // 將新的桌布服務的執行資訊儲存到WallpaperData中。
            wallpaper.wallpaperComponent = componentName;
            wallpaper.connection = newConn;
            newConn.mReply = reply;
            // 最後向WMS申請註冊一個WALLPAPER型別的視窗令牌。
            // 這個令牌會在onServiceConnected()方法之後被傳遞給WallpaperService用作後者新增視窗的通行證。
            try {
                if (wallpaper.userId == mCurrentUserId) {
                    if (DEBUG)
                        Slog.v(TAG, "Adding window token: " + newConn.mToken);
                    mIWindowManager.addWindowToken(newConn.mToken,
                            WindowManager.LayoutParams.TYPE_WALLPAPER);
                    mLastWallpaper = wallpaper;
                }
            } catch (RemoteException e) {
            }
        } catch (RemoteException e) {
            String msg = "Remote exception for " + componentName + "\n" + e;
            if (fromUser) {
                throw new IllegalArgumentException(msg);
            }
            Slog.w(TAG, msg);
            return false;
        }
        return true;
    }

可見WallpaperManagerService要求被啟動的目標Service必須滿足以下三個條件:

  • 該服務必須要以android.permission.BIND_WALLPAPER作為其訪問許可權。桌布雖然是一個標準的Android服務,但是通過其他途徑(如第三方應用程式)啟動桌布所在的服務是沒有意義的。因此Android要求作為桌布的Service必須使用這個簽名級的系統許可權進行訪問限制,以免被意外的應用程式啟動。

  • 該服務必須被宣告為可以處理android.service.wallpaper.WallpaperService這個Action。WallpaperManagerService會使用這個Action對此服務進行繫結。

  • 該服務必須在其AndroidManifest.xml中提供一個名為android.service.wallpaper的meta-data,用於提供動態桌布的開發者、縮圖與描述文字。

一旦目標服務滿足了上述條件,WallpaperManagerService就會著手進行目標服務的啟動與繫結。

bindWallpaperComponentLocked()主要做了如下幾件事情:

  • 建立WallpaperConnection。由於實現了ServiceConnection介面,因此它將負責監聽WallpaperManagerService與桌布服務之間的連線狀態。另外由於繼承了IWallpaperConnection.Stub,因此它具有跨程序通訊的能力。在桌布服務繫結成功後,WallpaperConnection例項會被傳遞給桌布服務作為桌布服務與WallpaperManagerService進行通訊的橋樑。

  • 啟動桌布服務。通過Context.bindServiceAsUser()方法完成。可見啟動桌布服務與啟動一個普通的服務沒有什麼區別。

  • 終止舊有的桌布服務:detachWallpaperLocked()。

  • 將屬於當前桌布的WallpaperConnection例項、componentName機器啟動時間戳儲存到WallpaperData中。

  • 向WMS註冊WALLPAPER型別的視窗令牌。這個視窗令牌儲存在WallpaperConnection.mToken中,並隨著WallpaperConnection的建立而建立。

僅僅將指定的桌布服務啟動起來尚無法使得桌布得以顯示,因為新啟動起來的桌布服務由於沒有可用的視窗令牌而導致其無法新增視窗。WallpaperManagerService必須通過某種方法將視窗令牌交給桌布服務才行。所以桌布顯示的後半部分的流程將在WallpaperConnection.onServiceConnected()回撥中繼續。同其他服務一樣,WallpaperManagerService會在這個回撥之中獲得一個Binder物件。因此在進行onServiceConnected()方法的討論之前,必須瞭解WallpaperManagerService在這個回撥中將會得到一個什麼樣的Binder物件。

現在把分析目標轉移到WallpaperService中。和普通服務一樣,WallpaperService的啟動也會經歷onCreate()、onBind()這樣的生命週期回撥。為了瞭解WallpaperManagerService可以從onServiceConnected()獲取怎樣的Binder物件,需要看下WallpaperService.onBind()的實現:

    /**
     * Implement to return the implementation of the internal accessibility
     * service interface.  Subclasses should not override.
     */
    @Override
    public final IBinder onBind(Intent intent) {
        return new IWallpaperServiceWrapper(this);
    }
onBind()新建了一個IWallpaperServiceWrapper例項,並將其返回給WallpaperManagerService。
IWallpaperServiceWrapper類繼承自IWallpaperService.Stub。它儲存了WallpaperService的例項,同時也實現了唯一的一個介面attach()。很顯然,當這個Binder物件返回給WallpaperManagerService之後,後者定會呼叫這個唯一的介面attach()以傳遞顯示桌布所必須的包括視窗令牌在內的一系列的引數。

向桌布服務傳遞建立視窗所需的資訊

    當WallpaperService建立IWallpaperServiceWrapper例項並返回後,WallpaperManagerService將在WallpaperConnection的onServiceConnected()方法中收到回撥。如下:
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            synchronized (mLock) {
                if (mWallpaper.connection == this) {
                    mService = IWallpaperService.Stub.asInterface(service);//將WallpaperService傳回的IWallpaperService介面儲存為沒Service。
                    attachServiceLocked(this, mWallpaper);//繫結桌布服務。attachServiceLocked()會呼叫IWallpaperService的attach()方法以傳遞桌布服務建立視窗所需的資訊。
                    // 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.
                    saveSettingsLocked(mWallpaper.userId);//儲存當前桌布的執行狀態到檔案系統中,以便在系統重啟或發生使用者切換時可以恢復。
                }
            }
        }
    進一步,attachServiceLocked()方法會呼叫IWallpaperService的attach()方法,將建立桌布視窗所需的資訊傳遞給桌布服務。
    void attachServiceLocked(WallpaperConnection conn, WallpaperData wallpaper) {
        try {
            conn.mService.attach(conn, conn.mToken,
                    WindowManager.LayoutParams.TYPE_WALLPAPER, false,
                    wallpaper.width, wallpaper.height, wallpaper.padding);//呼叫IWallpaperService的唯一介面attach(),將建立桌布視窗所需要資訊傳遞個WallpaperService。
        } catch (RemoteException e) {
            Slog.w(TAG, "Failed attaching wallpaper; clearing", e);
            if (!wallpaper.wallpaperUpdating) {
                bindWallpaperComponentLocked(null, false, false, wallpaper, null);
            }
        }
    }
    attach()方法的引數比較,我們看下他們的意義:
  • conn即WallpaperConnection,WallpaperService將通過它向WallpaperManagerService進行通訊。WallpaperConnection繼承自IWallpaperConnection,提供了三個介面的定義,即attachEngine()以及engineShown()和setWallpaper()。雖說WallpaperManager是WallpaperManagerService向外界提供的標準介面,但是這裡仍然選擇使用WallpaperConnection。實現這兩個介面的原因是由於attachEngine()以及engineShown()是隻有WallpaperService才需要用到,而且是它與 WallpaperManagerService之間比較底層且私密的交流,將它們的實現放在通用的介面WallpaperManager中顯然並不合適。Engine類是實現桌布的核心所在,而WallpaperService只是一個用於承載桌布的執行的容器而已。因此相對於WallpaperService,Engine是WallpaperManagerService更加關心的物件。所以當WallpaperService完成了Engine物件的建立之後,就會通過attachEngine()方法將Engine物件的引用交給WallpaperManagerService。
  • windowToken就是在bindWallpaperComponent()方法中向WMS註冊過的視窗令牌。是WallpaperService有權新增桌布視窗的憑證。
  • windowType,指明瞭WallpaperService需要新增TYPE_WALLPAPER型別的視窗。桌布除了是TYPE_WALLPAPER型別以外難道還有其他的可能麼?的確在實際的桌布顯示中WallpaperService必然需要使用TYPE_WALLPAPER型別新增視窗。但是有一個例外,即桌布預覽。在LivePicker應用中選擇一個動態桌布時,首先會使得使用者對選定的桌布進行預覽。這一預覽並不是真的將桌布設定給了WallpaperManagerService,而是LivePicker應用自行啟動了對應的桌布服務,並要求桌布服務使用TYPE_APPLICATION_MEDIA_OVERLAY型別建立視窗。這樣一來,桌布服務所建立的視窗將會以子視窗的形式襯在LivePicker的視窗之下,從而實現了動態桌布的預覽。
  • isPreview,用以指示啟動桌布服務的意圖。當被實際用作桌布時取值為false,而作為預覽時則為true。僅當LivePicker對桌布進行預覽時才會使用true作為isPreview的取值。桌布服務可以根據這一引數的取值對自己的行為作出調整。
    當WallpaperManagerService向WallpaperService提供了用於建立桌布視窗的足夠的資訊之後,WallpaperService便可以開始著手進行Engine物件的建立了。

Engine物件建立

    呼叫IWallpaperService.attach()是WallpaperManagerService在桌布服務啟動後第一次與桌布服務進行聯絡。我們看下呼叫WallpaperService的attach()方法,如下:
[WallpaperService.java-->IWallpaperServiceWrapper.attach()]
        public void attach(IWallpaperConnection conn, IBinder windowToken,
                int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding) {
            new IWallpaperEngineWrapper(mTarget, conn, windowToken,
                    windowType, isPreview, reqWidth, reqHeight, padding);//使用WallpaperManagerService提供的引數構建一個IWallpaperEngineWrapper物件。
        }
    }

    顧名思義,在attach()方法中所建立的IWallpaperEngineWrapper將會建立並封裝Engine例項。IWallpaperEngineWrapper繼承自IWallpaperEngine.Stub,因此它也支援跨Binder呼叫。在隨後的程式碼分析中可知,它將會被傳遞給WallpaperManagerService,作為WallpaperManagerService與Engine進行通訊的橋樑。

    另外需要注意的是,attach()方法的實現非常奇怪,它直接建立一個例項但是並沒有將這個例項賦值給某一個成員變數,在attach()方法結束時豈不是會被垃圾回收?不難想到,在IWallpaperEngineWrapper的建構函式中一定有些動作可以使得這個例項不被釋放。程式碼如下:

       IWallpaperEngineWrapper(WallpaperService context,
                IWallpaperConnection conn, IBinder windowToken,
                int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding) {
            mCaller = new HandlerCaller(context, context.getMainLooper(), this, true);//建立一個HandlerCaller物件
            mConnection = conn;//儲存WallpaperManagerService提供的引數
            mWindowToken = windowToken;
            mWindowType = windowType;
            mIsPreview = isPreview;
            mReqWidth = reqWidth;
            mReqHeight = reqHeight;
            mDisplayPadding.set(padding);
            
            Message msg = mCaller.obtainMessage(DO_ATTACH);//傳送DO_ATTACH訊息
            mCaller.sendMessage(msg);
        }
   HandlerCaller是Handler的一個封裝,而它與Handler的區別是額外提供了一個executeOrSendMessage()方法。當開發者在HandlerCaller所在的執行緒執行此方法時會使得訊息的處理函式立刻得到執行,在其他執行緒中執行此方法的效果則與Handler.sendMessage()別無二致。     在這裡所建立的mCaller具有十分重要的地位。它是一個重要的執行緒排程器,所有桌布相關的操作都會以訊息的形式傳送給mCaller,然後在IWallpaperEngineWrapper的executeMessage()方法中得到處理,從而這些操作轉移到mCaller所在的執行緒上進行(如桌布繪製、事件處理等)。可以說mCaller的執行緒就是桌布的工作執行緒。     繼續分析DO_ATTACH訊息的處理,如下:
        public void executeMessage(Message message) {
            switch (message.what) {
                case DO_ATTACH: {
                    try {
                        mConnection.attachEngine(this);//把IWallpaperEngineWrapper例項傳遞給WallpaperConnection進行儲存
                    } catch (RemoteException e) {
                        Log.w(TAG, "Wallpaper host disappeared", e);
                        return;
                    }
                    Engine engine = onCreateEngine();//onCreateEngine()方法建立一個Engine,在WallpaperService中是一個抽象方法,使用者可自行返回一個自定義的Engine子類。
                    mEngine = engine;
                    mActiveEngines.add(engine);//將新建的Engine新增到WallpaperService的mActiveEngines列表中。
                    engine.attach(this);//engine.attach()將會完成視窗的建立、第一幀的繪製等工作。
                    return;
                }
    正如前文所述,作為擁有跨Binder呼叫的IWallpaperEngineWrapper通過attachEngine()方法將自己傳遞給了WallpaperConnection,後者將其儲存在WallpaperConnection.mEngine成員之中。從此之後,WallpaperManagerService便可以通過WallpaperConnection.mEngine與桌布服務程序中的IWallpaperEngineWrapper進行通訊,而IWallpaperEngineWrapper進一步將來自WallpaperManagerService中的請求或設定轉發給Engine物件,從而實現了WallpaperManagerService對桌布的控制。

    到目前為止,WallpaperManagerService與桌布服務之間已經出現了三個用於跨Binder通訊的物件。它們分別是:

  • IWallpaperService,實現在桌布服務程序之中,它所提供的唯一的方法attach()用於在桌布服務啟動後接收視窗建立所需的資訊,或者說為了完成桌布的初始化工作。除此之外IWallpaperService不負責任何功能,WallpaperManagerService對桌布進行的請求與設定都交由在attach()的過程中所建立的IWallpaperEngineWrapper例項完成。

  • WallpaperConnection,實現在WallpaperManagerService中,並通過IWallpaperService.attach()方法傳遞給了IWallpaperEngineWrapper。桌布服務通過WallpaperConnection的attachEngine()方法將IWallpaperEngineWrapper例項傳遞給WallpaperManagerService進行儲存。另外桌布服務還通過它的engineShown()方法將桌布顯示完成的事件通知給WallpaperManagerService。

  • IWallpaperEngineWrapper,實現在桌布程序中。Engine例項是桌布實現的核心所在。作為Engine例項的封裝者,它是WallpaperManagerService對Engine進行請求或設定的唯一介面。

    總體來說,IWallpaperService與WallpaperConnection主要服務於桌布的建立階段,而IWallpaperEngineWrapper則用於在桌布的執行階段對Engine進行操作與設定。
    Engine建立完畢之後會通過Engine.attach()方法完成Engine的初始化工作,程式碼如下:
       void attach(IWallpaperEngineWrapper wrapper) {
            if (DEBUG) Log.v(TAG, "attach: " + this + " wrapper=" + wrapper);
            if (mDestroyed) {
                return;
            }
            //儲存必要資訊
            mIWallpaperEngine = wrapper;
            mCaller = wrapper.mCaller;
            mConnection = wrapper.mConnection;
            mWindowToken = wrapper.mWindowToken;
            mSurfaceHolder.setSizeFromLayout();//mSurfaceHolder是一個BaseSurfaceHolder型別的內部類的例項。Engine對其進行了簡單的定製。開發者可以通過mSurfaceHolder定製所需要的Surface型別

            mInitializing = true;
            mSession = WindowManagerGlobal.getWindowSession();//獲取WindowSession,用於與WMS通訊
            
            mWindow.setSession(mSession);//視窗建立之後,用於接收來自WMS的回撥

            mLayout.packageName = getPackageName();

            mDisplayManager = (DisplayManager)getSystemService(Context.DISPLAY_SERVICE);
            mDisplayManager.registerDisplayListener(mDisplayListener, mCaller.getHandler());
            mDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
            mDisplayState = mDisplay.getState();

            if (DEBUG) Log.v(TAG, "onCreate(): " + this);
            onCreate(mSurfaceHolder);//用Engine.onCreate()方法,Engine的子類往往需要重寫此方法以修改mSurfaceHolder的屬性,如畫素格式,尺寸等。注意此時尚未建立視窗,在這裡所設定的SurfaceHolder的屬性將會在建立視窗時生效。

            mInitializing = false;
            mReportedVisible = false;
            updateSurface(false, false, false);//最後updateSurface,將會根據SurfaceHolder的屬性建立視窗以及Surface,並進行桌布的第一次繪製

        }
    Engine.attach()方法執行的結束標誌著桌布啟動工作的完成,至此在最後的updateSurface()方法結束之後新的桌布便顯示出來了。

桌布的建立流程

    可見,桌布的建立過程比較複雜。在這個過程中存在著多個Binder物件之間的互相呼叫。因此有必要對此過程進行一個簡單的整理:
  • 首先,桌布管理程式(如LivePicker)呼叫IWallpaperManager.setWallpaperComponent()要求WallpaperManagerService設定指定的桌布

  • WallpaperManagerService通過呼叫bindWallpaperComponentLocked()將給定的桌布服務啟動起來。同時舊有的桌布服務會被終止。

  • WallpaperManagerService成功連線桌布服務後,呼叫桌布服務的attach()方法將視窗令牌等引數交給桌布服務。

  • 桌布服務響應attach()的呼叫,建立一個Engine。

  • Engine的updateSurface()方法將會建立桌布視窗及Surface,並進行桌布的繪製。

    而在這個過程中,WallpaperManagerService中存在如下重要的資料結構:
  • WallpaperInfo,儲存了動態桌布的開發者、縮圖與描述資訊。這個資料結構創建於WallpaperManagerService.bindWallpaperComponentLocked()方法,其內容來自於桌布所在應用程式的AndroidManifest.xml中名為android.service.wallpaper的meta-data。

  • WallpaperConnection,它不僅僅是桌布服務與WallpaperManagerService進行通訊的渠道,它同時也儲存了與桌布服務相關的重要的執行時資訊,如IWallpaperService、IWallpaperEngineWrapper、WallpaperInfo以及用於建立視窗所需的視窗令牌。WallpaperConnection創建於WallpaperManagerService.bindWallpaperComponentLocked()方法。

  • WallpaperData,它儲存了一個桌布在WallpaperManagerService中可能用到的所有資訊,包括桌布服務的ComponentName,WallpaperConnection,桌布服務的啟動時間等。WallpaperData被儲存在一個名為mWallpaperMap的SparseArray中,而且裝置中每一個使用者都會擁有一個固定的WallpaperData例項。當前使用者進行桌布切換時會更新WallpaperData的內容,而不是新建一個WallpaperData例項。另外,WallpaperData中還儲存了與靜態桌布相關的一些資訊。

理解UpdateSurface()方法

    Engine.attach()方法最後呼叫的Engine.updateSurface()方法是Engine所提供的桌布框架的核心所在。 updateSurface()方法比較大,下面分部分討論。[WallpaperService.java --> Engine.updateSurface()]:
        void updateSurface(boolean forceRelayout, boolean forceReport, boolean redrawNeeded) {
            if (mDestroyed) {
                Log.w(TAG, "Ignoring updateSurface: destroyed");
            }
            //獲取mSurfaceHolder中儲存的尺寸。如果這一尺寸為預設情況下的(-1,-1),則updateSurface()會認為其表示的尺寸為MATCH_PARENT.
            boolean fixedSize = false;
            int myWidth = mSurfaceHolder.getRequestedWidth();
            if (myWidth <= 0) myWidth = ViewGroup.LayoutParams.MATCH_PARENT;
            else fixedSize = true;
            int myHeight = mSurfaceHolder.getRequestedHeight();
            if (myHeight <= 0) myHeight = ViewGroup.LayoutParams.MATCH_PARENT;
            else fixedSize = true;
            //下面的一組變數是更新surface的條件。
            final boolean creating = !mCreated;//1、視窗尚未建立
            final boolean surfaceCreating = !mSurfaceCreated;//2、surface尚未建立
            final boolean formatChanged = mFormat != mSurfaceHolder.getRequestedFormat();//3、mSurfaceHolder中的畫素格式發生了變化
            boolean sizeChanged = mWidth != myWidth || mHeight != myHeight;//4、尺寸發生了變化
            boolean insetsChanged = !mCreated;
            final boolean typeChanged = mType != mSurfaceHolder.getRequestedType();//5、surface型別發生了變化
            final boolean flagsChanged = mCurWindowFlags != mWindowFlags ||
                    mCurWindowPrivateFlags != mWindowPrivateFlags;//6、視窗的flags發生了變化
            if (forceRelayout || creating || surfaceCreating || formatChanged || sizeChanged
                    || typeChanged || flagsChanged || redrawNeeded
                    || !mIWallpaperEngine.mShownReported) {//只要滿足一個條件,便有必要更新surface
                    ......//桌布視窗的建立、重新佈局以及在必要時觸發SurfaceHolder的回撥
從這些條件中可以看到updateSurface()方法可能進行的工作如下:
  • 建立桌布視窗,有mCreated成員指定;
  • 從WMS申請surface,有mSurfaceCreated成員指定;
  • 修改Surface的畫素格式,有SurfaceHolder.getRequestedFormat()的返回值指定;
  • 修改Surface的尺寸,由SurfaceHolder.getaRequestedWidth()/Height()的返回值指定;
  • 修改Surface的記憶體的型別,即NORMAL、GPU、HARDWARE、PUSH_BUFFERS。由SurfaceHolder.getRequestType()的返回值指定;
  • 修改視窗的flags,由mWindowFlags成員指定。對桌布視窗來說,視窗flags的變化是由於Engine.setTouchEventsEnabled()方法增加或刪除了FLAG_NOT_TOUCHABLE標記。
在updateSurface()後續程式碼中將會看到這些變數如何對Surface產生影響的。
            if (forceRelayout || creating || surfaceCreating || formatChanged || sizeChanged
                    || typeChanged || flagsChanged || redrawNeeded
                    || !mIWallpaperEngine.mShownReported) {

                if (DEBUG) Log.v(TAG, "Changes: creating=" + creating
                        + " format=" + formatChanged + " size=" + sizeChanged);

                try {
                    // 將SurfaceHolder中的設定轉儲到Engine的成員變數中,用於在下次updateSurface()的呼叫中檢查他們是否發生變化。
                    mWidth = myWidth;
                    mHeight = myHeight;
                    mFormat = mSurfaceHolder.getRequestedFormat();
                    mType = mSurfaceHolder.getRequestedType();

                    // 更新視窗的LayoutParam。上述的畫素格式、尺寸、記憶體型別以及視窗flags會使用LayoutParams經過視窗的重新佈局以完成設定
                    mLayout.x = 0;
                    mLayout.y = 0;
                    mLayout.width = myWidth;
                    mLayout.height = myHeight;
                    
                    mLayout.format = mFormat;
                    
                    mCurWindowFlags = mWindowFlags;
                    mLayout.flags = mWindowFlags
                            | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
                            | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                            | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                            ;
                    mCurWindowPrivateFlags = mWindowPrivateFlags;
                    mLayout.privateFlags = mWindowPrivateFlags;

                    mLayout.memoryType = mType;
                    //mWindowToken來自WallpaperManagerService的WallpaperConnection。
                    mLayout.token = mWindowToken;

                    // 償若桌布視窗尚未建立,則進行視窗建立
                    if (!mCreated) {
                        // Retrieve watch round info
                        TypedArray windowStyle = obtainStyledAttributes(
                                com.android.internal.R.styleable.Window);
                        windowStyle.recycle();

                        // Add window
                        // 視窗的型別來自IWallpaperEngineWrapper.attach()方法
                        mLayout.type = mIWallpaperEngine.mWindowType;
                        mLayout.gravity = Gravity.START|Gravity.TOP;
                        mLayout.setTitle(WallpaperService.this.getClass().getName());
                        mLayout.windowAnimations =
                                com.android.internal.R.style.Animation_Wallpaper;
                        mInputChannel = new InputChannel();
                        // 建立視窗,注意桌布視窗會被新增到DEFAULT_DISPLAY中
                        if (mSession.addToDisplay(mWindow, mWindow.mSeq, mLayout, View.VISIBLE,
                            Display.DEFAULT_DISPLAY, mContentInsets, mStableInsets, mOutsets,
                                mInputChannel) < 0) {
                            Log.w(TAG, "Failed to add window while updating wallpaper surface.");
                            return;//建立視窗失敗,直接返回
                        }
                        mCreated = true;//標記視窗已經建立完成

                        // 建立WallpaperInputEventReceiver物件,用於接收觸控事件
                        mInputEventReceiver = new WallpaperInputEventReceiver(
                                mInputChannel, Looper.myLooper());
                    }

                    // 接下來的操作會修改Surface,因此必須將SurfaceHolder鎖住,
                    //以免其他執行緒在這個過程中嘗試通過Surface.lockCanvas()修改Surface的內容。
                    mSurfaceHolder.mSurfaceLock.lock();
                    mDrawingAllowed = true;

                    if (!fixedSize) {
                        mLayout.surfaceInsets.set(mIWallpaperEngine.mDisplayPadding);
                        mLayout.surfaceInsets.left += mOutsets.left;
                        mLayout.surfaceInsets.top += mOutsets.top;
                        mLayout.surfaceInsets.right += mOutsets.right;
                        mLayout.surfaceInsets.bottom += mOutsets.bottom;
                    } else {
                        mLayout.surfaceInsets.set(0, 0, 0, 0);
                    }
                    // 重新佈局視窗。它將SurfaceHolder中的設定以及視窗屬性同步到Surface及其視窗值中。
                    // 倘若桌布視窗剛剛完成建立,則經過重新佈局後其Surface也會變得有效。
                    final int relayoutResult = mSession.relayout(
                        mWindow, mWindow.mSeq, mLayout, mWidth, mHeight,
                            View.VISIBLE, 0, mWinFrame, mOverscanInsets, mContentInsets,
                            mVisibleInsets, mStableInsets, mOutsets, mBackdropFrame,
                            mConfiguration, mSurfaceHolder.mSurface);

                    if (DEBUG) Log.v(TAG, "New surface: " + mSurfaceHolder.mSurface
                            + ", frame=" + mWinFrame);

                    int w = mWinFrame.width();
                    int h = mWinFrame.height();

                    if (!fixedSize) {
                        final Rect padding = mIWallpaperEngine.mDisplayPadding;
                        w += padding.left + padding.right + mOutsets.left + mOutsets.right;
                        h += padding.top + padding.bottom + mOutsets.top + mOutsets.bottom;
                        mOverscanInsets.left += padding.left;
                        mOverscanInsets.top += padding.top;
                        mOverscanInsets.right += padding.right;
                        mOverscanInsets.bottom += padding.bottom;
                        mContentInsets.left += padding.left;
                        mContentInsets.top += padding.top;
                        mContentInsets.right += padding.right;
                        mContentInsets.bottom += padding.bottom;
                        mStableInsets.left += padding.left;
                        mStableInsets.top += padding.top;
                        mStableInsets.right += padding.right;
                        mStableInsets.bottom += padding.bottom;
                    }

                    // 儘管SurfaceHolder的設定給出期望的尺寸,但是WMS擁有決定視窗最終尺寸的權利。
                    // updateSurface()將WMS的佈局結果設定給SurfaceHolder。
                    if (mCurWidth != w) {
                        sizeChanged = true;
                        mCurWidth = w;
                    }
                    if (mCurHeight != h) {
                        sizeChanged = true;
                        mCurHeight = h;
                    }

                    if (DEBUG) {
                        Log.v(TAG, "Wallpaper size has changed: (" + mCurWidth + ", " + mCurHeight);
                    }

                    insetsChanged |= !mDispatchedOverscanInsets.equals(mOverscanInsets);
                    insetsChanged |= !mDispatchedContentInsets.equals(mContentInsets);
                    insetsChanged |= !mDispatchedStableInsets.equals(mStableInsets);
                    insetsChanged |= !mDispatchedOutsets.equals(mOutsets);

                    mSurfaceHolder.setSurfaceFrameSize(w, h);
                    //surface更新成功,解除對SurfaceHolder的鎖定
                    mSurfaceHolder.mSurfaceLock.unlock();
這部分updateSurface()的程式碼完成Surface更新。
  • 倘若視窗尚未建立,則通過WMS.addWindow() 完成視窗的建立。
  • 通過WMS.relayoutWindow()對視窗進行重新佈局。重新佈局的結果是倘若視窗尚沒有一塊可用的surface,Engine將會擁有一塊可用的Surface。另外,儲存在layoutParams中與Surface或視窗有關的引數都會被WMS接納並據此修改Surface的屬性。