系统截屏源码浅析

android中实现截屏的方式有很多种,形如下面几种:

1、通过view.getDrawingCache获取屏幕的图像数据,这也是众多开发同行朋友经常使用的一种方式,可惜的是这种方式并不适用于surfaceview。

2、利用adb命令,adb shell screencap -p path,再利用runtime去执行,但是这种方式需要获得系统权限方可。

3、通过framebuffer实现截屏,帧缓冲(framebuffer)是Linux为显示设备提供的一个接口,允许上层应用程序在图形模式下直接对显示缓冲区进行读写等操,这些都是由Framebuffer设备驱动来完成的。android中的framebuffer数据是存放在 /dev/graphics/fb0 文件中的,所以只要获取到framebuffer中的数据再转换成图片就实现截屏的功能啦,这不是半本片文章的重点介绍内容,这个后面或许会最为一个章节共享个大家。

4、 利用系统TakeScreenShotService截图。android设备可以通过电源键+音量下键可以实现截屏,很多手机设备上用手下拉状态栏也有截屏的选项,都是使用TakeScreenShotService截屏的,本文要介绍的是如何通过TakeScreenShotService实现截屏。

TakeScreenShotService源码分析,源码位于

frameworks\base\packages\SystemUI\src\com\android\systemui\screenshot\TakeScreenshotService.java

瞧瞧manifest文件先:

<service android:name=".screenshot.TakeScreenshotService"
     android:process=":screenshot"
     android:exported="false" />

这个service是设置了exported属性的,如果设置为true,则能够被调用或交互,否则不能。设置为false时,只有同一个应用程序的组件或带有相同用户ID的应用程序才能启动或绑定该服务。然而TakeScreenshotService所在应用程序的id是android.uid.systemui,所以一般的应用程序是没办法做到这一点的。

public class TakeScreenshotService extends Service {
    private static final String TAG = "TakeScreenshotService";
    private static GlobalScreenshot mScreenshot;
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case 1:
                final Messenger callback = msg.replyTo;
                if (mScreenshot == null) {
                    mScreenshot = new GlobalScreenshot(TakeScreenshotService.this);
                }
                mScreenshot.takeScreenshot(new Runnable() {
                    @Override
                    public void run() {
                        Message reply = Message.obtain(null, 1);
                        try {
                            callback.send(reply);
                        } catch (RemoteException e) {
                        }
                    }
                }, msg.arg1 > 0, msg.arg2 > 0);
            }
        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        return new Messenger(mHandler).getBinder();
    }
}

TakeScreenshotService 源码就这么多,可以很清晰的看见截屏的功能是由mScreenshot.takeScreenshot实现的。

void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible) {
    .....
}

finisher是在截屏之后的回调,谁发起的截屏在截屏完成之后就需要告诉需要者已经完成了。第二个和第三个就是截屏时是否显示状态栏和导航栏。

上面也提到了手机上截屏在状态栏下拉时通常有个选项,所以我们就移步到PhoneStatusBar.java瞧瞧。

源码路径:frameworks\base\packages\SystemUI\src\com\android\systemui\statusbar\phone\PhoneStatusBar.java

