美文网首页AndroidAndroid 基础知识安卓开发相关
Android开罐头——WebView高可扩展性封装(三)

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

作者: youyuge | 来源:发表于2017-08-02 20:54 被阅读259次

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

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

    一、回顾与规划

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

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

    高级架构通信图
    • 我们已经建立了图中全部的东西(除了最左边的那个特殊子类),现在我们已经可以正常的使用,new一个默认子类WebDelegateDefault,就能获得一个里面是webView的Fragment,并且,它已经初始化三部曲配置完毕,生命周期也根本无需我们担心~

    • WebViewClientDefault

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


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

    • WebDelegateFirst 与 WebViewClientFirst

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

    最终的架构实现图

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

    • 准确的说,它应该放在上一篇中来写,因为它算WebView初始化三部曲里的第二步。但是,上一篇篇幅过长,就拿到这次来说了。首先先写一个页面开始和结束的接口,方便以后用到:
    
    /**
     * @function webView加载进度回调
     * Created by 尤晟 on 2017-08-02.
     */
    
    public interface IPageLoadListener {
    
        void onLoadStart();
    
        void onLoadEnd();
    }
    
    • 下面就是我们的默认的Client——WebViewClientDefault的代码:
    /**
     * @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);
    
        }
    }
    
    
    • 别忘了,在我们的默认webDelegate中,要把client改成我们上面创建的默认client
    //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 需求分析与实现方案

    • 我们看到,一开始加载的webDelegate是在发现tab中,看的到toolbar和底部栏,但是当我们点击了某个链接后,就跳转到了一个新的全屏WebView中,之后的页面前进/回退都是在这个WebDelegate里面。

    • 经过观察,我们发现有两个WebDelegate,而第二个就是我们默认实现的WebDelegateDefault类。而第一个WebDelegate就是我们的特殊类,需要实现的特殊功能是:点击页面内链接后,开启一个新的WebDelegateDefault。

    • 我们再来看下架构图:


      最终的架构实现图

    3.3 代码实现

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

    • 重写shouldOverrideUrlLoading方法--->所以要新建一个特殊的Client类

    • 特殊的WebDelegateFirst类里,不用拦截返回键了:毕竟就一个页面,不用再点击返回键网页后退了。

    分析完毕,好像就这两个点要改动,那么接下来就轻而易举了,建立两个类,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);
        }
    }
    
    • Router路由类中增加开启第二个WebDelegateDefault的方法:
     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交互的进一步封装,如果大家看到这里了,那就别吝啬,点个喜欢,点个关注吧~~嘿嘿嘿嘿

    相关文章

      网友评论

        本文标题:Android开罐头——WebView高可扩展性封装(三)

        本文链接:https://www.haomeiwen.com/subject/cxvylxtx.html