AOSP Android 14 壁纸架构深度分析
一、整体架构概览
┌─────────────────────────────────────────────────────────────────┐
│ Android 14 壁纸架构 │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ 应用层 (Apps) │ │
│ │ ┌──────────────┐ ┌────────────────────────┐ │ │
│ │ │ WallpaperPicker│ │ 第三方壁纸 App │ │ │
│ │ │ (壁纸选择器) │ │ (Live Wallpaper等) │ │ │
│ │ └──────┬───────┘ └──────────┬─────────────┘ │ │
│ │ │ WallpaperManager API │ │ │
│ └─────────┼────────────────────────┼────────────────┘ │
│ │ │ │
│ ┌─────────▼────────────────────────▼────────────────┐ │
│ │ Framework 层 │ │
│ │ │ │
│ │ ┌─────────────────────┐ ┌──────────────────────┐│ │
│ │ │ WallpaperManager │ │ WallpaperManager ││ │
│ │ │ (客户端 API) │ │ Service ││ │
│ │ └─────────┬───────────┘ │ (系统服务) ││ │
│ │ │ │ ││ │
│ │ │ Binder IPC │ ┌────────────────┐ ││ │
│ │ └──────────────►│ │WallpaperData │ ││ │
│ │ │ │(壁纸数据管理) │ ││ │
│ │ │ └────────────────┘ ││ │
│ │ │ ┌────────────────┐ ││ │
│ │ │ │WallpaperCropper│ ││ │
│ │ │ │(裁剪处理) │ ││ │
│ │ │ └────────────────┘ ││ │
│ │ └──────────┬───────────┘│ │
│ │ │ │ │
│ │ ┌────────────────────────────────────▼──────────┐│ │
│ │ │ WallpaperService ││ │
│ │ │ ┌──────────────────┐ ┌──────────────────────┐││ │
│ │ │ │ ImageWallpaper │ │ LiveWallpaperService ││ │ │
│ │ │ │ (静态壁纸) │ │ (动态壁纸) │││ │
│ │ │ │ └─Engine │ │ └─Engine │││ │
│ │ │ │ └─GLEngine │ │ └─自定义渲染 │││ │
│ │ │ └──────────────────┘ └──────────────────────┘││ │
│ │ └───────────────────────────────────────────────┘│ │
│ │ │ │
│ │ ┌───────────────────────────────────────────────┐│ │
│ │ │ WindowManagerService ││ │
│ │ │ ┌──────────────────────────────────────────┐ ││ │
│ │ │ │ WallpaperController │ ││ │
│ │ │ │ ├── 壁纸窗口(TYPE_WALLPAPER)管理 │ ││ │
│ │ │ │ ├── 壁纸偏移/视差计算 │ ││ │
│ │ │ │ ├── 壁纸可见性控制 │ ││ │
│ │ │ │ └── 壁纸过渡动画 │ ││ │
│ │ │ └──────────────────────────────────────────┘ ││ │
│ │ └───────────────────────────────────────────────┘│ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ SystemUI 层 │ │
│ │ ┌─────────────────┐ ┌─────────────────────────┐ │ │
│ │ │ ThemeOverlay │ │ Scrim / Shade │ │ │
│ │ │ Controller │ │ (壁纸上层遮罩) │ │ │
│ │ │ (Material You │ │ │ │ │
│ │ │ 颜色提取) │ │ NotificationShade │ │ │
│ │ └─────────────────┘ └─────────────────────────┘ │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ SurfaceFlinger / HWComposer │ │
│ │ 壁纸 Surface 最终合成到显示器 │ │
│ └────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
二、源码目录结构
═══════ Framework 层 ═══════
frameworks/base/
├── core/java/android/app/
│ ├── WallpaperManager.java // ★ 客户端 API
│ ├── WallpaperInfo.java // 壁纸元数据
│ ├── WallpaperColors.java // 壁纸颜色描述
│ └── IWallpaperManager.aidl // 系统服务 AIDL
│
├── core/java/android/service/wallpaper/
│ ├── WallpaperService.java // ★ 壁纸服务基类
│ │ └── Engine // 壁纸渲染引擎
│ └── IWallpaperEngine.aidl // 引擎 AIDL
│
├── core/java/com/android/internal/view/
│ └── IWallpaperConnection.aidl
│
├── services/core/java/com/android/server/wallpaper/
│ ├── WallpaperManagerService.java // ★★★ 系统服务
│ ├── WallpaperData.java // 壁纸数据
│ ├── WallpaperCropper.java // 壁纸裁剪 (Android 14新增)
│ ├── WallpaperDisplayHelper.java // 多显示器辅助
│ └── GLHelper.java // EGL 辅助
│
└── services/core/java/com/android/server/wm/
├── WallpaperController.java // ★★ WMS 壁纸控制
├── WallpaperWindowToken.java // 壁纸窗口令牌
├── WallpaperAnimationAdapter.java // 壁纸动画适配
└── WallpaperVisibilityListeners.java // 可见性监听
═══════ 默认壁纸实现 ═══════
frameworks/base/packages/SystemUI/
└── src/com/android/systemui/wallpapers/
├── ImageWallpaper.java // ★ 静态壁纸实现
└── gl/
├── ImageWallpaperRenderer.java // OpenGL 渲染器
├── ImageRevealWallpaperRenderer.java // 揭示动画渲染 (Android 14)
├── EglHelper.java // EGL 辅助
└── ...
═══════ 壁纸选择器 ═══════
packages/apps/WallpaperPicker2/
├── src/com/android/wallpaper/
│ ├── picker/ // 选择器 UI
│ │ ├── WallpaperPickerActivity.java
│ │ ├── preview/ // 预览
│ │ │ ├── WallpaperPreviewFragment.java
│ │ │ └── LiveWallpaperPreviewFragment.java
│ │ └── customization/ // 自定义
│ ├── model/ // 数据模型
│ │ ├── WallpaperInfo.java
│ │ ├── LiveWallpaperInfo.java
│ │ └── ImageWallpaperInfo.java
│ ├── module/ // Dagger 模块
│ └── util/
│ └── WallpaperConnection.java // 壁纸连接管理
│
└── res/
═══════ SystemUI 壁纸相关 ═══════
packages/SystemUI/src/com/android/systemui/
├── wallpapers/ // 壁纸渲染
│ ├── ImageWallpaper.java
│ └── gl/
├── statusbar/phone/
│ ├── ScrimController.java // 遮罩控制
│ └── LockscreenWallpaper.java // 锁屏壁纸
├── theme/
│ └── ThemeOverlayController.java // 主题/颜色
└── keyguard/
└── KeyguardSliceProvider.java // 锁屏信息
三、WallpaperManager — 客户端 API
3.1 核心 API
public class WallpaperManager {
public static final int FLAG_SYSTEM = 1 << 0;
public static final int FLAG_LOCK = 1 << 1;
public Drawable getDrawable() { ... }
public Drawable getDrawable(@SetWallpaperFlags int which) { ... }
public ParcelFileDescriptor getWallpaperFile(
@SetWallpaperFlags int which) { ... }
public WallpaperColors getWallpaperColors(
@SetWallpaperFlags int which) { ... }
public int setBitmap(Bitmap bitmap) throws IOException { ... }
public int setBitmap(Bitmap fullImage, Rect visibleCropHint,
boolean allowBackup, @SetWallpaperFlags int which)
throws IOException { ... }
public void setStream(InputStream bitmapData) throws IOException { ... }
public int setStream(InputStream bitmapData, Rect visibleCropHint,
boolean allowBackup, @SetWallpaperFlags int which)
throws IOException { ... }
public void setResource(@RawRes int resid) throws IOException { ... }
public void setWallpaperComponent(ComponentName name)
throws IOException { ... }
public int setBitmapWithCrops(
Bitmap fullImage,
Map<Point, Rect> cropHints, // 不同屏幕尺寸的裁剪区域
boolean allowBackup,
@SetWallpaperFlags int which) throws IOException { ... }
public void setWallpaperOffsetSteps(float xStep, float yStep) { ... }
public void setWallpaperOffsets(IBinder windowToken,
float xOffset, float yOffset) { ... }
public void addOnColorsChangedListener(
OnColorsChangedListener listener, Handler handler) { ... }
public void removeOnColorsChangedListener(
OnColorsChangedListener listener) { ... }
public WallpaperInfo getWallpaperInfo() { ... }
public WallpaperInfo getWallpaperInfo(@SetWallpaperFlags int which) { ... }
public boolean isLockscreenLiveWallpaperEnabled() { ... }
public boolean isWallpaperSupported() { ... }
public boolean isSetWallpaperAllowed() { ... }
public int getDesiredMinimumWidth() { ... }
public int getDesiredMinimumHeight() { ... }
public void suggestDesiredDimensions(int minimumWidth,
int minimumHeight) { ... }
public interface OnColorsChangedListener {
void onColorsChanged(WallpaperColors colors, int which);
}
}
3.2 WallpaperColors — 壁纸颜色描述
public final class WallpaperColors implements Parcelable {
private final Color mPrimaryColor;
private final Color mSecondaryColor;
private final Color mTertiaryColor;
public static final int HINT_SUPPORTS_DARK_TEXT = 1 << 0;
public static final int HINT_SUPPORTS_DARK_THEME = 1 << 1;
public static final int HINT_FROM_BITMAP = 1 << 2;
private int mColorHints;
public static WallpaperColors fromBitmap(@NonNull Bitmap bitmap) {
return fromBitmap(bitmap, false );
}
public static WallpaperColors fromBitmap(@NonNull Bitmap bitmap,
boolean darkTheme) {
Bitmap scaledBitmap = Bitmap.createScaledBitmap(
bitmap,
Math.min(bitmap.getWidth(), 112),
Math.min(bitmap.getHeight(), 112),
true );
Palette palette = new Palette.Builder(scaledBitmap)
.setQuantizer(new VariationalKMeansQuantizer())
.maximumColorCount(128)
.generate();
List<Palette.Swatch> swatches = palette.getSwatches();
swatches.sort((a, b) -> b.getPopulation() - a.getPopulation());
Color primaryColor = null;
Color secondaryColor = null;
Color tertiaryColor = null;
if (swatches.size() >= 1) {
primaryColor = Color.valueOf(swatches.get(0).getRgb());
}
if (swatches.size() >= 2) {
secondaryColor = Color.valueOf(swatches.get(1).getRgb());
}
if (swatches.size() >= 3) {
tertiaryColor = Color.valueOf(swatches.get(2).getRgb());
}
int hints = HINT_FROM_BITMAP;
float luminance = calculateLuminance(primaryColor);
if (luminance > 0.5f) {
hints |= HINT_SUPPORTS_DARK_TEXT;
}
if (luminance < 0.3f) {
hints |= HINT_SUPPORTS_DARK_THEME;
}
return new WallpaperColors(
primaryColor, secondaryColor, tertiaryColor, hints);
}
public WallpaperColors(@NonNull Color primaryColor,
@Nullable Color secondaryColor,
@Nullable Color tertiaryColor) {
this(primaryColor, secondaryColor, tertiaryColor, 0);
}
}
四、WallpaperManagerService — 核心系统服务 ★★★
4.1 服务注册和初始化
public class WallpaperManagerService extends IWallpaperManager.Stub
implements IWallpaperManagerService {
private static final String WALLPAPER = "wallpaper_orig";
private static final String WALLPAPER_CROP = "wallpaper";
private static final String WALLPAPER_LOCK_ORIG = "wallpaper_lock_orig";
private static final String WALLPAPER_LOCK_CROP = "wallpaper_lock";
private static final String WALLPAPER_INFO = "wallpaper_info.xml";
private final SparseArray<WallpaperData> mWallpaperMap = new SparseArray<>();
private final SparseArray<WallpaperData> mLockWallpaperMap = new SparseArray<>();
private final SparseArray<WallpaperConnection> mWallpaperConnections =
new SparseArray<>();
private final SparseArray<RemoteCallbackList<IWallpaperManagerCallback>>
mColorsChangedListeners = new SparseArray<>();
public WallpaperManagerService(Context context) {
mContext = context;
mImageWallpaper = ComponentName.unflattenFromString(
context.getResources().getString(
R.string.image_wallpaper_component));
mDefaultWallpaperComponent = WallpaperManager.getDefaultWallpaperComponent(
context);
}
public void systemReady() {
initializeFallbackWallpaper();
loadSettingsLocked(UserHandle.USER_SYSTEM, false);
switchWallpaper(mWallpaperMap.get(UserHandle.USER_SYSTEM), null);
registerUserSwitchReceiver();
registerPackageMonitor();
}
}
4.2 WallpaperData — 壁纸数据模型
class WallpaperData {
int userId;
File wallpaperFile;
File cropFile;
ComponentName wallpaperComponent;
ComponentName nextWallpaperComponent;
int wallpaperId;
Rect cropHint = new Rect(0, 0, 0, 0);
SparseArray<Rect> mCropHints;
int width;
int height;
int primaryColors;
WallpaperColors mWallpaperColors;
boolean mIsColorExtractedFromDim;
float mWallpaperXOffset;
float mWallpaperYOffset;
float mWallpaperXStep;
float mWallpaperYStep;
int mWallpaperDisplayOffsetX;
int mWallpaperDisplayOffsetY;
WallpaperConnection connection;
long lastDiedTime;
boolean wallpaperUpdating;
boolean mIsLockscreenLiveWallpaperEnabled;
boolean allowBackup = true;
boolean mShouldDimByDefault = true;
float mWallpaperDimAmount = 0.0f;
}
4.3 设置壁纸完整流程
@Override
public int setWallpaper(String name, String callingPackage,
@SetWallpaperFlags int which, Rect cropHint,
boolean allowBackup, Bundle extras,
int wallpaperId) {
checkCallingOrSelfPermission(SET_WALLPAPER);
if (!isWallpaperSettingAllowed(callingPackage)) {
throw new SecurityException("Not allowed to set wallpaper");
}
int userId = UserHandle.getCallingUserId();
synchronized (mLock) {
WallpaperData wallpaper;
if ((which & FLAG_LOCK) != 0) {
wallpaper = getOrCreateLockWallpaperDataLocked(userId);
} else {
wallpaper = mWallpaperMap.get(userId);
}
if (wallpaper == null) {
throw new IllegalStateException("No wallpaper data");
}
wallpaper.wallpaperId = makeWallpaperIdLocked();
if (cropHint != null && !cropHint.isEmpty()) {
wallpaper.cropHint.set(cropHint);
} else {
wallpaper.cropHint.set(0, 0, 0, 0);
}
wallpaper.allowBackup = allowBackup;
ParcelFileDescriptor pfd = updateWallpaperBitmapLocked(
name, wallpaper, extras);
if (pfd != null) {
wallpaper.imageWallpaperPending = true;
wallpaper.whichPending = which;
}
return wallpaper.wallpaperId;
}
}
private ParcelFileDescriptor updateWallpaperBitmapLocked(
String name, WallpaperData wallpaper, Bundle extras) {
try {
File dir = getWallpaperDir(wallpaper.userId);
if (!dir.exists()) {
dir.mkdirs();
}
File file = new File(dir, WALLPAPER);
ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file,
MODE_CREATE | MODE_READ_WRITE | MODE_TRUNCATE);
return pfd;
} catch (FileNotFoundException e) {
return null;
}
}
private void onWallpaperWriteComplete(WallpaperData wallpaper) {
generateCrop(wallpaper);
extractColors(wallpaper);
saveSettingsLocked(wallpaper.userId);
if (wallpaper.wallpaperComponent == null
|| wallpaper.wallpaperComponent.equals(mImageWallpaper)) {
bindWallpaperComponentLocked(mImageWallpaper,
true , false , wallpaper, null);
}
notifyWallpaperChanged(wallpaper);
notifyWallpaperColorsChanged(wallpaper, wallpaper.mWhich);
notifyWallpaperChangedToWms(wallpaper);
}
4.4 壁纸裁剪 — WallpaperCropper (Android 14 新增)
public class WallpaperCropper {
static void generateCrop(WallpaperData wallpaper) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(
wallpaper.wallpaperFile.getAbsolutePath(), options);
int origWidth = options.outWidth;
int origHeight = options.outHeight;
Rect cropHint = wallpaper.cropHint;
if (cropHint.isEmpty()) {
cropHint = new Rect(0, 0, origWidth, origHeight);
}
DisplayInfo displayInfo = getDefaultDisplayInfo();
int targetWidth = displayInfo.logicalWidth;
int targetHeight = displayInfo.logicalHeight;
float parallaxRatio = getParallaxRatio();
targetWidth = (int) (targetWidth * parallaxRatio);
int sampleSize = calculateSampleSize(
cropHint.width(), cropHint.height(),
targetWidth, targetHeight);
options.inJustDecodeBounds = false;
options.inSampleSize = sampleSize;
BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(
wallpaper.wallpaperFile.getAbsolutePath(), false);
Bitmap croppedBitmap = decoder.decodeRegion(cropHint, options);
if (croppedBitmap.getWidth() != targetWidth
|| croppedBitmap.getHeight() != targetHeight) {
croppedBitmap = Bitmap.createScaledBitmap(
croppedBitmap, targetWidth, targetHeight, true);
}
FileOutputStream fos = new FileOutputStream(wallpaper.cropFile);
croppedBitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.close();
croppedBitmap.recycle();
}
static void generateMultiCrop(WallpaperData wallpaper,
Map<Point, Rect> cropHints) {
for (Map.Entry<Point, Rect> entry : cropHints.entrySet()) {
Point screenSize = entry.getKey();
Rect crop = entry.getValue();
File cropFile = getCropFileForSize(
wallpaper, screenSize);
generateCropForSize(wallpaper, crop, screenSize, cropFile);
}
}
}
4.5 壁纸服务绑定
boolean bindWallpaperComponentLocked(ComponentName componentName,
boolean force, boolean fromUser, WallpaperData wallpaper,
IRemoteCallback reply) {
if (componentName == null) {
componentName = mDefaultWallpaperComponent;
if (componentName == null) {
componentName = mImageWallpaper;
}
}
Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);
intent.setComponent(componentName);
ServiceInfo si = mIPackageManager.getServiceInfo(
componentName, PackageManager.GET_META_DATA,
wallpaper.userId);
if (si == null) {
throw new SecurityException("Wallpaper service not found");
}
if (!si.permission.equals(BIND_WALLPAPER)) {
throw new SecurityException(
"Wallpaper service must require BIND_WALLPAPER permission");
}
WallpaperInfo wi = null;
if (!componentName.equals(mImageWallpaper)) {
wi = new WallpaperInfo(mContext,
mIPackageManager.resolveService(intent, null, 0, wallpaper.userId));
}
WallpaperConnection oldConn = wallpaper.connection;
if (oldConn != null) {
detachWallpaperLocked(oldConn);
}
WallpaperConnection newConn = new WallpaperConnection(
wi, wallpaper, componentName);
wallpaper.connection = newConn;
intent.setComponent(componentName);
boolean bound = mContext.bindServiceAsUser(
intent,
newConn,
Context.BIND_AUTO_CREATE
| Context.BIND_SHOWING_UI
| Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
| Context.BIND_INCLUDE_CAPABILITIES,
new UserHandle(wallpaper.userId));
if (!bound) {
if (!componentName.equals(mImageWallpaper)) {
return bindWallpaperComponentLocked(
mImageWallpaper, true, false, wallpaper, reply);
}
return false;
}
wallpaper.wallpaperComponent = componentName;
wallpaper.connection = newConn;
return true;
}
4.6 WallpaperConnection — 与壁纸服务的连接
class WallpaperConnection extends IWallpaperConnection.Stub
implements ServiceConnection {
final WallpaperInfo mInfo;
final WallpaperData mWallpaper;
final ComponentName mComponent;
IWallpaperService mService;
IWallpaperEngine mEngine;
boolean mConnected;
boolean mDimensionsChanged;
boolean mPaddingChanged;
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mLock) {
mService = IWallpaperService.Stub.asInterface(service);
attachServiceLocked(this, mWallpaper);
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
synchronized (mLock) {
mService = null;
mEngine = null;
if (mWallpaper.wallpaperUpdating) {
return;
}
if (!mWallpaper.wallpaperComponent.equals(mImageWallpaper)) {
bindWallpaperComponentLocked(mImageWallpaper,
true, false, mWallpaper, null);
}
}
}
@Override
public void attachEngine(IWallpaperEngine engine, int displayId) {
synchronized (mLock) {
mEngine = engine;
mConnected = true;
try {
engine.setDesiredSize(
mWallpaper.width, mWallpaper.height);
engine.setDisplayPadding(mWallpaper.padding);
engine.setWallpaperOffsets(
mWallpaper.mWallpaperXOffset,
mWallpaper.mWallpaperYOffset);
engine.applyDimming(mWallpaper.mWallpaperDimAmount);
} catch (RemoteException e) { }
notifyCallbacksLocked(mWallpaper);
}
}
@Override
public void onWallpaperColorsChanged(WallpaperColors colors,
int displayId) {
synchronized (mLock) {
mWallpaper.mWallpaperColors = colors;
notifyWallpaperColorsChangedOnDisplay(
mWallpaper, mWallpaper.mWhich, displayId);
}
}
@Override
public void engineShown(IWallpaperEngine engine) {
}
}
void attachServiceLocked(WallpaperConnection conn,
WallpaperData wallpaper) {
try {
conn.mService.attach(
conn,
conn.mToken,
TYPE_WALLPAPER,
false ,
wallpaper.width,
wallpaper.height,
wallpaper.padding,
wallpaper.mWhich
);
} catch (RemoteException e) {
}
}
五、WallpaperService — 壁纸服务基类
5.1 核心架构
public abstract class WallpaperService extends Service {
static final String SERVICE_INTERFACE =
"android.service.wallpaper.WallpaperService";
@Override
public final IBinder onBind(Intent intent) {
return new IWallpaperServiceWrapper(this);
}
public abstract Engine onCreateEngine();
class IWallpaperServiceWrapper extends IWallpaperService.Stub {
private final WallpaperService mTarget;
@Override
public void attach(IWallpaperConnection conn,
IBinder windowToken,
int windowType, boolean isPreview,
int reqWidth, int reqHeight,
Rect padding, int displayId) {
Engine engine = mTarget.onCreateEngine();
engine.attach(
IWallpaperEngineWrapper.wrap(engine),
conn, windowToken, windowType,
isPreview, reqWidth, reqHeight,
padding, displayId);
}
}
public class Engine {
final BaseSurfaceHolder mSurfaceHolder = new WallpaperSurfaceHolder();
SurfaceControl mSurfaceControl;
IWindowSession mSession;
boolean mVisible;
boolean mReportedVisible;
float mPendingXOffset;
float mPendingYOffset;
float mPendingXOffsetStep;
float mPendingYOffsetStep;
boolean mOffsetsChanged;
int mWidth;
int mHeight;
int mReqWidth;
int mReqHeight;
int mWallpaperFlags;
public void onCreate(SurfaceHolder surfaceHolder) { }
public void onDestroy() { }
public void onVisibilityChanged(boolean visible) { }
public void onSurfaceCreated(SurfaceHolder holder) { }
public void onSurfaceChanged(SurfaceHolder holder,
int format, int width, int height) { }
public void onSurfaceDestroyed(SurfaceHolder holder) { }
public void onOffsetsChanged(float xOffset, float yOffset,
float xOffsetStep, float yOffsetStep,
int xPixelOffset, int yPixelOffset) { }
public void onTouchEvent(MotionEvent event) { }
public void notifyColorsChanged() {
WallpaperColors colors = onComputeColors();
if (colors != null) {
try {
mConnection.onWallpaperColorsChanged(colors, mDisplayId);
} catch (RemoteException e) { }
}
}
public @Nullable WallpaperColors onComputeColors() {
return null;
}
public SurfaceHolder getSurfaceHolder() {
return mSurfaceHolder;
}
public void setTouchEventsEnabled(boolean enabled) {
mTouchEventsEnabled = enabled;
}
void attach(IWallpaperEngineWrapper wrapper,
IWallpaperConnection conn,
IBinder windowToken,
int windowType, boolean isPreview,
int reqWidth, int reqHeight,
Rect padding, int displayId) {
mConnection = conn;
mWindowToken = windowToken;
mIsPreview = isPreview;
mReqWidth = reqWidth;
mReqHeight = reqHeight;
mDisplayId = displayId;
mSession = WindowManagerGlobal.getWindowSession();
mWindow = new W(this);
WindowManager.LayoutParams lp =
new WindowManager.LayoutParams();
lp.type = WindowManager.LayoutParams.TYPE_WALLPAPER;
lp.token = windowToken;
lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
lp.format = PixelFormat.RGBX_8888;
lp.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
lp.setTitle("Wallpaper{" +
Integer.toHexString(System.identityHashCode(this)) + "}");
lp.inputFeatures = INPUT_FEATURE_NO_INPUT_CHANNEL;
int result = mSession.addToDisplayAsUser(
mWindow, lp, View.VISIBLE,
displayId, userId, mInsetsState,
mInputChannel, mTempInsets, mTempControls);
mSurfaceControl = new SurfaceControl.Builder()
.setName("WallpaperSurface")
.setCallsite("WallpaperService.Engine.attach")
.build();
onCreate(mSurfaceHolder);
updateSurface(true, false, false);
}
void updateSurface(boolean forceRelayout,
boolean forceReport, boolean redrawNeeded) {
mSession.relayout(mWindow, mLayout,
mWidth, mHeight, View.VISIBLE, 0,
mWinFrame, mOverscanInsets, mContentInsets,
mVisibleInsets, mStableInsets,
mBackdropFrame, mDisplayCutout,
mMergedConfiguration, mSurfaceControl,
mInsetsState, mTempControls, mSurfaceSize);
if (surfaceCreated) {
onSurfaceCreated(mSurfaceHolder);
}
if (sizeChanged) {
onSurfaceChanged(mSurfaceHolder,
mFormat, mWidth, mHeight);
}
}
public void applyDimming(float dimAmount) {
mWallpaperDimAmount = dimAmount;
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
if (dimAmount > 0) {
float[] colorMatrix = new float[]{
1-dimAmount, 0, 0, 0, 0,
0, 1-dimAmount, 0, 0, 0,
0, 0, 1-dimAmount, 0, 0,
0, 0, 0, 1, 0
};
t.setColorTransform(mSurfaceControl, colorMatrix,
new float[3]);
} else {
t.clearColorTransform(mSurfaceControl);
}
t.apply();
}
}
}
六、ImageWallpaper — 默认静态壁纸实现
6.1 整体结构
public class ImageWallpaper extends WallpaperService {
@Override
public Engine onCreateEngine() {
return new GLEngine();
}
class GLEngine extends Engine {
private EglHelper mEglHelper;
private ImageWallpaperRenderer mRenderer;
private Bitmap mBitmap;
private int mDisplayWidth;
private int mDisplayHeight;
@Override
public void onCreate(SurfaceHolder surfaceHolder) {
super.onCreate(surfaceHolder);
surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_GPU);
surfaceHolder.setFormat(PixelFormat.RGBA_8888);
mEglHelper = new EglHelper();
mRenderer = new ImageWallpaperRenderer(getApplicationContext());
}
@Override
public void onSurfaceCreated(SurfaceHolder holder) {
super.onSurfaceCreated(holder);
mEglHelper.init(holder, needSupportWideColorGamut());
loadWallpaperBitmap();
}
@Override
public void onSurfaceChanged(SurfaceHolder holder,
int format, int width, int height) {
mDisplayWidth = width;
mDisplayHeight = height;
mRenderer.setDisplaySize(width, height);
drawFrame();
}
@Override
public void onVisibilityChanged(boolean visible) {
super.onVisibilityChanged(visible);
if (visible) {
drawFrame();
}
}
@Override
public void onOffsetsChanged(float xOffset, float yOffset,
float xOffsetStep, float yOffsetStep,
int xPixelOffset, int yPixelOffset) {
mRenderer.setOffsets(xOffset, yOffset);
drawFrame();
}
private void drawFrame() {
if (!mEglHelper.hasEglContext()) return;
mEglHelper.makeCurrent();
mRenderer.draw(
mDisplayWidth, mDisplayHeight,
mBitmap,
mPendingXOffset, mPendingYOffset);
mEglHelper.swapBuffers();
reportEngineShown(true);
}
private void loadWallpaperBitmap() {
WallpaperManager wm = WallpaperManager.getInstance(
getApplicationContext());
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.HARDWARE;
ParcelFileDescriptor pfd = wm.getWallpaperFile(
WallpaperManager.FLAG_SYSTEM);
if (pfd != null) {
mBitmap = BitmapFactory.decodeFileDescriptor(
pfd.getFileDescriptor(), null, options);
pfd.close();
} else {
mBitmap = BitmapFactory.decodeResource(
getResources(),
com.android.internal.R.drawable.default_wallpaper,
options);
}
notifyColorsChanged();
}
@Override
public WallpaperColors onComputeColors() {
if (mBitmap != null) {
return WallpaperColors.fromBitmap(mBitmap);
}
return null;
}
@Override
public void onDestroy() {
super.onDestroy();
mEglHelper.finish();
if (mBitmap != null) {
mBitmap.recycle();
}
}
}
}
6.2 ImageWallpaperRenderer — OpenGL 渲染
public class ImageWallpaperRenderer {
private int mTextureId;
private int mProgram;
private FloatBuffer mVertexBuffer;
private FloatBuffer mTexCoordBuffer;
private float mXOffset = 0.5f;
private float mYOffset = 0.5f;
private float mBitmapAspectRatio;
private float mScreenAspectRatio;
public void draw(int screenWidth, int screenHeight,
Bitmap bitmap, float xOffset, float yOffset) {
GLES20.glViewport(0, 0, screenWidth, screenHeight);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glUseProgram(mProgram);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureId);
float[] texCoords = calculateTexCoords(
bitmap.getWidth(), bitmap.getHeight(),
screenWidth, screenHeight,
xOffset, yOffset);
int positionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
int texCoordHandle = GLES20.glGetAttribLocation(mProgram, "aTexCoord");
GLES20.glVertexAttribPointer(positionHandle, 2,
GLES20.GL_FLOAT, false, 0, mVertexBuffer);
GLES20.glEnableVertexAttribArray(positionHandle);
mTexCoordBuffer.put(texCoords).position(0);
GLES20.glVertexAttribPointer(texCoordHandle, 2,
GLES20.GL_FLOAT, false, 0, mTexCoordBuffer);
GLES20.glEnableVertexAttribArray(texCoordHandle);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
}
private float[] calculateTexCoords(
int bitmapWidth, int bitmapHeight,
int screenWidth, int screenHeight,
float xOffset, float yOffset) {
float visibleWidth = (float) screenWidth / bitmapWidth;
float visibleHeight = (float) screenHeight / bitmapHeight;
float maxOffsetX = 1.0f - visibleWidth;
float maxOffsetY = 1.0f - visibleHeight;
float texLeft = xOffset * maxOffsetX;
float texRight = texLeft + visibleWidth;
float texTop = yOffset * maxOffsetY;
float texBottom = texTop + visibleHeight;
texLeft = Math.max(0, Math.min(1, texLeft));
texRight = Math.max(0, Math.min(1, texRight));
texTop = Math.max(0, Math.min(1, texTop));
texBottom = Math.max(0, Math.min(1, texBottom));
return new float[]{
texLeft, texTop,
texRight, texTop,
texLeft, texBottom,
texRight, texBottom
};
}
private void uploadBitmapTexture(Bitmap bitmap) {
int[] textures = new int[1];
GLES20.glGenTextures(1, textures, 0);
mTextureId = textures[0];
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureId);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
}
private static final String VERTEX_SHADER =
"attribute vec4 aPosition;\n" +
"attribute vec2 aTexCoord;\n" +
"varying vec2 vTexCoord;\n" +
"void main() {\n" +
" gl_Position = aPosition;\n" +
" vTexCoord = aTexCoord;\n" +
"}\n";
private static final String FRAGMENT_SHADER =
"precision mediump float;\n" +
"varying vec2 vTexCoord;\n" +
"uniform sampler2D uTexture;\n" +
"void main() {\n" +
" gl_FragColor = texture2D(uTexture, vTexCoord);\n" +
"}\n";
}
七、WallpaperController (WMS) — 壁纸窗口管理
7.1 核心职责
class WallpaperController {
private final WindowManagerService mService;
private final DisplayContent mDisplayContent;
private WallpaperWindowToken mWallpaperToken;
private WindowState mWallpaperTarget;
private WindowState mPrevWallpaperTarget;
float mLastWallpaperX = -1;
float mLastWallpaperY = -1;
float mLastWallpaperXStep = -1;
float mLastWallpaperYStep = -1;
int mLastWallpaperDisplayOffsetX = Integer.MIN_VALUE;
int mLastWallpaperDisplayOffsetY = Integer.MIN_VALUE;
private boolean mWallpaperDrawing;
private boolean mWallpaperIsTarget;
private final WallpaperVisibilityListeners mWallpaperVisibilityListeners;
void findWallpaperTarget() {
WindowState newTarget = null;
mDisplayContent.forAllWindows(w -> {
if ((w.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) {
if (w.isVisible() || w.isDrawn()) {
mFoundWallpaperTarget = true;
return true;
}
}
return false;
}, true );
if (newTarget != mWallpaperTarget) {
WindowState oldTarget = mWallpaperTarget;
mWallpaperTarget = newTarget;
updateWallpaperWindowsTarget(newTarget);
if (mWallpaperTarget != null && oldTarget != null) {
handleWallpaperTargetChange(oldTarget, mWallpaperTarget);
}
}
}
void updateWallpaperWindowsTarget(WindowState target) {
if (mWallpaperToken == null) return;
for (int i = mWallpaperToken.getChildCount() - 1; i >= 0; i--) {
WindowState wallpaper = mWallpaperToken.getChildAt(i);
if (target != null) {
wallpaper.mToken.getParent().positionChildAt(
target.getParent().mChildren.indexOf(target),
mWallpaperToken,
false );
}
}
}
boolean updateWallpaperOffset(WindowState wallpaperWin,
boolean sync) {
float wpx = mWallpaperTarget != null
? mWallpaperTarget.mWallpaperX : 0.5f;
float wpy = mWallpaperTarget != null
? mWallpaperTarget.mWallpaperY : 0.5f;
int availableWidth = wallpaperWin.mRequestedWidth
- mDisplayContent.getDefaultDisplayInfo().logicalWidth;
int availableHeight = wallpaperWin.mRequestedHeight
- mDisplayContent.getDefaultDisplayInfo().logicalHeight;
int offsetX = availableWidth > 0
? -(int) (availableWidth * wpx + 0.5f) : 0;
int offsetY = availableHeight > 0
? -(int) (availableHeight * wpy + 0.5f) : 0;
boolean changed = false;
if (wallpaperWin.mXOffset != offsetX
|| wallpaperWin.mYOffset != offsetY) {
wallpaperWin.mXOffset = offsetX;
wallpaperWin.mYOffset = offsetY;
changed = true;
}
if (changed) {
SurfaceControl.Transaction t =
wallpaperWin.getPendingTransaction();
t.setPosition(
wallpaperWin.getSurfaceControl(),
offsetX + wallpaperWin.mFrame.left,
offsetY + wallpaperWin.mFrame.top);
if (sync) {
t.apply();
}
}
return changed;
}
void updateWallpaperVisibility() {
if (mWallpaperToken == null) return;
boolean visible = isWallpaperVisible();
for (int i = mWallpaperToken.getChildCount() - 1; i >= 0; i--) {
WindowState wallpaper = mWallpaperToken.getChildAt(i);
if (visible) {
wallpaper.show(false );
} else {
wallpaper.hide(false );
}
}
mWallpaperVisibilityListeners.notifyWallpaperVisibilityChanged(
mDisplayContent, visible);
}
boolean isWallpaperVisible() {
if (mWallpaperTarget != null && mWallpaperTarget.isVisible()) {
return true;
}
if (isWallpaperTransitionAnimating()) {
return true;
}
return false;
}
}
7.2 壁纸窗口层级关系
WMS 窗口层级示意:
(顶部)
┌────────────────────────────┐
│ TYPE_STATUS_BAR │ z=最高
│ TYPE_NAVIGATION_BAR │
├────────────────────────────┤
│ TYPE_APPLICATION │ ← 前台 App
│ (FLAG_SHOW_WALLPAPER) │ ← 如果有此 flag, 壁纸可见
├────────────────────────────┤
│ TYPE_WALLPAPER │ ← 壁纸窗口 ★
│ (紧贴在壁纸目标窗口后面) │
├────────────────────────────┤
│ TYPE_APPLICATION (其他App) │
│ (不可见) │
├────────────────────────────┤
│ 桌面壁纸底层 │ z=最低
└────────────────────────────┘
(底部)
当 Launcher 是前台时:
Launcher 设置了 FLAG_SHOW_WALLPAPER
→ WallpaperController 找到 Launcher 作为壁纸目标
→ 壁纸窗口放在 Launcher 后面
→ Launcher 背景透明 → 壁纸可见
当普通 App 是前台时 (不透明):
App 没有 FLAG_SHOW_WALLPAPER
→ 壁纸目标 = null
→ 壁纸窗口隐藏 (节省 GPU 资源)
八、壁纸偏移/视差效果
8.1 完整调用链
用户在 Launcher 左右滑动页面
│
▼
Launcher (Workspace.java):
├── computeScrollX()
│ └── 计算当前页面位置 → xOffset (0.0 ~ 1.0)
│
├── WallpaperManager.setWallpaperOffsets(windowToken, xOffset, yOffset)
│ │
│ └── → Binder → WallpaperManagerService.setWallpaperOffsets()
│ │
│ └── → 更新 WallpaperData.mWallpaperXOffset
│ │
│ └── → 通知 WallpaperConnection
│ │
│ └── → IWallpaperEngine.setWallpaperOffsets()
│ │
│ └── → WallpaperService.Engine.onOffsetsChanged()
│ │
│ ├── 静态壁纸 (ImageWallpaper):
│ │ └── 重新计算纹理坐标 → drawFrame()
│ │ └── 壁纸图片在屏幕上"滑动"
│ │
│ └── 动态壁纸 (Live Wallpaper):
│ └── 自定义逻辑 (例如粒子效果跟随偏移)
│
└── 同时通知 WMS:
WallpaperController.updateWallpaperOffset()
└── 移动壁纸 Surface 的位置
└── SurfaceControl.Transaction.setPosition()
视觉效果:
页面1 页面2 页面3
┌──────┐ ┌──────┐ ┌──────┐
│ │ │ │ │ │
│ App1 │ │ App2 │ │ App3 │
│ │ │ │ │ │
└──────┘ └──────┘ └──────┘
xOffset=0.0 xOffset=0.5 xOffset=1.0
壁纸 (比屏幕宽):
┌─────────────────────────────────────────┐
│ │
│ ▲可见区域在此位置 ▲这里 ▲这里 │
│ │
└─────────────────────────────────────────┘
→ 用户滑动页面时,壁纸以更慢的速度跟随移动
→ 产生"视差"效果
8.2 偏移计算细节
public class Workspace extends PagedView {
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
updateWallpaperOffset();
}
private void updateWallpaperOffset() {
int pageCount = getChildCount();
if (pageCount <= 1) return;
int scrollX = getScrollX();
int maxScrollX = getMaxScrollX();
float offset = maxScrollX > 0
? (float) scrollX / maxScrollX
: 0.5f;
float step = 1.0f / (pageCount - 1);
WallpaperManager wm = WallpaperManager.getInstance(getContext());
wm.setWallpaperOffsetSteps(step, 1.0f);
wm.setWallpaperOffsets(getWindowToken(), offset, 0.5f);
}
}
九、主屏幕壁纸 vs 锁屏壁纸
9.1 双壁纸架构
Android 7.0+ 支持独立的主屏幕和锁屏壁纸:
┌─────────────────────────────────────────────┐
│ 壁纸存储 │
│ │
│ /data/system/users/{userId}/ │
│ ├── wallpaper_orig (主屏幕原始壁纸) │
│ ├── wallpaper (主屏幕裁剪后壁纸) │
│ ├── wallpaper_lock_orig(锁屏原始壁纸) │
│ ├── wallpaper_lock (锁屏裁剪后壁纸) │
│ └── wallpaper_info.xml (壁纸配置信息) │
│ │
│ 如果没有单独设置锁屏壁纸, │
│ 则锁屏使用主屏幕壁纸 │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 壁纸服务 │
│ │
│ 主屏幕: │
│ ├── WallpaperData (FLAG_SYSTEM) │
│ └── WallpaperConnection → ImageWallpaper │
│ 或 LiveWallpaperService │
│ │
│ 锁屏: │
│ ├── WallpaperData (FLAG_LOCK) │
│ └── WallpaperConnection → ImageWallpaper │
│ 或 LiveWallpaperService │
│ (可以是不同的壁纸服务) │
│ │
│ Android 14: 锁屏也支持动态壁纸! │
└─────────────────────────────────────────────┘
9.2 锁屏壁纸处理
@Override
public ParcelFileDescriptor getWallpaperWithFeature(
String callingPkg, String callingFeatureId,
IWallpaperManagerCallback cb,
@SetWallpaperFlags int which,
Bundle outParams, int wallpaperId, int userId) {
synchronized (mLock) {
WallpaperData wallpaper;
if ((which & FLAG_LOCK) != 0) {
wallpaper = mLockWallpaperMap.get(userId);
if (wallpaper == null) {
wallpaper = mWallpaperMap.get(userId);
}
} else {
wallpaper = mWallpaperMap.get(userId);
}
if (wallpaper == null) return null;
return ParcelFileDescriptor.open(wallpaper.cropFile,
MODE_READ_ONLY);
}
}
@Override
public void setWallpaperDimAmount(float dimAmount) {
int userId = UserHandle.getCallingUserId();
synchronized (mLock) {
WallpaperData systemWallpaper = mWallpaperMap.get(userId);
WallpaperData lockWallpaper = mLockWallpaperMap.get(userId);
if (systemWallpaper != null) {
systemWallpaper.mWallpaperDimAmount = dimAmount;
if (systemWallpaper.connection != null
&& systemWallpaper.connection.mEngine != null) {
try {
systemWallpaper.connection.mEngine
.applyDimming(dimAmount);
} catch (RemoteException e) { }
}
}
if (lockWallpaper != null) {
lockWallpaper.mWallpaperDimAmount = dimAmount;
}
}
}
十、Material You — 壁纸颜色系统 ★★★
10.1 颜色提取流程
壁纸设置/变化
│
▼
WallpaperManagerService.onWallpaperWriteComplete()
│
├── extractColors(wallpaper)
│ │
│ ├── 解码壁纸 Bitmap
│ ├── WallpaperColors.fromBitmap(bitmap)
│ │ ├── Palette API 颜色量化
│ │ ├── 提取主色 / 副色 / 第三色
│ │ └── 计算颜色提示 (DARK_TEXT / DARK_THEME)
│ │
│ └── wallpaper.mWallpaperColors = colors
│
├── notifyWallpaperColorsChanged(wallpaper, FLAG_SYSTEM)
│ │
│ └── for (IWallpaperManagerCallback cb : mColorsChangedListeners) {
│ cb.onWallpaperColorsChanged(colors, which, userId);
│ }
│ │
│ ├── ★ SystemUI: ThemeOverlayController 收到通知
│ │ │
│ │ └── reevaluateSystemTheme()
│ │ │
│ │ ├── 1. 从壁纸颜色中选择种子色 (seed color)
│ │ │ └── primaryColor → seed
│ │ │
│ │ ├── 2. 生成 Material You 调色板
│ │ │ └── ColorScheme(seed)
│ │ │ ├── accent1[13] (主强调色)
│ │ │ ├── accent2[13] (次强调色)
│ │ │ ├── accent3[13] (第三强调色)
│ │ │ ├── neutral1[13] (中性色1)
│ │ │ └── neutral2[13] (中性色2)
│ │ │ // 每种 13 个色调级别 (0,10,50,100,...,900,1000)
│ │ │
│ │ ├── 3. 创建 FabricatedOverlay
│ │ │ └── 动态生成主题资源覆盖
│ │ │ ├── system_accent1_0 = accent1[0]
│ │ │ ├── system_accent1_10 = accent1[1]
│ │ │ ├── ...
│ │ │ └── 覆盖整个系统调色板
│ │ │
│ │ └── 4. 应用 Overlay
│ │ └── OverlayManagerService.setEnabled()
│ │ → 所有 App 的主题颜色随壁纸变化
│ │
│ ├── Settings: 主题颜色预览更新
│ │
│ └── Launcher: 图标着色更新
│
└── 或由壁纸引擎主动报告:
WallpaperService.Engine.notifyColorsChanged()
└── onComputeColors() → WallpaperColors
└── 通过 IWallpaperConnection.onWallpaperColorsChanged()
└── 同上流程
10.2 ThemeOverlayController 中的颜色处理
@SysUISingleton
public class ThemeOverlayController extends CoreStartable {
private final WallpaperManager mWallpaperManager;
private WallpaperColors mCurrentColors;
@Override
public void start() {
mWallpaperManager.addOnColorsChangedListener(
(colors, which) -> {
if ((which & WallpaperManager.FLAG_SYSTEM) != 0) {
handleWallpaperColors(colors);
}
},
null
);
WallpaperColors colors = mWallpaperManager.getWallpaperColors(
WallpaperManager.FLAG_SYSTEM);
if (colors != null) {
handleWallpaperColors(colors);
}
}
private void handleWallpaperColors(WallpaperColors colors) {
if (colors == null) return;
if (colors.equals(mCurrentColors)) return;
mCurrentColors = colors;
reevaluateSystemTheme(false );
}
void reevaluateSystemTheme(boolean forceReload) {
WallpaperColors colors = mCurrentColors;
if (colors == null) return;
int seedColor = getSeedColor(colors);
String storedColors = Settings.Secure.getStringForUser(
mContext.getContentResolver(),
Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
mCurrentUser);
ColorScheme scheme = new ColorScheme(seedColor, isDarkTheme());
FabricatedOverlay overlay = createOverlay(scheme);
mOverlayManager.commit(new OverlayManagerTransaction.Builder()
.setEnabled(overlay, true, mCurrentUser)
.build());
}
private int getSeedColor(WallpaperColors colors) {
Color primary = colors.getPrimaryColor();
int argb = primary.toArgb();
return Score.score(
ColorUtils.colorToCAM(argb)).get(0);
}
private FabricatedOverlay createOverlay(ColorScheme scheme) {
FabricatedOverlay.Builder builder = new FabricatedOverlay.Builder(
"com.android.systemui", "wallpaper_theme", "android");
builder.setResourceValue(
"android:color/system_accent1_0",
TypedValue.TYPE_INT_COLOR_ARGB8,
scheme.getAccent1().get(0));
builder.setResourceValue(
"android:color/system_accent1_10",
TypedValue.TYPE_INT_COLOR_ARGB8,
scheme.getAccent1().get(1));
return builder.build();
}
}
十一、壁纸过渡动画
11.1 壁纸切换动画
class WallpaperAnimationAdapter {
static RemoteAnimationTarget createWallpaperAnimationTarget(
WallpaperWindowToken wallpaperToken) {
WindowState wallpaperWin = wallpaperToken.getTopChild();
if (wallpaperWin == null) return null;
SurfaceControl animLeash = wallpaperWin.createAnimationLeash();
return new RemoteAnimationTarget(
wallpaperWin.mWallpaperToken.hashCode(),
RemoteAnimationTarget.MODE_OPENING,
animLeash,
false ,
null ,
null ,
wallpaperWin.getPrefixOrderIndex(),
new Point(0, 0),
wallpaperWin.getBounds(),
wallpaperWin.getWindowConfiguration(),
false
);
}
}
private void animateWallpaperForAppClose(
RemoteAnimationTarget[] wallpaperTargets) {
for (RemoteAnimationTarget wallpaper : wallpaperTargets) {
SurfaceControl surface = wallpaper.leash;
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
animator.setDuration(350);
animator.setInterpolator(DECELERATE);
animator.addUpdateListener(a -> {
float progress = (float) a.getAnimatedValue();
SurfaceControl.Transaction frameT =
new SurfaceControl.Transaction();
float scale = lerp(0.95f, 1.0f, progress);
frameT.setScale(surface, scale, scale);
frameT.setAlpha(surface, progress);
frameT.apply();
});
animator.start();
}
}
11.2 Android 14 壁纸揭示动画 (Reveal)
public class ImageRevealWallpaperRenderer {
private float mRevealProgress = 0f;
private float mRevealCenterX;
private float mRevealCenterY;
private int mOldTextureId;
private int mNewTextureId;
private static final String REVEAL_FRAGMENT_SHADER =
"precision mediump float;\n" +
"varying vec2 vTexCoord;\n" +
"uniform sampler2D uOldTexture;\n" +
"uniform sampler2D uNewTexture;\n" +
"uniform float uRevealProgress;\n" +
"uniform vec2 uRevealCenter;\n" +
"void main() {\n" +
" vec2 uv = vTexCoord;\n" +
" float dist = distance(uv, uRevealCenter);\n" +
" float maxDist = 1.5;\n" +
" float radius = uRevealProgress * maxDist;\n" +
" if (dist < radius) {\n" +
" gl_FragColor = texture2D(uNewTexture, uv);\n" +
" } else {\n" +
" gl_FragColor = texture2D(uOldTexture, uv);\n" +
" }\n" +
"}\n";
public void draw(float progress) {
mRevealProgress = progress;
GLES20.glUseProgram(mRevealProgram);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mOldTextureId);
GLES20.glUniform1i(mOldTextureHandle, 0);
GLES20.glActiveTexture(GLES20.GL_TEXTURE1);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mNewTextureId);
GLES20.glUniform1i(mNewTextureHandle, 1);
GLES20.glUniform1f(mRevealProgressHandle, progress);
GLES20.glUniform2f(mRevealCenterHandle,
mRevealCenterX, mRevealCenterY);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
}
}
十二、壁纸与 SystemUI 交互
12.1 ScrimController — 壁纸上层遮罩
@SysUISingleton
public class ScrimController {
private ScrimView mScrimBehind;
private ScrimView mScrimInFront;
private ScrimView mNotificationsScrim;
private void applyState() {
switch (mState) {
case KEYGUARD:
mScrimBehind.setAlpha(0.25f);
mNotificationsScrim.setAlpha(calculateNotifAlpha());
mScrimInFront.setAlpha(0f);
break;
case SHADE_LOCKED:
mScrimBehind.setAlpha(0.6f);
break;
case BOUNCER:
mScrimBehind.setAlpha(0.8f);
break;
case AOD:
mScrimBehind.setAlpha(1.0f);
mScrimInFront.setAlpha(
mDozeParameters.getAlwaysOnAlpha());
break;
case UNLOCKED:
mScrimBehind.setAlpha(0f);
mScrimInFront.setAlpha(0f);
break;
case PULSING:
mScrimBehind.setAlpha(0.5f);
break;
}
}
public void setNotificationPanelExpansion(float fraction) {
if (mState == ScrimState.KEYGUARD) {
float scrimAlpha = lerp(0.25f, 0.6f, fraction);
mScrimBehind.setAlpha(scrimAlpha);
}
}
}
12.2 壁纸可见性与 SystemUI 联动
class WallpaperVisibilityListeners {
private final ArrayMap<IBinder, WallpaperVisibilityListener>
mListeners = new ArrayMap<>();
void notifyWallpaperVisibilityChanged(
DisplayContent displayContent, boolean visible) {
for (WallpaperVisibilityListener listener : mListeners.values()) {
listener.onWallpaperVisibilityChanged(visible,
displayContent.getDisplayId());
}
}
}
private void registerWallpaperVisibilityListener() {
IWindowManager wms = WindowManagerGlobal.getWindowManagerService();
wms.registerWallpaperVisibilityListener(
new IWallpaperVisibilityListener.Stub() {
@Override
public void onWallpaperVisibilityChanged(
boolean visible, int displayId) {
mMainExecutor.execute(() -> {
mWallpaperVisible = visible;
updateScrimController();
updateDarkIconStatus();
});
}
},
DEFAULT_DISPLAY);
}
十三、动态壁纸 (Live Wallpaper)
13.1 动态壁纸声明
<service
android:name=".MyLiveWallpaperService"
android:label="My Live Wallpaper"
android:permission="android.permission.BIND_WALLPAPER">
<intent-filter>
<action android:name="android.service.wallpaper.WallpaperService" />
</intent-filter>
<meta-data
android:name="android.service.wallpaper"
android:resource="@xml/wallpaper" />
</service>
<wallpaper
xmlns:android="http://schemas.android.com/apk/res/android"
android:thumbnail="@drawable/preview"
android:description="@string/description"
android:settingsActivity=".WallpaperSettingsActivity"
android:author="@string/author"
android:contextUri="https://example.com"
android:contextDescription="@string/context_desc"
android:showMetadataInPreview="true"
android:supportsMultipleDisplays="false" />
13.2 动态壁纸实现示例
public class ParticleWallpaperService extends WallpaperService {
@Override
public Engine onCreateEngine() {
return new ParticleEngine();
}
class ParticleEngine extends Engine {
private final Handler mHandler = new Handler();
private boolean mVisible;
private float mXOffset = 0.5f;
private List<Particle> mParticles = new ArrayList<>();
@Override
public void onCreate(SurfaceHolder surfaceHolder) {
super.onCreate(surfaceHolder);
setTouchEventsEnabled(true);
initParticles();
}
@Override
public void onVisibilityChanged(boolean visible) {
mVisible = visible;
if (visible) {
mHandler.post(mDrawRunner);
} else {
mHandler.removeCallbacks(mDrawRunner);
}
}
@Override
public void onSurfaceChanged(SurfaceHolder holder,
int format, int width, int height) {
super.onSurfaceChanged(holder, format, width, height);
updateParticleBounds(width, height);
}
@Override
public void onOffsetsChanged(float xOffset, float yOffset,
float xOffsetStep, float yOffsetStep,
int xPixelOffset, int yPixelOffset) {
mXOffset = xOffset;
drawFrame();
}
@Override
public void onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
spawnParticleBurst(event.getX(), event.getY());
}
}
private final Runnable mDrawRunner = new Runnable() {
@Override
public void run() {
drawFrame();
if (mVisible) {
mHandler.postDelayed(this, 16);
}
}
};
private void drawFrame() {
SurfaceHolder holder = getSurfaceHolder();
Canvas canvas = null;
try {
canvas = holder.lockCanvas();
if (canvas != null) {
canvas.drawColor(Color.BLACK);
for (Particle p : mParticles) {
p.update(mXOffset);
p.draw(canvas);
}
}
} finally {
if (canvas != null) {
holder.unlockCanvasAndPost(canvas);
}
}
}
@Override
public WallpaperColors onComputeColors() {
return new WallpaperColors(
Color.valueOf(Color.BLUE),
Color.valueOf(Color.CYAN),
Color.valueOf(Color.WHITE)
);
}
@Override
public void onDestroy() {
super.onDestroy();
mHandler.removeCallbacks(mDrawRunner);
}
}
}
十四、多用户/多显示器支持
private final SparseArray<WallpaperData> mWallpaperMap;
private final SparseArray<WallpaperData> mLockWallpaperMap;
void switchUser(int userId) {
synchronized (mLock) {
WallpaperData oldWallpaper = mWallpaperMap.get(mCurrentUserId);
if (oldWallpaper != null && oldWallpaper.connection != null) {
detachWallpaperLocked(oldWallpaper.connection);
}
mCurrentUserId = userId;
if (!mWallpaperMap.contains(userId)) {
loadSettingsLocked(userId, false);
}
WallpaperData newWallpaper = mWallpaperMap.get(userId);
switchWallpaper(newWallpaper, null);
notifyWallpaperColorsChanged(newWallpaper, FLAG_SYSTEM);
WallpaperData lockWallpaper = mLockWallpaperMap.get(userId);
if (lockWallpaper != null) {
notifyWallpaperColorsChanged(lockWallpaper, FLAG_LOCK);
}
}
}
class WallpaperDisplayHelper {
Point getDesiredWallpaperSize(int displayId) {
DisplayInfo di = getDisplayInfo(displayId);
int width = (int) (Math.max(di.logicalWidth, di.logicalHeight)
* getParallaxRatio());
int height = Math.max(di.logicalWidth, di.logicalHeight);
return new Point(width, height);
}
float getParallaxRatio() {
return mContext.getResources().getFloat(
R.dimen.config_wallpaperParallaxRatio);
}
}
十五、壁纸设置持久化
15.1 wallpaper_info.xml
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<wp xmlns:wp="http://schemas.android.com/apk/res/android"
wp:width="2160"
wp:height="2400"
wp:cropLeft="0"
wp:cropTop="0"
wp:cropRight="2160"
wp:cropBottom="2400"
wp:name="wallpaper_orig"
wp:id="42"
wp:allowBackup="true"
wp:wallpaperComponent="com.android.systemui/.wallpapers.ImageWallpaper">
<colors
wp:colorValue="-14374589"
wp:colorValue2="-12236860"
wp:colorValue3="-6243049"
wp:colorHints="4" />
<dimAmount wp:value="0.0" />
</wp>
15.2 加载和保存
private void loadSettingsLocked(int userId, boolean keepDimensionHints) {
File wallpaperDir = getWallpaperDir(userId);
File infoFile = new File(wallpaperDir, WALLPAPER_INFO);
if (!infoFile.exists()) {
migrateFromOld(userId);
return;
}
FileInputStream fis = new FileInputStream(infoFile);
XmlPullParser parser = Xml.newPullParser();
parser.setInput(fis, StandardCharsets.UTF_8.name());
WallpaperData wallpaper = new WallpaperData(userId);
while (parser.next() != XmlPullParser.END_DOCUMENT) {
if (parser.getName().equals("wp")) {
wallpaper.width = getAttributeInt(parser, "width", 0);
wallpaper.height = getAttributeInt(parser, "height", 0);
wallpaper.cropHint.left = getAttributeInt(parser, "cropLeft", 0);
wallpaper.cropHint.top = getAttributeInt(parser, "cropTop", 0);
wallpaper.cropHint.right = getAttributeInt(parser, "cropRight", 0);
wallpaper.cropHint.bottom = getAttributeInt(parser, "cropBottom", 0);
wallpaper.wallpaperId = getAttributeInt(parser, "id", -1);
String component = parser.getAttributeValue(null,
"wallpaperComponent");
wallpaper.wallpaperComponent =
ComponentName.unflattenFromString(component);
wallpaper.mWallpaperDimAmount = getAttributeFloat(
parser, "dimAmount", 0f);
}
if (parser.getName().equals("colors")) {
int primary = getAttributeInt(parser, "colorValue", 0);
int secondary = getAttributeInt(parser, "colorValue2", 0);
int tertiary = getAttributeInt(parser, "colorValue3", 0);
int hints = getAttributeInt(parser, "colorHints", 0);
wallpaper.mWallpaperColors = new WallpaperColors(
Color.valueOf(primary),
secondary != 0 ? Color.valueOf(secondary) : null,
tertiary != 0 ? Color.valueOf(tertiary) : null,
hints);
}
}
mWallpaperMap.put(userId, wallpaper);
}
private void saveSettingsLocked(int userId) {
WallpaperData wallpaper = mWallpaperMap.get(userId);
WallpaperData lockWallpaper = mLockWallpaperMap.get(userId);
File infoFile = new File(getWallpaperDir(userId), WALLPAPER_INFO);
FileOutputStream fos = new FileOutputStream(infoFile);
XmlSerializer out = new FastXmlSerializer();
out.setOutput(fos, StandardCharsets.UTF_8.name());
out.startDocument(null, true);
out.startTag(null, "wp");
out.attribute(null, "width", String.valueOf(wallpaper.width));
out.attribute(null, "height", String.valueOf(wallpaper.height));
out.attribute(null, "cropLeft", String.valueOf(wallpaper.cropHint.left));
out.attribute(null, "wallpaperComponent",
wallpaper.wallpaperComponent.flattenToString());
out.attribute(null, "id", String.valueOf(wallpaper.wallpaperId));
out.attribute(null, "dimAmount",
String.valueOf(wallpaper.mWallpaperDimAmount));
if (wallpaper.mWallpaperColors != null) {
out.startTag(null, "colors");
out.attribute(null, "colorValue",
String.valueOf(wallpaper.mWallpaperColors
.getPrimaryColor().toArgb()));
out.endTag(null, "colors");
}
out.endTag(null, "wp");
if (lockWallpaper != null) {
out.startTag(null, "lockWp");
out.endTag(null, "lockWp");
}
out.endDocument();
fos.close();
}
十六、完整数据流总结
16.1 设置壁纸的完整路径
用户在壁纸选择器中选择一张图片
│
▼
WallpaperPicker2
├── 显示预览
├── 用户确认
└── 调用 WallpaperManager.setBitmap(bitmap, cropHint, true, FLAG_SYSTEM)
│
▼
WallpaperManager (客户端)
├── 调用 IWallpaperManager.setWallpaper(...)
│ │
│ └── Binder IPC
│ │
│ ▼
WallpaperManagerService (系统服务)
├── 权限检查
├── 生成壁纸 ID
├── 创建 ParcelFileDescriptor
│ └── 指向 /data/system/users/{userId}/wallpaper_orig
│
│ ← 返回 pfd 给客户端
│ │
│ ▼
WallpaperManager (客户端)
├── 通过 pfd 写入 Bitmap 数据
│ └── bitmap.compress(PNG, 100, pfd.getOutputStream())
├── 关闭 pfd
│ │
│ └── → 触发 WallpaperManagerService.onWallpaperWriteComplete()
│ │
│ ▼
WallpaperManagerService
├── 1. generateCrop(wallpaper)
│ └── 裁剪壁纸 → 保存到 wallpaper (cropFile)
│
├── 2. extractColors(wallpaper)
│ └── WallpaperColors.fromBitmap() → 提取主色/副色/第三色
│
├── 3. saveSettingsLocked()
│ └── 保存到 wallpaper_info.xml
│
├── 4. bindWallpaperComponentLocked(ImageWallpaper, ...)
│ │ └── 绑定壁纸服务
│ │ │
│ │ ▼
│ │ ImageWallpaper (SystemUI)
│ │ ├── onCreateEngine() → GLEngine
│ │ ├── Engine.attach() → 创建 TYPE_WALLPAPER 窗口
│ │ ├── Engine.onSurfaceCreated() → 初始化 EGL
│ │ ├── loadWallpaperBitmap() → 解码裁剪后壁纸
│ │ ├── uploadBitmapTexture() → 上传 GPU 纹理
│ │ ├── drawFrame() → OpenGL 渲染壁纸
│ │ └── reportEngineShown() → 通知首帧已显示
│ │
│ └── WMS:
│ └── WallpaperController
│ ├── 注册壁纸窗口
│ ├── findWallpaperTarget()
│ ├── updateWallpaperWindowsTarget()
│ └── updateWallpaperVisibility()
│
├── 5. notifyWallpaperChanged()
│ └── 通知所有注册的回调
│
└── 6. notifyWallpaperColorsChanged()
└── 通知颜色变化
│
├── SystemUI: ThemeOverlayController
│ └── reevaluateSystemTheme()
│ └── Material You 颜色更新
│ └── 全系统主题颜色变化
│
├── Launcher: 图标/Widget 颜色更新
│
└── Settings: 主题预览更新
16.2 壁纸显示/渲染路径
┌─────────────────────────────────────────────────────────┐
│ 渲染管线 │
│ │
│ WallpaperService.Engine │
│ (ImageWallpaper.GLEngine) │
│ │ │
│ ├── lockCanvas() 或 EGL makeCurrent() │
│ ├── 渲染壁纸内容 (Canvas 或 OpenGL) │
│ └── unlockCanvasAndPost() 或 eglSwapBuffers() │
│ │ │
│ ▼ │
│ SurfaceFlinger │
│ │ │
│ ├── 壁纸 Layer (TYPE_WALLPAPER) │
│ │ └── z-order: 在壁纸目标窗口后面 │
│ │ │
│ ├── 合成: │
│ │ ┌──────────────────────────────┐ │
│ │ │ NavigationBar Layer (顶层) │ │
│ │ ├──────────────────────────────┤ │
│ │ │ StatusBar Layer │ │
│ │ ├──────────────────────────────┤ │
│ │ │ App / Launcher Layer │ │
│ │ │ (可能半透明/透明,露出壁纸) │ │
│ │ ├──────────────────────────────┤ │
│ │ │ ★ Wallpaper Layer │ ← 壁纸在这里 │
│ │ ├──────────────────────────────┤ │
│ │ │ 底层 │ │
│ │ └──────────────────────────────┘ │
│ │ │
│ └── → Display (HDMI/DSI/...) │
│ │
└─────────────────────────────────────────────────────────┘
十七、关键类关系图
┌───────────────────────────────────────────────────────────────────┐
│ │
│ ┌──────────────┐ Binder ┌──────────────────────┐ │
│ │WallpaperManager│ ←──────────────────→ │WallpaperManager │ │
│ │(App 客户端 API)│ │Service (系统服务) │ │
│ └──────┬───────┘ └─────────┬────────────┘ │
│ │ │ │
│ │ setWallpaper() │ │
│ │ getWallpaperColors() │ │
│ │ setWallpaperOffsets() │ │
│ │ addOnColorsChangedListener() │ │
│ │ │ │
│ │ ┌─────────────────────┤ │
│ │ │ │ │
│ │ ┌────────▼────────┐ ┌────────▼────────┐ │
│ │ │ WallpaperData │ │ WallpaperData │ │
│ │ │ (FLAG_SYSTEM) │ │ (FLAG_LOCK) │ │
│ │ │ ├─wallpaperFile│ │ ├─wallpaperFile│ │
│ │ │ ├─cropFile │ │ ├─cropFile │ │
│ │ │ ├─colors │ │ ├─colors │ │
│ │ │ └─connection───│───│──└─connection │ │
│ │ └────────┬────────┘ └─────────────────┘ │
│ │ │ │
│ │ ┌─────────▼──────────┐ │
│ │ │WallpaperConnection │ │
│ │ │(ServiceConnection) │ │
│ │ └────────┬───────────┘ │
│ │ │ bindService() │
│ │ ▼ │
│ │ ┌──────────────────┐ │
│ │ │ WallpaperService │ (抽象基类) │
│ │ │ ├─onCreateEngine() │
│ │ │ └─Engine │ │
│ │ └────────┬─────────┘ │
│ │ │ │
│ │ ┌───────────┼───────────────┐ │
│ │ ▼ ▼ │
│ │ ┌──────────────┐ ┌──────────────────┐ │
│ │ │ImageWallpaper│ │LiveWallpaperXxx │ │
│ │ │(SystemUI) │ │(第三方) │ │
│ │ │ └─GLEngine │ │ └─CustomEngine │ │
│ │ │ ├─EGL │ │ ├─Canvas/GL │ │
│ │ │ ├─Texture │ │ └─自定义渲染 │ │
│ │ │ └─drawFrame│ │ │ │
│ │ └──────────────┘ └──────────────────┘ │
│ │ │
│ │ ┌──────────────────────────────────────────────┐ │
│ │ │ WMS (WindowManagerService) │ │
│ │ │ │ │
│ │ │ ┌───────────────────────────────────────┐ │ │
│ │ │ │ WallpaperController │ │ │
│ │ │ │ ├─findWallpaperTarget() │ │ │
│ │ │ │ │ └─遍历窗口找 FLAG_SHOW_WALLPAPER │ │ │
│ │ │ │ ├─updateWallpaperOffset() │ │ │
│ │ │ │ │ └─SurfaceControl.setPosition() │ │ │
│ │ │ │ ├─updateWallpaperVisibility() │ │ │
│ │ │ │ └─handleWallpaperTargetChange() │ │ │
│ │ │ └───────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ │ ┌───────────────────────────────────────┐ │ │
│ │ │ │ WallpaperWindowToken │ │ │
│ │ │ │ └─TYPE_WALLPAPER 窗口令牌 │ │ │
│ │ │ └───────────────────────────────────────┘ │ │
│ │ └──────────────────────────────────────────────┘ │
│ │ │
│ ┌──────▼──────────────────────────────────────────────────┐ │
│ │ SystemUI │ │
│ │ │ │
│ │ ┌────────────────────┐ ┌───────────────────────────┐ │ │
│ │ │ThemeOverlayController│ │ScrimController │ │ │
│ │ │ │ │├─ScrimBehind (壁纸暗化) │ │ │
│ │ │ onColorsChanged() │ │├─ScrimInFront (AOD遮罩) │ │ │
│ │ │ → ColorScheme │ │└─NotificationsScrim │ │ │
│ │ │ → FabricatedOverlay│ │ │ │ │
│ │ │ → 全系统主题更新 │ └───────────────────────────┘ │ │
│ │ └────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ SurfaceFlinger │ │
│ │ 壁纸 Surface Layer → 合成 → Display │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
└───────────────────────────────────────────────────────────────────┘
十八、总结
| 组件 | 职责 | 关键类 |
|---|
| WallpaperManager | 客户端 API | WallpaperManager.java |
| WallpaperManagerService | 壁纸管理核心服务 | WallpaperManagerService.java |
| WallpaperData | 壁纸数据模型 | WallpaperData.java |
| WallpaperCropper | 壁纸裁剪 (Android 14) | WallpaperCropper.java |
| WallpaperService.Engine | 壁纸渲染引擎基类 | WallpaperService.java |
| ImageWallpaper | 默认静态壁纸实现 | ImageWallpaper.java (SystemUI) |
| WallpaperController | WMS壁纸窗口管理 | WallpaperController.java |
| WallpaperColors | 壁纸颜色描述 | WallpaperColors.java |
| ThemeOverlayController | Material You颜色 | ThemeOverlayController.java (SystemUI) |
| ScrimController | 壁纸遮罩管理 | ScrimController.java (SystemUI) |
| WallpaperPicker2 | 壁纸选择器 App | WallpaperPickerActivity.java |