详解Android壁纸服务的启动过程
壁纸基础
android中的壁纸分为动态壁纸和静态壁纸两种,两种类型的壁纸都以Service的类型运行在系统后台。
- 静态壁纸:仅以图片的形式进行展示对于静态壁纸,可以使用WallpaperManager中的getDrawable()等接口获取到当前的bitmap图像。
- 动态壁纸:显示的内容为动态的内容,同时可以对用户的操作做出响应对于动态壁纸的实时图像,是没办法通过android中原生的接口获取到,需要获取到动态壁纸的图像得自己修改源码。
壁纸实现时涉及的几个主要的类:
- WallpaperService及其内部类Engine:壁纸在WallpaperService这个服务中运行,当需要实现自己的壁纸时,继承和实现这个类,是首先需要做的。Engine是WallpaperService中的一个内部类,实现了壁纸服务窗口的创建以及Surface的维护,同时Engine内部类还提供了onVisibilityChanged(),onCommand()等回调方法,用于可见状态变化和用户触摸事件等。Engine类因此也是壁纸实现的核心类,实现和重写其接口在开发中也相当重要。
- WallpaperManagerService和WallpaperManager:WallpaperManagerService用于管理壁纸的运行与切换,并通过WallpaperManager对外界提供操作壁纸的接口。
- WindowMangerService:该类用于计算壁纸窗口的Z序,可见性以及为壁纸窗口应用动画。
壁纸服务的两种启动场景
非首次重启壁纸服务启动流程
SystemService进程启动时,会启动各种系统服务。在该类的startOtherServices()方法中会首先拉起
WallpaperManagerService,通过该类,WallpaperService后面才得以启动。
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()方法中会通过loadSettingsLocked()方法加载用户设置过的壁纸信息,然后监听用户切换用户switchUser(),切换用户时,switchWallpaper()会调用bindWallpaperComponentLocked()方法拉起对应的壁纸服务。
手动切换时壁纸服务的启动流程
手动切换壁纸服务时需要通过WallpaperManager.getIWallpaperManager().setWallpaperComponent()方法完成,我们在这个接口中传入壁纸服务对应的ComponentName,getIWallpaperManager返回的是WallpaperManagerService的Bp(binder proxy binder代理)端,在WallpaperManagerService端,我们可以查看到setWallpaperComponent的具体实现,
private void setWallpaperComponent(ComponentName name, int userId) { ... /* 首先调用该方法的时候回去校验权限,该权限定义在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); } ... // 在这里真正会去拉起对应的WallPaperService if (bindWallpaperComponentLocked(name, false, true, wallpaper, null)) { ... }
setWallpaperComponent最终也是通过bindWallpaperComponentLocked拉起壁纸服务
壁纸服务启动过程
1.校验是否是壁纸服务
bindWallpaperComponentLocked()方法将会启动该ComponentName所指定的WallpaperService,在启动的时候首先会进行校验,以确定待拉起的服务是一个壁纸服务,
private boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force, boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) { ... int serviceUserId = wallpaper.userId; ServiceInfo si = mIPackageManager.getServiceInfo(componentName, PackageManager.GET_META_DATA | PackageManager.GET_PERMISSIONS, serviceUserId); if (si == null) { // The wallpaper component we're trying to use doesn't exist Slog.w(TAG, "Attempted wallpaper " + componentName + " is unavailable"); return false; } /* 第一个校验: 启动的时候首先会校验这个壁纸服务是否声明权限为BIND_WALLPAPER权限, 该权限的定义同样也在fwk/base/core/res/manifest.xml <permission android:name="android.permission.BIND_WALLPAPER" android:protectionLevel="signature|privileged" /> 该权限也是系统级别的,防止三方应用切换壁纸, */ 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)) { // Make sure the selected service is actually a wallpaper service. /* 第二个校验: 这个检查来校验服务是否声明了android.service.wallpaper.WallpaperService这个action。如果这个服务没有声明这个action的话那么,ris中就不会含有这个component信息, */ 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 { /* 第三个检查: 获取名为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) { 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); if (hasPrivilege != PackageManager.PERMISSION_GRANTED) { String msg = "Selected service does not have " + android.Manifest.permission.AMBIENT_WALLPAPER + ": " + componentName; if (fromUser) { throw new SecurityException(msg); } Slog.w(TAG, msg); return false; } } // 检验完毕,这里才会开始bind 壁纸服务,如果校验失败的话,会返回false ... }
上面的校验可以看出一共校验了三个条件:
- 启动的时候首先会校验这个壁纸服务是否声明权限为BIND_WALLPAPER权限, 该权限的定义在fwk/base/core/res/manifest.xml中,< permission android:name=“android.permission.BIND_WALLPAPER”
android:protectionLevel=“signature|privileged” />
该权限也是系统级别的,防止三方应用切换壁纸。 - 这个检查来校验服务是否声明了android.service.wallpaper.WallpaperService这个action。如果这个服务没有声明这个action的话那么,ris中就不会含有这个component信息。
- 获取名为android.service.wallpaper中的meta-data信息,该meta-data信息中提供了缩略图,开发者,简单的描述等。会将这些信息转换成WallpaperInfo。
2.绑定壁纸服务
壁纸服务的校验满足后,开始启动和绑定目标服务:
private boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force, boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) { // 校验服务是否符合要求结束后,开始着手启动服务 ... //1. 创建一个WallpaperConnection,该对象可以监听和WallpaperService之间的连接状态,同时归对象继承了IWallpaperConnection.Stub,这样该对象有拥有了跨进程通信的能力,当服务绑定成功后,onServiceConnected()方法调用中,WallpaperConnection实力会被发送到WallpaperService,该实例可以用于WallpaperService想WallpaperManagerService进行通信的桥梁。 intent.setComponent(componentName); intent.putExtra(Intent.EXTRA_CLIENT_LABEL, com.android.internal.R.string.wallpaper_binding_label); ... /* 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))) { } /*3. 新的壁纸服务启动之后,就开始销毁旧服务*/ if (wallpaper.userId == mCurrentUserId && mLastWallpaper != null && !wallpaper.equals(mFallbackWallpaper)) { detachWallpaperLocked(mLastWallpaper); } /* 4.将新的壁纸服务信息进行保存*/ wallpaper.wallpaperComponent = componentName; wallpaper.connection = newConn; ...
bindWallpaperComponentLocked函数在拉起壁纸服务的时候主要做了下面几件事情:
- 创建了WallpaperConnection对象,由于实现了ServiceConnection接口,所以WallpaperConnection可以用来监听和壁纸服务的连接状态,另外由于继承了IWallpoaperConnection.Stub接口,所以WallpaperConnection具有了跨进程通信的能力。
- 启动壁纸服务:这里仅仅是拉起服务,和拉起普通服务的方式基本一致,拉起方式上则使用了bindServiceAsUser,查看官方注解,该接口增加了校验该用户是否能拉起该服务,其余的行为和bindService相同。
- 保存当前WallpaperConnection实例,ConponentName,到WallpaperData中
bindWallpaperComponentLocked()函数将壁纸服务拉了起来,但是仅仅将壁纸服务拉起来是没有办法显示图像的,因为启动的服务并没有窗口令牌,这样就没办法添加窗口。剩下的这部分显示的工作在WallpaperConnection的onServiceConnected()方法中进行,在该回调中同样也能拿到壁纸服务端服务端提供的Binder对象。
WallpaperService在被bind的时候返回了一个IWallpaperServiceWrapper对象,从代码中可以看到,该对象中保存了WallpaperService实例,看了代码后再去理解这个对象的命名(包装WallpaperService),果然名副其实。
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) { ... } @Override public void detach() { ... } }
该接口中一共有两个接口,attach和detach,attach接口在创建的时候可以将相关信息传递到壁纸服务中,对应的,detach接口在服务销毁的时候调用。
3.引擎的创建和初始化
引擎的创建准备工作开始于onServiceConnected()回调处,该回调会传递壁纸服务需要的窗口令牌和ServiceConnection对象等。
WallpaperManagerService.java
@Override public void onServiceConnected(ComponentName name, IBinder service) { synchronized (mLock) { if (mWallpaper.connection == this) { mService = IWallpaperService.Stub.asInterface(service); attachServiceLocked(this, mWallpaper); // XXX should probably do saveSettingsLocked() later // when we have an engine, but I'm not sure about // locking there and anyway we always need to be able to // recover if there is something wrong. if (!mWallpaper.equals(mFallbackWallpaper)) { // 保存当前的壁纸信息到文件系统中,这样重启的时候就可以加载之前用户设置过的壁纸 saveSettingsLocked(mWallpaper.userId); } FgThread.getHandler().removeCallbacks(mResetRunnable); mContext.getMainThreadHandler().removeCallbacks(mTryToRebindRunnable); } } }
onServiceConnected()函数中,首先将返回的binder对象进行了保存,然后在attachServiceLocked()方法中会调用connectLocked()方法,connectLocked()接口中调用了attach方法传递了壁纸服务所需要的信息。
void connectLocked(WallpaperConnection connection, WallpaperData wallpaper) { ... 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); } ... }
attach接口回传了许多信息,其中
- connection为WallpaperConnection的实例。WallpaperConnection之所以具有跨进程通信的能力是因为继承了IWallpaperConnection.Stub类,该Stub对象中比较重要的一个接口就是attachEngine(),因为Engine实现才是动态壁纸的核心,WallpaperService会将创建好的Engine引用通过attachEngine()回传给WallpaperManagerService进行管理。
- mToken是向WMS注册过的窗口令牌,只有拥有了这个令牌,WallpaperService才有权添加壁纸窗口。
传递了WallpaperService需要的信息之后,WallPaperService开始进行引擎的创建。查看WallpaperService中attach()方法的实现,
class IWallpaperServiceWrapper extends IWallpaperService.Stub { ... @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); } ... }
attach方法创建了一个IWallpaperEngineWrapper,顾名思义,该对象有壁纸服务创建的引擎的引用,在创建IWallpaperEngineWrapper对象的时候,会发送DO_ATTACH消息,该消息用于壁纸服务引擎的创建,
IWallpaperEngineWrapper.java 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) { 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()方法进行引擎相关的初始化:
void attach(IWallpaperEngineWrapper wrapper) { ... 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显示图像。
壁纸服务的启动流程总结
壁纸服务的启动相比于普通服务的启动较为复杂,接下来用下面的示意图对整体的流程进行梳理:
壁纸服务在启动的时候,大体可以分为两个阶段,首先就要是拉起对应的服务,拉起服务后然后将WindowToken等参数传递给引擎进行窗口的创建,surface的创建。在WallpaperManagerService和WallpaperService交互的过程中,主要有下面三个跨进程通信的Binder对象:
- WallpaperConnection:实现在WallpaperManagerService中,并通过IWallpaperService.attach回调传递给了IWallpaperEngineWrapper,通过WallpaperConnection.attachEngine()方法,WallpaperService将IWallpaperEngineWrapper回传给了WallpaperManagerService,实现了双向的通信。
- IWallpaperService:实现在WallpaperService中,该对象提供了attach方法,用于从WallpaperManagerService获取引擎创建时需要的WindowToken等信息。
- IWallpaperEngineWrapper:实现在壁纸服务进程中,同时引用交给了WallpaperManagerService,该对象封装了Engine类,WallpaperManagerService对引擎相关的控制需要通过该对象提供的接口实现。
自己最近因为需要定位一个开机壁纸服务启动慢的问题,所以熟悉了下壁纸服务的启动过程,在此记录启动流程。定位该问题梳理从bind到onServiceConnected和引擎相关初始化,对比每一个阶段的时间,最终确定问题的原因。
参考:
- 《深入理解Android卷3》第八章:深入理解android壁纸服务
- framework-base源码
到此这篇关于详解Android壁纸服务的启动过程的文章就介绍到这了,更多相关Android壁纸内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!