Android浅谈Webview的Loading
前言
在开发webview的loading效果的时候会有一些问题,这边记录一些碰到的常见的问题,并且设计出一套Loading的方案来解决相关的问题。
1. loading的选择
开发loading效果的原因在于webview加载页面的时候,有时候会耗时,导致不显示内容又没有任何提示,效果不太好,所以需要在webview使用的地方加上loading的效果,其实更好的体验是还要加上EmptyView,我这边主要就以loadingView来举例。
那开发这loading基本有两种方式,一种是使用window,也就是Dialog这些弹窗的方式,在加载时弹出弹窗,在加载结束后关闭弹窗,有些人可能会封装好一些loading弹窗,然后在这里复用。
这个方法的好处是如果你封装好了,能直接复用,省去很多代码。缺点也很明显,弹窗弹出的时候是否处于一个不允许交互的情况,如果这个流程有问题,那便一直无法和页面做交互
另一种方法是直接在webview的上层覆盖一个LoadingView,webview是继承FrameLayout,就是也可以直接addView。
这个方法的好处就是不会出现上面的问题,因为我webview所在的页面关闭了,它的loading也会跟着一起消失,而且显示的效果会好一些。缺点就是可能一些特殊的webview你会单独做操作,导致会多写一些代码
没有说哪种方法是实现会比较好,主要看使用的场景和具体的需求。
2. loading显示时机的问题
我们做loading的思路就是加载开始的时候显示,加载完成之后关闭,那选择这个开始的时机和结束的时机就比较重要了。
大多数人都会直接使用WebViewClient的onPageStarted回调作为开始时机,把onPageFinished的回调,觉得直接这样写就行了,无所谓,反正webview会出手。
这个思路确实能在正常的情况下显示正常,但是在弱网情况下呢?复杂的网络环境下呢?有些人可能也会碰到一些这样的情况,loading的show写在onPageStarted中,加载时会先白屏一下,才开始显示loading,但是这个白屏的时间很短,所以觉得无所谓。但有没有想过这在正常网络环境下的白屏一下放到复杂的有问题的网络环境中会被放大成什么样。
这个加载过程其实大体分为两个阶段,从loadurl到WebViewClient的onPageStarted和从WebViewClient的从onPageStarted到onPageFinished
所以我的做法是在loadurl的时候去start loading,而不是WebViewClient的onPageStarted回调的时候。
这个是开始的时机,那结束的时机会不会有问题,还真可能有,有时候你会发现一种现象,加载完之后,你的H5内容和loading会同时显示一段时间,才关闭loading(几年前有碰到过,写这篇文章的时候测试没有复现过,不知道是不是版本更新修复了这个问题)
那如果碰到这个问题该怎么解决呢?碰到这个问题,说明onPageFinished的回调时机在页面加载完之后,所以不可信。我们知道除了这个方法之外,BaseWebChromeClient也有个方法onProgressChanged表示加载的进度,当然这个进度你拿去判断也会有问题,因为它并不会每次都会回调100给你,可能有时候给你96,就没了。
我以前的做法是双重判断,判断是进度先返回>85还是onPageFinished先调用,只要有一个调用,我都会关闭loading
3. 体验优化
当然处理好显示的关闭的时机还不行,想想如果在loadurl中show loading会怎样,没错,就算网速快的情况,页面让loading一闪而过,那这样所造成的体验就很不好,所以我们需要做一个延迟显示,我个人习惯是延迟0.5秒。当然延迟显示也会有延迟显示的问题,比如延迟到0.3秒的时候你关闭页面怎么办,再0.2秒之后我总不不能让它显示吧。
说了显示,再说关闭。无论是onPageFinished方法还是onProgressChanged,你能保证它一定会有回调吗?这些代码都不是可控的,里面会不会出现既没抛异常,也没给回调的情况。也许有人说不会的,我都用了这么多年了,没出现过这种问题,但是既然不是我们可控的代码,加一层保险总没错吧。
其实这也简单,定一个timeout的逻辑就行,我个人是定义10秒超时时间,如果10秒后没有关闭loading,我就手动关闭并显示emptyview的error页面。这个超时时间还是比较实用,最上面说了loading的选择,如果你的loading做成view,那即便没有这个逻辑也影响不大,最多就会菊花一直转,但如果你是window做的,没有超时的处理,又没有回调,那你的window会一直显示卡住页面。
4. loading最终设计效果
基于上面的情况,我写个Demo,首先loading的选择,我选择基于view,所以要写个自定义View
public class WebLoadingView extends RelativeLayout {
private Context mContext;
// 0:正常状态;1:loading状态;2:显示loadingview状态
private AtomicInteger state;
private Handler lazyHandler;
private Handler timeOutHandler;
public BaseWebLoadingView(Context context) {
super(context);
init(context);
}
public BaseWebLoadingView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public BaseWebLoadingView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
this.mContext = context;
state = new AtomicInteger(0);
lazyHandler = new Handler(Looper.getMainLooper());
timeOutHandler = new Handler(Looper.getMainLooper());
initView();
}
private void initView() {
LayoutInflater.from(mContext).inflate(R.layout.demo_loading, this, true);
}
public void show() {
if (state.compareAndSet(0, 1)) {
lazyHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (state.compareAndSet(1, 2)) {
setVisibility(View.VISIBLE);
}
}
}, 500);
timeOutHandler.postDelayed(new Runnable() {
@Override
public void run() {
close();
}
}, 10000);
}
}
public void close() {
state.set(0);
setVisibility(View.GONE);
try {
lazyHandler.removeCallbacksAndMessages(null);
timeOutHandler.removeCallbacksAndMessages(null);
} catch (Exception e) {
e.printStackTrace();
}
}
}
代码应该都比较好理解,就不过多介绍了,然后在自定义webview的loadurl里面展示
@Override
public void loadUrl(String url) {
if (webLoadingView != null && !TextUtils.isEmpty(url) && url.startsWith("http")) {
webLoadingView.show();
}
super.loadUrl(url);
}
写这里主要是有个地方要注意,就是调方法时也会执行这个loadUrl,所以要判断是加载网页的时候才显示loading。
5. 总结
总结几个重点吧,第一个是对第三方的东西(webview这个也类似第三方吧,坑真的很多),我们没办法把控它的流程,或者说没办法把控它的生命周期,所以要封装一套流程逻辑去给调用端方便去使用。
第二个问题是版本的问题,也许会出现不同的版本所体现的效果不同,这个是需要留意的。
如果要完美解决这堆loading相关的问题,最好的方法就是看源码,你知道它里面是怎么实现的,为什么会出现onPageStarted之前还会有一段间隔时间,那就去看loadUrl和onPageStarted回调之间的源码,看它做了什么操作嘛。我个人是没看源码,所以这里只能说是浅谈。
链接:https://juejin.cn/post/7195393339691106364
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。