|
Now that we’ve explored the lower-level components, it’s time to see how they fit into the higher-level components that apps are built from. 现在我们已经研究了底层的一些组件,是时候来看下更高层次上组件是如何工作的了。 The Android app framework UI is based on a hierarchy of objects that start with View. Most of the details don’t matter for this discussion, but it’s helpful to understand that UI elements go through a complicated measurement and layout process that fits them into a rectangular area. All visible View objects are rendered to a SurfaceFlinger-created Surface that was set up by the WindowManager when the app was brought to the foreground. The layout and rendering is performed on the app’s UI thread. 一个Android的app Framework层的UI是从视图上而来的基于对象的层次结构。大多数的细节对我们的讨论来说无关紧要,但是理解一下过程依然是对我们有帮助的:即UI元素是如何通过负责的测量和布局过程来将他们部署在一个矩形区域里面的。所有可见的view对象都呈现给了Surfaceflinger—当app由后台转到前台后,通过WindowManager创建了Surface。Layout和渲染都是在app的UI线程里面执行的。 Regardless of how many Layouts and Views you have, everything gets rendered into a single buffer. This is true whether or not the Views are hardware-accelerated. 根据你有多少layout和view,每个对象都在一个独立的buffer中渲染。无论是否使用硬件加速,都是如此。 A SurfaceView takes the same sorts of parameters as other views, so you can give it a position and size, and fit other elements around it. When it comes time to render, however, the contents are completely transparent. The View part of a SurfaceView is just a see-through placeholder. 一个SurfaceView有跟其他view一样的一些参数,所以你可以设置它的位置和大小等等。当它被渲染时,我们可以认为他的所有内容都是透明的。SurfaceView的视图部分只不过是一个透明的占位区域。 When the SurfaceView’s View component is about to become visible, the framework asks the WindowManager to ask SurfaceFlinger to create a new Surface. (This doesn’t happen synchronously, which is why you should provide a callback that notifies you when the Surface creation finishes.) By default, the new Surface is placed behind the app UI Surface, but the default “Z-ordering” can be overridden to put the Surface on top. 当SurfaceView的组件即将变为可见时,Framework层要求WindowManager请求Surfaceflinger创建一个新的Surface(这个过程是异步发生的,这就是为什么你应该提供一个回调函数,这样当Surface创建完成时你才能得到通知)。缺省情况下,新创建的Surface在app UI Surface的下面,但是Z轴顺序可能将这个Surface放在上面。 Whatever you render onto this Surface will be composited by SurfaceFlinger, not by the app. This is the real power of SurfaceView: the Surface you get can be rendered by a separate thread or a separate process, isolated from any rendering performed by the app UI, and the buffers go directly to SurfaceFlinger. You can’t totally ignore the UI thread — you still have to coordinate with the Activity lifecycle, and you may need to adjust something if the size or position of the View changes — but you have a whole Surface all to yourself, and blending with the app UI and other layers is handled by the Hardware Composer. 渲染在这个Surface(SurfaceView的surface)上的内容将由Surfaceflinger来混合,而不是由app。这才是SurfaceView的真正作用:这个surface可以被一个独立的线程或者进程来渲染,和app UI上其他的渲染工作分开,这些缓冲区数据将直接传递给Surfaceflinger。当然你不能完全忽略UI线程—-你依然要和activity的生命周期保持一致,并且一旦view的大小或者位置发生了改变,你可能也需要做些调整—但是你拥有了一个完整的Surface,并且这个Surface和app UI以及其他layer的混合工作将由HWC来完成。 It’s worth taking a moment to note that this new Surface is the producer side of a BufferQueue whose consumer is a SurfaceFlinger layer. You can update the Surface with any mechanism that can feed a BufferQueue. You can: use the Surface-supplied Canvas functions, attach an EGLSurface and draw on it with GLES, and configure a MediaCodec video decoder to write to it. 值得一提的是,新创建的surface实际上是生产者端,而消费者端则是一个Surfaceflinger的layer。你可以通过任何可以填充BufferQueue的途径来更新这个surface。你可以:使用Surface提供的Canvas相关的函数,附加一个EGLSurface然后使用GLES在上面绘制,配置一个MediaCodec 视频解码器直接在上面写数据。 Composition and the Hardware Scaler Now that we have a bit more context, it’s useful to go back and look at a couple of fields from dumpsys SurfaceFlinger that we skipped over earlier on. Back in the Hardware Composer discussion, we looked at some output like this: 我们现在有了更多的上下文知识,所以让我们回去看看前面讲到dumpsys SurfaceFlinger时我们忽略的几个字段。我们来看看如下的几个输出: type | source crop | frame name------------+-----------------------------------+-------------------------------- HWC | [ 0.0, 0.0, 320.0, 240.0] | [ 48, 411, 1032, 1149] SurfaceView HWC | [ 0.0, 75.0, 1080.0, 1776.0] | [ 0, 75, 1080, 1776] com.android.grafika/com.android.grafika.PlayMovieSurfaceActivity HWC | [ 0.0, 0.0, 1080.0, 75.0] | [ 0, 0, 1080, 75] StatusBar HWC | [ 0.0, 0.0, 1080.0, 144.0] | [ 0, 1776, 1080, 1920] NavigationBar FB TARGET | [ 0.0, 0.0, 1080.0, 1920.0] | [ 0, 0, 1080, 1920] HWC_FRAMEBUFFER_TARGETThis was taken while playing a movie in Grafika’s “Play video (SurfaceView)” activity, on a Nexus 5 in portrait orientation. Note that the list is ordered from back to front: the SurfaceView’s Surface is in the back, the app UI layer sits on top of that, followed by the status and navigation bars that are above everything else. The video is QVGA (320x240). 这个抓取自Nexus 5竖屏模式,当在Grafika里面播放视频的时候。注意这列表是按照从后到前的顺序排列的:SurfaceView的Surface在最后面,app ui layer在上面,然后是状态栏和导航栏。视频是QVGA的。 The “source crop” indicates the portion of the Surface’s buffer that SurfaceFlinger is going to display. The app UI was given a Surface equal to the full size of the display (1080x1920), but there’s no point rendering and compositing pixels that will be obscured by the status and navigation bars, so the source is cropped to a rectangle that starts 75 pixels from the top, and ends 144 pixels from the bottom. The status and navigation bars have smaller Surfaces, and the source crop describes a rectangle that begins at the the top left (0,0) and spans their content. “source crop”指示了Surface的buffer中要被SurfaceFlinger显示的部分。App UI的surface大小是整个显示的大小(1080*1920),但是由于需要显示状态栏和导航栏,因此从上面裁剪了75个像素,从下面裁剪了144个像素。 The “frame” is the rectangle where the pixels end up on the display. For the app UI layer, the frame matches the source crop, because we’re copying (or overlaying) a portion of a display-sized layer to the same location in another display-sized layer. For the status and navigation bars, the size of the frame rectangle is the same, but the position is adjusted so that the navigation bar appears at the bottom of the screen. Frame一栏是指最终显示在屏幕上的位置。APP UI因为是完全相同位置的拷贝,因此这个值和前一列完全相同。而对于状态栏和导航栏,大小和前面一列是相似的,但是位置已经发生了改变。 Now consider the layer labeled “SurfaceView”, which holds our video content. The source crop matches the video size, which SurfaceFlinger knows because the MediaCodec decoder (the buffer producer) is dequeuing buffers that size. The frame rectangle has a completely different size — 984x738. 现在我们来看下SurfaceView这个layer,这个layer里面是视频的内容。source crop一列和视频的大小一致,SurfaceFlinger之所以知道这个信息是因为MediaCodec解码器申请的出队的buffer的大小就是这么大。而Frame则有一个完全不同的大小:984*738. SurfaceFlinger handles size differences by scaling the buffer contents to fill the frame rectangle, upscaling or downscaling as needed. This particular size was chosen because it has the same aspect ratio as the video (4:3), and is as wide as possible given the constraints of the View layout (which includes some padding at the edges of the screen for aesthetic reasons). SurfaceFlinger处理了这种大小的不同,通过缩放缓冲区数据的方式来填充到frame中,根据需要放大或者缩小。之所以选择这个特殊的大小,是因为这个大小和视频有相同的高宽比,并且在View Layout允许的宽度下尽可能的大(基于美观的考虑,在屏幕边缘也放置了一些填充物。)。 If you started playing a different video on the same Surface, the underlying BufferQueue would reallocate buffers to the new size automatically, and SurfaceFlinger would adjust the source crop. If the aspect ratio of the new video is different, the app would need to force a re-layout of the View to match it, which causes the WindowManager to tell SurfaceFlinger to update the frame rectangle. 如果我们在同一个surface上面播放了一个不同大小的视频,那么底层的BufferQueue会重新使用新的大小重新分配缓冲区,SurfaceFlinger也会调整它的source crop。如果新的视频的高宽比也发生了变化,app需要强制要求View来re-layout来匹配当前大小,这将导致WindowManager通知SurfaceFlinger来更新每个frame矩形的大小。 If you’re rendering on the Surface through some other means, perhaps GLES, you can set the Surface size using the SurfaceHolder#setFixedSize() call. You could, for example, configure a game to always render at 1280x720, which would significantly reduce the number of pixels that must be touched to fill the screen on a 2560x1440 tablet or 4K television. The display processor handles the scaling. If you don’t want to letter- or pillar-box your game, you could adjust the game’s aspect ratio by setting the size so that the narrow dimension is 720 pixels, but the long dimension is set to maintain the aspect ratio of the physical display (e.g. 1152x720 to match a 2560x1600 display). You can see an example of this approach in Grafika’s “Hardware scaler exerciser” activity. 如果你使用这个Surface来做其他用途,比如GLES,那么你可以通过调用SurfaceHolder#setFixedSize() 函数来设置Surface的大小。比如你可以设置一个游戏的大小为1280*720,这样当你去运行在一个大小为2K或者4K的屏幕上时,你可以显著的减少需要填充的像素的数目。显示处理器会来处理缩放。。。。
|