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;
}
}