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——