Android如何在已Root的设备上截屏

9
更新: 有很多其他帖子都在问如何在Android上获取屏幕截图,但似乎没有一个完整的答案。最初,我发布了这篇文章作为一个问题,因为我尝试打开一个与Frame Buffer相关的流时遇到了特定的问题。现在我已经转而将Frame Buffer转储到文件中,所以我更新了我的帖子来展示我是如何做到的。供参考(并致谢),我从这篇文章中找到了将Frame Buffer发送到文件的命令(不幸的是他没有提供他是如何到达那一步的)。我只是不知道如何将我从Frame Buffer中提取的原始数据转换成实际的图像文件。

我的意图是对Android设备上的实际屏幕进行全面转储。我发现唯一的方法是直接访问系统的Frame Buffer,而不使用adb桥接。显然,这种方法需要设备和运行它的应用程序具有root权限!幸运的是,对于我的目的,我可以控制设备的设置,并且通过设备获得root权限并提供给我的应用程序是可行的。我的测试目前正在运行2.2.3的旧版Droid上进行。

我从https://dev59.com/wm445IYBdhLWcg3wLXMK#6970338中找到了第一条接近的线索。经过更多的研究,我发现另一篇文章,描述了如何正确地以root身份运行shell命令。他们用它来执行重新启动,我用它来将当前的frame buffer发送到实际的文件中。我的当前测试只是通过ADB和一个基本的Activity(每个都提供root)进行了这样的操作。我将从后台运行的Service进行进一步的测试,敬请期待!下面是可以将当前屏幕导出到文件的整个测试活动:

public class ScreenshotterActivity extends Activity {
    public static final String TAG = "ScreenShotter";