public class PhoneStatusBar extends BaseStatusBar implements DemoMode {
    .......
    .......
    private void takeScreenshot() {
        // 截屏图片存放位置
        String imageDir = Settings.System.getString(mContext.getContentResolver(), Settings.System.SCREENSHOT_LOCATION);
        File file = new File(imageDir + UserHandle.myUserId() + "/Screenshots");
        String text = null;
        Log.e(">>>>>>", "imageDir=" + imageDir);
        file.mkdir();
        if (!file.exists()) {
            if (imageDir.equals("/mnt/sdcard")) {
                text = mContext.getResources().getString(R.string.sdcard_unmount);
            } else if (imageDir.equals("/mnt/external_sd")) {
                text = mContext.getResources().getString(R.string.external_sd_unmount);
            } else if (imageDir.equals("/mnt/usb_storage")) {
                text = mContext.getResources().getString(R.string.usb_storage_unmount);
            }
            Toast.makeText(mContext, text, 3000).show();
            return;
        }
        synchronized (mScreenshotLock) {
            if (mScreenshotConnection != null) {
                return;
            }
            // 在这里绑定了截屏的TakeScreenshotService
            ComponentName cn = new ComponentName("com.android.systemui",
                    "com.android.systemui.screenshot.TakeScreenshotService");
            Intent intent = new Intent();
            intent.setComponent(cn);
            ServiceConnection conn = new ServiceConnection() {
                @Override
                public void onServiceConnected(ComponentName name, IBinder service) {
                    synchronized (mScreenshotLock) {
                        if (mScreenshotConnection != this) {
                            return;
                        }
                        Messenger messenger = new Messenger(service);
                        Message msg = Message.obtain(null, 1);
                        final ServiceConnection myConn = this;
                        Handler h = new Handler(mHandler.getLooper()) {
                            @Override
                            public void handleMessage(Message msg) {
                                synchronized (mScreenshotLock) {
                                    if (mScreenshotConnection == myConn) {
                                        mContext.unbindService(mScreenshotConnection);
                                        mScreenshotConnection = null;
                                        mHandler.removeCallbacks(mScreenshotTimeout);
                                    }
                                }
                            }
                        };
                        // 截屏完成后需要回调告知,由h来处理
                        msg.replyTo = new Messenger(h);
                        // 是否显示状态栏
                        msg.arg1 = 0;
                        // 是否显示导航栏
                        msg.arg2 = 1;
                        try {
                            messenger.send(msg);
                        } catch (RemoteException e) {
                        }
                    }
                }

                @Override
                public void onServiceDisconnected(ComponentName name) {
                }
            };
            if (mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE)) {
                mScreenshotConnection = conn;
                mHandler.postDelayed(mScreenshotTimeout, 10000);
            }
        }
    }
    .......
}

从上述代码中可以知道,TakeScreenshotService绑定成功后便开始往进行截屏操作,当截屏操作成功后,便会unbind这个service。

再回到TakeScreenshotService来,截屏是由GlobalScreenshot.takeScreenshot()来完成的,

    /**
     * Takes a screenshot of the current display and shows an animation.
     */
    void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible) {
        // We need to orient the screenshot correctly (and the Surface api seems to take screenshots
        // only in the natural orientation of the device :!)
        mDisplay.getRealMetrics(mDisplayMetrics);
        // 屏幕的高度和宽度
        float[] dims = {mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels};
        // 当前屏幕所处的角度
        float degrees = getDegreesForRotation(mDisplay.getRotation());
        boolean requiresRotation = (degrees > 0);
        if (requiresRotation) {
            // Get the dimensions of the device in its native orientation
            mDisplayMatrix.reset();
            mDisplayMatrix.preRotate(-degrees);
            mDisplayMatrix.mapPoints(dims);
            dims[0] = Math.abs(dims[0]);
            dims[1] = Math.abs(dims[1]);
        }
        // Take the screenshot 进行截屏
        mScreenBitmap = SurfaceControl.screenshot((int) dims[0], (int) dims[1]);
        if (mScreenBitmap == null) {
            // 截取的图片为null,截屏失败
            notifyScreenshotError(mContext, mNotificationManager);
            // 回调告知截屏结束
            finisher.run();
            return;
        }

        if (requiresRotation) {
            // Rotate the screenshot to the current orientation
            Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels,
                    mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888);
            Canvas c = new Canvas(ss);
            c.translate(ss.getWidth() / 2, ss.getHeight() / 2);
            c.rotate(degrees);
            c.translate(-dims[0] / 2, -dims[1] / 2);
            c.drawBitmap(mScreenBitmap, 0, 0, null);
            c.setBitmap(null);
            // Recycle the previous bitmap
            mScreenBitmap.recycle();
            mScreenBitmap = ss;
        }

        // Optimizations
        mScreenBitmap.setHasAlpha(false);
        mScreenBitmap.prepareToDraw();

        // 展示动画,就是截屏后在页面上有个动画展示效果
        startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
                statusBarVisible, navBarVisible);
    }

