爱踢门之锤子系统区域截屏功能

           周围有位罗总的忠实粉丝,于是乎平时有意无意的被灌注了大量“锤子”的信息。也许就是这些无意,我昨晚也就无意中点进了锤子手机发布会的直播,看着看着最后就看完了。

       锤子的硬件设计是超出期待的,软件是稍微有点没有感觉的,整个发布会后,我是融入的,感动了。罗总为人良心,整个发布确实让人很觉得他们在认真的做,认真的想,认真的夸人。当做手机变成了一种态度,一种做人,自然带入感就比较强烈。比较能够相信罗总的支持者们是能够举得起雷神般的锤子不停的tuning,只等那最后一道闪电解救世界。

         闲话一大段,有人下锤了,必须进入话题。

         锥子手机的系统是Android系统,本文自然说的是Android的自由截屏实现。

         说到截屏,我们首先必须讲屏幕上的内容是怎么显示出来的,接下来我们以下图为例来讲解。

           

 绘制原理

         总的来说Android通过两级绘制系统来实现内容输出的

1)程序的绘制

         这一级别的绘制的基本单元是view,各种view的组合最后绘制成了丰富多彩的程序内容。如下图

                  

图(1)

                   这一过程的绘制采用的是传统的canvas绘制方法。

        2)程序的组合

                  程序内容组合就是将系统中的所有可见的程序组合显示,这又是一个绘制过程。

           这一级别绘制的基本单元是window(另一层面上叫layer),绝大多数程序只有一个window,但是也有程序有多个window,比如下图的”系统程序”就有两个window.

           这一级别的绘制是由surfaceflinger来执行的,而这一级别的绘制也使用了更高级图形绘制系统­—OpenGl,OpenGl有很好的composer能力,比如下图的dim效果(全屏灰色部分)。这一级别的绘制对象很少,比如下图只有四个window, 正常情况只有3个(没有关机程序),当手机有实体back, home, menu键后,又少一个navigationBar(最下面那个window),所以大部分手机的大部分时间只有2个window,也很适合opengl的特性。但是opengl操作比较耗电,android的大部分平台上又采用了overlay技术。Overlay技术是一种芯片级别的内存拷贝,lcd 控制器负责将overlay的内存和主显示的内容做composer效果(比如alpha,缩放等),效率和效能都高。你可以将overlay看成是一个轻型的GPU,对于android等移动设备(layer基本都是全屏,且数量少),video应用(特殊格式yuv)场景,有很大的提升作用。这个我可能在后面合适的时机单独讲一章。

 

            

 

图二

 

大部分情况下两个layer

 

 

 

截屏方式         

从上面的绘制分层看来截屏肯定也有3种:应用截屏,整个屏幕的截屏,读取显示芯片内存截屏

1)应用截屏

         应用截屏就是应用截取自己的内容。

         要实现截取应用的内容,从上图可以很容易看出,只要将图(1)中的buffer替换为我们可以控制的buffer,然后让根view重新draw一次就可以了。代码告诉我们Android就是这么做的。

          

//这个就是根节点view
View decorView = this.getWindow().getDecorView(); 
//开启view的drawing cache 
decorView.setDrawingCacheEnabled(true);   

//这个就是截屏的关键接口
//getDrawingCache负责创建bm的buffer,并让view都在上面绘制
Bitmap bm = decorView.getDrawingCache();

文件View.java:

    public Bitmap getDrawingCache(boolean autoScale) {
        if ((mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED) {
            //这个会更新截屏的mDrawingCache
            buildDrawingCache(autoScale);
        }
        return autoScale ? mDrawingCache : mUnscaledDrawingCache;
    }

    public void buildDrawingCache(boolean autoScale) {
            Canvas canvas;
            Bitmap bitmap = autoScale ? mDrawingCache : mUnscaledDrawingCache;
            if (attachInfo != null) {
                canvas = attachInfo.mCanvas;
                if (canvas == null) {
                    canvas = new Canvas();
                }
                canvas.setBitmap(bitmap);
                // Temporarily clobber the cached Canvas in case one of our children
                // is also using a drawing cache. Without this, the children would
                // steal the canvas by attaching their own bitmap to it and bad, bad
                // thing would happen (invisible views, corrupted drawings, etc.)
                attachInfo.mCanvas = null;
            } else {
                // This case should hopefully never or seldom happen
                canvas = new Canvas(bitmap);
            }
            //让该view及子view绘制,程序的内容就更新在mDrawingCache上了
            draw(canvas);
        }
}

