Android系統的壁紙是其核心模組之一,但是一直以來壁紙Android的壁紙又有其一直的BUG。例如使用單屏的圖片作為壁紙,在手機重啟後,會自動展開圖片變為隨案頭一起滑動的案頭。還有就是在這種情況下使用案頭,壁紙後面會有惱人的黑色,在壁紙的開始、結束部分會有一部分黑屏,再次啟動後黑條會消失,但壁紙還是處於展開狀態。
近期對該問題通過學習WallpaperManager的相關機制,解決了上述問題,先特分享出來。
1.WallpaperManager的使用,WallpaperManager在使用時通過Context的getSystemService來擷取,通過WallpaperManager我們可以實現設定壁紙,擷取壁紙的寬度、高度、設定所需壁紙的寬度高度。這裡需要說明的是,對於類似於Launcher一類的案頭應用來說,一般對壁紙的要求都是高度全屏,寬度為螢幕寬度的兩倍。這個數值基本是系統的標準數值。從一定程度上來說也是標準。我們知道在Android手機中,案頭滑動時,每次壁紙進行的滑動與案頭實際滑動距離不一樣,相差很多的,會根據案頭的寬度,滑動響應的距離。正因為如此,對壁紙的實際寬度的指定就變得沒有那麼重要了,因為本來案頭的滑動跟後面壁紙的滑動二者沒有數值上的對應關係,所以各家案頭基本都遵守Android標準的規定,使用壁紙時指定壁紙寬度為螢幕寬度的兩倍。同樣的,案頭也可以不指定,在滑動時壁紙由系統自動繪製。
WallpaperManager採用的標準的AIDL服務的形式實現,其實現代碼
通過閱讀WallpaperManagerService會發現WallpaperManager中進行壁紙切換的方式,簡單來說在我們設定壁紙時,WallpaperManager把壁紙以檔案的形式儲存在/data/data/com.android.settings/files/WallpaperManager。同時還有一個關鍵的檔案,/data/system/wallpaper_info.xml,發現該設定檔中儲存有壁紙的期望寬度與高度。但是閱讀WallpaperManager並沒有發現壁紙真正繪製的地方,仔細閱讀後,發現Android中的壁紙管理與壁紙繪製同樣通過ServiceBind的方式來通知繪製壁紙,在Android中預設的繪製方式SystemUI的ImageWallpaper,通過閱讀該部分代碼,可以發現壁紙的真正繪製通過Surfice上使用Canvas,或硬體加速的方式實現繪製。瞭解到這裡基本就可以解釋、解決WallpaperManager的問題了。
1.WallpaperManager的問題一:
指定豎屏、單屏壁紙後,開機壁紙被展開。這個主要是在WallpaperManagerService中load設定檔的問題,在Android中,設定壁紙後,直接會將壁紙的寬度、高度儲存在設定檔中,在啟動時,讀取設定檔,指導繪製方進行繪製。問題就出在讀取設定檔的時候,在Android中,不允許壁紙的寬度比高度小,具體的原因沒有看明白,系統中在發現儲存的寬度比高度小後,會對其寬度進行展開,拉到與高度一樣高,這就是為什麼原來豎屏、單屏的壁紙重啟後變成可以滑動的了。主要問題代碼如下:
private void loadSettingsLocked() { if (DEBUG) Slog.v(TAG, "loadSettingsLocked"); JournaledFile journal = makeJournaledFile(); FileInputStream stream = null; File file = journal.chooseForRead(); boolean success = false; try { stream = new FileInputStream(file); XmlPullParser parser = Xml.newPullParser(); parser.setInput(stream, null); int type; do { type = parser.next(); if (type == XmlPullParser.START_TAG) { String tag = parser.getName(); if ("wp".equals(tag)) { mWidth = Integer.parseInt(parser.getAttributeValue(null, "width")); mHeight = Integer.parseInt(parser.getAttributeValue(null, "height")); mName = parser.getAttributeValue(null, "name"); String comp = parser.getAttributeValue(null, "component"); mNextWallpaperComponent = comp != null ? ComponentName.unflattenFromString(comp) : null; if (mNextWallpaperComponent == null || "android".equals(mNextWallpaperComponent.getPackageName())) { mNextWallpaperComponent = mImageWallpaperComponent; } if (DEBUG) { Slog.v(TAG, "mWidth:" + mWidth); Slog.v(TAG, "mHeight:" + mHeight); Slog.v(TAG, "mName:" + mName); Slog.v(TAG, "mNextWallpaperComponent:" + mNextWallpaperComponent); } } } } while (type != XmlPullParser.END_DOCUMENT); success = true; } catch (NullPointerException e) { Slog.w(TAG, "failed parsing " + file + " " + e); } catch (NumberFormatException e) { Slog.w(TAG, "failed parsing " + file + " " + e); } catch (XmlPullParserException e) { Slog.w(TAG, "failed parsing " + file + " " + e); } catch (IOException e) { Slog.w(TAG, "failed parsing " + file + " " + e); } catch (IndexOutOfBoundsException e) { Slog.w(TAG, "failed parsing " + file + " " + e); } try { if (stream != null) { stream.close(); } } catch (IOException e) { // Ignore } if (!success) { mWidth = -1; mHeight = -1; mName = ""; } // We always want to have some reasonable width hint. WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); Display d = wm.getDefaultDisplay(); int baseSize = d.getMaximumSizeDimension(); if (mWidth < baseSize) { mWidth = baseSize; } if (mHeight < baseSize) { mHeight = baseSize; } }
WallpaperManager的問題二,豎屏壁紙兩側會有黑邊。這個問題就是具體的壁紙繪製的問題了,簡單來講就是壁紙本身沒有被展開到案頭所需要的寬度,在ImageWallpaper中進行繪製時,會根據Wallpaper的需要寬度、壁紙的寬度來指定壁紙在什麼位置來進行繪製,黑邊就是由於畫布太寬,留出黑邊繪製壁紙,造成這個問題。
void drawFrameLocked() { if (!mVisible) { if (DEBUG) { Log.d(TAG, "Suppressed drawFrame since wallpaper is not visible."); } return; } if (!mRedrawNeeded && !mOffsetsChanged) { if (DEBUG) { Log.d(TAG, "Suppressed drawFrame since redraw is not needed " + "and offsets have not changed."); } return; } if (mBackgroundWidth < 0 || mBackgroundHeight < 0) { // If we don't yet know the size of the wallpaper bitmap, // we need to get it now. updateWallpaperLocked(); } SurfaceHolder sh = getSurfaceHolder(); final Rect frame = sh.getSurfaceFrame(); final int dw = frame.width(); final int dh = frame.height(); final int availw = dw - mBackgroundWidth; final int availh = dh - mBackgroundHeight; int xPixels = availw < 0 ? (int)(availw * mXOffset + .5f) : (availw / 2); int yPixels = availh < 0 ? (int)(availh * mYOffset + .5f) : (availh / 2); mOffsetsChanged = false; if (!mRedrawNeeded && xPixels == mLastXTranslation && yPixels == mLastYTranslation) { if (DEBUG) { Log.d(TAG, "Suppressed drawFrame since the image has not " + "actually moved an integral number of pixels."); } return; } mRedrawNeeded = false; mLastXTranslation = xPixels; mLastYTranslation = yPixels; if (mBackground == null) { // If we somehow got to this point after we have last flushed // the wallpaper, well we really need it to draw again. So // seems like we need to reload it. Ouch. updateWallpaperLocked(); } if (mIsHwAccelerated) { if (!drawWallpaperWithOpenGL(sh, availw, availh, xPixels, yPixels)) { drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels); } } else { drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels); } if (FIXED_SIZED_SURFACE) { // If the surface is fixed-size, we should only need to // draw it once and then we'll let the window manager // position it appropriately. As such, we no longer needed // the loaded bitmap. Yay! mBackground = null; mWallpaperManager.forgetLoadedWallpaper(); } }
最後一個思考就是為什麼有黑邊後,重啟後就沒有了那?
這個就需要把整個壁紙管理、繪製的方式綜合來看了。
1.首先,設定一張壁紙後,/data/system/wallpaper_info.xml儲存的是該壁紙的真是寬高。
2.開機後,load壁紙資訊時,發現寬度比高度小,所以對壁紙的寬高進行修改,返回的寬度與高度相等。舉例同為800,返回給ImageWallpaper,ImageWallpaper據此展開壁紙圖片為800x800。
3.在標準案頭中,指定寬度為480*2=960,要求ImageWallpaper進行展開繪製,ImageWallpaper通過計算,留出足夠黑邊後繪製壁紙,此時出現黑邊。同時WallpaperManager儲存寬度到wallpaper_info.xml中,也就是wallpaper_info中的寬度變為960。
4.再次啟動後,返回的壁紙寬度變為960,在ImageWallpaper中按照960進行展開圖片,所以再次開機後,已經沒有黑邊了,可以全螢幕顯示。
——歡迎轉載,請註明出處http://blog.csdn.net/zyplus——