What?
什么是壁纸?
android wallpaper包括锁屏壁纸和桌面壁纸,壁纸又区分静态和动态两种。我们每天使用手机第一眼看到的就是壁纸,好看的壁纸对于手机的颜值也有大大的提升(滑稽),就让我们对壁纸一探究竟吧。
本文基于Android 8.1源码,相关文件如下:
1./frameworks/base/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
2./frameworks/base/services/core/java/android/app/WallpaperManager.java
3./frameworks/base/core/java/android/service/wallpaper/WallpaperService.java
4./frameworks/base/core/java/android/service/wallpaper/IwallpaperService.aidl
5./frameworks/base/packages/apps/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
6./frameworks/base/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
7./frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
How?
1.作为开发者如何去设置壁纸?
2.壁纸是怎么显示的?
3.壁纸存储在什么位置?
如何设置壁纸?举个栗子。
咱们手机中一般有内置主题的应用,在这里可以下载使用非常多好看的壁纸,点击即可设置为锁屏或桌面壁纸。
设置方法很简单,look:
WallpaperManager wallpaperManager = WallpaperManager.getInstance(this);
try {
wallpaperManager.setStream(InputStream,null,true,WallpaperManager.FLAG_LOCK);
} catch (IOException e) {
e.printStackTrace();
}
三步骤:
1.添加设置壁纸的权限
<uses-permission android:name="android.permission.SET_WALLPAPER"/>
2.获取WallpaperManager对象
3.设置壁纸,四个参数分别对应:
a.InputStream:图片对于的输入流
b.visibleCropHint:图片裁剪相关,一般默认为null
c.allowBack:是否允许回退
d.which:壁纸分为锁屏壁纸和桌面壁纸,所以需要设置FLGA:锁屏壁纸--WallpaperManager.FLAG_LOCK,桌面壁纸:WallpaperManager.FLAG_SYSTEM
壁纸是如何显示的?
下面就对如上图中的每个过程做一个简单的分析:
1.setStream
try {
//sGlobals.mService即WallpaperManagerService
ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null,
mContext.getOpPackageName(), visibleCropHint, allowBackup,
result, which, completion, UserHandle.myUserId());
if (fd != null) {
FileOutputStream fos = null;
try {
//将壁纸copy一份并存储到对应目录,默认是/data/system/users/0/wallpaper(或wallpaper_lock),其中0是主用户的userId,支持多用户
fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
copyStreamToWallpaperFile(bitmapData, fos);
fos.close();
completion.waitForCompletion();
} finally {
IoUtils.closeQuietly(fos);
}
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
2.WallpaperManagerService.java#setWallpaper
@Override
public ParcelFileDescriptor setWallpaper(String name, String callingPackage,
Rect cropHint, boolean allowBackup, Bundle extras, int which,
IWallpaperManagerCallback completion, int userId) {
//检查有没有设置壁纸的权限
checkPermission(android.Manifest.permission.SET_WALLPAPER);
//调用setStream方法的时候参数which必须是正确的
if ((which & (FLAG_LOCK|FLAG_SYSTEM)) == 0) {
final String msg = "Must specify a valid wallpaper category to set";
Slog.e(TAG, msg);
throw new IllegalArgumentException(msg);
}
/* If we're setting system but not lock, and lock is currently sharing the system
* wallpaper, we need to migrate that image over to being lock-only before
* the caller here writes new bitmap data.
*/
//如果当前没有锁屏壁纸的话,并且是设置桌面壁纸即which == FLAG_SYSTEM,那么同时设置为锁屏壁纸
if (which == FLAG_SYSTEM && mLockWallpaperMap.get(userId) == null) {
if (DEBUG) {
Slog.i(TAG, "Migrating system->lock to preserve");
}
migrateSystemToLockWallpaperLocked(userId);
}
ParcelFileDescriptor pfd = updateWallpaperBitmapLocked(name, wallpaper, extras);
}
3.最主要的方法:WallpaperObserver#onEvent
WallpaperObserver是WallpaperManagerservice.java的内部类,它的主要职责是监听文件变化,也就是壁纸对应的文件更新,看下源码中关于它的注释:
/**
* Observes the wallpaper for changes and notifies all IWallpaperServiceCallbacks
* that the wallpaper has changed. The CREATE is triggered when there is no
* wallpaper set and is created for the first time. The CLOSE_WRITE is triggered
* every time the wallpaper is changed.
*/
监听wallpaper变化并通知IWallpaperServiceCallbacks,前文提到的LockscreenWallpaper就是继承了
IWallpaperServiceCallbacks,并重写了它的onWallppaerChanged方法,在这里更新锁屏壁纸的。
@Override
public void onEvent(int event, String path) {
//如果是锁屏壁纸更新
if (moved && lockWallpaperChanged) {
notifyLockWallpaperChanged();
//android 8.0新增的一个变化,锁屏包括下拉快捷的主题会根据当前的壁纸来变化,避免壁纸和锁屏的图标颜色一致导致的显示不清问题,但是有一个缺陷就是:
//获取的是当前壁纸的主色调,而不是某个区域的主色调,这样就会导致虽然主色调是白色,比如时间的区域是黑色,这一点小米做的比较好,它是根据当前区域的壁纸的主色调来进行反色的。
notifyWallpaperColorsChanged(wallpaper, FLAG_LOCK);
return;
}
if (sysWallpaperChanged || lockWallpaperChanged) {
notifyCallbacksLocked(wallpaper);
}
if (sysWallpaperChanged) {
//桌面壁纸变化,那么bind ImageWallpaper,ImageWallpaper是负责显示静态桌面壁纸的
// If this was the system wallpaper, rebind...
bindWallpaperComponentLocked(mImageWallpaper, true,
false, wallpaper, null);
notifyColorsWhich |= FLAG_SYSTEM;
}
if (lockWallpaperChanged
|| (wallpaper.whichPending & FLAG_LOCK) != 0) {
if (DEBUG) {
Slog.i(TAG, "Lock-relevant wallpaper changed");
}
// either a lock-only wallpaper commit or a system+lock event.
// if it's system-plus-lock we need to wipe the lock bookkeeping;
// we're falling back to displaying the system wallpaper there.
//如果参数which是system+lock,也就是同时设置锁屏和桌面壁纸,那么remove锁屏壁纸,因为已经是同一张壁纸了
if (!lockWallpaperChanged) {
mLockWallpaperMap.remove(wallpaper.userId);
}
// and in any case, tell keyguard about it
notifyLockWallpaperChanged();
notifyColorsWhich |= FLAG_LOCK;
}
}
先看锁屏壁纸更新这一部分:
void notifyLockWallpaperChanged() {
final IWallpaperManagerCallback cb = mKeyguardListener;
if (cb != null) {
try {
cb.onWallpaperChanged();
} catch (RemoteException e) {
// Oh well it went away; no big deal
}
}
}
mKeyguardListener赋值的地方:
@Override
public boolean setLockWallpaperCallback(IWallpaperManagerCallback cb) {
checkPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW);
synchronized (mLock) {
mKeyguardListener = cb;
}
return true;
}
前面我们说过LockscreenWallpaper.java是继承了IWallpaperManagerCallback的,那么setLockWallpaperCallback调用的地方应该是在这里:
public LockscreenWallpaper(Context ctx, PhoneStatusBar bar, Handler h) {
mService = IWallpaperManager.Stub.asInterface(
ServiceManager.getService(Context.WALLPAPER_SERVICE));
mWallpaperManager = (WallpaperManager) ctx.getSystemService(Context.WALLPAPER_SERVICE);
try {
//在这里给mKeyguardListener赋值的
mService.setLockWallpaperCallback(this);
} catch (RemoteException e) {
Log.e(TAG, "System dead?" + e);
}
}
4.LockscreenWallpaper.java#onWallpaperChanged:
@Override
public void onWallpaperChanged() {
// Called on Binder thread.
mH.removeCallbacks(this);
mH.post(this);
}
LockscreenWallpaper实现了Runnable接口的,所以看下它的run方法:
@Override
public void run() {
// Called in response to onWallpaperChanged on the main thread.
mLoader = new AsyncTask<Void, Void, LoaderResult>() {
@Override
protected LoaderResult doInBackground(Void... params) {
return loadBitmap(currentUser, selectedUser);
}
@Override
protected void onPostExecute(LoaderResult result) {
super.onPostExecute(result);
if (isCancelled()) {
return;
}
if (result.success) {
mCached = true;
mCache = result.bitmap;
mUpdateMonitor.setHasLockscreenWallpaper(result.bitmap != null);
//通知StatsuBar更新壁纸
mBar.updateMediaMetaData(
true /* metaDataChanged */, true /* allowEnterAnimation */);
}
mLoader = null;
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
异步获取壁纸,并通知StatusBar去更新壁纸。
5.StatusBar.java#updateMediaMetaData:
这里主要就是对锁屏壁纸所在的View做最基本的setImageBitmap。
6.再看桌面壁纸部分:
6.1 bindWallpaperComponentLocked
if (sysWallpaperChanged) {
// If this was the system wallpaper, rebind...
bindWallpaperComponentLocked(mImageWallpaper, true,
false, wallpaper, null);
notifyColorsWhich |= FLAG_SYSTEM;
}
mImageWallpaper = ComponentName.unflattenFromString(
context.getResources().getString(R.string.image_wallpaper_component));
也就是一开始提到的:
/frameworks/base/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force,
boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) {
Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);
WallpaperConnection newConn = new WallpaperConnection(wi, wallpaper);
//componentName就是ImageWallpaper
intent.setComponent(componentName);
intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
com.android.internal.R.string.wallpaper_binding_label);
intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivityAsUser(
mContext, 0,
Intent.createChooser(new Intent(Intent.ACTION_SET_WALLPAPER),
mContext.getText(com.android.internal.R.string.chooser_wallpaper)),
0, null, new UserHandle(serviceUserId)));
}
ImageWallpaper继承了Service,既然是bindService,那么主要看下conn,也就是WallpaperConnection。
它的onServiceConnected方法:
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mLock) {
if (mWallpaper.connection == this) {
mService = IWallpaperService.Stub.asInterface(service);
attachServiceLocked(this, mWallpaper);
// XXX should probably do saveSettingsLocked() later
// when we have an engine, but I'm not sure about
// locking there and anyway we always need to be able to
// recover if there is something wrong.
saveSettingsLocked(mWallpaper.userId);
FgThread.getHandler().removeCallbacks(mResetRunnable);
}
}
}
6.2 继续看attachServcieLocked方法:
void attachServiceLocked(WallpaperConnection conn, WallpaperData wallpaper) {
try {
conn.mService.attach(conn, conn.mToken,
TYPE_WALLPAPER, false,
wallpaper.width, wallpaper.height, wallpaper.padding);
} catch (RemoteException e) {
Slog.w(TAG, "Failed attaching wallpaper; clearing", e);
if (!wallpaper.wallpaperUpdating) {
bindWallpaperComponentLocked(null, false, false, wallpaper, null);
}
}
}
conn.mService.attach是调用了IWallpaperServiceWrapper 的attach方法,IWallpaperServiceWrapper 继承了
IWallpaperService.Stub。
@Override
public void attach(IWallpaperConnection conn, IBinder windowToken,
int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding) {
new IWallpaperEngineWrapper(mTarget, conn, windowToken,
windowType, isPreview, reqWidth, reqHeight, padding);
}
在看它的构造方法,发送了一个DO_ATTACH的消息:
Message msg = mCaller.obtainMessage(DO_ATTACH);
mCaller.sendMessage(msg);
case DO_ATTACH: {
try {
mConnection.attachEngine(this);
} catch (RemoteException e) {
Log.w(TAG, "Wallpaper host disappeared", e);
return;
}
Engine engine = onCreateEngine();
mEngine = engine;
mActiveEngines.add(engine);
engine.attach(this);
return;
}
onCreateEngine也是一个抽象的方法:
/**
* Must be implemented to return a new instance of the wallpaper's engine.
* Note that multiple instances may be active at the same time, such as
* when the wallpaper is currently set as the active wallpaper and the user
* is in the wallpaper picker viewing a preview of it as well.
*/
public abstract Engine onCreateEngine();
实现的地方仍然是在ImageWallpaper.java里
@Override
public Engine onCreateEngine() {
mEngine = new DrawableEngine();
return mEngine;
}
DrawableEngine是自定义的继承Engine的内部类
最后调用engine.attach方法。
WallpaperService.java的attach方法:
void attach(IWallpaperEngineWrapper wrapper) {
onCreate(mSurfaceHolder);
mInitializing = false;
mReportedVisible = false;
updateSurface(false, false, false);
}
6.3 onCreate(mSurfaceHolder)
它是一个抽象方法
/**
* Called once to initialize the engine. After returning, the
* engine's surface will be created by the framework.
*/
public void onCreate(SurfaceHolder surfaceHolder) {
}
它是一个抽象方法,那么真正的实现是在它的子类,也就是ImageWallpaper.java里
@Override
public void onCreate(SurfaceHolder surfaceHolder) {
if (DEBUG) {
Log.d(TAG, "onCreate");
}
super.onCreate(surfaceHolder);
mDefaultDisplay = getSystemService(WindowManager.class).getDefaultDisplay();
setOffsetNotificationsEnabled(false);
updateSurfaceSize(surfaceHolder, getDefaultDisplayInfo(), false /* forDraw */);
}
surfaceHolder是在父类里初始化的,surfaceHolder = new BaseSurfaceHolder();
6.4 updateSurfaceSize
在这里主要是继续调用loadWallpaper方法去解析壁纸并最终绘制到surfaceHolder上。
6.5 drawFrame
对壁纸进行一些裁剪操作,根据是否支持硬件加速来决定绘制的方法:
//支持硬件加速
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!
// hw-accelerated renderer retains bitmap for faster rotation
unloadWallpaper(false /* forgetSize */);
}
}
到这里,把壁纸的设置的简单过程基本上就讲完了,作为笔记做一个记录。
如有错误的地方,欢迎指正。