下面就是将bitmap转化为图片了,在此略去….

从上面的代码分析可以看出,其实我们可以截取任一view而不是整个屏幕,关键看你调用哪个view的getDrawingCache函数。

         该方法的局限性在于只有程序自己才能截取,也就是说必须在程序内部显式的实现截屏逻辑才能截屏。该方法还有另外一个问题,如果应用中包含SurfaceView(比如camera, video播放等应用),截取后,你会发现SurfaceView所在的区域是黑色的。这是因为SurfaceView是一种特殊的view,它有自己的buffer,它的draw函数不会在canvas上绘制任何东西,自然就没法截取到它了。

         Android中的著名的调试利器hierachviewer就是使用类似的机制来显示程序的各个view的。调用流程如下:

 

       上面红色部分view的createSnapShot函数就和上面的getDrawingCache函数功能一样,核心就是创建buffer,重新draw.

2)整个屏幕截屏

        从上面图线绘制逻辑分析来看,要实现全部屏幕截屏,只需将上图2中的buffer替换为截屏程序可控的buffer,然后重新绘制一次就可以了。我们来看源码:

     

Screencap.cpp 
 {
    //heap就是内存的生产者
    sp<IMemoryHeap> heap;
    uint32_t w, h;
    PixelFormat f;
    sp<IBinder> display(composer->getBuiltInDisplay(ISurfaceComposer::eDisplayIdMain));
    status_t err = composer->captureScreen(display, &heap, &w, &h, &f, 0, 0);

    //转化为了大家熟悉的bitmap了
    SkBitmap b;
    b.setConfig(SkBitmap::kARGB_8888_Config, w, h);
    b.setPixels(heap->getBase());
}

