AndroidAndroid 基础知识安卓开发相关

Android开罐头——WebView高可扩展性封装(三)

2017-08-02  本文已影响259人  youyuge

阅读之前推荐阅读博客大佬的这2篇
Android开发:最全面、最易懂的Webview使用详解
最全面总结 Android WebView与 JS 的交互方式

本文作者: @youyuge
个人博客站点: https://youyuge.cn
参考自imooc实战课程,感谢猿猿老师,另外猿猿老师让我发一下课程链接~~(笑,非广告)http://coding.imooc.com/learn/list/116.html

一、回顾与规划

回顾一下,我们在第一、二章中已经完成了一些封装:

Android开罐头——WebView高可扩展性封装(一)
Android开罐头——WebView高可扩展性封装(二)

高级架构通信图

可是,我们上节课中,我们用内部匿名类的方式,简单的给默认子类设置了一个简陋的WebViewClient,我们这节课想建立一个单独的默认WebViewClientDefault.java类,在里面完成网页的loading界面。


基本的封装到这里正式完结了~~~可是,我们还有业务需求呢!

终于,我们迎来了业务需求,我们封装的一套东西终于要有用武之地了!简单地说,根据我们的业务需求,我们创立了两个文件,一个是特殊的webView碎片,一个是特殊的webViewClient,具体关系如下图,具体的业务实现我们正文再说:

最终的架构实现图

二、默认Client的实现——WebViewClientDefault.java


/**
 * @function webView加载进度回调
 * Created by 尤晟 on 2017-08-02.
 */

public interface IPageLoadListener {

    void onLoadStart();

    void onLoadEnd();
}
/**
 * @function 默认的 WebViewClient实体类,页面内跳转,带loading
 * Created by 尤晟 on 2017-08-02.
 */

public class WebViewClientDefault extends WebViewClient {
    protected final WebDelegate DELEGATE;
    private IPageLoadListener mIPageLoadListener = null;
    //这是我之前自己封装好的一个全局Handler
    private Handler HANDLER = Latte.getHandler();

    public void setIPageLoadListener(IPageLoadListener mIPageLoadListener) {
        this.mIPageLoadListener = mIPageLoadListener;
    }

    public WebViewClientDefault(WebDelegate DELEGATE) {
        this.DELEGATE = DELEGATE;
    }

    //表示重定向和url跳转,由这个webView自己来处理,不会打开外部浏览器
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        return false;
    }

    //页面开始加载时回调(页面后退也会回调)
    @Override
    public void onPageStarted(WebView view, String url, Bitmap favicon) {
        super.onPageStarted(view, url, favicon);
        if(mIPageLoadListener!=null){
            mIPageLoadListener.onLoadStart();
        }
        //之前自己封装的loading工具类,大家可替换成自己的,github多得是
        LatteLoader.showLoading(view.getContext());
    }

    //页面完成加载时回调(返回页面也会回调)
    @Override
    public void onPageFinished(WebView view, String url) {
        super.onPageFinished(view, url);
        if(mIPageLoadListener != null){
            mIPageLoadListener.onLoadEnd();
        }
        //加一个延时,让效果更加明显,可去除
        HANDLER.postDelayed(new Runnable() {
            @Override
            public void run() {
                //之前自己封装的loading工具类,大家可替换成自己的,github多得是
                LatteLoader.stopLoading();
            }
        },600);

    }
}

//WebDelegateDefault中进行改动
 @Override
    public WebViewClient initWebViewClient() {
        WebViewClientDefault clientDefault = new WebViewClientDefault(this);
        //设置页面start和end时候的回调,这里我们只是留了个接口,以后之后扩展,所以传入null了
        clientDefault.setIPageLoadListener(null);
        return clientDefault;
    }

至此,基础架构全部封装完毕,当然js和native交互方面还可以继续来封装,以后再说。现在,我们把焦点放到业务实现上。

三、业务需求实现

3.1 业务需求与效果图

我们现在默认的是点击页面内链接后直接在内部跳转,但是,第一次点击不应该是这样。比如淘宝,京东的发现页面,上下滑动的时候,是看的到底部的那个tab的,而第一次点击某个链接后,会跳转到一个全屏的WebView,之后的链接跳转全部在这个WebView里实现,后退键也是回退到上一个页面,最后才返回到我们的app主界面,最终效果如下gif图所示:

业务实现效果图

3.2 需求分析与实现方案

3.3 代码实现

经过上面的实现分析,我们发现,要实现点击页面内链接后,开启一个新的WebDelegateDefault功能。大家都想到了,重写shouldOverrideUrlLoading方法不就分分钟搞定了嘛~确实如此,需要改动的地方有:

分析完毕,好像就这两个点要改动,那么接下来就轻而易举了,建立两个类,WebDelegateFirstWebViewClientFirst

WebDelegateFirst 中,代码真是干净的不行,多亏了我们之前的封装:

/**
 * @function 默认的特殊具体实现子类, 首次点击内部链接后会开启新的Delegate
 * Created by 尤晟 on 2017-07-30.
 */

