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

阅读之前推荐阅读博客大佬的这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交互的进一步封装,如果大家看到这里了,那就别吝啬,点个喜欢,点个关注吧~~嘿嘿嘿嘿

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容