Android P 图形显示系统(四) Android VirtualDisplay解析

本文深入解析Android P中的VirtualDisplay,探讨其在录屏、WFD显示等场景的应用。通过ImageReader与VirtualDisplay的结合,展示了如何创建和使用VirtualDisplay,包括ImageReader的初始化、VirtualDisplay的创建过程,以及数据流分析和ImageReader如何获取屏幕数据。文章详细分析了从Java层到Native层的实现细节,揭示了数据从SurfaceFlinger到ImageReader的流转路径。

Android VirtualDisplay解析

Android支持多个屏幕:主显,外显,和虚显,虚显就是我们要说的VirtualDisplay。VirtualDisplay的使用场景很多,比如录屏,WFD显示等。其作用就是抓取屏幕上显示的内容。VirtualDisplay抓取屏幕内容,其实现方式有很多。在API中就提供了ImageReader进行读取VirtualDisplay里的内容。

下面我们就结合ImageReader,来看看VirtualDisplay及其相关流程。

ImageReader和VirtualDisplay使用示例

我们以VirtualDisplayTest为示例:

1.在测试setUp时,初始化 DisplayManager, ImageReader 和 ImageListener ,代码如下:

* frameworks/base/core/tests/coretestssrc/android/hardware/display/VirtualDisplayTest.java

    protected void setUp() throws Exception {
        super.setUp();

        mDisplayManager = (DisplayManager)mContext.getSystemService(Context.DISPLAY_SERVICE);
        mHandler = new Handler(Looper.getMainLooper());
        mImageListener = new ImageListener();

        mImageReaderLock.lock();
        try {
            mImageReader = ImageReader.newInstance(WIDTH, HEIGHT, PixelFormat.RGBA_8888, 2);
            mImageReader.setOnImageAvailableListener(mImageListener, mHandler);
            mSurface = mImageReader.getSurface();
        } finally {
            mImageReaderLock.unlock();
        }
    }

  • DisplayManager 管理Display的,系统中有对应的DisplayManagerService。
  • ImageListener实现OnImageAvailableListener接口。
  • ImageReader是一个图片读取器,它是OnImageAvailableListener接口的触发者
  • 另外,注意这里的mSurface。

2.以测试项目testPrivateVirtualDisplay为例

    public void testPrivateVirtualDisplay() throws Exception {
        VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(NAME,
                WIDTH, HEIGHT, DENSITY, mSurface, 0);
        assertNotNull("virtual display must not be null", virtualDisplay);

        Display display = virtualDisplay.getDisplay();
        try {
            assertDisplayRegistered(display, Display.FLAG_PRIVATE);

            // Show a private presentation on the display.
            assertDisplayCanShowPresentation("private presentation window",
                    display, BLUEISH,
                    WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION, 0);
        } finally {
            virtualDisplay.release();
        }
        assertDisplayUnregistered(display);
    }
  • 测试时,先通过mDisplayManager,创建一个虚拟显示。
  • 通过assertDisplayRegistered判断虚显是否已经注册
  • 通过assertDisplayCanShowPresentation判断是否能显示私有的Presentation
  • 将虚显释放后,通过assertDisplayUnregistered判断是否已经撤销注册。

这里Presentation是Andorid的一个显示控件,能够实现将要显示的内容显示到制定的显示屏上。

实例代码就这么多,接下来,我们来看具体的流程。

ImageReader介绍

ImageReader,简单来说,就是使应用能够以图片数据的形式读取绘制到Surface中的内容。图片数据用Image描述。

1.ImageReader的定义
ImageReader的定义如下:

* frameworks/base/media/java/android/media/ImageReader.java

    public static ImageReader newInstance(int width, int height, int format, int maxImages) {
        return new ImageReader(width, height, format, maxImages, BUFFER_USAGE_UNKNOWN);
    }

这里的参数maxImages表示,能同时访问的Image数量,这里概念上和BufferQueue中的maxnumber也是类似的。

