Android中创建Surface和TYPE_WALLPAPER类型的window

本文详细介绍了在Android系统中如何创建Surface和Wallpaper类型的Window。创建Surface需要系统权限ACCESS_SURFACE_FLINGER,通过SurfaceSession和SurfaceControl构建。Wallpaper类型的Window涉及到WallpaperService和WallpaperManagerService,利用窗口动效实现动画。创建自定义Window时,需关注窗口层次和时机,确保正确显示。此外,编译涉及隐藏接口的代码,可通过添加framework.jar到编译依赖解决。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上面这两个技术的出发点:因为Android中同一时刻只能运行单个WallpaperService,一个WallpaperService只有一个Surface,因此当你需要在一个WallpaperService中使用多个Surface的时候,可以使用上面的方式创建Surface来进行显示。

创建surface

当需要自己创建一个surface的时候,首先应用必须是系统应用,因为底层的一些接口和权限只有系统应用才能获取到。
创建Surface的时候我们需要下面的权限:

<uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER"/>

查看系统中对该权限的定义:

<!-- @SystemApi Allows an application to use SurfaceFlinger's low level features.
         <p>Not for use by third-party applications.
         @hide
    -->
    <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 /* postDrawTransaction */)

最后,当服务销毁的时候,应该将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

关于窗口的层次关系:同一类型的窗口后添加的窗口是在之前添加的窗口之上的,所以要让你自己新建的窗口位于原生窗口之下的话,你需要让你的窗口创建时机早于wallpaperService原生创建wallpaper类型Window的时机,即你自己的创建时机早于updateSurface函数执行时机就可以

在完成自建window的时候,同时自己也学习了一些基本的调试和定位命令:
dump surfacelinger信息:

  • adb shell dumpsys SurfaceFlinger

dump window查看窗口层级信息(连续抓取多帧的时候可以for循环抓取,但是dump需要时间,不可能每一帧都抓到):

  • adb shell dumpsys window

同时,我们可以使用:
adb shell dumpsys –help 查看基本命令帮助
使用 adb shell dumpsys -l 查看支持dump的模块

如何编译代码

上面的代码很多都调用了hide接口,android studio直接编译是编译不过的,下面给出两种解决办法:

  1. 下载全量的android源码,在源码中增加单独的module,这样就会编译得到一个你需要的jar
  2. app的编译依赖中增加framework.jar,这样app中调用hide接口的代码就可以直接编译过了,这样就很方便(推荐)

参考:SurfaceView源码,WallpaperService和WallpaperServiceManager源码。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值