安卓WebView的一点总结
最近的项目有一个页面类似于网易新闻的详情页,刚开始想用原生的activity来开发,后来发现里面的内容样式是要变化的,后台的MM不定时的更新里面的内容,那么如果用activity去网络请求数据的方法,在页面的样式编辑上就不好做,可能这一个页面需要有图片排版,下一个页面就没有图片了,或者多出个链接..这些需求很常见,所以就想着用html的形式来加载.
切入正题,webview的简单加载url就不多说了,这里给一下常见的初始化操作
//直接显示.
WebSettings settings = webView.getSettings();
settings.setDefaultTextEncodingName("utf-8");
settings.setSupportZoom(true);//设置缩放
settings.setBuiltInZoomControls(true);//缩放控制
settings.setUseWideViewPort(true);
settings.setLoadWithOverviewMode(true);
settings.setJavaScriptEnabled(true);
if (NetWorkUtils.isNetWorkEnable(HaotouguApp.getInstance())) {
settings.setCacheMode(WebSettings.LOAD_DEFAULT);
} else {
settings.setCacheMode(WebSettings.LOAD_CACHE_ONLY);
}
settings.setDomStorageEnabled(true);
settings.setUseWideViewPort(true); //将图片调整到适合webview的大小
settings.setLoadWithOverviewMode(true); // 缩放至屏幕的大小
settings.setDatabaseEnabled(true); //开启 database storage API 功能
settings.setAppCacheEnabled(true);//开启 Application Caches 功能
webView.setWebViewClient(new MyWebViewClient());
webView.setWebChromeClient(new MyWebChromeClient());
webView.clearCache(true);
webView.addJavascriptInterface(new WebAppInterfaceForHtml(), "WebAppInterfaceForHtml");
mPresenter.setHtml(id, time);
//监听返回键
webView.setOnKeyListener((v, keyCode, event) -> {
if (keyCode == KeyEvent.KEYCODE_BACK && webView.canGoBack() && KeyEvent.ACTION_UP == event.getAction()) {
webView.goBack();
return true;
}
return false;
});
这里面的有些不常用的也都有注释,一看便知,这是初始化webview的操作,那么webview的两个比较重要的管理类,不得不提一下
WebViewClient和WebChromeClient这两个方法的区别不懂可以谷歌,那么WebViewClient中复写的方法,有以下这几个常见的
1.主要是拦截webview请求的html中的资源,如css,js,图片等
@Override
public WebResourceResponse shouldInterceptRequest(WebView webView, String s) {
return super.shouldInterceptRequest(webView, s);
}
2.这个有注释,主要是让html的跳转在当前app中,有的机型,如果不复写这个方法,可能就会在webview中再请求一个html会跳转到手机的浏览器中
//指定链接在当前app中打开,否则在浏览器中打开
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
return true;
}
3.html开始加载的回调,我在这里面做了个简单的webview超时操作,用的rxjava的timer操作符,当加载开始之后,如果进度不到100%且没有走缓存,那么我就判断其超时,关闭本次加载,然后显示重新加载的按钮
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
KLog.e(TAG,"开始加载!!!");
av_load.show();
webView.setEnabled(true);
ll_reload.setVisibility(View.GONE);
mSubscribe = Observable.timer(10 * 1000, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.unsubscribeOn(AndroidSchedulers.mainThread())
.subscribe(call -> {
KLog.e(TAG,"达到超时时间限制,加载进度:"+webView.getProgress());
if (webView.getProgress() < 100 && !mPresenter.hasCache()) {
showNetError();
}
});
}
4.页面加载完成,可以展示了,不多说
@Override
public void onPageFinished(WebView view, String url) {
}
5.这是两个回调,请求出错的时候的回调,一个http的,一个https的
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
if (!mPresenter.hasCache()) {
showNetError();
}
}
@Override
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
super.onReceivedError(view, request, error);
if (!mPresenter.hasCache()) {
showNetError();
}
}
WebChromeClient有以下的方法,其实这个实现类比较常用,里面的一些实现方法可以自行谷歌看看,还是比较全面的
1.我只用了这一个方法,就是加载进度,可以在这里获取到当前webview页面的加载进度
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
if (newProgress == 100) {
if (null != mPresenter) {
mPresenter.getImage();
mTextJs = mPresenter.getTextJs();
mClickJs = mPresenter.getClickJs();
mShareClickJs = mPresenter.getShareClickJs();
mItemClickJs = mPresenter.getItemClickJs();
}
if (null != mTextJs && null != mClickJs) {
//注入js
webView.loadUrl("javascript:" + mTextJs);
webView.loadUrl("javascript:" + mClickJs);
//回调js的onClick
String injectImgClick = "javascript:injectImgClick()";
webView.loadUrl(injectImgClick);
}
//注入分享按钮的js
if (null != mShareClickJs) {
webView.loadUrl("javascript:" + mShareClickJs);
}
//注入item点击的js
if (null != mItemClickJs) {
webView.loadUrl("javascript:" + mItemClickJs);
}
//缓存当前页数据
mPresenter.saveTimeCache(time, id);
}
}
以上就是初始化的一些代码和必要的配置,下面说一下我做这个页面的思路:
最核心的思路就是将这个html页面的相关资源保存到本地(html,css,js,固定的图片如:分享按钮图片等等),在启动这个webview页面的时候,请求接口只拿里面的文章和图片的链接,而且文章通过接口请求一次之后,缓存到本地,图片链接下载下来之后也缓存到本地,那么这样第二次加载速度就特别快,几乎接近原生的页面
这个思路也是现在的一些新闻类APP的详情页面的用的比较多的,可能有大神觉得我这样的方法比较low,有更好的,那就当我没说 ,哈哈,至少我目前想到的就这些,希望能有大神支招,告诉更好的方法
webview如何加载本地资源(html,css,js,图片等),搜一下,发现webview加载的资源,只要符合file:///android_asset/xxx就可以,也就是说webview.load(“file:///android_asset/xxx”)就能加载一个本地存储的html页面,那么有了这个,就好说了;
我将这个详情页面的html,css,js,分享的icon全部放在main/assets下面了,在当前webview初始化完之后,就去请求接口,我的presenter实现类里面做了如下判断:
拿着该条详情页的时间戳和id(主要做缓存,后面说)去数据库中查询,如果有,说明之前请求过接口,数据已经缓存到本地了(我指的是文字内容和图片链接,有了链接也就相当于有了图片),我将时间戳的字符串存在了数据库,文本和链接存到了sp里面,因为数据库里面不想存储大量的文本,不知道会不会影响速度,个人理解可能会;有缓存就好办,直接去加载就可以了,这里又用了另一个加载方法 webView.loadDataWithBaseURL("file:///android_asset/index.html", html, "text/html", "UTF-8", null);
需要了解各参数的意思的,自行google
那么如果没找到时间戳,说明就没有缓存,这时候就去请求网络接口,请求网络接口就有点小复杂,第一,请求回来的只是文字内容和图片链接,我们需要自己将文字内容和图片链接拼接到本地放着的html中去,拼成完整的html文本形式,放到webview中加载,其实单独用webview加载一个url也是这样,webview会拿着这个html从网络请求完整的html页面,把html解析出来,显示到屏幕上.那么怎么拼呢,说着简单,做起来不简单,首先要读取本地html,
这有个工具,直接拿着用!
public static String getStringToAsset(InputStream inputStream) {
InputStreamReader inputStreamReader = null;
try {
inputStreamReader = new InputStreamReader(inputStream, "utf-8");
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
}
BufferedReader reader = new BufferedReader(inputStreamReader);
StringBuilder sb = new StringBuilder("");
String line;
try {
while ((line = reader.readLine()) != null) {
sb.append(line);
sb.append("\n");
}
} catch (IOException e) {
e.printStackTrace();
}
return sb.toString();
}
String html = FileUtils.getStringToAsset(mView.context().getAssets().open("index.html"));
这样就将assets中放得index.html以字符串的形式读取出来了,这个html就是我们全部index.html页面的内容,接下来就是拼接图片和显示占位图的时候了,比较恶心
这里我用了js回调的方法,就是当接口返回来文章和图片链接的时候,先不加载图片,因为加载图片很耗时,需要拿着这个图片url去下载,先使用一张占位图去占位,就像网易新闻详情页中那样,当图片下载完成之后,使用js去刷新一下页面的占位图,替换成我下载好的图片就OK了..完整的借用了网易新闻,哈哈
还要说一下,由于图片都是后台编辑的,给多大尺寸都是不一定了,所以这里就需要适配一下,传图片的时候默认计算出图片的宽和高一起通过接口传过来,在拿图片url的时候,一起拿到了这个宽和高,将占位图直接设置好
int w = (int) DPUtils.px2dp(mView.context(), AppUtils.getScreenWidth(mView.context()));
//如果图片宽大于屏幕宽,则等比例缩放图片
if (w < StringUtils.String2int(imgBean.getWidth())) {
imageWidth = w - 24;
imageHight = imageWidth * StringUtils.String2int(imgBean.getHeight()) / StringUtils.String2int(imgBean.getWidth());
} else {
imageWidth = StringUtils.String2int(imgBean.getWidth());
imageHight = StringUtils.String2int(imgBean.getHeight());
}
好了,设置好图片的大小,就要拼图片链接了,注意千万别拼错了,拼错了就不会显示了
String newImageChar = "<img width=" + "\"" + imageWidth + "px\"" + " height=" + "\"" + imageHight + "px\"" + " src= " + "file:///android_asset/gray_holder.png" + " asrc= " + "\"" + imgBean.getSrc() + "\"" + " />";
这里之所以有src和asrc是因为需要有对应关系,js需要这个对应关系,去刷新下载之后的图片;图片下载好之后是在缓存中,根据url有对应的缓存图片,相当于key,value的关系,拿着这两个url(原图url和缓存地址url)调用js的方法,就可以刷新这个图片,将占位图替换成新下载的图片,这是比较好的一种方法,解决了webview加载html不能缓存图片的问题!!!
拼接文字啥的就好说多了,将返回来的文本以heml中body的标签一样,拼接进去就好了
好了.基本拼接就这样,还有一个比较重要的,就是图片点击和链接跳转,这里就涉及到js和android交互了
对于安卓中响应js的方法回调很简单,只需要做如下操作:
webView.addJavascriptInterface(new WebAppInterfaceForHtml(), "WebAppInterfaceForHtml");
这个WebAppInterfaceForHtml的标志是跟js的工程师商量好的,js中需要这样写
function onOtherNewsClick(url) {
console.log(url);
window.WebAppInterfaceForHtml.onOtherNewsClick(url);
}
这是固定的写法,js中只有这样写,安卓中才能响应js中的function,然后安卓代码这样写
public class WebAppInterfaceForHtml {
//js加载图片替换的回调
@JavascriptInterface
public void repleaseImage(String pOldUrl, String pNewUrl) {
}
//js图片点击回调
@JavascriptInterface
public void getImageInfo(int x, int y, int width, int height, String src) {
if (null != src) {
webView.post(() -> {
ZoomImageActivity_.intent(mContext).extra("src", src).start();
});
}
}
//js图片点击回调
@JavascriptInterface
public void doShareClick(int type) {
webView.post(() -> {
mPresenter.doShare(type);
});
}
//item点击回调
@JavascriptInterface
public void onOtherNewsClick(String url) {
KLog.e(TAG, "item点击:url" + url);
webView.post(() -> {
UrlUtils.resolvingUrl(url, mContext);
});
}
}
注意这个里面有个webView.post(),不这样写不会执行,具体google
另外一个重点就是,我们的js是放在本地的,这里面图片的刷新,图片的点击都需要js去实现,那么就有js注入了,google一番,webview支持在html加载之后注入js,webview官方说需要在onPageFinished或者onProgressChande加载到100%之后注入js,这个也比较好理解,注入完成之后就可以响应js的点击和刷新等function了
好了,我能想到的就这么多,如果有错误,请指正