android7.0 wallpaper显示流程

看完此文后就知道壁纸怎么绘制出来的,什么情况下壁纸会随着桌面滑动而移动,为什么壁纸设置后被剪裁了。

这里主要讲解静态壁纸的显示流程,不考虑动态壁纸,所以下面提到的壁纸都是指静态壁纸。壁纸即是一个壁纸服务,每换一张壁纸 ,就是将该图片写入壁纸文件,再启动一个壁纸服务读取该壁纸文件显示出来的过程。

壁纸服务实现在SystemUI里面,所以其会跟随SystemUI进程的启动而启动,实现路径在:

/frameworks/base/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java

初步看看其实现

/**
 * Default built-in wallpaper that simply shows a static image.
 */
@SuppressWarnings({"UnusedDeclaration"})
public class ImageWallpaper extends WallpaperService {
    private static final String TAG = "ImageWallpaper";

    ... ... ...

   @Override
    public void onCreate() {
        super.onCreate();
        mWallpaperManager = (WallpaperManager) getSystemService(WALLPAPER_SERVICE);

        ... ... ...

     }

     ... ... ...

}

可以看到该服务继承自WallpaperService.java,是不是毛孔顿开。

壁纸的加载使用的是Engine引擎,其定义在ImageWallpaper.java的内部类class DrawableEngine extends Engine {,可以看到DrawableEngine继承自Engine,从此壁纸想怎么浪就怎么浪。壁纸服务启动后开始加载资源流程如下:

class DrawableEngine extends Engine {

   ... ... ...

    @Override
        public void onCreate(SurfaceHolder surfaceHolder) {

            ... ... ...

            updateSurfaceSize(surfaceHolder, getDefaultDisplayInfo(), false /* forDraw */);

        }

        ... ... ...

}

见updateSurfaceSize的实现

 boolean updateSurfaceSize(SurfaceHolder surfaceHolder, DisplayInfo displayInfo,boolean forDraw) {
            boolean hasWallpaper = true;
            //如果没有保存背景图片尺寸信息就重新载入
            // Load background image dimensions, if we haven't saved them yet
            if (mBackgroundWidth <= 0 || mBackgroundHeight <= 0) {
                // Need to load the image to get dimensions
                mWallpaperManager.forgetLoadedWallpaper();
                loadWallpaper(forDraw);
                if (DEBUG) {
                    Log.d(TAG, "Reloading, redoing updateSurfaceSize later.");
                }
                hasWallpaper = false;
            }

            // Force the wallpaper to cover the screen in both dimensions
            int surfaceWidth = Math.max(displayInfo.logicalWidth, mBackgroundWidth);
            int surfaceHeight = Math.max(displayInfo.logicalHeight, mBackgroundHeight);

            if (FIXED_SIZED_SURFACE) {//使用固定尺寸
                // Used a fixed size surface, because we are special.  We can do
                // this because we know the current design of window animations doesn't
                // cause this to break.
                surfaceHolder.setFixedSize(surfaceWidth, surfaceHeight);
                mLastRequestedWidth = surfaceWidth;
                mLastRequestedHeight = surfaceHeight;
            } else {
                surfaceHolder.setSizeFromLayout();//或者根据layout设置尺寸
            }
            return hasWallpaper;
        }

tips:这里只提一个注意点对于FIXED_SIZED_SURFACE值如果配置为true则采用最大尺寸设置为壁纸surface的尺寸即图片大就用图片的尺寸,屏幕分辨率大就使用屏幕的尺寸。所以如果图片大就可以滑动桌面时壁纸随着移动。如果配置为false则以屏幕视图尺寸为准。图片大的话会遭到剪切,即使图片大也不会随着桌面滑动。

关键看方法:loadWallpaper(forDraw);

 private void loadWallpaper(boolean needsDraw) {//加载wallpaper壁纸资源

     mLoader = new AsyncTask() {
                @Override
                protected Bitmap doInBackground(Void... params) {
                    Throwable exception;
                    try {
                        return mWallpaperManager.getBitmap();//去取wallpaper图片资源
                    } catch (RuntimeException | OutOfMemoryError e) {
                        exception = e;
                    }

                    if (exception != null) {//取wallapper资源过程报错不为空的话,再重新去取wallpaper资源
                        // Note that if we do fail at this, and the default wallpaper can't
                        // be loaded, we will go into a cycle.  Don't do a build where the
                        // default wallpaper can't be loaded.
                        Log.w(TAG, "Unable to load wallpaper!", exception);
                        try {
                            mWallpaperManager.clear();
                        } catch (IOException ex) {
                            // now we're really screwed.
                            Log.w(TAG, "Unable reset to default wallpaper!", ex);
                        }

                        try {
                            return mWallpaperManager.getBitmap();
                        } catch (RuntimeException | OutOfMemoryError e) {
                            Log.w(TAG, "Unable to load default wallpaper!", e);
                        }
                    }
                    return null;//最后还取不到就只能返回空了
                }

               @Override
                protected void onPostExecute(Bitmap b) {
                    mBackground = null;
                    mBackgroundWidth = -1;
                    mBackgroundHeight = -1;

                    if (b != null) {
                        mBackground = b;
                        mBackgroundWidth = mBackground.getWidth();
                        mBackgroundHeight = mBackground.getHeight();
                    }

                    if (DEBUG) {  Log.d(TAG, "Wallpaper loaded: " + mBackground); }
                    updateSurfaceSize(getSurfaceHolder(), getDefaultDisplayInfo(),false /* forDraw */);
                    if (mNeedsDrawAfterLoadingWallpaper) {
                        drawFrame();
                    }

                    mLoader = null;
                    mNeedsDrawAfterLoadingWallpaper = false;
                }
            }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
        }

            

从上面的代码中我们看到,通过异步AsyncTask调用mWallpaperManager.getBitmap()去获取壁纸资源,并且一次获取失败就会再去获取一遍,如果还是没有得到就只好返回null,这时我们看到的结果就是左面背景为黑色,当然这里就不啰嗦这个特殊,我们只看正常。得到了图片资源后会重新调用updateSurfaceSize(getSurfaceHolder(), getDefaultDisplayInfo(),false /* forDraw */);对长宽尺寸进行简单调整后,调用drawFrame(),我们先看看如何获取mWallpaperManager.getBitmap()再来研究看这个drawFrame()重要的壁纸绘制方法。

先看getBitmap这个方法:定义在framework/base/core/java/android/app/WallpaperManager.java里面

public Bitmap getBitmap() {//在SystemUI的ImageWallpaper.java里面调用
        return getBitmapAsUser(mContext.getUserId());
    }

    /**
     * Like {@link #getDrawable()} but returns a Bitmap for the provided user.
     *
     * @hide
     */
    public Bitmap getBitmapAsUser(int userId) {
        return sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, userId);
    }

