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中,不允许壁纸的宽度比高度小,具体的原因没有看明白,系统中在发现保存的宽度比高度小后,会对其宽度进行拉伸,拉到与高度一样高,这就是为什么原来竖屏、单屏的壁纸重启后变成可以滑动的了。主要问题代码如下:

[java] view plain copy
  1. privatevoid loadSettingsLocked() {

  2. if (DEBUG) Slog.v(TAG, "loadSettingsLocked");

  3. JournaledFile journal = makeJournaledFile();

  4. FileInputStream stream = null;

  5. File file = journal.chooseForRead();

  6. boolean success = false;

  7. try {

  8. stream = new FileInputStream(file);

  9. XmlPullParser parser = Xml.newPullParser();

  10. parser.setInput(stream, null);

  11. int type;

  12. do {

  13. type = parser.next();

  14. if (type == XmlPullParser.START_TAG) {

  15. String tag = parser.getName();

  16. if ("wp".equals(tag)) {

  17. mWidth = Integer.parseInt(parser.getAttributeValue(null, "width"));

  18. mHeight = Integer.parseInt(parser.getAttributeValue(null, "height"));

  19. mName = parser.getAttributeValue(null, "name");

  20. String comp = parser.getAttributeValue(null, "component");

  21. mNextWallpaperComponent = comp != null

  22. ? ComponentName.unflattenFromString(comp)

  23. : null;

  24. if (mNextWallpaperComponent == null ||

  25. "android".equals(mNextWallpaperComponent.getPackageName())) {

  26. mNextWallpaperComponent = mImageWallpaperComponent;

  27. }

  28. if (DEBUG) {

  29. Slog.v(TAG, "mWidth:" + mWidth);

  30. Slog.v(TAG, "mHeight:" + mHeight);

  31. Slog.v(TAG, "mName:" + mName);

  32. Slog.v(TAG, "mNextWallpaperComponent:" + mNextWallpaperComponent);

  33. }

  34. }

  35. }

  36. } while (type != XmlPullParser.END_DOCUMENT);

  37. success = true;

  38. } catch (NullPointerException e) {

  39. Slog.w(TAG, "failed parsing " + file + " " + e);

  40. } catch (NumberFormatException e) {

  41. Slog.w(TAG, "failed parsing " + file + " " + e);

  42. } catch (XmlPullParserException e) {

  43. Slog.w(TAG, "failed parsing " + file + " " + e);

  44. } catch (IOException e) {

  45. Slog.w(TAG, "failed parsing " + file + " " + e);

  46. } catch (IndexOutOfBoundsException e) {

  47. Slog.w(TAG, "failed parsing " + file + " " + e);

  48. }

  49. try {

  50. if (stream != null) {

  51. stream.close();

  52. }

  53. } catch (IOException e) {

  54. // Ignore

  55. }

  56. if (!success) {

  57. mWidth = -1;

  58. mHeight = -1;

  59. mName = "";

  60. }

  61. // We always want to have some reasonable width hint.

  62. WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);

  63. Display d = wm.getDefaultDisplay();

  64. int baseSize = d.getMaximumSizeDimension();

  65. if (mWidth < baseSize) {

  66. mWidth = baseSize;

  67. }

  68. if (mHeight < baseSize) {

  69. mHeight = baseSize;

  70. }

  71. }


WallpaperManager的问题二,竖屏壁纸两侧会有黑边。这个问题就是具体的壁纸绘制的问题了,简单来讲就是壁纸本身没有被拉伸到桌面所需要的宽度,在ImageWallpaper中进行绘制时,会根据Wallpaper的需要宽度、壁纸的宽度来指定壁纸在什么位置来进行绘制,黑边就是由于画布太宽,留出黑边绘制壁纸,造成这个问题。