    private Button _SSButton;
    private PullScreenAsyncTask _Puller;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);


        _SSButton = (Button)findViewById(R.id.main_screenshotButton);
        _SSButton.setOnClickListener(new OnClickListener() {

            public void onClick(View v) {
                if (_Puller != null)
                    return;
                //TODO: Verify that external storage is available! Could always use internal instead...

                _Puller = new PullScreenAsyncTask();
                _Puller.execute((Void[])null);
            }
        });
    }

    private void runSuShellCommand(String cmd) {
        Runtime runtime = Runtime.getRuntime();
        Process proc = null;
        OutputStreamWriter osw = null;
        StringBuilder sbstdOut = new StringBuilder();
        StringBuilder sbstdErr = new StringBuilder();

        try { // Run Script
            proc = runtime.exec("su");
            osw = new OutputStreamWriter(proc.getOutputStream());
            osw.write(cmd);
            osw.flush();
            osw.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            if (osw != null) {
                try {
                    osw.close();
                } catch (IOException e) {
                    e.printStackTrace();                    
                }
            }
        }

        try {
            if (proc != null)
                proc.waitFor();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        sbstdOut.append(readBufferedReader(new InputStreamReader(proc.getInputStream())));
        sbstdErr.append(readBufferedReader(new InputStreamReader(proc.getErrorStream())));
    }

    private String readBufferedReader(InputStreamReader input) {

        BufferedReader reader = new BufferedReader(input);
        StringBuilder found = new StringBuilder();
        String currLine = null;
        String sep = System.getProperty("line.separator");
        try {
            // Read it all in, line by line.
            while ((currLine = reader.readLine()) != null) {
                found.append(currLine);
                found.append(sep);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                reader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return null;
    }

    class PullScreenAsyncTask extends AsyncTask<Void, Void, Void> {

        @Override
        protected Void doInBackground(Void... params) {

            File ssDir = new File(Environment.getExternalStorageDirectory(), "/screenshots");
            if (ssDir.exists() == false) {
                Log.i(TAG, "Screenshot directory doesn't already exist, creating...");
                if (ssDir.mkdirs() == false) {
                    //TODO: We're kinda screwed... what can be done?
                    Log.w(TAG, "Failed to create directory structure necessary to work with screenshots!");
                    return null;
                }
            }
            File ss = new File(ssDir, "ss.raw");            
            if (ss.exists() == true) {
                ss.delete();
                Log.i(TAG, "Deleted old Screenshot file.");
            }
            String cmd = "/system/bin/cat /dev/graphics/fb0 > "+ ss.getAbsolutePath();
            runSuShellCommand(cmd);
            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            super.onPostExecute(result);
            _Puller = null;
        }
    }
}

这还需要在清单文件中添加android.permission.WRITE_EXTERNAL_STORAGE权限,如此帖子所建议的。否则,应用程序运行时不会报错,但也不会创建目录或文件。
最初我无法从帧缓冲区获取可用数据,因为我不了解如何正确运行 shell 命令。现在,我已经切换到使用流来执行命令,可以使用'>'将帧缓冲区的当前数据发送到实际文件中...

1
感谢您的编码。然而,.raw文件可能存在问题。我试图使用Google Picassa打开.raw图像文件,但它说有一部分Picassa无法读取。你知道问题在哪里吗?我该如何得到正常的图像文件?谢谢。 - user1468308
@swk 这不是一个简单的问题。我的要求更多地是确定连续屏幕截图之间的差异,因此我还没有完全确定如何将它们转换回图像!我建议在 Stack Overflow 的其余部分进行彻底搜索,特别是关于如何将在 /dev/graphics/fb0 找到的数据转换为图像的方法。 - cgolden
@swk 你找到将帧缓冲转换为图像文件的方法了吗? - Yogesh Maheshwari
3个回答

10

您可以通过编程以以下方式运行“adb shell /system/bin/screencap -p /sdcard/img.png”:

Process sh = Runtime.getRuntime().exec("su", null,null);
OutputStream  os = sh.getOutputStream();
os.write(("/system/bin/screencap -p " + "/sdcard/img.png").getBytes("ASCII"));
os.flush();
os.close();
sh.waitFor();

你使用的是哪个安卓版本? - Viswanath Lekshmanan
1
好的,你没有解释,实际上KitKat版本存在一个问题,涉及到<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />权限。你能否请参考一下关于这个权限在KitKat版本上的问题? - Viswanath Lekshmanan
非常适用于GoogleTango。 - henderso
使用sh.waitFor()的目的是什么?此外,这段代码对我有效,但我想在大约10秒钟后再次拍摄另一张截图,但当我再次尝试时,它不起作用。 - Susheel
1
没关系:它在我root的设备上完美运行。谢谢! - Susheel
显示剩余6条评论

5
一种适用于ICS设备的简单解决方案是使用以下命令行:
adb shell /system/bin/screencap -p /sdcard/screenshot.png
adb pull /sdcard/screenshot.png screenshot.png

这将在当前目录保存screenshot.png文件。

已在运行4.0.3的三星Galaxy SII上进行测试。


这个问题与从独立机器工作无关。这纯粹是针对设备上的现场情况。ADB很好,但在这种情况下对我没有任何帮助。 - cgolden

3
这对于不同的手机会有所不同。它取决于您设备的底层图形格式。您可以使用系统调用来轮询图形格式。如果您只打算在您知道图形格式的设备上运行此操作,那么您可以编写一个转换器将其转换为已知格式。
您可以查看以下项目:http://code.google.com/p/android-fb2png/ 如果您查看fb2png.c的源代码,您会发现他们轮询了包含关于设备如何将屏幕图像存储在内存中的信息的FBIOGET_VSCREENINFO。一旦您知道了这个,您应该能够将其转换成您可以使用的格式。
希望这能帮到您。

那看起来是解决这个问题最有可能的途径。由于似乎没有人对这个话题表现出进一步的兴趣,我将其标记为答案。如果有人能够完全解决这个问题,那真的太好了,但不幸的是我现在有其他事情要忙! - cgolden

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接