Android WallpaperManager解析及BUG解决

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

 

你可能感兴趣的:(Android WallpaperManager解析及BUG解决)