就下来就是要去重点了解下面的代码到底干了啥

SurfaceControl.screenshot((int) dims[0], (int) dims[1])
 public static Bitmap screenshot(int width, int height) {
        // TODO: should take the display as a parameter
        IBinder displayToken = SurfaceControl.getBuiltInDisplay(
                SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN);
        return nativeScreenshot(displayToken, width, height, 0, 0, true);
    }

终于发现截屏操作竟然是在natvie层实现的,native返回了一个bitmap对象。下面移步native。nativeScreenshot方法的实现在下面的源码文件中:

frameworks\base\core\jni\android_view_SurfaceControl.cpp

static jobject nativeScreenshotBitmap(JNIEnv* env, jclass clazz, jobject displayTokenObj,
        jint width, jint height, jint minLayer, jint maxLayer, bool allLayers) {
    sp<IBinder> displayToken = ibinderForJavaObject(env, displayTokenObj);
    if (displayToken == NULL) {
        return NULL;
    }

    // 持有图像的数据
    ScreenshotPixelRef* pixels = new ScreenshotPixelRef(NULL);
    if (pixels->update(displayToken, width, height,
            minLayer, maxLayer, allLayers) != NO_ERROR) {
        delete pixels;
        return NULL;
    }

    uint32_t w = pixels->getWidth();
    uint32_t h = pixels->getHeight();
    uint32_t s = pixels->getStride();
    uint32_t f = pixels->getFormat();
    ssize_t bpr = s * android::bytesPerPixel(f);

    SkBitmap* bitmap = new SkBitmap();
    bitmap->setConfig(convertPixelFormat(f), w, h, bpr);
    if (f == PIXEL_FORMAT_RGBX_8888) {
        bitmap->setIsOpaque(true);
    }

    if (w > 0 && h > 0) {
        bitmap->setPixelRef(pixels)->unref();
        bitmap->lockPixels();
    } else {
        // be safe with an empty bitmap.
        delete pixels;
        bitmap->setPixels(NULL);
    }
    // 创建bitmap对象
    return GraphicsJNI::createBitmap(env, bitmap,
            GraphicsJNI::kBitmapCreateFlag_Premultiplied, NULL);
}
时间: 2024-08-02 11:04:17

系统截屏源码浅析的相关文章

Android应用Preference相关及源码浅析(Preference组件家族篇)

1 前言 前一篇(点我阅读前一篇<Android应用Preference相关及源码浅析(SharePreferences篇)>)我们讨论分析使用了Android的SharePreferences,相信看过的朋友都有了自己的感悟与理解,这一篇我们继续乘热打铁来说说SharePreferences的衍生品--Preference组件. 其实Preference组件大家一定不陌生,因为Android系统的Setting应用及我们市面上一些符合Android设计思想的应用的设置界面一般都会用它来实现,

Android应用Loaders全面详解及源码浅析

1 背景 在Android中任何耗时的操作都不能放在UI主线程中,所以耗时的操作都需要使用异步实现.同样的,在ContentProvider中也可能存在耗时操作,这时也该使用异步操作,而3.0之后最推荐的异步操作就是Loader.它可以方便我们在Activity和Fragment中异步加载数据,而不是用线程或AsyncTask,他的优点如下: 提供异步加载数据机制: 对数据源变化进行监听,实时更新数据: 在Activity配置发生变化(如横竖屏切换)时不用重复加载数据: 适用于任何Activit

Android系统截屏的实现(附代码)