ImageReader关键代码如下:

    protected ImageReader(int width, int height, int format, int maxImages, long usage) {
        mWidth = width;
        mHeight = height;
        mFormat = format;
        mMaxImages = maxImages;

        .. ...

        mNumPlanes = ImageUtils.getNumPlanesForFormat(mFormat);

        nativeInit(new WeakReference<>(this), width, height, format, maxImages, usage);

        mSurface = nativeGetSurface();

        mIsReaderValid = true;
        // Estimate the native buffer allocation size and register it so it gets accounted for
        // during GC. Note that this doesn't include the buffers required by the buffer queue
        // itself and the buffers requested by the producer.
        // Only include memory for 1 buffer, since actually accounting for the memory used is
        // complex, and 1 buffer is enough for the VM to treat the ImageReader as being of some
        // size.
        mEstimatedNativeAllocBytes = ImageUtils.getEstimatedNativeAllocBytes(
                width, height, format, /*buffer count*/ 1);
        VMRuntime.getRuntime().registerNativeAllocation(mEstimatedNativeAllocBytes);
    }
  • 我们的格式是PixelFormat.RGBA_8888,所以这里的mNumPlanes值为1
  • nativeInit,native方法,创建一个native的ImageReader实例。
  • nativeGetSurface,native方法,获取对应的Native实例的Surface,注意,我们的Surface是从哪儿来的。

2.ImageReader的JNI实现
ImageReader的JNI实现如下,这里包含了ImageReader的方法和SurfaceImage的方法。

* frameworks/base/media/jni/android_media_ImageReader.cpp

static const JNINativeMethod gImageReaderMethods[] = {
    {"nativeClassInit",        "()V",                        (void*)ImageReader_classInit },
    {"nativeInit",             "(Ljava/lang/Object;IIIIJ)V",  (void*)ImageReader_init },
    {"nativeClose",            "()V",                        (void*)ImageReader_close },
    {"nativeReleaseImage",     "(Landroid/media/Image;)V",   (void*)ImageReader_imageRelease },
    {"nativeImageSetup",       "(Landroid/media/Image;)I",   (void*)ImageReader_imageSetup },
    {"nativeGetSurface",       "()Landroid/view/Surface;",   (void*)ImageReader_getSurface },
    {"nativeDetachImage",      "(Landroid/media/Image;)I",   (void*)ImageReader_detachImage },
    {"nativeDiscardFreeBuffers", "()V",                      (void*)ImageReader_discardFreeBuffers }
};

static const JNINativeMethod gImageMethods[] = {
    {"nativeCreatePlanes",      "(II)[Landroid/media/ImageReader$SurfaceImage$SurfacePlane;",
                                                              (void*)Image_createSurfacePlanes },
    {"nativeGetWidth",         "()I",                        (void*)Image_getWidth },
    {"nativeGetHeight",        "()I",                        (void*)Image_getHeight },
    {"nativeGetFormat",        "(I)I",                        (void*)Image_getFormat },
};

nativeInit对应的方法为ImageReader_init:

static void ImageReader_init(JNIEnv* env, jobject thiz, jobject weakThiz, jint width, jint height,
                             jint format, jint maxImages, jlong ndkUsage)
{
    ... ...
    sp<JNIImageReaderContext> ctx(new JNIImageReaderContext(env, weakThiz, clazz, maxImages));

    sp<IGraphicBufferProducer> gbProducer;
    sp<IGraphicBufferConsumer> gbConsumer;
    BufferQueue::createBufferQueue(&gbProducer, &gbConsumer);
    sp<BufferItemConsumer> bufferConsumer;
    String8 consumerName = String8::format("ImageReader-%dx%df%xm%d-%d-%d",
            width, height, format, maxImages, getpid(),
            createProcessUniqueId());
    ... ...
    bufferConsumer = new BufferItemConsumer(gbConsumer, consumerUsage, maxImages,
            /*controlledByApp*/true);
    if (bufferConsumer == nullptr) {
        jniThrowExceptionFmt(env, "java/lang/RuntimeException",
                "Failed to allocate native buffer consumer for format 0x%x and usage 0x%x",
                nativeFormat, consumerUsage);
        return;
    }
    ctx->setBufferConsumer(bufferConsumer);
    bufferConsumer->setName(consumerName);

    ctx->setProducer(gbProducer);
    bufferConsumer->setFrameAvailableListener(ctx);
    ImageReader_setNativeContext(env, thiz, ctx);
    ctx->setBufferFormat(nativeFormat);
    ctx->setBufferDataspace(nativeDataspace);
    ctx->setBufferWidth(width);
    ctx->setBufferHeight(height);

    // Set the width/height/format/dataspace to the bufferConsumer.
    res = bufferConsumer->setDefaultBufferSize(width, height);
    if (res != OK) {
        jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
                          "Failed to set buffer consumer default size (%dx%d) for format 0x%x",
                          width, height, nativeFormat);
        return;
    }
    res = bufferConsumer->setDefaultBufferFormat(nativeFormat);
    if (res != OK) {
        jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
                          "Failed to set buffer consumer default format 0x%x", nativeFormat);
    }
    res = bufferConsumer->setDefaultBufferDataSpace(nativeDataspace);
    if (res != OK) {
        jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
                          "Failed to set buffer consumer default dataSpace 0x%x", nativeDataspace);
    }
}
  • 创建了一个JNIImageReaderContext实例,这个就是ImageReader的Native对应的对象。
