AOSP Android 14 壁纸架构深度分析
一、整体架构概览
scss
复制代码
┌─────────────────────────────────────────────────────────────────┐
│ 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 最终合成到显示器 │ │
│ └────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
二、源码目录结构
ruby
复制代码
═══════ 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
java
复制代码
// frameworks/base/core/java/android/app/WallpaperManager.java
public class WallpaperManager {
// ═══════ 壁纸目标 (Android 7.0+ 支持分别设置) ═══════
/** 主屏幕壁纸 */
public static final int FLAG_SYSTEM = 1 << 0; // 1
/** 锁屏壁纸 */
public static final int FLAG_LOCK = 1 << 1; // 2
// 两者都设置: FLAG_SYSTEM | FLAG_LOCK = 3
// ═══════ 获取壁纸 ═══════
/** 获取当前壁纸 Drawable */
public Drawable getDrawable() { ... }
/** 获取当前壁纸 Drawable (指定 flag) */
public Drawable getDrawable(@SetWallpaperFlags int which) { ... }
/** 获取壁纸文件描述符 */
public ParcelFileDescriptor getWallpaperFile(
@SetWallpaperFlags int which) { ... }
/** 获取壁纸颜色 (Material You 使用) */
public WallpaperColors getWallpaperColors(
@SetWallpaperFlags int which) { ... }
// ═══════ 设置壁纸 ═══════
/** 设置静态壁纸 (Bitmap) */
public int setBitmap(Bitmap bitmap) throws IOException { ... }
/** 设置静态壁纸 (带裁剪区域) */
public int setBitmap(Bitmap fullImage, Rect visibleCropHint,
boolean allowBackup, @SetWallpaperFlags int which)
throws IOException { ... }
/** 设置静态壁纸 (InputStream) */
public void setStream(InputStream bitmapData) throws IOException { ... }
/** 设置静态壁纸 (带裁剪区域 + 返回 ID) */
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 { ... }
// ═══════ Android 14 新增: 多维度壁纸 ═══════
/**
* ★ Android 14: 设置壁纸带多维度信息
* 支持壁纸暗化版本、模糊版本等
*/
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() { ... }
/** 获取指定 flag 的壁纸信息 */
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 {
/**
* @param colors 新的壁纸颜色
* @param which FLAG_SYSTEM 或 FLAG_LOCK
*/
void onColorsChanged(WallpaperColors colors, int which);
}
}
3.2 WallpaperColors --- 壁纸颜色描述
java
复制代码
// frameworks/base/core/java/android/app/WallpaperColors.java
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;
/** 壁纸颜色从 Bitmap 分析得到 (而非手动指定) */
public static final int HINT_FROM_BITMAP = 1 << 2;
private int mColorHints;
// ═══════ 从 Bitmap 提取颜色 ═══════
/**
* ★ 从 Bitmap 自动提取壁纸颜色
* 使用 Palette API + 颜色量化算法
*/
public static WallpaperColors fromBitmap(@NonNull Bitmap bitmap) {
return fromBitmap(bitmap, false /* darkTheme */);
}
public static WallpaperColors fromBitmap(@NonNull Bitmap bitmap,
boolean darkTheme) {
// 1. 缩小 Bitmap (性能优化)
Bitmap scaledBitmap = Bitmap.createScaledBitmap(
bitmap,
Math.min(bitmap.getWidth(), 112),
Math.min(bitmap.getHeight(), 112),
true /* filter */);
// 2. 使用 Palette 提取颜色
Palette palette = new Palette.Builder(scaledBitmap)
.setQuantizer(new VariationalKMeansQuantizer())
.maximumColorCount(128)
.generate();
// 3. 获取主要颜色
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());
}
// 4. 计算颜色提示
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 服务注册和初始化
java
复制代码
// frameworks/base/services/core/java/com/android/server/wallpaper/
// WallpaperManagerService.java
public class WallpaperManagerService extends IWallpaperManager.Stub
implements IWallpaperManagerService {
// ═══════ 壁纸文件路径 ═══════
// /data/system/users/{userId}/
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"; // 壁纸元信息
// ═══════ 壁纸数据管理 ═══════
// 每个用户每个显示器的壁纸数据
// SparseArray<WallpaperData>: key = userId
// WallpaperData 包含 system 和 lock 两套壁纸信息
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));
// 通常是: com.android.systemui/.wallpapers.ImageWallpaper
// 默认壁纸资源
mDefaultWallpaperComponent = WallpaperManager.getDefaultWallpaperComponent(
context);
}
/**
* SystemServer 调用的初始化
*/
public void systemReady() {
// 1. 初始化默认显示器上的壁纸
initializeFallbackWallpaper();
// 2. 加载已保存的壁纸设置
loadSettingsLocked(UserHandle.USER_SYSTEM, false);
// 3. 绑定壁纸服务
switchWallpaper(mWallpaperMap.get(UserHandle.USER_SYSTEM), null);
// 4. 注册用户切换接收器
registerUserSwitchReceiver();
// 5. 注册包变化接收器(壁纸 App 更新/卸载)
registerPackageMonitor();
}
}
4.2 WallpaperData --- 壁纸数据模型
java
复制代码
// frameworks/base/services/core/java/com/android/server/wallpaper/
// WallpaperData.java
class WallpaperData {
// ═══════ 基本信息 ═══════
int userId;
// 壁纸文件
File wallpaperFile; // 原始壁纸文件 (wallpaper_orig)
File cropFile; // 裁剪后壁纸文件 (wallpaper)
// 壁纸组件 (动态壁纸时有效)
ComponentName wallpaperComponent; // 当前壁纸服务组件
ComponentName nextWallpaperComponent; // 正在切换到的壁纸
// 壁纸 ID
int wallpaperId; // 唯一标识
// ═══════ 壁纸显示参数 ═══════
// 裁剪区域
Rect cropHint = new Rect(0, 0, 0, 0);
// Android 14: 多维度裁剪
SparseArray<Rect> mCropHints; // key = 屏幕方向/尺寸
// 壁纸尺寸
int width; // 原始宽度
int height; // 原始高度
// 填充颜色 (壁纸不够大时)
int primaryColors;
// ═══════ 壁纸颜色 (Material You) ═══════
WallpaperColors mWallpaperColors; // 提取的壁纸颜色
boolean mIsColorExtractedFromDim; // 颜色是否从暗化版本提取
// ═══════ 壁纸偏移 ═══════
float mWallpaperXOffset; // 水平偏移 0.0-1.0
float mWallpaperYOffset; // 垂直偏移 0.0-1.0
float mWallpaperXStep; // 水平步进
float mWallpaperYStep; // 垂直步进
int mWallpaperDisplayOffsetX; // 显示器偏移 X
int mWallpaperDisplayOffsetY; // 显示器偏移 Y
// ═══════ 连接状态 ═══════
WallpaperConnection connection; // 壁纸服务连接
long lastDiedTime; // 上次崩溃时间
boolean wallpaperUpdating; // 正在更新中
boolean mIsLockscreenLiveWallpaperEnabled;
// ═══════ 标志 ═══════
boolean allowBackup = true; // 允许备份
/**
* Android 14: 壁纸是否支持暗模式
*/
boolean mShouldDimByDefault = true;
float mWallpaperDimAmount = 0.0f; // 壁纸暗化程度 0.0-1.0
}
4.3 设置壁纸完整流程
java
复制代码
// WallpaperManagerService.java
/**
* ★★★ 设置静态壁纸的完整流程
*/
@Override
public int setWallpaper(String name, String callingPackage,
@SetWallpaperFlags int which, Rect cropHint,
boolean allowBackup, Bundle extras,
int wallpaperId) {
// 1. 权限检查
checkCallingOrSelfPermission(SET_WALLPAPER);
// 确认是否允许设置壁纸
if (!isWallpaperSettingAllowed(callingPackage)) {
throw new SecurityException("Not allowed to set wallpaper");
}
int userId = UserHandle.getCallingUserId();
synchronized (mLock) {
// 2. 获取或创建 WallpaperData
WallpaperData wallpaper;
if ((which & FLAG_LOCK) != 0) {
wallpaper = getOrCreateLockWallpaperDataLocked(userId);
} else {
wallpaper = mWallpaperMap.get(userId);
}
if (wallpaper == null) {
throw new IllegalStateException("No wallpaper data");
}
// 3. 生成新的壁纸 ID
wallpaper.wallpaperId = makeWallpaperIdLocked();
// 4. 设置裁剪参数
if (cropHint != null && !cropHint.isEmpty()) {
wallpaper.cropHint.set(cropHint);
} else {
wallpaper.cropHint.set(0, 0, 0, 0);
}
wallpaper.allowBackup = allowBackup;
// 5. ★ 创建 ParcelFileDescriptor 供调用者写入壁纸数据
ParcelFileDescriptor pfd = updateWallpaperBitmapLocked(
name, wallpaper, extras);
if (pfd != null) {
// 壁纸文件准备好了
wallpaper.imageWallpaperPending = true;
wallpaper.whichPending = which;
}
return wallpaper.wallpaperId;
}
}
/**
* 创建壁纸文件的 ParcelFileDescriptor
*/
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);
// 调用者会通过这个 pfd 写入壁纸数据
// 写入完成后会触发 settingsRestored() 或 onCloseReceived()
return pfd;
} catch (FileNotFoundException e) {
return null;
}
}
/**
* ★ 壁纸数据写入完成后的处理
*/
private void onWallpaperWriteComplete(WallpaperData wallpaper) {
// 1. 裁剪壁纸
generateCrop(wallpaper);
// 2. 提取壁纸颜色
extractColors(wallpaper);
// 3. 保存壁纸设置到 XML
saveSettingsLocked(wallpaper.userId);
// 4. 如果是静态壁纸,绑定到 ImageWallpaper 服务
if (wallpaper.wallpaperComponent == null
|| wallpaper.wallpaperComponent.equals(mImageWallpaper)) {
// 静态壁纸 → 绑定 ImageWallpaper
bindWallpaperComponentLocked(mImageWallpaper,
true /* force */, false /* fromUser */, wallpaper, null);
}
// 5. 通知壁纸变化
notifyWallpaperChanged(wallpaper);
// 6. 通知颜色变化 (Material You)
notifyWallpaperColorsChanged(wallpaper, wallpaper.mWhich);
// 7. 通知 WMS
notifyWallpaperChangedToWms(wallpaper);
}
4.4 壁纸裁剪 --- WallpaperCropper (Android 14 新增)
java
复制代码
// frameworks/base/services/core/java/com/android/server/wallpaper/
// WallpaperCropper.java
/**
* Android 14 新增的壁纸裁剪器
* 支持多种屏幕尺寸/方向的智能裁剪
*/
public class WallpaperCropper {
/**
* ★ 生成裁剪后的壁纸
*/
static void generateCrop(WallpaperData wallpaper) {
// 1. 读取原始壁纸
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(
wallpaper.wallpaperFile.getAbsolutePath(), options);
int origWidth = options.outWidth;
int origHeight = options.outHeight;
// 2. 确定裁剪区域
Rect cropHint = wallpaper.cropHint;
if (cropHint.isEmpty()) {
// 无裁剪 → 使用整张图
cropHint = new Rect(0, 0, origWidth, origHeight);
}
// 3. 确定目标尺寸
// Android 14: 支持多维度
DisplayInfo displayInfo = getDefaultDisplayInfo();
int targetWidth = displayInfo.logicalWidth;
int targetHeight = displayInfo.logicalHeight;
// 壁纸通常比屏幕宽 (用于滑动视差)
float parallaxRatio = getParallaxRatio();
targetWidth = (int) (targetWidth * parallaxRatio);
// parallaxRatio 通常为 1.0 ~ 2.0
// 4. 计算采样率 (inSampleSize)
int sampleSize = calculateSampleSize(
cropHint.width(), cropHint.height(),
targetWidth, targetHeight);
// 5. 解码裁剪区域
options.inJustDecodeBounds = false;
options.inSampleSize = sampleSize;
BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(
wallpaper.wallpaperFile.getAbsolutePath(), false);
Bitmap croppedBitmap = decoder.decodeRegion(cropHint, options);
// 6. 缩放到目标尺寸
if (croppedBitmap.getWidth() != targetWidth
|| croppedBitmap.getHeight() != targetHeight) {
croppedBitmap = Bitmap.createScaledBitmap(
croppedBitmap, targetWidth, targetHeight, true);
}
// 7. 保存裁剪后的壁纸
FileOutputStream fos = new FileOutputStream(wallpaper.cropFile);
croppedBitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.close();
// 8. 释放 Bitmap
croppedBitmap.recycle();
}
/**
* Android 14: 多屏幕尺寸裁剪
*/
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 壁纸服务绑定
java
复制代码
// WallpaperManagerService.java
/**
* ★★★ 绑定壁纸服务组件
*
* 静态壁纸 → 绑定 ImageWallpaper
* 动态壁纸 → 绑定第三方 LiveWallpaper 服务
*/
boolean bindWallpaperComponentLocked(ComponentName componentName,
boolean force, boolean fromUser, WallpaperData wallpaper,
IRemoteCallback reply) {
// 1. 验证壁纸服务
if (componentName == null) {
componentName = mDefaultWallpaperComponent;
if (componentName == null) {
componentName = mImageWallpaper;
}
}
// 2. 查询壁纸服务信息
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");
}
// 3. 检查权限
if (!si.permission.equals(BIND_WALLPAPER)) {
throw new SecurityException(
"Wallpaper service must require BIND_WALLPAPER permission");
}
// 4. 解析壁纸元数据 (动态壁纸的 XML 配置)
WallpaperInfo wi = null;
if (!componentName.equals(mImageWallpaper)) {
wi = new WallpaperInfo(mContext,
mIPackageManager.resolveService(intent, null, 0, wallpaper.userId));
}
// 5. 断开旧连接
WallpaperConnection oldConn = wallpaper.connection;
if (oldConn != null) {
detachWallpaperLocked(oldConn);
}
// 6. 创建新连接
WallpaperConnection newConn = new WallpaperConnection(
wi, wallpaper, componentName);
wallpaper.connection = newConn;
// 7. ★ 绑定服务
intent.setComponent(componentName);
boolean bound = mContext.bindServiceAsUser(
intent,
newConn, // ServiceConnection
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;
}
// 8. 更新壁纸组件信息
wallpaper.wallpaperComponent = componentName;
wallpaper.connection = newConn;
return true;
}
4.6 WallpaperConnection --- 与壁纸服务的连接
java
复制代码
// WallpaperManagerService 内部类
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;
// ═══════ ServiceConnection 回调 ═══════
@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);
}
}
}
// ═══════ IWallpaperConnection 接口 ═══════
/** 壁纸引擎连接就绪 */
@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);
// ★ 设置壁纸暗化 (Android 14)
engine.applyDimming(mWallpaper.mWallpaperDimAmount);
} catch (RemoteException e) { }
// 通知 WMS 壁纸已就绪
notifyCallbacksLocked(mWallpaper);
}
}
/** 壁纸引擎报告颜色 */
@Override
public void onWallpaperColorsChanged(WallpaperColors colors,
int displayId) {
synchronized (mLock) {
mWallpaper.mWallpaperColors = colors;
// ★ 通知所有颜色监听器 (Material You)
notifyWallpaperColorsChangedOnDisplay(
mWallpaper, mWallpaper.mWhich, displayId);
}
}
/** 壁纸引擎报告尺寸 */
@Override
public void engineShown(IWallpaperEngine engine) {
// 壁纸渲染完成,首帧已显示
}
}
/**
* 连接壁纸引擎
*/
void attachServiceLocked(WallpaperConnection conn,
WallpaperData wallpaper) {
try {
// ★ 调用壁纸服务创建引擎
conn.mService.attach(
conn, // IWallpaperConnection
conn.mToken, // 窗口令牌
TYPE_WALLPAPER, // 窗口类型
false /* isPreview */,
wallpaper.width, // 期望宽度
wallpaper.height, // 期望高度
wallpaper.padding, // 内边距
wallpaper.mWhich // FLAG_SYSTEM / FLAG_LOCK
);
} catch (RemoteException e) {
// 连接失败
}
}
五、WallpaperService --- 壁纸服务基类
5.1 核心架构
java
复制代码
// frameworks/base/core/java/android/service/wallpaper/WallpaperService.java
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);
}
}
// ═══════ Engine --- 壁纸渲染引擎 ★★★ ═══════
public class Engine {
// 窗口相关
final BaseSurfaceHolder mSurfaceHolder = new WallpaperSurfaceHolder();
SurfaceControl mSurfaceControl;
// 窗口 Session (与 WMS 通信)
IWindowSession mSession;
// 可见性状态
boolean mVisible;
boolean mReportedVisible;
// 壁纸偏移
float mPendingXOffset;
float mPendingYOffset;
float mPendingXOffsetStep;
float mPendingYOffsetStep;
boolean mOffsetsChanged;
// 尺寸
int mWidth; // Surface 宽度
int mHeight; // Surface 高度
int mReqWidth; // 请求宽度
int mReqHeight; // 请求高度
// 壁纸标志
int mWallpaperFlags; // FLAG_SYSTEM / FLAG_LOCK
// ══════ 生命周期回调 ══════
/** 引擎创建时 */
public void onCreate(SurfaceHolder surfaceHolder) { }
/** 引擎销毁时 */
public void onDestroy() { }
/** 可见性变化 */
public void onVisibilityChanged(boolean visible) { }
/** Surface 创建 */
public void onSurfaceCreated(SurfaceHolder holder) { }
/** Surface 尺寸变化 */
public void onSurfaceChanged(SurfaceHolder holder,
int format, int width, int height) { }
/** Surface 销毁 */
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() {
// 通知 WallpaperManagerService 颜色变化
WallpaperColors colors = onComputeColors();
if (colors != null) {
try {
mConnection.onWallpaperColorsChanged(colors, mDisplayId);
} catch (RemoteException e) { }
}
}
/** 子类可覆盖 --- 计算壁纸颜色 */
public @Nullable WallpaperColors onComputeColors() {
return null;
}
// ══════ Surface 管理 ══════
/**
* 壁纸引擎内部使用 SurfaceHolder 来渲染壁纸
*/
/** 获取 SurfaceHolder */
public SurfaceHolder getSurfaceHolder() {
return mSurfaceHolder;
}
/** 设置触摸事件是否可接收 */
public void setTouchEventsEnabled(boolean enabled) {
mTouchEventsEnabled = enabled;
}
// ══════ ★ attach --- 引擎初始化核心 ══════
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;
// 1. 创建与 WMS 的窗口 Session
mSession = WindowManagerGlobal.getWindowSession();
// 2. ★ 添加壁纸窗口到 WMS
mWindow = new W(this); // IWindow 实现
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);
// 3. 获取 SurfaceControl
mSurfaceControl = new SurfaceControl.Builder()
.setName("WallpaperSurface")
.setCallsite("WallpaperService.Engine.attach")
.build();
// 4. 调用 onCreate 回调
onCreate(mSurfaceHolder);
// 5. 设置为可见
updateSurface(true, false, false);
}
/**
* ★ 更新 Surface
*/
void updateSurface(boolean forceRelayout,
boolean forceReport, boolean redrawNeeded) {
// 与 WMS 协商 Surface 参数
mSession.relayout(mWindow, mLayout,
mWidth, mHeight, View.VISIBLE, 0,
mWinFrame, mOverscanInsets, mContentInsets,
mVisibleInsets, mStableInsets,
mBackdropFrame, mDisplayCutout,
mMergedConfiguration, mSurfaceControl,
mInsetsState, mTempControls, mSurfaceSize);
// Surface 创建/变化回调
if (surfaceCreated) {
onSurfaceCreated(mSurfaceHolder);
}
if (sizeChanged) {
onSurfaceChanged(mSurfaceHolder,
mFormat, mWidth, mHeight);
}
}
/**
* ★ Android 14: 暗化壁纸
*/
public void applyDimming(float dimAmount) {
// 0.0 = 不暗化, 1.0 = 完全暗化
mWallpaperDimAmount = dimAmount;
// 通过 SurfaceControl 设置 Color filter
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 整体结构
java
复制代码
// packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
public class ImageWallpaper extends WallpaperService {
@Override
public Engine onCreateEngine() {
return new GLEngine();
}
/**
* ★ GLEngine --- 使用 OpenGL ES 渲染壁纸
*
* 为什么用 OpenGL 而不是 Canvas?
* 1. GPU 渲染性能更好
* 2. 支持高分辨率壁纸的高效渲染
* 3. 支持硬件加速特效(模糊、暗化等)
* 4. 与 SurfaceFlinger 的 Layer 直接对接
*/
class GLEngine extends Engine {
// EGL 上下文
private EglHelper mEglHelper;
// 渲染器
private ImageWallpaperRenderer mRenderer;
// 壁纸 Bitmap
private Bitmap mBitmap;
// 显示参数
private int mDisplayWidth;
private int mDisplayHeight;
@Override
public void onCreate(SurfaceHolder surfaceHolder) {
super.onCreate(surfaceHolder);
// 设置 Surface 类型
surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_GPU);
surfaceHolder.setFormat(PixelFormat.RGBA_8888);
// 初始化 EGL
mEglHelper = new EglHelper();
// 创建渲染器
mRenderer = new ImageWallpaperRenderer(getApplicationContext());
}
@Override
public void onSurfaceCreated(SurfaceHolder holder) {
super.onSurfaceCreated(holder);
// 初始化 EGL Surface
mEglHelper.init(holder, needSupportWideColorGamut());
// 加载壁纸 Bitmap
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;
// 1. 设置 EGL 为当前上下文
mEglHelper.makeCurrent();
// 2. 渲染壁纸
mRenderer.draw(
mDisplayWidth, mDisplayHeight,
mBitmap,
mPendingXOffset, mPendingYOffset);
// 3. 交换缓冲区 (显示到屏幕)
mEglHelper.swapBuffers();
// 4. 报告帧已渲染
reportEngineShown(true);
}
/**
* ★ 加载壁纸 Bitmap
*/
private void loadWallpaperBitmap() {
// 从 WallpaperManagerService 获取壁纸文件
WallpaperManager wm = WallpaperManager.getInstance(
getApplicationContext());
// 使用 BitmapFactory 解码
// 针对大壁纸做下采样
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.HARDWARE;
// HARDWARE Config: Bitmap 直接存储在 GPU 内存中
// 避免 CPU → GPU 的数据传输
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 渲染
java
复制代码
// packages/SystemUI/src/com/android/systemui/wallpapers/gl/
// ImageWallpaperRenderer.java
public class ImageWallpaperRenderer {
// OpenGL 纹理
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) {
// 1. 设置视口
GLES20.glViewport(0, 0, screenWidth, screenHeight);
// 2. 清除颜色
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
// 3. 使用着色器程序
GLES20.glUseProgram(mProgram);
// 4. 绑定壁纸纹理
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureId);
// 5. ★ 计算纹理坐标(实现视差偏移)
float[] texCoords = calculateTexCoords(
bitmap.getWidth(), bitmap.getHeight(),
screenWidth, screenHeight,
xOffset, yOffset);
// 6. 设置顶点和纹理坐标
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);
// 7. 绘制
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
}
/**
* ★★ 计算视差纹理坐标
*
* 壁纸通常比屏幕宽,通过偏移显示不同区域实现视差效果
*
* 壁纸图片:
* ┌──────────────────────────────────────┐
* │ │
* │ ┌──────────┐ │
* │ │ 可见区域 │ ← 随 xOffset 移动 │
* │ │ (屏幕大小)│ │
* │ └──────────┘ │
* │ │
* └──────────────────────────────────────┘
*
* xOffset = 0.0 → 显示最左边
* xOffset = 0.5 → 显示中间
* xOffset = 1.0 → 显示最右边
*/
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;
// 限制在 [0, 1] 范围
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 // 右下
};
}
/**
* 上传 Bitmap 到 GPU 纹理
*/
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);
// 上传 Bitmap 数据
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 核心职责
java
复制代码
// frameworks/base/services/core/java/com/android/server/wm/
// WallpaperController.java
/**
* WMS 中的壁纸控制器
*
* 职责:
* 1. 管理壁纸窗口 (TYPE_WALLPAPER) 的位置/大小/可见性
* 2. 确定哪个窗口"需要壁纸"(哪个 Activity 显示壁纸背景)
* 3. 控制壁纸偏移(视差效果)
* 4. 壁纸过渡动画
*/
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;
// ═══════ 核心方法 ═══════
/**
* ★ 查找壁纸目标窗口
*
* 壁纸目标 = 当前需要显示壁纸的窗口
* 例如: Launcher 的窗口 (FLAG_SHOW_WALLPAPER)
*/
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 /* traverseTopToBottom */);
// 更新壁纸目标
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 /* includingParents */);
}
}
}
/**
* ★ 更新壁纸偏移(视差效果)
*
* 当 Launcher 滑动页面时调用
*/
boolean updateWallpaperOffset(WindowState wallpaperWin,
boolean sync) {
// 1. 获取当前偏移
float wpx = mWallpaperTarget != null
? mWallpaperTarget.mWallpaperX : 0.5f;
float wpy = mWallpaperTarget != null
? mWallpaperTarget.mWallpaperY : 0.5f;
// 2. 计算像素偏移
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;
// 3. 应用偏移
boolean changed = false;
if (wallpaperWin.mXOffset != offsetX
|| wallpaperWin.mYOffset != offsetY) {
wallpaperWin.mXOffset = offsetX;
wallpaperWin.mYOffset = offsetY;
changed = true;
}
if (changed) {
// 更新壁纸 Surface 位置
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 /* animate */);
} else {
wallpaper.hide(false /* animate */);
}
}
// 通知壁纸可见性监听器
mWallpaperVisibilityListeners.notifyWallpaperVisibilityChanged(
mDisplayContent, visible);
}
/**
* 判断壁纸是否应该可见
*/
boolean isWallpaperVisible() {
// 有壁纸目标且目标窗口可见
if (mWallpaperTarget != null && mWallpaperTarget.isVisible()) {
return true;
}
// 正在执行壁纸过渡动画
if (isWallpaperTransitionAnimating()) {
return true;
}
return false;
}
}
7.2 壁纸窗口层级关系
ini
复制代码
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 完整调用链
scss
复制代码
用户在 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 偏移计算细节
java
复制代码
// Launcher3 中的壁纸偏移设置
// packages/apps/Launcher3/src/com/android/launcher3/Workspace.java
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();
// 计算偏移 (0.0 ~ 1.0)
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);
// 示例:
// 5 个页面: step = 0.25
// 页面0: offset = 0.0
// 页面1: offset = 0.25
// 页面2: offset = 0.5
// 页面3: offset = 0.75
// 页面4: offset = 1.0
}
}
九、主屏幕壁纸 vs 锁屏壁纸
9.1 双壁纸架构
scss
复制代码
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 锁屏壁纸处理
java
复制代码
// SystemUI 中的锁屏壁纸处理
// packages/SystemUI/src/com/android/systemui/statusbar/phone/
// LockscreenWallpaper.java (概念性代码)
/**
* 处理锁屏壁纸的显示
*
* 锁屏时:
* 1. 如果有单独的锁屏壁纸 → 显示锁屏壁纸
* 2. 如果没有 → 显示主屏幕壁纸 (可能加暗化效果)
*
* Android 14:
* 3. 锁屏也支持动态壁纸
* 4. 壁纸暗化 (dim) 控制
*/
// WallpaperManagerService.java 中的锁屏壁纸管理
/**
* 获取锁屏壁纸
*/
@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);
}
}
/**
* ★ Android 14: 壁纸暗化控制
*
* 锁屏时壁纸可以被暗化,使文字更清晰
*/
@Override
public void setWallpaperDimAmount(float dimAmount) {
// dimAmount: 0.0 = 不暗化, 1.0 = 完全暗
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 颜色提取流程
scss
复制代码
壁纸设置/变化
│
▼
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 中的颜色处理
java
复制代码
// packages/SystemUI/src/com/android/systemui/theme/
// ThemeOverlayController.java
@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 /* handler --- 使用主线程 */
);
// 获取初始壁纸颜色
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 /* forceReload */);
}
/**
* ★★★ 从壁纸颜色生成系统主题
*/
void reevaluateSystemTheme(boolean forceReload) {
WallpaperColors colors = mCurrentColors;
if (colors == null) return;
// 1. 获取种子色
int seedColor = getSeedColor(colors);
// 2. 检查用户是否手动选择了颜色
String storedColors = Settings.Secure.getStringForUser(
mContext.getContentResolver(),
Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
mCurrentUser);
// 3. 生成 ColorScheme
ColorScheme scheme = new ColorScheme(seedColor, isDarkTheme());
// 4. 构建 Overlay
FabricatedOverlay overlay = createOverlay(scheme);
// 5. 应用 Overlay
mOverlayManager.commit(new OverlayManagerTransaction.Builder()
.setEnabled(overlay, true, mCurrentUser)
.build());
}
/**
* 从壁纸颜色提取种子色
*/
private int getSeedColor(WallpaperColors colors) {
// 使用 Material Color Utilities 库
// 从壁纸颜色中选择最适合的种子色
Color primary = colors.getPrimaryColor();
// 转换到 CAM16 色彩空间
int argb = primary.toArgb();
// 使用 Material You 的种子色算法
// 考虑色相、饱和度、亮度
return Score.score(
ColorUtils.colorToCAM(argb)).get(0);
}
/**
* 从 ColorScheme 创建 FabricatedOverlay
*/
private FabricatedOverlay createOverlay(ColorScheme scheme) {
FabricatedOverlay.Builder builder = new FabricatedOverlay.Builder(
"com.android.systemui", "wallpaper_theme", "android");
// accent1 (主强调色系列)
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));
// ... 13 个色调级别
// accent2, accent3, neutral1, neutral2 同理
// 共 5 × 13 = 65 个颜色值
return builder.build();
}
}
十一、壁纸过渡动画
11.1 壁纸切换动画
java
复制代码
// WallpaperController.java 中的动画处理
/**
* 壁纸过渡动画场景:
*
* 1. App → Launcher (返回桌面时壁纸出现)
* 2. Launcher → App (离开桌面时壁纸消失)
* 3. 锁屏解锁 (壁纸可能变化)
* 4. 壁纸更换 (新壁纸替换旧壁纸)
*/
// 场景1: App 退出,壁纸出现
// WallpaperAnimationAdapter.java
class WallpaperAnimationAdapter {
/**
* 创建壁纸动画目标
* 用于 App 过渡动画中控制壁纸 Surface
*/
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 /* isTranslucent */,
null /* clipRect */,
null /* contentInsets */,
wallpaperWin.getPrefixOrderIndex(),
new Point(0, 0),
wallpaperWin.getBounds(),
wallpaperWin.getWindowConfiguration(),
false /* isNotInRecents */
);
}
}
/**
* Launcher 在 App 过渡动画中处理壁纸
*/
// Launcher3 QuickStep:
// 当从 App 返回 Home 时
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();
// 壁纸缩放: 0.95 → 1.0 (轻微放大效果)
float scale = lerp(0.95f, 1.0f, progress);
frameT.setScale(surface, scale, scale);
// 壁纸透明度: 0.0 → 1.0 (渐显)
frameT.setAlpha(surface, progress);
frameT.apply();
});
animator.start();
}
}
11.2 Android 14 壁纸揭示动画 (Reveal)
java
复制代码
// packages/SystemUI/src/com/android/systemui/wallpapers/gl/
// ImageRevealWallpaperRenderer.java
/**
* ★ Android 14 新增: 壁纸切换时的揭示动画
*
* 效果: 新壁纸从中心或某个点向外扩展,
* 像"揭开"一样替换旧壁纸
*
* ┌──────────────────┐ ┌──────────────────┐
* │ 旧壁纸 │ │ 旧壁 ┌──┐纸 │
* │ │ │ │新│ │
* │ │ → │ │壁│ │
* │ │ │ │纸│ │
* │ │ │ └──┘ │
* └──────────────────┘ └──────────────────┘
* ↓
* ┌──────────────────┐
* │ ┌────────────┐ │
* │ │ │ │
* │ │ 新壁纸 │ │
* │ │ │ │
* │ └────────────┘ │
* └──────────────────┘
* ↓
* ┌──────────────────┐
* │ │
* │ 新壁纸 │
* │ (全屏) │
* │ │
* └──────────────────┘
*/
public class ImageRevealWallpaperRenderer {
// 动画参数
private float mRevealProgress = 0f; // 0=旧壁纸, 1=新壁纸
private float mRevealCenterX; // 揭示中心 X
private float mRevealCenterY; // 揭示中心 Y
// 两张壁纸纹理
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 --- 壁纸上层遮罩
java
复制代码
// packages/SystemUI/src/com/android/systemui/statusbar/phone/
// ScrimController.java
/**
* 管理壁纸上方的遮罩层
*
* 遮罩层级 (从底到顶):
*
* 壁纸 Surface (WallpaperService 渲染)
* ↑
* ScrimBehind (后遮罩 --- 壁纸暗化)
* ↑
* App 内容 / Launcher
* ↑
* ScrimInFront (前遮罩 --- AOD 使用)
* ↑
* Notification Shade
* ↑
* Status Bar
*/
@SysUISingleton
public class ScrimController {
// 遮罩视图
private ScrimView mScrimBehind; // 壁纸后面的遮罩
private ScrimView mScrimInFront; // 所有内容前面的遮罩
private ScrimView mNotificationsScrim; // 通知背后的遮罩
// 各场景的遮罩透明度
/** 锁屏状态 */
// ScrimBehind: 有一定暗度 (让时钟/通知更清晰)
// 壁纸可见但被暗化
/** 通知面板展开 */
// ScrimBehind: 随展开程度加深
// 壁纸逐渐被遮挡
/** AOD (Always-on Display) */
// ScrimBehind: 几乎全黑
// ScrimInFront: 完全黑 (只露出AOD内容)
/** 正常解锁状态 */
// ScrimBehind: 透明
// 壁纸完全可见 (通过 Launcher 的透明背景)
/**
* ★ 根据状态更新遮罩
*/
private void applyState() {
switch (mState) {
case KEYGUARD:
// 锁屏: 壁纸后方有暗化遮罩
mScrimBehind.setAlpha(0.25f); // 25% 暗化
mNotificationsScrim.setAlpha(calculateNotifAlpha());
mScrimInFront.setAlpha(0f);
break;
case SHADE_LOCKED:
// 锁屏下拉: 壁纸暗化加深
mScrimBehind.setAlpha(0.6f); // 60% 暗化
break;
case BOUNCER:
// 密码界面: 壁纸大幅暗化
mScrimBehind.setAlpha(0.8f);
break;
case AOD:
// Always-on: 几乎全黑
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) {
// fraction: 0.0 = 收起, 1.0 = 完全展开
if (mState == ScrimState.KEYGUARD) {
// 从锁屏下拉通知面板
float scrimAlpha = lerp(0.25f, 0.6f, fraction);
mScrimBehind.setAlpha(scrimAlpha);
}
}
}
12.2 壁纸可见性与 SystemUI 联动
java
复制代码
// SystemUI 中监听壁纸可见性
// WallpaperVisibilityListeners.java (WMS 内部)
class WallpaperVisibilityListeners {
// 注册的监听器 (SystemUI 等)
private final ArrayMap<IBinder, WallpaperVisibilityListener>
mListeners = new ArrayMap<>();
void notifyWallpaperVisibilityChanged(
DisplayContent displayContent, boolean visible) {
for (WallpaperVisibilityListener listener : mListeners.values()) {
listener.onWallpaperVisibilityChanged(visible,
displayContent.getDisplayId());
}
}
}
// SystemUI 中注册监听
// CentralSurfacesImpl.java (简化)
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 动态壁纸声明
xml
复制代码
<!-- AndroidManifest.xml -->
<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>
xml
复制代码
<!-- res/xml/wallpaper.xml -->
<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 动态壁纸实现示例
java
复制代码
// 动态壁纸服务示例
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); // ~60fps
}
}
};
/**
* ★ 渲染一帧
*/
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() {
// ★ 报告壁纸颜色给系统 (Material You)
return new WallpaperColors(
Color.valueOf(Color.BLUE), // 主色
Color.valueOf(Color.CYAN), // 副色
Color.valueOf(Color.WHITE) // 第三色
);
}
@Override
public void onDestroy() {
super.onDestroy();
mHandler.removeCallbacks(mDrawRunner);
}
}
}
十四、多用户/多显示器支持
java
复制代码
// WallpaperManagerService.java
/**
* 多用户壁纸管理
*/
// 每个用户有独立的壁纸数据
private final SparseArray<WallpaperData> mWallpaperMap; // userId → data
private final SparseArray<WallpaperData> mLockWallpaperMap; // userId → data
/**
* 用户切换时的壁纸处理
*/
void switchUser(int userId) {
synchronized (mLock) {
// 1. 断开旧用户的壁纸连接
WallpaperData oldWallpaper = mWallpaperMap.get(mCurrentUserId);
if (oldWallpaper != null && oldWallpaper.connection != null) {
detachWallpaperLocked(oldWallpaper.connection);
}
mCurrentUserId = userId;
// 2. 加载新用户的壁纸设置
if (!mWallpaperMap.contains(userId)) {
loadSettingsLocked(userId, false);
}
// 3. 绑定新用户的壁纸
WallpaperData newWallpaper = mWallpaperMap.get(userId);
switchWallpaper(newWallpaper, null);
// 4. 通知壁纸颜色变化 (Material You 需要刷新)
notifyWallpaperColorsChanged(newWallpaper, FLAG_SYSTEM);
WallpaperData lockWallpaper = mLockWallpaperMap.get(userId);
if (lockWallpaper != null) {
notifyWallpaperColorsChanged(lockWallpaper, FLAG_LOCK);
}
}
}
/**
* 多显示器壁纸管理 (Android 14)
*/
// WallpaperDisplayHelper.java
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);
}
/**
* 获取视差比率
* 1.0 = 壁纸与屏幕等宽 (无视差)
* 1.3 = 壁纸比屏幕宽 30% (有视差)
*/
float getParallaxRatio() {
// 可由配置或 OEM 定制
return mContext.getResources().getFloat(
R.dimen.config_wallpaperParallaxRatio);
}
}
十五、壁纸设置持久化
15.1 wallpaper_info.xml
xml
复制代码
<!-- /data/system/users/{userId}/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" />
<!-- Android 14: 暗化参数 -->
<dimAmount wp:value="0.0" />
</wp>
15.2 加载和保存
java
复制代码
// WallpaperManagerService.java
/**
* 加载壁纸设置
*/
private void loadSettingsLocked(int userId, boolean keepDimensionHints) {
File wallpaperDir = getWallpaperDir(userId);
File infoFile = new File(wallpaperDir, WALLPAPER_INFO);
if (!infoFile.exists()) {
// 没有设置过壁纸,使用默认
migrateFromOld(userId);
return;
}
// 解析 XML
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()));
// ... secondary, tertiary, hints
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 设置壁纸的完整路径
scss
复制代码
用户在壁纸选择器中选择一张图片
│
▼
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 壁纸显示/渲染路径
scss
复制代码
┌─────────────────────────────────────────────────────────┐
│ 渲染管线 │
│ │
│ 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/...) │
│ │
└─────────────────────────────────────────────────────────┘
十七、关键类关系图
scss
复制代码
┌───────────────────────────────────────────────────────────────────┐
│ │
│ ┌──────────────┐ 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 |