最近公司领导要求我做一个截屏的功能,说是为了方便监控小屏。本来以为没什么难度,然后就答应了下来。谁知道全都是坑。
这里要说明一点,我这里做的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及以上的系统不适用的问题)。
额,就这样吧。写的比较烂,将就看吧。