上面这两个技术的出发点:因为Android中同一时刻只能运行单个WallpaperService,一个WallpaperService只有一个Surface,因此当你需要在一个WallpaperService中使用多个Surface的时候,可以使用上面的方式创建Surface来进行显示。
创建surface
当需要自己创建一个surface的时候,首先应用必须是系统应用,因为底层的一些接口和权限只有系统应用才能获取到。 创建Surface的时候我们需要下面的权限:
<uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER"/>
查看系统中对该权限的定义:
<permission android:name="android.permission.ACCESS_SURFACE_FLINGER"
android:protectionLevel="signature" />
该权限只有系统签名的应用才能使用,当app获得了该权限,会在system/etc/permissions/下面根据app的预置目录生成响应的xml,该xml中包含了该路径下app的系统权限。 拥有了权限之后,就可以开始进行Surface的创建了,创建的源码参考View中Surface的创建,也可以参考SurfaceView中Surface的创建:
final SurfaceSession session = new SurfaceSession();
final SurfaceControl surfaceControl = new SurfaceControl.Builder(session)
.setName("drag surface")
.setParent(root.getSurfaceControl())
.setBufferSize(width, height)
.setFormat(PixelFormat.TRANSLUCENT)
.build();
final Surface surface = new Surface();
surface.copyFrom(surfaceControl);
如果没有parent的话,可以省略这个。然后,使用下面的方式让Surface显示出来:
SurfaceControl.openTransaction();
try{
surfaceControl.show();
}finally{
SurfaceControl.closeTransaction();
}
上面的接口有很多都是Hide接口,只有系统应用才能直接调用,创建了Surface之后,就可以使用创建的Surface显示图像了,具体可以参考View和SurfaceView中的源码创建Surface中。
创建Wallpaper类型的window
在壁纸服务中是没有办法使用View的,如何哪位同学知道咋使用View希望能帮忙解答下。 wallpaper类型的window可以使用窗口动效,因而我们可以简单的实现缩放,透明度变化等动画效果,上面的创建Surface的方式可以创建一个单独的图像缓冲区,但是创建的Surface没有依赖于window,所以没办法使用窗口动效,那么如何让新建的Surface拥有窗口动效的能力呢,可以参考WallPaperService创建window的过程。主要涉及WallpaperService和WallaperManagerService两个类。 Android原生壁纸启动的时候,WallpaperManagerService会向WallpaperService传递一个比较重要的对象,mToken,这个其实是一个Binder对象,wms用这个来标识可以来管理窗口。
mWindowManagerInternal.addWindowToken(mToken, TYPE_WALLPAPER, mDisplayId);
同时WallpaperManagerService还给WallpaperService传递了displayid,宽高等参数。 在WallpaperService中,首先会获取windowSession,并将session和window相关联:
mSession = WindowManagerGlobal.getWindowSession();
mWindow.setSession(mSession);
关联了后,会调用mSession.addToDisplay添加window,添加了Window之后,调用mSession.relayout,具体这里的作用是在做什么其实不大清楚,但是看代码传递进去了一个SurfaceControl,之后从SurfaceControl中获得了创建好的Surface,至此我们就可以使用创建好的Surface了。window相关参数的在WallpaperService和WallaperManagerService中加日志,然后在自己的window中设置相应的参数。
final int relayoutResult = mSession.relayout(mWindow, mWindow.mSeq, mLayout, mWidth, mHeight,View.VISIBLE, 0, -1, mWinFrame, mContentInsets,mVisibleInsets, mStableInsets, mBackdropFrame,mDisplayCutout, mMergedConfiguration, mSurfaceControl,mInsetsState, mTempControls, mSurfaceSize, mTmpSurfaceControl);
if (mSurfaceControl.isValid()) { mSurfaceHolder.mSurface.copyFrom(mSurfaceControl);
}
如果是我们自己创建window的话,还需要自己调用下面的方法让图像显示出来:
SurfaceControl.openTransaction();
try{
surfaceControl.show();
}finally{
SurfaceControl.closeTransaction();
}
当绘制的图像显示出来的时候,应该调用下面的方法向wms汇报绘制完成,这样窗口状态才能正确,不然会奇怪的问题,比如闪一帧黑屏。
mSession.finishDrawing(mWindow, null )
最后,当服务销毁的时候,应该将window和创建的wallpaperWindowtoken移除。
mSession.remove(mWindow);
mWindowManagerInternal.removeWindowToken(mToken, false,mDisplayId)
其它:addWindowToken首先需要权限: WindowManagerService在添加窗口的时候首先会进行权限校验:
private int addWindowTokenWithOptions(IBinder binder, int type, int displayId, Bundle options,
String packageName, boolean fromClientToken) {
final boolean callerCanManageAppTokens =
checkCallingPermission(MANAGE_APP_TOKENS, "addWindowToken()");
需要下面的系统权限,只有是系统应用才能拿到
android.permission.MANAGE_APP_TOKENS
在完成自建window的时候,同时自己也学习了一些基本的调试和定位命令: dump surfacelinger信息:
- adb shell dumpsys SurfaceFlinger
dump window查看窗口层级信息(连续抓取多帧的时候可以for循环抓取,但是dump需要时间,不可能每一帧都抓到):
同时,我们可以使用: adb shell dumpsys –help 查看基本命令帮助 使用 adb shell dumpsys -l 查看支持dump的模块
参考:SurfaceView源码,WallpaperService和WallpaperServiceManager源码。
|