JNIImageReaderContext::JNIImageReaderContext(JNIEnv* env,
        jobject weakThiz, jclass clazz, int maxImages) :
    mWeakThiz(env->NewGlobalRef(weakThiz)),
    mClazz((jclass)env->NewGlobalRef(clazz)),
    mFormat(0),
    mDataSpace(HAL_DATASPACE_UNKNOWN),
    mWidth(-1),
    mHeight(-1) {
    for (int i = 0; i < maxImages; i++) {
        BufferItem* buffer = new BufferItem;
        mBuffers.push_back(buffer);
    }
}

这里的mDataSpace是数据空间,用以描述格式的。native的Buffer用BufferItem描述,在mBuffers中。

  • 创建对应的BufferQueue,生产者gbProducer,消费者gbConsumer。
    这里用的还是BufferQueue,Consumer端用BufferItemConsumer进行了封装。还记得我们Androdi正常显示的时候,Consumer是什么吗?没错BufferLayerConsumer,需要注意这其间的差别。BufferItemConsumer中持有gbConsumer对象。

  • 创建完BufferQueue后,再设置到 JNIImageReaderContext 中。注意BufferItemConsumer的FrameAvailableListener为JNIImageReaderContext中实现的FrameAvailableListener。

  • 最后通过ImageReader_setNativeContext,将native对象和Java的对象关联。

JNIImageReaderContext的类图
ImageReaderContext的类图

VirtualDisplay的创建

通过DisplayManager创建VirtualDisplay。

* frameworks/base/core/java/android/hardware/display/DisplayManager.java

    public VirtualDisplay createVirtualDisplay(@Nullable MediaProjection projection,
            @NonNull String name, int width, int height, int densityDpi, @Nullable Surface surface,
            int flags, @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler,
            @Nullable String uniqueId) {
        return mGlobal.createVirtualDisplay(mContext, projection,
                name, width, height, densityDpi, surface, flags, callback, handler, uniqueId);
    }

DisplayManagerGlobal是一个单例,Android系统中就这么一个。

    public DisplayManager(Context context) {
        mContext = context;
        mGlobal = DisplayManagerGlobal.getInstance();
    }

DisplayManagerGlobal的createVirtualDisplay方法实现如下:

