静态壁纸的设置
静态壁纸就是在SystemUI中名为ImageWallpaper的特殊动态壁纸,继承了WallpaperService,实现的架构就是动态壁纸的架构,只不过它实现的内容就是一张静态图片,将它和动态壁纸分开来是因为Android给ImageWallpaper提供了很多方便的API
API29之前
静态壁纸的设置不像动态壁纸一样需要签名级系统权限,仅仅需要android.permission.SET_WALLPAPER权限就可以通过WallpaperManager设置静态壁纸,WallpaperManager可以设置壁纸文件的位置,在它设置完壁纸的位置后,Android提供了WallpaperObserver,在WallpaperManagerService中(WallpaperManagerService.WallpaperObserver)
private class WallpaperObserver extends FileObserver {
final int mUserId;
final WallpaperData mWallpaper;
final File mWallpaperDir;
final File mWallpaperFile;
final File mWallpaperLockFile;
public WallpaperObserver(WallpaperData wallpaper) {
super(getWallpaperDir(wallpaper.userId).getAbsolutePath(),
CLOSE_WRITE | MOVED_TO | DELETE | DELETE_SELF);
mUserId = wallpaper.userId;
mWallpaperDir = getWallpaperDir(wallpaper.userId);
mWallpaper = wallpaper;
mWallpaperFile = new File(mWallpaperDir, WALLPAPER);
mWallpaperLockFile = new File(mWallpaperDir, WALLPAPER_LOCK_ORIG);
}
这个类继承自FileObserver,这是android提供的一个工具类,用来监听文件系统文件的增加,修改删除等操作,FileObserver可以监听一个文件或者是文件夹,当它发生变化的时候,onEvent方法里面回调根据参数就能获得这些操作的文件路径,主要监听的参数是CLOSE_WRITE,DELETE,DELETE_SELF,后两种是因为位图被保存在文件系统中,保护非常弱,需要处理其被删除的情况
public void onEvent(int event, String path) {
if (path == null) {
return;
}
final boolean moved = (event == MOVED_TO);
final boolean written = (event == CLOSE_WRITE || moved);
final File changedFile = new File(mWallpaperDir, path);
在onEvent中最后调用的是bindWallpaperComponentLocked方法,这和动态壁纸的设置是一样的,这也就意味着在壁纸文件位置重新设置之后,不会通知原有的DrawableEngine(ImageWallpaper.DrawableEngine)进行更新和重绘,而是销毁原有的。重新创建,
DrawableEngin设置的流程如下;
mLoader = new AsyncTask<Void, Void, Bitmap>() {
@Override
protected Bitmap doInBackground(Void... params) {
Throwable exception;
try {
if (needsReset) {
mWallpaperManager.forgetLoadedWallpaper();
}
- 调用wallpaperManager的forgetLoadedWallpaper,将mDefaultWallpaper和mCachedWallpaper设置成null。这两个变量一个存储系统默认的壁纸,另一个存储现有的设置壁纸
void forgetLoadedWallpaper() {
synchronized (this) {
mCachedWallpaper = null;
mCachedWallpaperUserId = 0;
mDefaultWallpaper = null;
}
}
- 调用WallpaperManager.getBitmap获取作为壁纸的位图,在这个方法里又会调用Globals.peekWallpaperBitmap方法。在其中有两套保证静态壁纸稳定运行的机制:缓存以及备用方案。它会尽量返回已经加载的位图,如果失败就会返回默认的位图。
}
return mWallpaperManager.getBitmap();
} catch (RuntimeException | OutOfMemoryError e) {
exception = e;
}
public Bitmap getBitmap() {
return getBitmapAsUser(mContext.getUserId());
}
*/
public Bitmap getBitmapAsUser(int userId) {
return sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, userId);
}
- 获取被设置为壁纸的位图使用的是Globals.getCurrentWallpaperLocked方法。这个方法会去前面WallpaperManager设置的文件位置加载位图并设置为壁纸。
public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault,
@SetWallpaperFlags int which, int userId) {
//省略代码
try {
mCachedWallpaper = getCurrentWallpaperLocked(context, userId);
mCachedWallpaperUserId = userId;
} catch (OutOfMemoryError e) {
API29
静态壁纸的服务仍然位于SytemUI中,ImageWallpaper继承自WallpaperService,在其onCreateEngine方法中创建的Engine从DrawableEngine换成了GLEngine:
class GLEngine extends Engine implements GLWallpaperRenderer.SurfaceProxy, StateListener
这个类继承了 Engine类,并且实现了GLWallpaperRenderer.SurfaceProxy,StateListener接口,GLWallpaperRenderer是一个渲染器,负责发送一个openGL调用来渲染一个帧,而SurfaceProxy拥有surfaceHolder的代理,通过这个代理能将openGL的渲染帧发送到serface上。
StateListener接口位于StatusBarStateController中,用于接受状态栏状态更新以及Dozing 的状态改变。
在GLEngine构造方法中调用了ImageWallpaperRenderer,ImageWallpaperRenderer继承自GLWallpaperRenderer,在它的构造方法中调用loadBitmap方法进行了Bitmap的加载。
public ImageWallpaperRenderer(Context context, SurfaceProxy proxy) {
mWallpaperManager = context.getSystemService(WallpaperManager.class);
//。。省略代码
if (loadBitmap()) {
// Compute threshold of the image, this is an async work.
mImageProcessHelper.start(mBitmap);
}
}
首先就是调用了WallpaperManager的getBitmap方法去获取Bitmap。
private boolean loadBitmap() {
if (mWallpaperManager != null && mBitmap == null) {
//省略代码
mBitmap = mWallpaperManager.getBitmap();
}
mWallpaperManager.forgetLoadedWallpaper();
if (mBitmap != null) {
mSurfaceSize.set(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
}
}
return mBitmap != null;
}
getBitmap中又调用了Wallpaper的静态内部类Globals的peekWallpaperBitmap方法,其同旧版本一样提供了两套保证尽态比值稳定运行的机制:缓存和备用方案。如果缓存为空的话就调用getCurrentWallpaperLocked方法来从文件中获取Bitmap。
@UnsupportedAppUsage
public Bitmap getBitmap(boolean hardware) {
return getBitmapAsUpeekWallpaperBitmapser(mContext.getUserId(), hardware);
}
public Bitmap getBitmapAsUser(int userId, boolean hardware) {
return sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, userId, hardware);
}
从文件中获取Bitmap的流程相对于老版本没有改变太多,只是不再直接在WallpaperManagerService.getWallpaper方法中调用getWallpaperDir来获取文件路径以及句柄了,而是将这个获取过程移到了WallpaperData的初始化中。而Wallpaper对象又会在loadSetttingLocked方法中被创建。
private WallpaperData getWallpaperSafeLocked(int userId, int which) {
//..........
loadSettingsLocked(userId, false);
wallpaper = whichSet.get(userId);
//................
return wallpaper;
}
在loadSettingsLocked方法中做了这些事:
- 从mWallpaperMap中尝试获取Wallpaper,如果为空开始WallpaperData的初始化(一般刚开机这个肯定为空)
private void loadSettingsLocked(int userId, boolean keepDimensionHints) {
JournaledFile journal = makeJournaledFile(userId);
FileInputStream stream = null;
File file = journal.chooseForRead();
WallpaperData wallpaper = mWallpaperMap.get(userId);
if (wallpaper == null) {
// Do this once per boot
migrateFromOld();
- 调用migrateFromOld方法对WALLPAPER_CROP,WALLPAPER进行赋值,这两个参数用来指定WallpaperData初始化时的inputFileName,cropFileName
private void migrateFromOld() {
// Pre-N, what existed is the one we're now using as the display crop
File preNWallpaper = new File(getWallpaperDir(0), WALLPAPER_CROP);
// In the very-long-ago, imagery lived with the settings app
File originalWallpaper = new File(WallpaperBackupHelper.WALLPAPER_IMAGE_KEY);
File newWallpaper = new File(getWallpaperDir(0), WALLPAPER);
- 获取完wallpaper之后放入mWallpaperMap中,如果cropWallpaper不存在,就会调用generateCrop方法对原始的Wallpaper进行裁剪,如果连wallpaper文件都不存在就会采用调用默认壁纸的逻辑
wallpaper = new WallpaperData(userId, WALLPAPER, WALLPAPER_CROP);
wallpaper.allowBackup = true;
mWallpaperMap.put(userId, wallpaper);
if (!wallpaper.cropExists()) {
if (wallpaper.sourceExists()) {
generateCrop(wallpaper);
} else {
Slog.i(TAG, "No static wallpaper imagery; defaults will be shown");
}
- 获取完Wallpaper,且确认存在,就会读取xml文件中对壁纸的设定,设置wallpaper中的各种参数
stream = new FileInputStream(file);
XmlPullParser parser = Xml.newPullParser();
parser.setInput(stream, StandardCharsets.UTF_8.name());
int type;
do {
type = parser.next();
if (type == XmlPullParser.START_TAG) {
String tag = parser.getName();
if ("wp".equals(tag)) {
// Common to system + lock wallpapers
parseWallpaperAttributes(parser, wallpaper, keepDimensionHints);
// A system wallpaper might also be a live wallpaper
String comp = parser.getAttributeValue(null, "component");
wallpaper.nextWallpaperComponent = comp != null
? ComponentName.unflattenFromString(comp)
: null;
if (wallpaper.nextWallpaperComponent == null
|| "android".equals(wallpaper.nextWallpaperComponent
.getPackageName())) {
wallpaper.nextWallpaperComponent = mImageWallpaper;
}
接收到返回的文件句柄之后,就会在Globals类中使用BitmapFactory.decodeFileDescriptor进行解码生成Bitmap文件然后一路返回到ImageWallpaperRender中。然后就会调用forgetLoadedWallpaper方法删除掉所有上一张加载的壁纸的内部引用。调用这个方法对于那些只想短暂拥有壁纸,想要减少内存消耗的应用是有用的。但是在调用这个方法之后想要调用这张壁纸又要从硬盘中重新读取。
都结束之后就将Surface的大小调整为Bitmap的宽高。至此Bitmap的获取就结束了。
获取完Bitmap,之后的壁纸渲染工作GLEngine都会交给GLWallpaperRenderer的各种方法,由其发送OpenGL调用来渲染,并通过SurfaceProxy来对SurfaceHolder进行处理,来将壁纸渲染到Surface上。设置壁纸的过程和老版本基本一致