public class WebDelegateFirst extends WebDelegateDefault {

    public static WebDelegateFirst create(String url) {
        final Bundle bundle = new Bundle();
        bundle.putString(RouteKeys.URL.name(), url);
        final WebDelegateFirst delegate = new WebDelegateFirst();
        delegate.setArguments(bundle);
        return delegate;
    }


    @Override
    public WebViewClient initWebViewClient() {
        final WebViewClientFirst client = new WebViewClientFirst(this);
        return client;
    }

    @Override
    public boolean onBackPressedSupport() {
        return false;
    }

}

重头戏WebViewClientFirst来了,写之前,大家要注意shouldOverrideUrlLoading这个方法有大坑!!!

shouldOverrideUrlLoading方法触发的条件有:

注意,loadUrl方法不会触发 shouldOverrideUrlLoading方法。那么问题来了,当我们在 WebViewClientFirstshouldOverrideUrlLoading方法中,start第二个WebDelegate(即WebDelegateDefault),理论上是没问题的。
可是如果遇到了首页重定向,我们的第一个WebDelegateFirst 还没加载完就会触发 shouldOverrideUrlLoading方法,就会立刻在第二个WebDelegate中开启页面,我们的第一个WebDelegateFirst 会成空白页面。

举个例子,我们让WebDelegateFirstloadUrl("m.baidu.com");一切都正常,因为这个过程不会触发 shouldOverrideUrlLoading方法,只触发onPageStartedonPageFinished。我们点击了某个页面内url后,才会执行shouldOverrideUrlLoading方法,然后在这个方法中截获url,开启第二个全屏WebDelegateDefault

而我们让WebDelegateFirstloadUrl("www.baidu.com");的时候,问题来了,它会立刻自己重定向到"m.baidu.com"手机版百度,而这个重定向是会触发 shouldOverrideUrlLoading方法的,导致立刻会开启第二个全屏WebDelegateDefault,显示"m.baidu.com"页面,第一个WebDelegateFirst是白屏。
在这个情况下,方法调用顺序为:onPageStarted--->shouldOverrideUrlLoading--->onPageStarted--->onPageFinished。

因此,重定向问题解决方案:设置一个false标志位,只有经过了onPageFinished方法,才变为为true。而只有true时候,shouldOverrideUrlLoading方法才拦截。


/**
 * @function 一个client的具体特殊实现
 * Created by 尤晟 on 2017-07-30.
 */

public class WebViewClientFirst extends WebViewClientDefault {
    //过滤重定向。应用内部页面是否加载完毕,因为重定向也会触发shouldOverrideUrlLoading方法
    private boolean isPageOk = false;

    public WebViewClientFirst(WebDelegate DELEGATE) {
        super(DELEGATE);
    }


    // 复写shouldOverrideUrlLoading()方法,
    //若没有设置 WebViewClient 则在点击链接之后由系统处理该 url,通常是使用浏览器打开或弹出浏览器选择对话框。
    // 若设置 WebViewClient 且该方法返回 true ,则说明由应用的代码处理该 url,WebView 不处理。
    // 若设置 WebViewClient 且该方法返回 false,则说明由 WebView 处理该 url,即用 WebView 加载该 url。
    //使用旧方法,兼容旧机型,在网页上的所有加载都经过这个方法,这个函数我们可以做很多操作。
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        LatteLogger.d("shouldOverrideUrlLoading", url);
        if (isPageOk) {
            return Router.getInstance().handleWebUrl(DELEGATE, url);
        } else
            return false;
    }

    @Override
    public void onPageStarted(WebView view, String url, Bitmap favicon) {
        LatteLogger.d("onPageStart:" + url);
        super.onPageStarted(view, url, favicon);
    }

    @Override
    public void onPageFinished(WebView view, String url) {
        LatteLogger.d("onPageFinish: " + url);
        isPageOk = true;
        super.onPageFinished(view, url);
    }
}
 public final boolean handleWebUrl(WebDelegate webDelegate, String url) {
        //如果js里包含电话协议
        if (url.contains("tel:")) {
            callPhone(webDelegate.getContext(), url);
            return true;
        }

        //必须要是第一个WebDelegate的父布局开启一个新的delegate,这样才是全屏,否则还是下面有bar,上面有toolbar
        final LatteDelegate topDelegate = webDelegate.getTopDelegate();

        final WebDelegate toWebDelegate = WebDelegateDefault.create(url);
        topDelegate.getSupportDelegate().start(toWebDelegate);
        return true;
    }

 private void callPhone(Context context, String uri) {
        final Intent intent = new Intent(Intent.ACTION_DIAL);
        final Uri data = Uri.parse(uri);
        intent.setData(data);
        ContextCompat.startActivity(context, intent, null);
    }

四、总结

基本的封装到此结束,可能还会出一篇有关js和native交互的进一步封装,如果大家看到这里了,那就别吝啬,点个喜欢,点个关注吧~~嘿嘿嘿嘿

上一篇 下一篇

猜你喜欢

热点阅读