1.背景 写博客快两年了,写了100+的文章,最火的文章也是大家最关注的就是如何实现android系统截屏.其实我们google android_screen_shot就会找到很对办法,但那些都是很多年前的了,在android4.*版本后,android对于源码进行了更正,使得以前的方法都不能够使用. 感谢cjd6568358这名网友,我们一起讨论,最终由他实现了android系统截屏功能,为了让以后想要这个功能的coder可以少走一些弯路,我们整理的代码做成开源项目. 2.思路 其实主要思路还

Android 系统截屏与系统内置DVR录像冲突,导致SystemUI重启的问题解决与分享

上周六加班在解决一个关于SystemUI内嵌的DVR录像与系统截屏操作冲突的问题,介于问题的复杂性,所以我把这个分享出来便 于以后自己更加的理解,又方便以后遇到此问题的同行能够提供一些帮助,若有疑问可向鄙人的博客提供你的宝贵意见! 首先我们需要找到系统截屏的按键定义,并且知道它在哪里执行的,先摈弃从硬件底层的协议,我们直接从framework层开始 讲,因为底层底层硬件返回的结果由.c.o.h这些文件,再由Binder aidl 将结果给到framework,所以我们就从开始从framework

Android源码浅析(一)——VMware Workstation Pro和Ubuntu Kylin 16.04 LTS安装配置

Android源码浅析(一)--VMware Workstation Pro和Ubuntu Kylin 16.04 LTS安装配置 最近地方工作,就是接触源码的东西了,所以好东西还是要分享,系列开了这么多,完结 的也没几个,主要还是自己覆盖的太广了,却又不精通,嘿嘿,工作需要,所以写下了本篇博客 一.VMware 12 我选择的虚拟机试VMware,挺好用的感觉,下载VMware就不说了,善用搜索键嘛,这里我提供一个我现在在用的 下载地址:链接:http://pan.baidu.com/s/1k

ReactiveCocoa2 源码浅析

ReactiveCocoa2 源码浅析 标签(空格分隔): ReactiveCocoa iOS Objective-C ? 开车不需要知道离合器是怎么工作的,但如果知道离合器原理,那么车子可以开得更平稳. ReactiveCocoa 是一个重型的 FRP 框架,内容十分丰富,它使用了大量内建的 block,这使得其有强大的功能的同时,内部源码也比较复杂.本文研究的版本是2.4.4,小版本间的差别不是太大,无需担心此问题. 这里只探究其核心 RACSignal 源码及其相关部分.本文不会详细解释里

【深入浅出jQuery】源码浅析2--奇技淫巧

最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐进增强)优雅的处理能力以及 Ajax 等方面周到而强大的定制功能无不令人惊叹. 另外,阅读源码让我接触到了大量底层的知识.对原生JS .框架设计.代码优化有了全新的认识,接下来将会写一系列关于 jQuery 解析的文章. 我在 github 上关于 jQuery 源码的全文注解,感兴趣的可以围观一下

Android开发之Theme、Style探索及源码浅析

1 背景 前段时间群里有伙伴问到了关于Android开发中Theme与Style的问题,当然,这类东西在网上随便一搜一大把模板,所以关于怎么用的问题我想这里也就不做太多的说明了,我们这里把重点放在理解整个Android中Theme.Style的关系及结构,这样我们就能游刃有余的面对实际开发中遇到的很多问题了,也就免得在自定义时遇到各种坑,譬如不清楚该继承哪个parent.不清楚为何背景会有一个黑边等. 本文主要分两部分来进行简单粗略的浅析,首先会围绕Theme与Style的定义及在App开发中的

Android一键锁屏源码

APK下载 源程序下载 锁屏流程如下(参考于Android一键锁屏开发全过程[源码][附图]) 源码参考于一键锁屏 源码 一共有2个Java文件: 1 package com.example.onekeylock.app; 2 3 import android.app.admin.DeviceAdminReceiver; 4 5 public class AdminReceiver extends DeviceAdminReceiver{} AdminReceiver.java 1 packag