日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

android窗口动画和壁纸关系,Android壁纸管理(Android N)

發布時間:2023/12/20 Android 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 android窗口动画和壁纸关系,Android壁纸管理(Android N) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

初識Android壁紙

本章將對壁紙的實現原理進行討論。在Android中,壁紙分為靜態與動態兩種。靜態壁紙是一張圖片,而動態壁紙則以動畫為表現形式,或者可以對用戶的操作作出反應。這兩種形式看似差異很大,其實二者的本質是統一的。它們都以一個Service的形式運行在系統后臺,并在一個類型為TYPE_WALLPAPER的窗口上繪制內容。進一步講,靜態壁紙是一種特殊的動態壁紙,它僅在窗口上渲染一張圖片,并且不會對用戶的操作作出反應。因此本章將首先通過動態壁紙的實現討論Android壁紙的實現與管理原理,然后在對靜態壁紙的實現做介紹。

Android壁紙的實現與管理分為三個層次:

WallpaperService與Engine。同SystemUI一樣,壁紙運行在一個Android服務之中,這個服務的名字叫做WallpaperService。當用戶選擇了一個壁紙之后,此壁紙所對應的WallpaperService便會啟動并開始進行壁紙的繪制工作,因此繼承并定制WallpaperService是開發者進行壁紙開發的第一步。Engine是WallpaperService中的一個內部類,實現了壁紙窗口的創建以及Surface的維護工作。另外,Engine提供了可供子類重寫的一系列回調,用于通知壁紙開發者關于壁紙的生命周期、Surface狀態的變化以及對用戶的輸入事件進行響應。可以說,Engine類是壁紙實現的核心所在。壁紙開發者需要繼承Engine類,并重寫其提供的回調以完成壁紙的開發。這一層次的內容主要體現了壁紙的實現原理。

WallpaperManagerService,這個系統服務用于管理壁紙的運行與切換,并通過WallpaperManager類向外界提供操作壁紙的接口。當通過WallpaperManagaer的接口進行壁紙的切換時,WallpaperManagerService會取消當前壁紙的WallpaperService的綁定,并啟動新壁紙的WallpaperService。另外,Engine類進行窗口創建時所使用的窗口令牌也是由WallpaperManagerService提供的。這一層次主要體現了Android對壁紙的管理方式。

WindowManagerService,用于計算壁紙窗口的Z序、可見性以及為壁紙應用窗口動畫。壁紙窗口(TYPE_WALLPAPER)的Z序計算不同于其他類型的窗口。其他窗口依照其類型會有固定的mBaseLayer以及mSubLayer,并結合它們所屬的Activity的順序或創建順序進行Z序的計算,因此這些窗口的Z序相對固定。而壁紙窗口則不然,它的Z序會根據FLAG_SHOW_WALLPAPER標記在其它窗口的LayoutParams.flags中的存在情況而不斷地被調整。這一層次主要體現了Android對壁紙窗口的管理方式。

深入理解動態壁紙

1、啟動動態壁紙的方法

啟動動態壁紙可以通過調用WallpaperManager.getIWallpaperManager().setWallpaperComponent()方法完成。它接受一個ComponentName類型的參數,用于將希望啟動的壁紙的WallpaperService的ComponentName告知WallpaperManagerService。WallpaperManager.getIWallpaperManager()方法返回的是WallpaperManagerService的Bp端。因此setWallpaperComponent()方法的實現位于WallpaperManagerService之中。

@Override

public void setWallpaperComponent(ComponentName name) {

checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);//設置動態壁紙需要調用者擁有的權限

synchronized (mLock) {

if (DEBUG) Slog.v(TAG, "setWallpaperComponent name=" + name);

int userId = UserHandle.getCallingUserId();

WallpaperData wallpaper = mWallpaperMap.get(userId);//首先從mWallpaperMap中獲取壁紙的運行信息WallpaperData。

if (wallpaper == null) {

throw new IllegalStateException("Wallpaper not yet initialized for user " + userId);

}

final long ident = Binder.clearCallingIdentity();

try {

wallpaper.imageWallpaperPending = false;

if (bindWallpaperComponentLocked(name, false, true, wallpaper, null)) {//啟動新壁紙的WallpaperService

wallpaper.wallpaperId = makeWallpaperIdLocked();

notifyCallbacksLocked(wallpaper);

}

} finally {

Binder.restoreCallingIdentity(ident);

}

}

}WallpaperManagerService支持多用戶機制,因此設備上的每個用戶都可以設置自己的壁紙。mWallpaperMap為每個用戶保存了一個WallpaperData實例,這個實例中保存和壁紙運行狀態相關的信息。例如WallpaperService的ComponentName,到WallpaperService的ServiceConnection等。于是當發送用戶切換時,WallpaperManagerService可以從mWallpaperMap中獲取新用戶的WallpaperData,并通過保存在其中的ComponentName重新啟動該用戶所設置的壁紙。因此,當通過setWallpaperComponent方法設置新壁紙時,需要獲取當前用戶的WallpaperData,并在隨后更新其內容使之保存新壁紙的信息。

注意,WallpaperManager.getIWallpaperManager()并沒有作為SDK提高給開發者,因此第三方應用程序不能進行動態壁紙的設置。

2、壁紙服務的啟動原理

壁紙服務的驗證與啟動

bindWallpaperComponentLocked()方法將會啟動由ComponentName所指定的WallpaperService,并向WMS申請用于添加壁紙窗口的窗口令牌。不過在此之前,bindWallpaperComponentLocked()會對ComponentName所描述的Service進行一系列的驗證,以確保它是一個壁紙服務。而這一系列的驗證過程體現了一個Android服務可以被當作壁紙必要的條件。

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 ris =

mIPackageManager.queryIntentServices(intent,

intent.resolveTypeIfNeeded(mContext.getContentResolver()),

PackageManager.GET_META_DATA, serviceUserId).getList();

// 再次,要求這個service必須在其meta-data中提供關于壁紙的描述信息。

// 如果即將啟動的服務位于查詢結果中,便可以確定這是一個壁紙服務。此時會創建一個WallpaperInfo對象,以解析并存儲此壁紙服務的描述信息。

for (int i=0; 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的屬性。

總結

以上是生活随笔為你收集整理的android窗口动画和壁纸关系,Android壁纸管理(Android N)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。