接着调用peekWallpaperBitmap其实现在 内部类Globals中

 public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault,@SetWallpaperFlags int which, int userId) {//依附壁纸资源
           ... ... ...
            synchronized (this) {
                if (mCachedWallpaper != null && mCachedWallpaperUserId == userId) {//如果已经设置过壁纸,壁纸缓存肯定不为空,直接返回壁纸资源即可,否则继续往下调用
                    return mCachedWallpaper;
                }
                mCachedWallpaper = null;
                mCachedWallpaperUserId = 0;
                try {
                    mCachedWallpaper = getCurrentWallpaperLocked(userId);//从路径data//system/users/{userid}/wallpaper取得当前用户壁纸,如果手机不是第一次启动这个一般能取到壁纸资源
                    mCachedWallpaperUserId = userId;
                } catch (OutOfMemoryError e) {
                    Log.w(TAG, "No memory load current wallpaper", e);
                }
                if (mCachedWallpaper != null) {
                    return mCachedWallpaper;
                }
            }//如果上面没有得到壁纸资源就在这里取得默认壁纸即路径com.android.internal.R.drawable.default_wallpaper并 把图片写入、data/system/users/{userid}/wallpaper
            if (returnDefault) {
                Bitmap defaultWallpaper = mDefaultWallpaper;
                if (defaultWallpaper == null) {
                    defaultWallpaper = getDefaultWallpaper(context, which);
                    synchronized (this) {
                        mDefaultWallpaper = defaultWallpaper;
                    }
                }
                return defaultWallpaper;
            }
            return null;
        }