[java] view plain copy
  1. void drawFrameLocked() {

  2. if (!mVisible) {

  3. if (DEBUG) {

  4. Log.d(TAG, "Suppressed drawFrame since wallpaper is not visible.");

  5. }

  6. return;

  7. }

  8. if (!mRedrawNeeded && !mOffsetsChanged) {

  9. if (DEBUG) {

  10. Log.d(TAG, "Suppressed drawFrame since redraw is not needed "

  11. + "and offsets have not changed.");

  12. }

  13. return;

  14. }

  15. if (mBackgroundWidth < 0 || mBackgroundHeight < 0) {

  16. // If we don't yet know the size of the wallpaper bitmap,

  17. // we need to get it now.

  18. updateWallpaperLocked();

  19. }

  20. SurfaceHolder sh = getSurfaceHolder();

  21. final Rect frame = sh.getSurfaceFrame();

  22. finalint dw = frame.width();

  23. finalint dh = frame.height();

  24. finalint availw = dw - mBackgroundWidth;

  25. finalint availh = dh - mBackgroundHeight;

  26. int xPixels = availw < 0 ? (int)(availw * mXOffset + .5f) : (availw / 2);

  27. int yPixels = availh < 0 ? (int)(availh * mYOffset + .5f) : (availh / 2);

  28. mOffsetsChanged = false;

  29. if (!mRedrawNeeded && xPixels == mLastXTranslation && yPixels == mLastYTranslation) {

  30. if (DEBUG) {

  31. Log.d(TAG, "Suppressed drawFrame since the image has not "

  32. + "actually moved an integral number of pixels.");

  33. }

  34. return;

  35. }

  36. mRedrawNeeded = false;

  37. mLastXTranslation = xPixels;

  38. mLastYTranslation = yPixels;

  39. if (mBackground == null) {

  40. // If we somehow got to this point after we have last flushed

  41. // the wallpaper, well we really need it to draw again. So

  42. // seems like we need to reload it. Ouch.

  43. updateWallpaperLocked();

  44. }

  45. if (mIsHwAccelerated) {

  46. if (!drawWallpaperWithOpenGL(sh, availw, availh, xPixels, yPixels)) {

  47. drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);

  48. }

  49. } else {

  50. drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);

  51. }

  52. if (FIXED_SIZED_SURFACE) {

  53. // If the surface is fixed-size, we should only need to

  54. // draw it once and then we'll let the window manager

  55. // position it appropriately. As such, we no longer needed

  56. // the loaded bitmap. Yay!

  57. mBackground = null;

  58. mWallpaperManager.forgetLoadedWallpaper();

  59. }

  60. }


最后一个思考就是为什么有黑边后,重启后就没有了那?

这个就需要把整个壁纸管理、绘制的方式综合来看了。

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进行拉伸图片,所以再次开机后,已经没有黑边了,可以全屏显示。


更多相关文章

  1. Android获取View的宽度和高度
  2. 【Bugly干货分享】那些年我们用过的显示性能指标
  3. android-opengles3.0开发-2-绘制图形
  4. Android自定义星星评分控件
  5. Android(安卓)过度渲染及优化方法--3D效果(JakeWharton大神的scal
  6. Android中View的绘制机制
  7. 第12章、布局Layouts之LinearLayout线性布局(从零开始学Android)
  8. Android(安卓)界面过度绘制优化tips
  9. android——截屏共享的坑,mmp

随机推荐

  1. Android(安卓)Activity间的过渡动画
  2. android 广播的知识积累
  3. Android执行打开文件(PDF,PPT,WORD,EXCEL
  4. Android客户端通过GET和POST向服务器发送
  5. android之在启动运用程序的时候彻底隐藏T
  6. Android(安卓)RxJava创建操作符Timer的方
  7. android -------- 安装APK报错:Installati
  8. Android(安卓)App的国际化-代码里实现
  9. Android(安卓)8.1 【FriendlyARM】读取 B
  10. Android(安卓)打正式包报错:Execution fai