* frameworks/base/core/java/android/hardware/display/DisplayManagerGlobal.java

    public VirtualDisplay createVirtualDisplay(Context context, MediaProjection projection,
            String name, int width, int height, int densityDpi, Surface surface, int flags,
            VirtualDisplay.Callback callback, Handler handler, String uniqueId) {
        ... ...
        int displayId;
        try {
            displayId = mDm.createVirtualDisplay(callbackWrapper, projectionToken,
                    context.getPackageName(), name, width, height, densityDpi, surface, flags,
                    uniqueId);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
        if (displayId < 0) {
            Log.e(TAG, "Could not create virtual display: " + name);
            return null;
        }
        Display display = getRealDisplay(displayId);
        if (display == null) {
            Log.wtf(TAG, "Could not obtain display info for newly created "
                    + "virtual display: " + name);
            try {
                mDm.releaseVirtualDisplay(callbackWrapper);
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
            return null;
        }
        return new VirtualDisplay(this, display, callbackWrapper, surface);
    }

mDm是DisplayManagerservice(DMS)的Stub。mDm.createVirtualDisplay直接看DMS的实现:

* frameworks/base/services/core/java/com/android/server/display/DisplayManagerService.java

        @Override // Binder call
<think>嗯,用户想找关于Android图形显示系统的PDF文档或资料。首先,我需要回忆一下之前提供的引用内容,看看有没有相关的信息。引用[1]提到Skia库生成PDF的过程,引用[2]和[4]讨论了图形驱动和显示设备类型,引用[3]和[5]是关于Android显示在线PDF的方法。不过用户的需求是找资料或教程,而不是具体实现。接下来,我应该考虑官方文档和资源。Android开发者网站应该有图形系统的文档,特别是图形架构部分。另外,可能有一些官方的PDF或技术文章,比如“AndroidGraphics Architecture”这样的文档。需要建议用户去官网查找。 然后是开源项目,比如AOSP的代码库,里面可能有详细的文档。用户如果愿意深入代码,可以访问Android开源项目查看相关模块,比如SurfaceFlinger和HWC的文档。技术博客和社区也是重要的来源。Medium、StackOverflow可能有开发者分享的经验,或者一些公司发布的案例分析。比如引用[1]和[2]来自技术博客,可以推荐用户搜索类似的关键词。学术论文和会议资料,比如GoogleI/O的演讲,可能会有深入的技术解析。用户如果有学术需求,可以建议他们查找相关的论文或会议PPT。 书籍方面,虽然引用中没有提到,但像《Android系统编程》或《深入理解Android内核设计思想》这类书可能涵盖图形系统内容,建议用户查阅。 另外,引用[3]和[5]提到WebView显示PDF的问题,但用户的问题是关于图形显示系统的资料,所以这部分可能不太相关。需要避免混淆,专注于系统架构而不是应用层实现。 还要考虑用户可能需要中文资料,所以推荐一些中文社区或翻译的文档。比如国内的技术论坛可能有翻译的教程或分享。最后,生成相关问题时要覆盖资料获取、核心组件、学习难点、调试工具、版本差异和应用场景,这样用户可以有更全面的了解。</think>### Android图形显示系统资料获取建议 #### 1. 官方技术文档 - **Android图形架构文档**:访问[Android开发者官网](https://developer.android.com/guide/topics/graphics/architecture)获取最新架构说明,包含从应用层到显示驱动的完整流程[^2][^4]。 - **SurfaceFlinger与HWC**:官方文档详细描述合成器(SurfaceFlinger)和硬件混合器(Hardware Composer)的交互机制[^4]。 #### 2. 开源项目资源 - **AOSP源码文档**: ```bash # 在AOSP代码库中查看 /frameworks/native/services/surfaceflinger/README.md /hardware/interfaces/graphics/composer/ ``` 包含显示设备类型定义(如主显示/扩展显示/虚拟显示)和硬件抽象层接口说明[^4]。 #### 3. 技术博客与案例 - **图形驱动初始化分析**:参考`egl_connection_t`结构体解析,涉及EGL Wrapper库与GLESv1/v2的加载过程[^2]。 - **Skia库应用实例**:研究Skia与Freetype结合生成PDF的代码实现(需注意该文档主要面向图形生成而非显示系统)[^1]。 #### 4. 调试工具与API - **GPU调试工具**:使用`adb shell dumpsys SurfaceFlinger`查看合成器状态。 - **DisplayDevice类**:分析显示设备类型定义源码(DISPLAY_PRIMARY/EXTERNAL/VIRTUAL)[^4]。 #### 5. 替代方案参考 - **PDF显示实现**:虽然WebView直接显示PDF需要Google服务,但可参考`PdfRenderer`类实现本地PDF渲染[^3][^5]。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夕月风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值