- 普通
WebView
如何截取长图 - 针对
X5内核中WebView
如何截取长图
日常开发中,遇到为WebView
截取长图算是一种常见的需求。网上聪明的程序员们提供了多种截取WebView
长图的方法,这为我们的开发提供了很多便利。现在,也有很多APP是集成了X5内核的,网上对于X5内核的截长图方案介绍比较少,所以这里我整理了对WebView
截取长图的比较通用可行的方法,并且对使用了x5内核的WebView
的截图方法进行分享。
普通WebView
截长图方案
普通WebView
截取长图,这里是指项目中没有集成X5内核的情况。利用Google
文档上的api可以顺利截图。以Android5.0
为版本分界线,截图采用不同的处理方式。
1. Android5.0以下版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
/** * 对WebView进行截屏,虽然使用过期方法,但在当前Android版本中测试可行 * * @param webView * @return */ private static Bitmap captureWebViewKitKat(WebView webView) { Picture picture = webView.capturePicture(); int width = picture.getWidth(); int height = picture.getHeight(); if (width > 0 && height > 0) { Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); Canvas canvas = new Canvas(bitmap); picture.draw(canvas); return bitmap; } return null; } } |
2. Android5.0及以上版本
在Android5.0及以上版本,Android对WebView
进行了优化,为了减少内存使用和提高性能,使用WebView
加载网页时只绘制显示部分。如果我们不做处理,仍然使用上述代码截图的话,就会出现只截到屏幕内显示的WebView
内容,其它部分是空白的情况。
这时候,我们通过调用WebView.enableSlowWholeDocumentDraw()
方法可以关闭这种优化,但要注意的是,该方法需要在WebView
实例被创建前就要调用,否则没有效果。
另外这个方法一旦开启,会影响到整个进程中的WebView
实例,并且没有办法关闭。
这个代码的本质是设置了一个全局变量,并且没有提供关闭接口。其真实调用的代码如下:
1 2 3 4 |
private static boolean sRecordWholeDocumentEnabledByApi = false; static void enableSlowWholeDocumentDraw() { sRecordWholeDocumentEnabledByApi = true; } |
我们在WebView
实例被创建前加入代码:
1 2 3 |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { android.webkit.WebView.enableSlowWholeDocumentDraw(); } |
另外,当应用存在多个进程的时候,比如消息推送进程,LBS定位进程存在的情况下,务必确保只在主进程中初始化这个设置,否则运行时可能报错。
根据Google
文档中描述,capturePicture()
方法已不鼓励使用,推荐我们通过webView
的onDraw(Canvas)
去获取图像,所以这里我们去拿到网页的宽高后,就调用webView.draw(Canvas)
方法生成webView
截图。
1 2 3 4 5 6 7 8 9 |
private void captureWebViewLollipop(WebView webView) { float scale = webView.getScale(); int width = webView.getWidth(); int height = (int) (webView.getContentHeight() * scale + 0.5); Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); Canvas canvas = new Canvas(bitmap); webView.draw(canvas); return bitmap; } |
X5内核截取长图
使用X5内核截取长图有两种方法,并且都可以不用考虑版本问题,这为我们提供了方便。在X5内核下,如果使用WebView
的onDraw(Canvas)
方法,会出现或多或少的问题,所以对这个方法弃坑了。以下是两个截图方法:
1. 使用X5内核方法snapshotWholePage(Canvas, boolean, boolean)
在X5
内核中提供了一个截取整个WebView
界面的方法snapshotWholePage(Canvas, boolean, boolean)
,但是这个方法有个缺点,就是不以屏幕上WebView
的宽高截图,只是以WebView
的contentWidth
和contentHeight
为宽高截图,所以截出来的图片会不怎么清晰,但作为缩略图效果还是不错了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
private static Bitmap captureX5WebViewUnsharp(Context context, WebView webView) { if (webView == null) { return null; } if (context == null) { context = webView.getContext(); } int width = webView.getContentWidth(); int height = webView.getContentHeight(); Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); Canvas canvas = new Canvas(bitmap); webView.getX5WebViewExtension().snapshotWholePage(canvas, false, false); return bitmap; } |
2. 使用capturePicture()
截取清晰长图
如果想要在X5
内核下截到清晰的长图,不能使用snapshotWholePage()
,依然可以采用capturePicture()
。X5内核下使用capturePicture()
进行截图,可以直接拿到WebView
的清晰长图,但这是个Deprecated
的方法,使用的时候要做好异常处理。
总结
以上是WebView
截长图方法的总结和分享,对X5内核的截图也是尝试了多种途径最后找到满意的解决方案。另外,截长图会占用大量内存,容易触发OOM,所以代码中也要注意对OOM的处理。
在使用了X5
内核的项目中,使用WebView
截取长图的判断逻辑可以是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// 有x5内核没有生效,并且Android版本是5.0及以上时,调用enableSlowWholeDocumentDraw()方便截取长图 if (!isX5Enabled() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { android.webkit.WebView.enableSlowWholeDocumentDraw(); } /* 创建WebView ×/ ... // 网页截图 public void captureWholePage() { try { Bitmap bitmap = captureWebView(); /* 对拿到的bitmap根据需要进行处理 */ } catch (OutOfMemoryError oom) { /* 对OOM做处理 } } |
目前(2020/08/01
)之前版本的X5 SDK
,如果编译APK
的时候指定targetSdkVersion
版本高于 28
(Android O
)的情况下,调用snapshotWholePage(Canvas, boolean, boolean)
可能会无法获取到截图,图片内容全黑。
观察日志发生如下报错:
1 2 3 4 5 6 7 |
2020-07-31 17:14:18.538 16536-16536/com.xxxx.xxx W/com.xxxx.xxx: Accessing hidden field Landroid/graphics/Canvas;->mBitmap:Landroid/graphics/Bitmap; (greylist-max-p, reflection, denied) 2020-07-31 17:14:18.539 16536-16536/com.xxxx.xxx W/System.err: java.lang.NoSuchFieldException: No field mBitmap in class Landroid/graphics/Canvas; (declaration of 'android.graphics.Canvas' appears in /system/framework/framework.jar) 2020-07-31 17:14:18.540 16536-16536/com.xxxx.xxx W/System.err: at java.lang.Class.getDeclaredField(Native Method) 2020-07-31 17:14:18.540 16536-16536/com.xxxx.xxx W/System.err: at com.tencent.smtt.os.SMTTAdaptation.a(TbsJavaCore:115) 2020-07-31 17:14:18.540 16536-16536/com.xxxx.xxx W/System.err: at com.tencent.smtt.webkit.WebViewChromiumExtension.a(TbsJavaCore:2519) 2020-07-31 17:14:18.540 16536-16536/com.xxxx.xxx W/System.err: at com.tencent.tbs.core.webkit.tencent.TencentWebViewProxy.snapshotWholePage(TbsJavaCore:2366) 2020-07-31 17:14:18.540 16536-16536/com.xxxx.xxx W/System.err: at com.tencent.tbs.core.webkit.adapter.X5WebViewAdapter.snapshotWholePage(TbsJavaCore:851) |
原因为从API 29
(Android P
)开始,Google
对于某些反射调用私有方法的行为进行了限制,比如动态反射赋值android.graphics.Canvas.java
的私有变量mBitmap
。这些调用会被抛出异常阻止。