Android后台截屏功能

前言

最近公司领导要求我做一个截屏的功能,说是为了方便监控小屏。本来以为没什么难度,然后就答应了下来。谁知道全都是坑。
这里要说明一点,我这里做的Android程序是 安装在 Android小屏上和机顶盒上的。其次机器都是root过的。然后 机器是自用的。所以,也就不会有 涉及到 隐私什么的。

第一次测试

一开始也没感觉有什么难度于是就写了一个demo

View dView = getWindow().getDecorView();
dView.setDrawingCacheEnabled(true);
dView.buildDrawingCache();
Bitmap bitmap = Bitmap.createBitmap(dView.getDrawingCache());
if (bitmap != null) {
  try {
    // 获取内置SD卡路径
    String sdCardPath = Environment.getExternalStorageDirectory().getPath();
    // 图片文件路径
    String filePath = sdCardPath + File.separator + "screenshot.png";
    File file = new File(filePath);
    FileOutputStream os = new FileOutputStream(file);
    bitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
    os.flush();
    os.close();
    DebugLog.d("a7888", "存储完成");
  } catch (Exception e) {
  }
}

然后测试了一下,ok没问题可以用。于是,就去找老板说,可以了。然后给他演示了一下。谁知道,接下来他说希望这个截图功能可以运行在后台服务中。当时也没多想,便应了下来 。然后就掉坑里了,差点没爬上来。

第二次测试

getWindow()方法在service中无法正常使用。即使,通过某些手段使其变的可用。然而截下来的图却是黑屏,所以第一种方法不符合要求,pass。
既然自己解决不了,那就查资料吧。在网上查了好久,很多人都推荐使用 “修改并编译源码中的screencap类然后通过JNI来调用这种方法”。然而 我不会 编译源码 这就很尴尬了。第二方法因为个人能力原因,pass。
之后又找到一种方法 “通过反射android.view.Surface类的 screenshot 方法完成截图”

sc = Class.forName("android.view.Surface");
method=sc.getMethod("screenshot", new Class[] {int.class, int.class});  
Object o = method.invoke(sc, new Object[]{(int) dims[0],(int) dims[1]});  
mScreenBitmap =(Bitmap)o;

虽然说这个方法可以用但是他却存在一个很严重的缺点 只支持4.0~4.2。之后的版本出于安全等方面原因screenshot方法所属的整个类android.view.SurfaceControl都被hide了,所以通过一般的反射是无法调用的。虽然,公司小屏4.0–4.2的Android版本比较多但是高版本的机器也不在少数,然而此方法只能解决部分机型所以不可用,pass。

第三次测试

之后又查了一些其他的资料。比如说Android5.0之后支持了实时录屏的功能。通过实时录屏我们可以拿到截屏的图像。同时可以通过在Service中处理实现后台的录屏。

//初始化数据
 private void init() {
        mediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
        it = mediaProjectionManager.createScreenCaptureIntent();
        mImagePath = Environment.getExternalStorageDirectory().getPath() + "/jietu/";
        mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
        mWindowWidth = mWindowManager.getDefaultDisplay().getWidth();
        mWindowHeight = mWindowManager.getDefaultDisplay().getHeight();
        mImageReader = ImageReader.newInstance(mWindowWidth, mWindowHeight, 0x1, 2);
        DisplayMetrics displayMetrics = new DisplayMetrics();
        mWindowManager.getDefaultDisplay().getMetrics(displayMetrics);
        mScreenDensity = displayMetrics.densityDpi;

    }
