1、先简单介绍下webview截屏,看代码:
//开启缓存
webview.setDrawingCacheEnabled(true);
webview.buildDrawingCache();
Bitmap bm = webview.getDrawingCache();
...
保存图片代码部分已省略
...
//关闭缓存
webview.setDrawingCacheEnabled(false);
思路就是强制webview构建当前可视区域的缓存,然后读取缓存。
2、上边说的是截屏,下边来看下如何截长图,截长图的思路跟截屏类似,通过滚动webview滚动条来拼接视图缓存,具体的实现需要借助Canvas进行绘制。在绘制前,我们需要先获取当前html文档的真实宽度、高度,高度webview已经提供了方法:getContentHeight(),宽度比较复杂,我们需要借助js来获取。这里,我使用DSBridge框架实现JS交互,其他框架类似,我们的目的是获取当前文档的真实宽度,我们先注册一个JS回调接口供Native调用:
//注册一个横向滚动条宽度接口供Native调用
dsBridge.register('getScrollInfo',function(){
return {scrollWidth:document.body.scrollWidth};
});
这样,我们就能获得真实内容宽度了,下边的代码是Java 调用JS回调接口的逻辑:
//先获取内容宽度
webview.callHandler("getScrollInfo", null, new OnReturnValue<JSONObject>() {
@Override
public void onValue(final JSONObject retData) {
float scale = webview.getScale();
HashMap<String,Object> map = JsonUtils.parseJsonToMap(retData.toString());
float scrollWidth = Float.valueOf(map.get("scrollWidth").toString());
//真实内容宽度
final int contentWidth = (int)(scrollWidth * scale);
//真实内容高度
final int contentHeight = (int) (webview.getContentHeight() * scale);
final int oldScrollLeft = webview.getScrollX();
final int oldScrollTop = webview.getScrollY();
final int webviewWidth = webview.getWidth();
final int webviewHeight = webview.getHeight();
new Thread(new Runnable() {
@Override
public void run() {
int contentWidths = contentWidth;
int contentHeights = contentHeight;
//创建一张与WebView内容大小相同的位图
Bitmap bm = Bitmap.createBitmap(contentWidth, contentHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bm);
int heightTmp = contentHeights;
int widthTmp = contentWidths;
while (widthTmp > 0 ){
if (contentWidth < webviewWidth) {
contentWidths = 0;
} else {
if(contentWidths > webviewWidth)
{
contentWidths -= webviewWidth;
}
widthTmp -= webviewWidth;
}
heightTmp = contentHeights;
while (heightTmp > 0) {
if (contentHeight < webviewHeight) {
contentHeights = 0;
} else {
if (contentHeights > webviewHeight)
{
contentHeights -= webviewHeight;
}
heightTmp -= webviewHeight;
}
//保存状态
canvas.save();
//小于0说明快结束了,调整剪辑区域为剩余区域,避免被覆盖
if (widthTmp <= 0)
{
if (heightTmp <= 0)
{
//设置剪辑区域
canvas.clipRect(0, 0, contentWidths, contentHeights);
webview.scrollTo(0, 0);
}else
{
//设置剪辑区域
canvas.clipRect(0, contentHeights, contentWidths, contentHeights + webviewHeight);
webview.scrollTo(0, contentHeights);
}
}else
{
if (heightTmp <= 0)
{
//设置剪辑区域
canvas.clipRect(contentWidths, 0, contentWidths+webviewWidth, contentHeights);
webview.scrollTo(contentWidths, 0);
}else
{
//设置剪辑区域
canvas.clipRect(contentWidths, contentHeights, contentWidths+webviewWidth, contentHeights + webviewHeight);
webview.scrollTo(contentWidths, contentHeights);
}
}
webview.draw(canvas);
canvas.restore();
}
contentHeights = contentHeight;
}
webview.scrollTo(oldScrollLeft,oldScrollTop);
...
已忽略保存图片部分代码
...
}
}).start();
}
});
上边的代码,我们使用了2个while循环,外边的负责滚动横向滚动条,里边的负责滚动纵向滚动条。当然了,比较耗时的绘图操作我们放在了一个线程里头完成,最终将会形成一张真实缩放比例的大图,缩放比例越大,图片越大。
关于OOM问题:
图片过大一般会触发OOM,即内存不够用了,因为我们这个是合成图片,还是大图,所以内存消耗不可避免。简单快捷的办法是在AndroidManifest.xml文件中application节点添加属性android:largeHeap="true",这样能拿到App能用的最大内存,一般能拿到物理内存的1/8大小,比如4G内存的手机,我们一般能拿到500MB左右,具体大小也跟手机系统有关系。
为了防止App崩溃我们还需要增加内存使用量的检测代码,如果检测到内存不足,就停止相关操作即可,可参考下边的代码(对应上边的代码):
//先计算图片占用多少内存,避免OOM导致App崩溃,ARGB_8888格式每像素占用4字节
BigInteger big_W = BigInteger.valueOf(contentWidth);
BigInteger big_H = BigInteger.valueOf(contentHeight);
BigInteger big_P = BigInteger.valueOf(4);
big_W = big_W.multiply(big_H);
big_W = big_W.multiply(big_P);
//得到图片需要的内存
long needMemory = big_W.longValue();
//获取可用内存
Runtime runtime = Runtime.getRuntime();
//App可用的最大内存
long maxMemory = runtime.maxMemory();
//App已用的内存
long totalMemory = runtime.totalMemory();
if(needMemory > maxMemory - totalMemory)
{
callback.onCallBack(false,"亲,内存不够了哟~缩小点吧!");
return;
}
这段代码应该在绘图之前检测,这样App就不会触发OOM导致崩溃了。