本文主要是介绍Android 10.0 动态壁纸 LiveWallpaper,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
前言
在 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
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
public static class Lifecycle extends SystemService {private IWallpaperManagerService mService;public Lifecycle(Context context) {super(context);}@Overridepublic void onStart() {// 省略部分代码......}@Overridepublic void onBootPhase(int phase) {if (mService != null) {mService.onBootPhase(phase);}}@Overridepublic void onUnlockUser(int userHandle) {// 省略部分代码......}}@Overridepublic 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() {@Overridepublic 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
private void setWallpaperComponent(ComponentName name, int userId) {userId = ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId,false /* all */, true /* full */, "changing live wallpaper", null /* pkg */);/* ⾸先调⽤该⽅法的时候回去校验权限,该权限定义在frameworks/base/core/res/AndroidManifest.xml,<permission android:name="android.permission.SET_WALLPAPER_COMPONENT"android:protectionLevel="signature|privileged" />查看protectionLevel,只有是特权应⽤或者系统签名的应⽤才能获取到这个系统权限,所以普通的应⽤是没有办法进⾏壁纸设置的*/// 设置动态壁纸需要调用者拥有一个签名级的系统权限checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);int which = FLAG_SYSTEM;boolean shouldNotifyColors = false;WallpaperData wallpaper;synchronized (mLock) {Slog.v(TAG, "setWallpaperComponent name=" + name);/*此处会先通过当前的⽤户ID获取到与该⽤户相关的壁纸信息,WallpaperManagerService⽀持多⽤户机制,⽤户的信息在mWallpaperMap中存储,每⼀个⽤户对应⼀个WallpaperData,WallpaperData存储壁纸相关信息,并在随后更新其内容使之保存新壁纸的信息。*/wallpaper = mWallpaperMap.get(userId);if (wallpaper == null) {throw new IllegalStateException("Wallpaper not yet initialized for user " + userId);}// 省略部分代码......// New live wallpaper is also a lock wallpaper if nothing is setif (mLockWallpaperMap.get(userId) == null) {which |= FLAG_LOCK;}try {wallpaper.imageWallpaperPending = false;boolean same = changingToSame(name, wallpaper);// 在这⾥真正会去拉起对应的 WallPaperServiceif (bindWallpaperComponentLocked(name, false, true, wallpaper, null)) {// 省略部分代码......}} finally {Binder.restoreCallingIdentity(ident);}}// 省略部分代码......}
上述两种场景都是通过 bindWallpaperComponentLocked() 方法来拉起相关服务的。下面看看 bindWallpaperComponentLocked() 方法:
frameworks/base/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
private boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force,boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) {// 省略部分代码......try {// 当componentName为null时表示使用默认壁纸。// 这里会将componentName参数改为默认壁纸的componentNameif (componentName == null) {// mDefaultWallpaperComponent是从com.android.internal.R.string.default_wallpaper_component// 中获取默认壁纸的componentName。这个值的设置位于res/values/config.xml中,// 开发者可以通过修改这个值设置默认壁纸componentName = mDefaultWallpaperComponent;// 倘若在上述的资源文件中没有指定一个默认壁纸,即default_wallpaper_component的// 值被设置为@null),则使用ImageWallpaper代替默认壁纸。ImageWallpaper就是前文所述的静态壁纸,// 路径在:R.string.image_wallpaper_componentif (componentName == null) {componentName = mImageWallpaper;}}// 接下来WallpaperMangerService会尝试从PackageManager中尝试获取ComponentName所// 指定的Service的描述信息,获取此信息的目的在于确认该Service是一个符合要求的壁纸服务int serviceUserId = wallpaper.userId;ServiceInfo si = mIPackageManager.getServiceInfo(componentName,PackageManager.GET_META_DATA | PackageManager.GET_PERMISSIONS, serviceUserId);if (si == null) {return false;}// 第一个检查,要求这个Service必须声明其访问权限为 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;}WallpaperInfo wi = null;// 第二个检查,要求这个Service必须可以用来处理android.service.wallpaper.WallpaperService这个Action// 其检查方式是从PackageManager中查询所有可以处理 android.service.wallpaper.WallpaperService的服务,// 然后检查即将启动的服务是否在PackageManager的查询结果之中Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);if (componentName != null && !componentName.equals(mImageWallpaper)) {// Make sure the selected service is actually a wallpaper service.// 获取所有可以处理android.service.wallpaper.WallpaperService的服务信息List<ResolveInfo> ris =mIPackageManager.queryIntentServices(intent,intent.resolveTypeIfNeeded(mContext.getContentResolver()),PackageManager.GET_META_DATA, serviceUserId).getList();// 第二个检查,这个检查来校验服务是否声明了android.service.wallpaper.WallpaperService这个action。// 如果这个服务没有声明这个action的话那么,ris中就不会含有这个component信息。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 {// 第三个检查,获取名为android.service.wallpaper中的meta-data信息,// 该meta-data信息中提供了缩略图,开发者,简单的描述等。会将这些信息转换成 WallpaperInfo// 如果即将启动的服务位于查询结果之中,便可以确定这是一个壁纸服务。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) {// wi为null表示即将启动的服务没有位于查询结果之中,或者没有提供必须的meta-data。// 此时返回false表示绑定失败String msg = "Selected service is not a wallpaper: "+ componentName;if (fromUser) {throw new SecurityException(msg);}Slog.w(TAG, msg);return false;}}// 当壁纸服务⽀持在ambient模式下进⾏绘制的时候,需要检查是否有 AMBIENT_WALLPAPER 权限。if (wi != null && wi.supportsAmbientMode()) {final int hasPrivilege = mIPackageManager.checkPermission(android.Manifest.permission.AMBIENT_WALLPAPER, wi.getPackageName(),serviceUserId);// 省略部分代码......}// Bind the service!if (DEBUG) Slog.v(TAG, "Binding to:" + componentName);final int componentUid = mIPackageManager.getPackageUid(componentName.getPackageName(),MATCH_DIRECT_BOOT_AUTO, wallpaper.userId);// 1、这里创建一个WallpaperConnection,它不仅实现 ServiceConnetion 接口用于监听 WallpaperService 之间的连接状态,// 同时还实现了 IWallpaperService.Stub,也就说它支持跨进程通信。// 在服务绑定成功后的 WallpaperConnection.ServiceConnetion()方法调用中,WallpaperConnection 的实例回发送给 WallpaperService,// 使其作为 WallpaperService 向 WallpaperManagerService 通信的桥梁。WallpaperConnection newConn = new WallpaperConnection(wi, wallpaper, componentUid);// 为启动壁纸服务准备Intentintent.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)));// 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;}// 3、新的的壁纸服务启动成功后,便通过detachWallpaperLocked()销毁旧有的壁纸服务if (wallpaper.userId == mCurrentUserId && mLastWallpaper != null&& !wallpaper.equals(mFallbackWallpaper)) {detachWallpaperLocked(mLastWallpaper);}// 4、将新的壁纸服务的运行信息保存到WallpaperData中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
@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {synchronized (mLock) {if (mWallpaper.connection == this) {// 客户端 拿 WallpaperService 的 BindermService = IWallpaperService.Stub.asInterface(service);// 绑定壁纸服务。attachServiceLocked()会调用connectLocked()方法.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
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);// 向 WMS 申请注册一个WALLPAPER类型的窗口令牌,// 且之后被传递给 WallpaperService 用于作为后者添加窗口的通行证。mWindowManagerInternal.addWindowToken(mToken, TYPE_WALLPAPER, mDisplayId);final DisplayData wpdData = getDisplayDataOrCreate(mDisplayId);try {// 调用 IWallpaperService.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 */);}}}
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
class IWallpaperServiceWrapper extends IWallpaperService.Stub {private final WallpaperService mTarget;private IWallpaperEngineWrapper mEngineWrapper;public IWallpaperServiceWrapper(WallpaperService context) {mTarget = context;}@Overridepublic 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);}@Overridepublic void detach() {mEngineWrapper.detach();}}
在 attach() 方法中创建了一个 IWallpaperEngineWrapper 对象 mEngineWrapper 。
IWallpaperEngineWrapper
frameworks/base/core/java/android/service/wallpaper/WallpaperService.java
class IWallpaperEngineWrapper extends IWallpaperEngine.Stubimplements 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);}// 省略部分代码......@Overridepublic void executeMessage(Message message) {if (mDetached.get()) {if (mActiveEngines.contains(mEngine)) {doDetachEngine();}return;}switch (message.what) {case DO_ATTACH: {try {// 将IWallpaperEngineWapper对象传递给WallpaperConnection进行保存,// 通过这个引用,WallpaperManagerService也可以通过它与engine进行通信mConnection.attachEngine(this, mDisplayId);} catch (RemoteException e) {Log.w(TAG, "Wallpaper host disappeared", e);return;}// 创建一个引擎,该方法为抽象方法,需要子类根据自身实现具体的引擎Engine engine = onCreateEngine();mEngine = engine;mActiveEngines.add(engine);// 该方法中会完成窗口的创建、surface创建等工作。engine.attach(this);return;}// 省略部分代码......}}}
由于 mConnection.attachEngine() 方法将 IWallpaperEngineWrapper 传递给了WallpaperManagerService,因此 WallpaperManagerService 可以转发相关的请求和设置到 Engine 对象中,实现 WallpaperManagerService 到壁纸的通信。onCreateEngine() 方法执行后,引擎创建完成,之后通过 engine.attach()方法进行引擎相关的初始化。
frameworks/base/core/java/android/service/wallpaper/WallpaperService.java
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;// 这个session用于和WMS进行通信mSession = WindowManagerGlobal.getWindowSession();// mWindow是一个IWindow对象,用于接收从WMS发送过来的消息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);// 子类可以重写该接口,在该接口中可以修改mSurfaceHolder相关的属性,这个时候// 窗口尚未创建。设置的相关属性将在updateSurface中创建窗口时使用onCreate(mSurfaceHolder);mInitializing = false;mReportedVisible = false;// updateSurface会进行窗口以及Surface的创建。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对引擎相关的控制需要通过该对象提供的接口实现。
这篇关于Android 10.0 动态壁纸 LiveWallpaper的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!