//开始截图
    private void startCapture() {
        mImageName = "截图" + System.currentTimeMillis() + ".png";
        Log.i(TAG, "image name is : " + mImageName);
        Image image = mImageReader.acquireLatestImage();
        if (image == null) {
            Log.e(TAG, "image is null.");
            return;
        }
        int width = image.getWidth();
        int height = image.getHeight();
        final Image.Plane[] planes = image.getPlanes();
        final ByteBuffer buffer = planes[0].getBuffer();
        int pixelStride = planes[0].getPixelStride();
        int rowStride = planes[0].getRowStride();
        int rowPadding = rowStride - pixelStride * width;
        mBitmap = Bitmap.createBitmap(width + rowPadding / pixelStride, height, Bitmap.Config.ARGB_8888);
        mBitmap.copyPixelsFromBuffer(buffer);
        mBitmap = Bitmap.createBitmap(mBitmap, 0, 0, width, height);
        image.close();

        stopScreenCapture();
        saveToFile();
    }

    private void stopScreenCapture() {
        if (mVirtualDisplay != null) {
            mVirtualDisplay.release();
            mVirtualDisplay = null;
        }
    }
//保存图片的功能
    private void saveToFile() {
        try {
            File fileFolder = new File(mImagePath);
            if (!fileFolder.exists())
                fileFolder.mkdirs();
            File file = new File(mImagePath, mImageName);
            if (!file.exists()) {
                Log.d(TAG, "file create success ");
                file.createNewFile();
            }
            FileOutputStream out = new FileOutputStream(file);
            mBitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
            out.flush();
            out.close();
            Log.d(TAG, "file save success ");
            Toast.makeText(this.getApplicationContext(), "截图成功", Toast.LENGTH_SHORT).show();
        } catch (IOException e) {
            Log.e(TAG, e.toString());
            Toast.makeText(this.getApplicationContext(), "截图失败", Toast.LENGTH_SHORT).show();
            e.printStackTrace();
        }
    }
//在result中处理 获取到的截屏图片
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        MediaProjection mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data);
        if (null != mediaProjection) {

            mVirtualDisplay = mediaProjection.createVirtualDisplay("ScreenCapture", mWindowWidth, mWindowHeight, mScreenDensity
                    , DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, mImageReader.getSurface(), null, null);
            startCapture();
        }
    }

本来是想如果这种方法可行,那么就让老板 更新一波机器。毕竟现在4.0-4.3版本的Android系统机器已经不常见了。还有就是低版本Android系统有局限性。很多新功能,新技术在上面用不了。然而我的想法还是太天真,虽然说实现了Service后台截屏。但是这个方法有个很坑的地方,每次截屏都会跳出一个弹框,必须用户手动点击 “开始截屏” 后才可以用。而且弹框是不能取消的。第四中方法,pass。

第四次测试

经过前几次的失败,说实话我已经想放弃了。这时候突然间想到,Android 的 adb 命令既然说 可以通过 adb 命令进行apk的安装、卸载,app应用的强制打开和关闭。那为什么不能 通过 adb 命令来进行 截屏呢?于是网上搜了一下,你别说还真的可以用。

adb shell screencap -p /sdcard/pic.png  //一句命令搞定

在pc上测试成功后,便开始在Service中进行实验

public static void savecreen(String name) {
        String cmd="screencap -p /sdcard/pic/"+name+".png";
        try {
            // 权限设置
            Process p = Runtime.getRuntime().exec("su");
            // 获取输出流
            OutputStream outputStream = p.getOutputStream();
            DataOutputStream dataOutputStream = new DataOutputStream(
                    outputStream);
            // 将命令写入
            dataOutputStream.writeBytes(cmd);
            // 提交命令
            dataOutputStream.flush();
            // 关闭流操作
            dataOutputStream.close();
            outputStream.close();
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }

经过测试,可用。第五中方法可行。

总结

最后说一点,通过shell 命令进行截屏 操作并不是在 所有的机器上都可行了。首先,你必须要保证你的机器的root的,如果没root一切免谈。当然了,也不是说 root 的机器就一定能用。经过,各Android系统机型的测试。这个方法在Android 8.0及以上的系统 好像并不适用(这里我指的是root的Android手机)。(因为,我们公司Android小屏和机顶盒都是定制的机器,系统也是根据要求刷的并且机器也都是root的。所以并不会出现Android 8.0及以上的系统不适用的问题)。
额,就这样吧。写的比较烂,将就看吧。

你可能感兴趣的:(Android后台截屏功能)