看完此文后就知道壁纸怎么绘制出来的,什么情况下壁纸会随着桌面滑动而移动,为什么壁纸设置后被剪裁了。
这里主要讲解静态壁纸的显示流程,不考虑动态壁纸,所以下面提到的壁纸都是指静态壁纸。壁纸即是一个壁纸服务,每换一张壁纸 ,就是将该图片写入壁纸文件,再启动一个壁纸服务读取该壁纸文件显示出来的过程。
壁纸服务实现在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则以屏幕视图尺寸为准。图片大的话会遭到剪切,即使图片大也不会随着桌面滑动。
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;//最后还取不到就只能返回空了
}
从上面的代码中我们看到,通过异步AsyncTask调用mWallpaperManager.getBitmap()去获取壁纸资源,并且一次获取失败就会再去获取一遍,如果还是没有得到就只好返回null,这时我们看到的结果就是左面背景为黑色,当然这里就不啰嗦这个特殊,我们只看正常。得到了图片资源后会重新调用updateSurfaceSize(getSurfaceHolder(), getDefaultDisplayInfo(),false /* forDraw */);对长宽尺寸进行简单调整后,调用drawFrame(),我们先看看如何获取mWallpaperManager.getBitmap()再来研究看这个drawFrame()重要的壁纸绘制方法。
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);
}
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中,之后取这个文件即可 。具体怎么写作者懒的贴代码,自己看。
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();
}
}
}
主要是对壁纸居中处理,并做相应的剪裁工作,难后再绘制出来。