首页 > 代码库 > 系统截屏源码浅析

系统截屏源码浅析

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);
}
<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    系统截屏源码浅析