SurfaceFlinger.cpp
//重要的参数就是buffer的producer
status_t SurfaceFlinger::captureScreenImplLocked(
        const sp<const DisplayDevice>& hw,
        const sp<IGraphicBufferProducer>& producer,
        uint32_t reqWidth, uint32_t reqHeight,
        uint32_t minLayerZ, uint32_t maxLayerZ)
{
    //producer转化为surface,你可以看成是buffer的另外一种形式
    sp<Surface> sur = new Surface(producer, false);
    //surface转化为ANativeWindow,你可以把它看成是buffer的另外一种包装形式
    ANativeWindow* window = sur.get();

    if (native_window_api_connect(window, NATIVE_WINDOW_API_EGL) == NO_ERROR) {
        if (err == NO_ERROR) {
            ANativeWindowBuffer* buffer;
            //从window中拿出buffer,这个地方涉及直接的buffer了
            result = native_window_dequeue_buffer_and_wait(window,  &buffer);
            if (result == NO_ERROR) {
                //buffer的又一种形式,转化为EGLImageKHR,这样opengl系统就认得了
                EGLImageKHR image = eglCreateImageKHR(mEGLDisplay, EGL_NO_CONTEXT,
                        EGL_NATIVE_BUFFER_ANDROID, buffer, NULL);
                if (image != EGL_NO_IMAGE_KHR) {
                    //绑定到绘制引擎上,你可以看成是将bitmap绑定到canvas一样
                    //还是buffer的一种形式
                    RenderEngine::BindImageAsFramebuffer imageBond(getRenderEngine(), image);
                    if (imageBond.getStatus() == NO_ERROR) {
                       //开始进入绘制逻辑了,hw已经绑定了buffer
                        renderScreenImplLocked(hw, reqWidth, reqHeight,
                                minLayerZ, maxLayerZ, true);
                       //这个会通知上面的buffer produce buffer已经绘制好了,可以用了
                       window->queueBuffer(window, buffer, -1);
            }
        } else {
            result = BAD_VALUE;
        }
        native_window_api_disconnect(window, NATIVE_WINDOW_API_EGL);
    }

    return result;
}

void SurfaceFlinger::renderScreenImplLocked(
        const sp<const DisplayDevice>& hw,
        uint32_t reqWidth, uint32_t reqHeight,
        uint32_t minLayerZ, uint32_t maxLayerZ,
        bool yswap)
{
    const LayerVector& layers( mDrawingState.layersSortedByZ );
    const size_t count = layers.size();
    for (size_t i=0 ; i<count ; ++i) {
        const sp<Layer>& layer(layers[i]);
        const Layer::State& state(layer->getDrawingState());
        if (state.layerStack == hw->getLayerStack()) {
            if (state.z >= minLayerZ && state.z <= maxLayerZ) {
                if (layer->isVisible()) {
                    //核心:每个window(layer)将自己的内容绘制到buffer上
                    layer->draw(hw);
                }
            }
        }
    }
}

       有了全屏的bitmap,接下来做自由剪切就容易了吧。截完屏幕,用service弹出dialog,dialog里面加一个imageView显示截取的bitmap,再在外面套一个剪切域view即可实现锥子的自由切屏功能。

       当然,上面为了简化逻辑,使用了native的代码来实现调用surfaceFlinger的captureScreenImplLocked函数。事实上,android的apk的ndk代码只能使用ndk里library提供的接口,是没法实现上面sample代码。再事实上,android的一般的apk的java层也是没法调用到captureScreenImplLocked接口的,只有WindowManagerService等system的java逻辑才可以调用的。

       所以你要实现自由截屏,你必须是要是系统级别的程序。要不你是一个系统级别的native程序,要么你是系统级别的java逻辑,比如在WindowManagerService添加逻辑,java层实现可以参考如下代码:

      

    文件WindowManagerService.java
    @Override
    public Bitmap screenshotApplications(IBinder appToken, int displayId, int width,
            int height, boolean force565) {
        if (!checkCallingPermission(android.Manifest.permission.READ_FRAME_BUFFER,
                "screenshotApplications()")) {
            throw new SecurityException("Requires READ_FRAME_BUFFER permission");
        }

        Bitmap rawss = null;
        do {
            //这个代码就会最终调用到surfaceFlinger的renderScreenImplLocked
            //来截取指定layer的内容
            //SurfaceControl的逻辑会提供buffer
            rawss = SurfaceControl.screenshot(dw, dh, minLayer, maxLayer);
        }       
        if (rawss == null) {
            return null;
        }
        //有了bitmap,一切就完美了,你懂得
        return bm;
    }

         Android中最近程序历史记录里的程序截屏就是调用这个接口做到的,如下图:

         

        Java贯穿到native的surfaceflinger的逻辑如下:

        

        

3)读取显示芯片内存截屏

         即直接读取显示设备(通常为/dev/graphics/fb0)的数据,有overlay的还有可能有/dev/graphics/fb1。较早版本的DDMS截屏就是通过这种方式,ddms调用adbd,adbd(system/core/adb/framebuffer_service.c)直接读取/dev/graphics/fb0来实现截屏。但是由于有些设备出于安全,产权保护(避免录制高清视频)在出厂时是禁止直接读取显示设备内容的,故有些机型ddms是没法截取到屏幕的。故新版本的DDMS直接调用上面我提到过得screencap程序来实现的,由于这个方式是通过直接draw layer来实现的,所有机型都是可以截取到屏幕的。

附录

         目前主流手机助手的截屏都是通过ddms的方式实现的。Android 4.0+才开始内置了截屏幕功能,可以通过快捷键快速截屏。

          Bitmap保存为文件的代码如下:

public void saveBitmap(Bitmap bitmap, String fileName) {
    if (bitmap == null) {
        return;
    }
	File file = File("/sdcard", fileName); 
	file.createNewFile();
    try {
        FileOutputStream out = new FileOutputStream(file);
        bitmap.compress(Bitmap.CompressFormat.PNG, 90, out);
        out.flush();
        out.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/********************************

* 本文来自博客  “爱踢门”

* 转载请标明出处:http://blog.csdn.net/itleaks

******************************************/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值