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后台截屏 为了在Android应用程序中实现后台自动截屏功能,有几种不同的方法可以选择。每种方法都有其特点和适用场景。 #### 方法一:使用`SurfaceControl.screenshot` 这种方法利用了系统的截屏API `SurfaceControl.screenshot()`,该函数可以直接获取整个屏幕的内容而无需用户交互[^2]: ```java Bitmap screenshot = SurfaceControl.screenshot(null); if (screenshot != null) { File file = new File(Environment.getExternalStorageDirectory(), "screenshot.png"); try (FileOutputStream out = new FileOutputStream(file)) { screenshot.compress(Bitmap.CompressFormat.PNG, 90, out); } } ``` 此方法的优点在于它不需要额外的权限声明,并且不会触发任何形式的通知给最终用户。然而需要注意的是,由于这是系统级别的调用,可能仅限于特定版本或设备上有效。 #### 方法二:基于命令行工具`screencap` 另一种常见的做法是在具有root权限的情况下执行shell命令来完成截图操作[^3]: ```java public String screenShot() { Log.i("TAG", "screenShot!"); @SuppressLint("SdCardPath") String capFile = "/sdcard/AIUI/ocr/pic.png"; String cmd = "screencap -p " + capFile; CmdUtils.fetchRoot(cmd); // 假设这是一个封装好的获得ROOT并执行指令的方法 return capFile; } ``` 这种方式相对简单直接,但是依赖于设备已经被授予超级用户访问权,这显然不是所有情况下都可行的选择。 #### 方法三:借助`MediaProjectionManager` 对于非root环境下的解决方案,则可以通过`MediaProjectionManager`配合`startActivityForResult()`发起一个用于请求屏幕共享权限的对话框[^4]: ```java private static final int REQUEST_CODE = 1; // 获取MediaProjectionManager实例 mediaProjectionManager = (MediaProjectionManager)getSystemService(Context.MEDIA_PROJECTION_SERVICE); // 创建意图对象 Intent intent = mediaProjectionManager.createScreenCaptureIntent(); startActivityForResult(intent, REQUEST_CODE); ``` 一旦获得了用户的许可之后就可以继续处理图像数据流了。不过这里涉及到隐私保护机制,因此必须先得到授权才能正常工作。 综上所述,在不考虑特殊条件的前提下推荐采用第三种方案——即通过`MediaProjectionManager`来进行合法合规的操作;而对于那些确实需要更灵活控制的应用来说,前两种技术或许更加合适一些。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值