Android中的壁纸分为静态壁纸和动态壁纸, 这两类壁纸本质都是一样的, 都是通过继承WallpaperService来实现的,只不过是绘制方面的差异。WallpaperManagerService用于管理壁纸的运行与切换,并通过WallpaperManager类向外界提供操作壁纸的接口,主要体现了对壁纸的管理方式。WallpaperService则对应壁纸的具体实现,实现壁纸服务相关的核心是WallpaperService中的Engine类
1 简单壁纸案列
1.1 新建壁纸服务
public class MyWallPaperService extends WallpaperService {
private static final String TAG = "MyWallPaperService";
@Override
public Engine onCreateEngine() {
return new MyEngine();
}
class MyEngine extends Engine {
@Override
public void onSurfaceCreated(SurfaceHolder holder) {
super.onSurfaceCreated(holder);
Canvas canvas = holder.lockCanvas();
canvas.drawColor(Color.GREEN);
holder.unlockCanvasAndPost(canvas);
}
}
}
1.2 配置文件中配置相关
<!-- AndroidManifest.xml中service配置 -->
<service android:name=".MyWallPaperService"
android:enabled="true"
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_resource"/>
</service>
<!-- wallpaper_resource.xml配置 -->
<?xml version="1.0" encoding="utf-8"?>
<wallpaper xmlns:android="http://schemas.android.com/apk/res/android"
/>
1.3 通过壁纸选择器设置壁纸,最终壁纸显示为绿色背景
void setWallpaper() {
Intent intent = new Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER);
ComponentName componentName = new ComponentName(this, MyWallPaperService.class);
intent.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT, componentName);
startActivity(intent);
}
2 WallpaperManager相关介绍
// 主屏幕壁纸flag
public static final int FLAG_SYSTEM = 1 << 0;
// 锁屏壁纸flag
public static final int FLAG_LOCK = 1 << 1;
// 设置动态壁纸
public boolean setWallpaperComponent(ComponentName name)
// 清除锁屏壁纸
public void clear(WallpaperManager.FLAG_LOCK)
// 清除所有壁纸
public void clearWallpaper()
// 获取静态壁纸图片
public Bitmap getBitmap()
// 设置静态壁纸
public void setBitmap(Bitmap bitmap)
2.1 WallpaperService和WallpaperManagerService之间的关系
2.2 WallpaperManager和WallpaperManagerService之间的关系
2.3 壁纸涉及到的类介绍
// 壁纸服务信息封装
WallpaperInfo
// 提供接口对壁纸服务的访问
WallpaperManager
// IWallpaperEngine接口服务端对象
// class IWallpaperEngineWrapper extends IWallpaperEngine.Stub
IWallpaperEngineWrapper
// IWallpaperService接口服务端对象
// IWallpaperServiceWrapper extends IWallpaperService.Stub
IWallpaperServiceWrapper
// 用户壁纸相关信息存储
WallpaperData
// 监听WallPaperService之间的连接状态和实现IWallpaperConnection接口服务端实现
// class WallpaperConnection extends IWallpaperConnection.Stub
// implements ServiceConnection
WallpaperConnection
// 壁纸服务
// public class WallpaperManagerService extends IWallpaperManager.Stub
WallpaperManagerService
// 壁纸核心和壁纸绘制相关
Engine
3 壁纸相关代码分析
WallpaperManager的setWallpaperComponent函数为设置动态壁纸函数,接下来以动态壁纸为入口分析相关代码。Globals实现了IWallpaperManagerCallback接口,sGlobals.mService为WallpaperManagerService的代理对象,sGlobals.mService最终通过binder调用WallpaperManagerService的setWallpaperComponent函数,name为对应壁纸服务的包名。
private static class Globals extends IWallpaperManagerCallback.Stub {
private final IWallpaperManager mService;
}
public boolean setWallpaperComponent(ComponentName name, int userId) {
if (sGlobals.mService == null) {
Log.w(TAG, "WallpaperService not running");
throw new RuntimeException(new DeadSystemException());
}
try {
// step 1, 调用WallpaperManagerService的setWallpaperComponentChecked函数
sGlobals.mService.setWallpaperComponentChecked(name, mContext.getOpPackageName(),
userId);
return true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
3.1 WallpaperManagerService::setWallpaperComponent
拥有android.Manifest.permission.SET_WALLPAPER_COMPONENT权限的apk才能设置动态壁纸,壁纸选择器有这个设置权限,首先进行权限的相关检查, 然后获取用户设置的壁纸相关信息, 错误判断等, 接下来bindWallpaperComponentLocked函数才是核心,对WallpaperService的条件判断以及绑定
private void setWallpaperComponent(ComponentName name, int userId) {
userId = ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId,
false /* all */, true /* full */, "changing live wallpaper", null /* pkg */);
// step 1, 权限检查
checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);
int which = FLAG_SYSTEM;
boolean shouldNotifyColors = false;
WallpaperData wallpaper;
synchronized (mLock) {
if (DEBUG) Slog.v(TAG, "setWallpaperComponent name=" + name);
// step 2, 获取用户壁纸相关信息
wallpaper = mWallpaperMap.get(userId);
if (wallpaper == null) {
throw new IllegalStateException("Wallpaper not yet initialized for user " + userId);
}
final long ident = Binder.clearCallingIdentity();
// step 3 , 如果锁屏壁纸没有添加,则将锁屏壁纸flag添加上
if (mLockWallpaperMap.get(userId) == null) {
which |= FLAG_LOCK;
}
try {
wallpaper.imageWallpaperPending = false;
boolean same = changingToSame(name, wallpaper);
// step 3, 绑定WallpaperService服务
if (bindWallpaperComponentLocked(name, false, true, wallpaper, null)) {
if (!same) {
wallpaper.primaryColors = null;
}
wallpaper.wallpaperId = makeWallpaperIdLocked();
notifyCallbacksLocked(wallpaper);
shouldNotifyColors = true;
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
if (shouldNotifyColors) {
// step 4, 通知壁纸状态改变
notifyWallpaperColorsChanged(wallpaper, which);
notifyWallpaperColorsChanged(mFallbackWallpaper, FLAG_SYSTEM);
}
}
3.2 WallpaperManagerService::bindWallpaperComponentLocked
- bindWallpaperComponentLocked函数较长,分开来分析。
- step 1, 如果设置的壁纸服务包名和现在设置的包名相同则返回不做处理
- step 2, 根据componentName来查询对应的服务相关信息,即ServiceInfo,si为空说明对应包名的服务不存在,也返回不做处理了
- step 3, 对应包名的WallpaperService没有包含android.Manifest.permission.BIND_WALLPAPER权限,返回不做后续处理
// step 1 , 壁纸对应的包名是否改变,不是则是相同的壁纸返回,不做处理
if (!force && changingToSame(componentName, wallpaper)) {
return true;
}
int serviceUserId = wallpaper.userId;
// step 2, 通过PackageManager查找是否存在对应包名的Service相关信息,不存在则返回
ServiceInfo si = mIPackageManager.getServiceInfo(componentName,
PackageManager.GET_META_DATA | PackageManager.GET_PERMISSIONS, serviceUserId);
if (si == null) {
Slog.w(TAG, "Attempted wallpaper " + componentName + " is unavailable");
return false;
}
// step 3, 如果service不包含android.Manifest.permission.BIND_WALLPAPER权限则返回
if (!android.Manifest.permission.BIND_WALLPAPER.equals(si.permission)) {
String msg = "Selected service does not have "
+ android.Manifest.permission.BIND_WALLPAPER
+ ": " + componentName;
if (fromUser) {
throw new SecurityException(msg);
}
Slog.w(TAG, msg);
return false;
}
3.3 WallpaperManagerService::bindWallpaperComponentLocked
ris为查询系统中所有的WallpaperService,筛选componentName包名相同的WallpaperService,并创建相关WallpaperInfo,wi为null的情况, 则说明对应的WallpaperService不存在,返回
WallpaperInfo wi = null;
Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);
if (componentName != null && !componentName.equals(mImageWallpaper)) {
// Make sure the selected service is actually a wallpaper service.
List<ResolveInfo> ris =
mIPackageManager.queryIntentServices(intent,
intent.resolveTypeIfNeeded(mContext.getContentResolver()),
PackageManager.GET_META_DATA, serviceUserId).getList();
for (int i=0; i<ris.size(); i++) {
ServiceInfo rsi = ris.get(i).serviceInfo;
// step 1, 如果componentName包名对应的service存在,创建WallpaperInfo保存ServiceInfo信息
if (rsi.name.equals(si.name) &&
rsi.packageName.equals(si.packageName)) {
try {
wi = new WallpaperInfo(mContext, ris.get(i));
} catch (XmlPullParserException e) {
if (fromUser) {
throw new IllegalArgumentException(e);
}
Slog.w(TAG, e);
return false;
} catch (IOException e) {
if (fromUser) {
throw new IllegalArgumentException(e);
}
Slog.w(TAG, e);
return false;
}
break;
}
}
// step 2, componentName包名对应的service不存在, 直接返回
if (wi == null) {
String msg = "Selected service is not a wallpaper: "
+ componentName;
if (fromUser) {
throw new SecurityException(msg);
}
Slog.w(TAG, msg);
return false;
}
}
3.4 WallpaperManagerService::bindWallpaperComponentLocked
wi不为空, 对应的WallpaperService存在,后续做绑定服务的工作,创建WallpaperConnection对象,WallpaperConnection继承IWallpaperConnection接口, 并且实现了ServiceConnection接口。接下来就是通过mContext.bindServiceAsUser来绑定componentName对应的WallpaperService,绑定服务成功后会回调ServiceConnection接口的onServiceConnected函数,即WallpaperConnection的onServiceConnected函数,onServiceConnected函数放到后面分析,同一个用户,并且上次用户设置的信息不为空(mLastWallpaper != null),取消掉上一次的壁纸设定。
class WallpaperConnection extends IWallpaperConnection.Stub
ServiceConnection {}
final int componentUid = mIPackageManager.getPackageUid(componentName.getPackageName(),
MATCH_DIRECT_BOOT_AUTO, wallpaper.userId);
// step 1, 创建WallpaperConnection对象
WallpaperConnection newConn = new WallpaperConnection(wi, wallpaper, componentUid);
intent.setComponent(componentName);
intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
com.android.internal.R.string.wallpaper_binding_label);
intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivityAsUser(
mContext, 0,
Intent.createChooser(new Intent(Intent.ACTION_SET_WALLPAPER),
mContext.getText(com.android.internal.R.string.chooser_wallpaper)),
0, null, new UserHandle(serviceUserId)));
// step 2, 绑定服务, 服务绑定成功会回调WallpaperConnection的onServiceConnected函数
if (!mContext.bindServiceAsUser(intent, newConn,
Context.BIND_AUTO_CREATE | Context.BIND_SHOWING_UI
| Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
| Context.BIND_INCLUDE_CAPABILITIES,
new UserHandle(serviceUserId))) {
String msg = "Unable to bind service: "
+ componentName;
if (fromUser) {
throw new IllegalArgumentException(msg);
}
Slog.w(TAG, msg);
return false;
}
// step 3, 取消上一次的壁纸服务
if (wallpaper.userId == mCurrentUserId && mLastWallpaper != null
&& !wallpaper.equals(mFallbackWallpaper)) {
detachWallpaperLocked(mLastWallpaper);
}
wallpaper.wallpaperComponent = componentName;
wallpaper.connection = newConn;
newConn.mReply = reply;
if (wallpaper.userId == mCurrentUserId && !wallpaper.equals(mFallbackWallpaper)) {
mLastWallpaper = wallpaper;
}
3.5 detachWallpaperLocked
wallpaper.connection不为空, 说明WallPaperService还在运行,wallpaper.connection.mService为IWallpaperService接口,detach函数Android9.0以前不存在,回调IWallpaperService的detach函数,最终调用WallPaperService的detach函数来回收相关资源,和attach函数相对应,通过mContext.unbindService(wallpaper.connection)取消绑定服务来销毁服务。WallpaperConnection.DisplayConnector::disconnectLocked善后工作
private void detachWallpaperLocked(WallpaperData wallpaper) {
// step 1, wallpaper.connection不为空, 说明WallPaperService还在运行
if (wallpaper.connection != null) {
try {
// step 2, 回调WallpaperService的detach函数
if (wallpaper.connection.mService != null) {
wallpaper.connection.mService.detach();
}
} catch (RemoteException e) {
Slog.w(TAG, "Failed detaching wallpaper service ", e);
}
// step 3, 取消绑定WallpaperService
mContext.unbindService(wallpaper.connection);
// step 4调用WallpaperConnection.DisplayConnector::disconnectLocked函数
// 将壁纸的WindowToken从WindowManagerService中移除,壁纸将无法显示
wallpaper.connection.forEachDisplayConnector(
WallpaperConnection.DisplayConnector::disconnectLocked);
wallpaper.connection.mService = null;
wallpaper.connection.mDisplayConnector.clear();
wallpaper.connection = null;
if (wallpaper == mLastWallpaper) mLastWallpaper = null;
}
}
3.6 WallpaperConnection::DisplayConnector::disconnectLocked
void WallpaperConnection::DisplayConnector::disconnectLocked() {
try {
// step 1 清除壁纸的WindowToken
mIWindowManager.removeWindowToken(mToken, mDisplayId);
} catch (RemoteException e) {
}
try {
// step 2 回调mEngine的destroy函数
if (mEngine != null) {
mEngine.destroy();
}
} catch (RemoteException e) {
}
mEngine = null;
}
3.7 WallpaperConnection::onServiceConnected
public void WallpaperConnection::onServiceConnected(ComponentName name, IBinder service) {
synchronized (mLock) {
if (mWallpaper.connection == this) {
mService = IWallpaperService.Stub.asInterface(service);
// step 1, 调用attachServiceLocked函数
attachServiceLocked(this, mWallpaper);
// step 2, 将用户相关数据保存到XML文件中
if (!mWallpaper.equals(mFallbackWallpaper)) {
saveSettingsLocked(mWallpaper.userId);
}
FgThread.getHandler().removeCallbacks(mResetRunnable);
if (mPerformance != null) {
// step 3, 通知壁纸相关信息改变
mPerformance.notifyWallpaperChanged(name.getPackageName());
}
}
}
}
3.8 WallpaperConnection::attachServiceLocked
private void attachServiceLocked(WallpaperConnection conn, WallpaperData wallpaper) {
conn.forEachDisplayConnector(connector-> connector.connectLocked(conn, wallpaper));
}
3.9 WallpaperConnection::DisplayConnector::connectLocked
connection.mService为IWallpaperService接口,如果为空直接返回, 接下来添加壁纸的token到WindowManagerService中, 这样壁纸窗口才能添加到WindowManagerService中显示出来.IWallpaperService的attach为WallpaperService的attach函数,回调attach函数来对WallpaperService的Engine等相关资源的初始化。
void WallpaperConnection::DisplayConnector::connectLocked(
WallpaperConnection connection, WallpaperData wallpaper) {
// step 1, IWallpaperService为空直接返回
if (connection.mService == null) {
Slog.w(TAG, "WallpaperService is not connected yet");
return;
}
try {
// step 2, 将壁纸相关的WindowToken添加到WMS中
// 得有壁纸Token, WallPaperService才能往WMS中添加壁纸窗口
mIWindowManager.addWindowToken(mToken, TYPE_WALLPAPER, mDisplayId);
} catch (RemoteException e) {
Slog.e(TAG, "Failed add wallpaper window token on display " + mDisplayId, e);
return;
}
final DisplayData wpdData = getDisplayDataOrCreate(mDisplayId);
try {
// step 3, 回调WallPaperService的attach函数
connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false,
wpdData.mWidth, wpdData.mHeight,
wpdData.mPadding, mDisplayId);
} catch (RemoteException e) {
Slog.w(TAG, "Failed attaching wallpaper on display", e);
if (wallpaper != null && !wallpaper.wallpaperUpdating
&& connection.getConnectedEngineSize() == 0) {
bindWallpaperComponentLocked(null /* componentName */, false /* force */,
false /* fromUser */, wallpaper, null /* reply */);
}
}
}
4 WallPaperService代码分析
4.1 WallpaperService::attach
如果服务以及销毁,则返回,接下来是初始化相关.mSession变量的初始化, 用来向WindowManagerService添加壁纸窗口,surfeace相关初始化后,初始化完成, 更新surface相关信息,最后调用updateSurface函数
void attach(IWallpaperEngineWrapper wrapper) {
// step 1 ,如果已经销毁,返回
if (mDestroyed) {
return;
}
mInitializing = true;
// step 2 ,获取Session对象, 和WMS直接通信
mSession = WindowManagerGlobal.getWindowSession();
mWindow.setSession(mSession);
// step 3 , surface相关初始化
onCreate(mSurfaceHolder);
mInitializing = false;
mReportedVisible = false;
updateSurface(false, false, false);
}
WallpaperService::updateSurface
创建事件接收者对象mInputEventReceiver ,调用mSession的addToDisplay将壁纸窗口mWinodw添加到WindowManagerservice中,mSession.relayout将为mWindow分配surface,以及窗口大小告知WallpaperService。mIWallpaperEngine.reportShown()来告知WallpaperManagerService壁纸已经开始显示了。
void updateSurface(boolean forceRelayout, boolean forceReport, boolean redrawNeeded) {
if (mDestroyed) {
Log.w(TAG, "Ignoring updateSurface: destroyed");
}
if (forceRelayout || creating || surfaceCreating || formatChanged || sizeChanged
|| typeChanged || flagsChanged || redrawNeeded
|| !mIWallpaperEngine.mShownReported) {
try {
if (!mCreated) {
mInputChannel = new InputChannel();
// step 1, 将mWindow添加到WindowManagerService中
if (mSession.addToDisplay(mWindow, mWindow.mSeq, mLayout, View.VISIBLE,
mDisplay.getDisplayId(), mWinFrame, mContentInsets, mStableInsets,
mOutsets, mDisplayCutout, mInputChannel,
mInsetsState) < 0) {
Log.w(TAG, "Failed to add window while updating wallpaper surface.");
return;
}
mCreated = true;
// step 2, 创建按键接收者
mInputEventReceiver = new WallpaperInputEventReceiver(
mInputChannel, Looper.myLooper());
}
// step 3, 给壁纸窗口布局和分配surface
final int relayoutResult = mSession.relayout(
mWindow, mWindow.mSeq, mLayout, mWidth, mHeight,
View.VISIBLE, 0, -1, mWinFrame, mOverscanInsets, mContentInsets,
mVisibleInsets, mStableInsets, mOutsets, mBackdropFrame,
mDisplayCutout, mMergedConfiguration, mSurfaceControl,
mInsetsState);
} finally {
mIsCreating = false;
mSurfaceCreated = true;
if (redrawNeeded) {
mSession.finishDrawing(mWindow);
}
// step 4, 通知WallPaperManagerService已经显示了
mIWallpaperEngine.reportShown();
}
} catch (RemoteException ex) {
}
}
}
壁纸相关总结
- 1 壁纸服务相关的配置需要在配置文件中声明
- 2 实现壁纸服务相关的核心是WallpaperService中的Engine类和surface相关操作
- 3 静态壁纸和动态壁纸都继承自WallpaperService
- 4 壁纸窗口WindowToken是通过WindowPaperManagerService来向WMS中来添加和移除操作,和输入法窗口相似
- 5 Android中的壁纸实现采用系统服务和四大组件中的服务两层框架来实现, 只需要我们实现自定义的WallpaperService, 就能够通过系统服务来控制整个壁纸的显示,隐藏,切换等, 隐藏服务之间的接口调用细节,能够很轻松的来开发壁纸服务