上,面这个获得壁纸的过程大致为,如果从缓存得到了壁纸资源就直接返回,否则从data/system/users/{userid}/wallpaper得到壁纸资源,如果还是为空就继续从系统默认壁纸 得到路径为com.android.internal.R.drawable.default_wallpaper并把它写入data/system/users/{userid}/wallpaper中,之后取这个文件即可 。具体怎么写作者懒的贴代码,自己看。

得到资源后开始绘制了,回到ImageWallpaper.java见drawwFrame()方法:

 void drawFrame() {//加载绘制出壁纸,会对视图尺寸改变,重新设置壁纸,重新回到主页面(即壁纸页面)等情况下调用,先忘记原来的壁纸再重新加载
            if (!mSurfaceValid) {
                return;
            }
            try {
                Trace.traceBegin(Trace.TRACE_TAG_VIEW, "drawWallpaper");
                DisplayInfo displayInfo = getDefaultDisplayInfo();
                int newRotation = displayInfo.rotation;

                // Sometimes a wallpaper is not large enough to cover the screen in one dimension.
                // Call updateSurfaceSize -- it will only actually do the update if the dimensions
                // should change
                if (newRotation != mLastRotation) {
                    // Update surface size (if necessary)
                    if (!updateSurfaceSize(getSurfaceHolder(), displayInfo, true /* forDraw */)) {
                        return; // had to reload wallpaper, will retry later
                    }
                    mRotationAtLastSurfaceSizeUpdate = newRotation;
                    mDisplayWidthAtLastSurfaceSizeUpdate = displayInfo.logicalWidth;
                    mDisplayHeightAtLastSurfaceSizeUpdate = displayInfo.logicalHeight;
                }
                SurfaceHolder sh = getSurfaceHolder();
                final Rect frame = sh.getSurfaceFrame();
                final int dw = frame.width();
                final int dh = frame.height();
                boolean surfaceDimensionsChanged = dw != mLastSurfaceWidth
                        || dh != mLastSurfaceHeight;

                boolean redrawNeeded = surfaceDimensionsChanged || newRotation != mLastRotation;
                if (!redrawNeeded && !mOffsetsChanged) {
                    return;
                }
                mLastRotation = newRotation;

                // Load bitmap if it is not yet loaded
                if (mBackground == null) {
                    mWallpaperManager.forgetLoadedWallpaper();//忘记已经加载的壁纸
                    loadWallpaper(true /* needDraw */);//重新加载壁纸
                    return;
                }
                //将壁纸居中,所以会涉及到壁纸的剪切
                // Center the scaled image
                mScale = Math.max(1f, Math.max(dw / (float) mBackground.getWidth(),
                        dh / (float) mBackground.getHeight()));
                final int availw = dw - (int) (mBackground.getWidth() * mScale);
                final int availh = dh - (int) (mBackground.getHeight() * mScale);
                int xPixels = availw / 2;
                int yPixels = availh / 2;

                // Adjust the image for xOffset/yOffset values. If window manager is handling offsets,
                // mXOffset and mYOffset are set to 0.5f by default and therefore xPixels and yPixels
                // will remain unchanged
                final int availwUnscaled = dw - mBackground.getWidth();
                final int availhUnscaled = dh - mBackground.getHeight();
                if (availwUnscaled < 0)
                    xPixels += (int) (availwUnscaled * (mXOffset - .5f) + .5f);
                if (availhUnscaled < 0)
                    yPixels += (int) (availhUnscaled * (mYOffset - .5f) + .5f);

                mOffsetsChanged = false;
                if (surfaceDimensionsChanged) {
                    mLastSurfaceWidth = dw;
                    mLastSurfaceHeight = dh;
                }
                if (!redrawNeeded && xPixels == mLastXTranslation && yPixels == mLastYTranslation) {
                    return;
                }
                mLastXTranslation = xPixels;
                mLastYTranslation = yPixels;

                //重新绘制壁纸出来
                if (mIsHwAccelerated) {
                    if (!drawWallpaperWithOpenGL(sh, availw, availh, xPixels, yPixels)) {
                        drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);
                    }
                } else {
                    drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);
                }
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
                if (FIXED_SIZED_SURFACE && !mIsHwAccelerated) {
                    mBackground = null;
                    mWallpaperManager.forgetLoadedWallpaper();
                }
            }
        }

主要是对壁纸居中处理,并做相应的剪裁工作,难后再绘制出来。

你可能感兴趣的:(android)