前言
在 Android 中,壁纸分为动态与静态两种,但其实两者得本质都是一样。都以一个 Service 得形式在后台运行,在一个类型为 TYPE_WALLPAPER 的窗口上绘制内容。也可以这么去理解:静态壁纸是一种特殊的动态壁纸,它仅在窗口上渲染了一张图片,而不会对用户的操作做出反应。动态壁纸不能只应用于锁屏。
壁纸实现时涉及的几个主要的类:
- WallpaperService 及其内部类 Engine: 壁纸在 WallpaperService 这个服务中运⾏,当需要实现⾃⼰的壁纸时,继承和实现这个类,是⾸先需要做的。Engine是WallpaperService中的⼀个内部类,实现了壁纸服务窗⼝的创建以及 Surface 的维护,同时 Engine 内部类还提供了 onVisibilityChanged(),onCommand() 等回调⽅法,⽤于可见状态变化和⽤户触摸事件等。Engine 类因此也是壁纸实现的核⼼类,实现和重写其接⼝在开发中也相当重要。
- WallpaperManagerService 和 WallpaperManager: WallpaperManagerService ⽤于管理壁纸的运⾏与切换,并通过 WallpaperManager 对外界提供操作壁纸的接⼝。
- WindowMangerService: 该类⽤于计算壁纸窗⼝的 Z 序,可见性以及为壁纸窗⼝应⽤动画。
壁纸服务的两种启动场景:
1、重启壁纸服务启动流程:
SystemService 进程启动时,会启动各种系统服务。在该类的 startOtherServices() ⽅法中会⾸先拉起 WallpaperManagerService,通过该类,WallpaperService 后⾯才得以启动。
frameworks/base/services/core/java/com/android/server/SystemService.java
1 2 3 4 5 6 7
| if (context.getResources().getBoolean(R.bool.config_enableWallpaperService)) { t.traceBegin("StartWallpaperManagerService"); mSystemServiceManager.startService(WALLPAPER_SERVICE_CLASS); t.traceEnd(); } else { Slog.i(TAG, "Wallpaper service disabled by config"); }
|
WallpaperManagerService 启动之后回调用到 systemReady() ⽅法。
frameworks/base/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| public static class Lifecycle extends SystemService { private IWallpaperManagerService mService; public Lifecycle(Context context) { super(context); } @Override public void onStart() { } @Override public void onBootPhase(int phase) { if (mService != null) { mService.onBootPhase(phase); } } @Override public void onUnlockUser(int userHandle) { } } @Override public void onBootPhase(int phase) { if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) { systemReady(); } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) { switchUser(UserHandle.USER_SYSTEM, null); } } void systemReady() { if (DEBUG) Slog.v(TAG, "systemReady"); initialize(); try { ActivityManager.getService().registerUserSwitchObserver( new UserSwitchObserver() { @Override public void onUserSwitching(int newUserId, IRemoteCallback reply) { switchUser(newUserId, reply); } }, TAG); } catch (RemoteException e) { e.rethrowAsRuntimeException(); } }
|
会通过 initialize() 方法调用 loadSettingsLocked() ⽅法加载⽤户设置过的壁纸信息,然后监听⽤户是否切换⽤户 switchUser(),当切换⽤户时,switchWallpaper() 会调⽤ bindWallpaperComponentLocked() ⽅法拉起对应的壁纸服务。
2、⼿动切换时壁纸服务的启动流程
⼿动切换壁纸(这里说的是动态壁纸)时需要通过WallpaperManager.getInstance(activity).setWallpaperComponent() ⽅法完成,我们在这个接⼝中传⼊壁纸服务对应的 ComponentName,getIWallpaperManager 返回的是 WallpaperManagerService 的 Bp(binder proxy binder代理) 端,在 WallpaperManagerService 端,我们可以查看到 setWallpaperComponent 的具体实现:
注意:WallpaperManager.getIWallpaperManager() 并没有作为 SDK 的一部分提供给开发者。因此第三方应用无法进行动态壁纸的设置。
frameworks/base/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| private void setWallpaperComponent(ComponentName name, int userId) { userId = ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId, false , true , "changing live wallpaper", null );
checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT); int which = FLAG_SYSTEM; boolean shouldNotifyColors = false; WallpaperData wallpaper; synchronized (mLock) { Slog.v(TAG, "setWallpaperComponent name=" + name);
wallpaper = mWallpaperMap.get(userId); if (wallpaper == null) { throw new IllegalStateException("Wallpaper not yet initialized for user " + userId); } if (mLockWallpaperMap.get(userId) == null) { which |= FLAG_LOCK; } try { wallpaper.imageWallpaperPending = false; boolean same = changingToSame(name, wallpaper); if (bindWallpaperComponentLocked(name, false, true, wallpaper, null)) { } } finally { Binder.restoreCallingIdentity(ident); } } }
|
上述两种场景都是通过 bindWallpaperComponentLocked() 方法来拉起相关服务的。下面看看 bindWallpaperComponentLocked() 方法:
frameworks/base/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
| private boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force, boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) { try { if (componentName == null) { componentName = mDefaultWallpaperComponent; if (componentName == null) { componentName = mImageWallpaper; } } int serviceUserId = wallpaper.userId; ServiceInfo si = mIPackageManager.getServiceInfo(componentName, PackageManager.GET_META_DATA | PackageManager.GET_PERMISSIONS, serviceUserId); if (si == null) { return false; } 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; } WallpaperInfo wi = null; Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE); if (componentName != null && !componentName.equals(mImageWallpaper)) { 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; 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; } } if (wi == null) { String msg = "Selected service is not a wallpaper: " + componentName; if (fromUser) { throw new SecurityException(msg); } Slog.w(TAG, msg); return false; } } if (wi != null && wi.supportsAmbientMode()) { final int hasPrivilege = mIPackageManager.checkPermission( android.Manifest.permission.AMBIENT_WALLPAPER, wi.getPackageName(), serviceUserId); } if (DEBUG) Slog.v(TAG, "Binding to:" + componentName); final int componentUid = mIPackageManager.getPackageUid(componentName.getPackageName(), MATCH_DIRECT_BOOT_AUTO, wallpaper.userId); 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)), PendingIntent.FLAG_IMMUTABLE, null, new UserHandle(serviceUserId))); 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; } 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; } updateFallbackConnection(); } catch (RemoteException e) { String msg = "Remote exception for " + componentName + "\n" + e; if (fromUser) { throw new IllegalArgumentException(msg); } Slog.w(TAG, msg); return false; } return true; }
|
上述代码主要做了两件事:
1、检查拉起服务的条件
- 检查这个 Service 必须声明其访问权限为 BIND_WALLPAPER。为了防止壁纸服务被第三方应用程序启动而产生混乱。
- 检查服务是否声明了 android.service.wallpaper.WallpaperService 这个 action。如果这个服务没有声明这个 action 的话那么, ris 中就不会含有这个 component 信息。
- 检查这个 Service 在 meta-data 中有没有提供壁纸描述信息,该 meta-data 信息中提供了缩略图,开发者,简单的描述等。会将这些信息转换成 WallpaperInfo。
2、拉起壁纸服务
- 创建了 WallpaperConnection 对象,由于实现了 ServiceConnection 接⼝,所以WallpaperConnection 可以⽤来监听和壁纸服务的连接状态,另外由于继承了IWallpoaperConnection.Stub 接⼝,所以 WallpaperConnection 具有了跨进程通信的能⼒。
- 启动壁纸服务:这⾥仅仅是拉起服务,和拉起普通服务的⽅式基本⼀致,拉起⽅式上则使⽤了 bindServiceAsUser,查看官⽅注解,该接口增加了校验该⽤户是否能拉起该服务,其余的⾏为和 bindService 相同。
- 保存当前 WallpaperConnection 实例,ConponentName,到 WallpaperData 中。
bindWallpaperComponentLocked() 函数将壁纸服务拉了起来,但是仅仅将壁纸服务拉起来是没有办法显⽰图像的,因为启动的服务并没有窗口令牌,这样就没办法添加窗⼝。剩下的这部分显⽰的⼯作在 WallpaperConnection#onServiceConnected() ⽅法中进⾏,在该回调中同样也能拿到壁纸服务端提供的 Binder 对象。
WallpaperService 在被 bind(绑定 )的时候返回了一个 IWallpaperServiceWrapper 对象,从代码中可以看到,该对象中保存了 WallpaperService 实例:
WallpaperConnection#onServiceConnected()
frameworks/base/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @Override public void onServiceConnected(ComponentName name, IBinder service) { synchronized (mLock) { if (mWallpaper.connection == this) { mService = IWallpaperService.Stub.asInterface(service); attachServiceLocked(this, mWallpaper); if (!mWallpaper.equals(mFallbackWallpaper)) { saveSettingsLocked(mWallpaper.userId); } FgThread.getHandler().removeCallbacks(mResetRunnable); mContext.getMainThreadHandler().removeCallbacks(mTryToRebindRunnable); if (mPerformance != null) { mPerformance.notifyWallpaperChanged(name.getPackageName()); } } } }
|
在 attachServiceLocked() 方法中会调用 connectLocked() 方法,connectLocked() 接口中调用了 IWallpaperServiceWrapper#attach() 方法传递了壁纸服务所需要的信息。
WallpaperConnection#connectLocked()
frameworks/base/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| void connectLocked(WallpaperConnection connection, WallpaperData wallpaper) { if (connection.mService == null) { Slog.w(TAG, "WallpaperService is not connected yet"); return; } if (DEBUG) Slog.v(TAG, "Adding window token: " + mToken); mWindowManagerInternal.addWindowToken(mToken, TYPE_WALLPAPER, mDisplayId); final DisplayData wpdData = getDisplayDataOrCreate(mDisplayId); try { 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 , false , false , wallpaper, null ); } } }
|
attach() 方法回传了很多信息,其中 connection 为 WallpaperConnection 的实例。WallpaperConnection 之所以具有跨进程通信的能力是因为继承了IWallpaperConnection.Stub 类。
该 Stub 对象中有一个重要的方法 attachEngine() 方法,因为 Engine 实现才是动态壁纸的核心,WallpaperService 会将创建好的 Engine 引用通过 attachEngine() 回传给 WallpaperManagerService 进行管理。
IWallpaperServiceWrapper 继承了 IWallpaperService.Stub,并实现了该接口的两个方法 attach() 和 detach()。
IWallpaperServiceWrapper
frameworks/base/core/java/android/service/wallpaper/WallpaperService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class IWallpaperServiceWrapper extends IWallpaperService.Stub { private final WallpaperService mTarget; private IWallpaperEngineWrapper mEngineWrapper; public IWallpaperServiceWrapper(WallpaperService context) { mTarget = context; } @Override public void attach(IWallpaperConnection conn, IBinder windowToken, int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding,int displayId) { mEngineWrapper = new IWallpaperEngineWrapper(mTarget, conn, windowToken, windowType, isPreview, reqWidth, reqHeight, padding, displayId); } @Override public void detach() { mEngineWrapper.detach(); } }
|
在 attach() 方法中创建了一个 IWallpaperEngineWrapper 对象 mEngineWrapper 。
IWallpaperEngineWrapper
frameworks/base/core/java/android/service/wallpaper/WallpaperService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| class IWallpaperEngineWrapper extends IWallpaperEngine.Stub implements HandlerCaller.Callback { private final HandlerCaller mCaller; final IWallpaperConnection mConnection; final IBinder mWindowToken; final int mWindowType; final boolean mIsPreview; boolean mShownReported; int mReqWidth; int mReqHeight; final Rect mDisplayPadding = new Rect(); final int mDisplayId; final DisplayManager mDisplayManager; final Display mDisplay; private final AtomicBoolean mDetached = new AtomicBoolean(); Engine mEngine; IWallpaperEngineWrapper(WallpaperService context, IWallpaperConnection conn, IBinder windowToken, int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding, int displayId) { mCaller = new HandlerCaller(context, context.getMainLooper(), this, true); Message msg = mCaller.obtainMessage(DO_ATTACH); mCaller.sendMessage(msg); } @Override public void executeMessage(Message message) { if (mDetached.get()) { if (mActiveEngines.contains(mEngine)) { doDetachEngine(); } return; } switch (message.what) { case DO_ATTACH: { try { mConnection.attachEngine(this, mDisplayId); } catch (RemoteException e) { Log.w(TAG, "Wallpaper host disappeared", e); return; } Engine engine = onCreateEngine(); mEngine = engine; mActiveEngines.add(engine); engine.attach(this); return; } } } }
|
由于 mConnection.attachEngine() 方法将 IWallpaperEngineWrapper 传递给了WallpaperManagerService,因此 WallpaperManagerService 可以转发相关的请求和设置到 Engine 对象中,实现 WallpaperManagerService 到壁纸的通信。onCreateEngine() 方法执行后,引擎创建完成,之后通过 engine.attach()方法进行引擎相关的初始化。
frameworks/base/core/java/android/service/wallpaper/WallpaperService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| public class Engine { void attach(IWallpaperEngineWrapper wrapper) { if (DEBUG) Log.v(TAG, "attach: " + this + " wrapper=" + wrapper); if (mDestroyed) { return; } mIWallpaperEngine = wrapper; mCaller = wrapper.mCaller; mConnection = wrapper.mConnection; mWindowToken = wrapper.mWindowToken; mSurfaceHolder.setSizeFromLayout(); mInitializing = true; mSession = WindowManagerGlobal.getWindowSession(); mWindow.setSession(mSession); mLayout.packageName = getPackageName(); mIWallpaperEngine.mDisplayManager.registerDisplayListener(mDisplayListener, mCaller.getHandler()); mDisplay = mIWallpaperEngine.mDisplay; mDisplayContext = createDisplayContext(mDisplay); mDisplayState = mDisplay.getState(); if (DEBUG) Log.v(TAG, "onCreate(): " + this); onCreate(mSurfaceHolder); mInitializing = false; mReportedVisible = false; updateSurface(false, false, false); } }
|
attach() 方法执行的完成,标志着壁纸启动的完成,之后可以调用壁纸的 surface 显示图像。

在WallpaperManagerService和WallpaperService交互的过程中,主要有下面三个跨进程通信的Binder对象:
- WallpaperConnection:实现在WallpaperManagerService中,并通过IWallpaperService.attach回调传递给了IWallpaperEngineWrapper,通过WallpaperConnection.attachEngine()方法,WallpaperService将IWallpaperEngineWrapper回传给了WallpaperManagerService,实现了双向的通信。
- IWallpaperService:实现在WallpaperService中,该对象提供了attach方法,用于从WallpaperManagerService获取引擎创建时需要的WindowToken等信息。
- IWallpaperEngineWrapper:实现在壁纸服务进程中,同时引用交给了WallpaperManagerService,该对象封装了Engine类,WallpaperManagerService对引擎相关的控制需要通过该对象提供的接口实现。
本文链接:
http://longzhiye.top/2023/12/31/2023-12-31/