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

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

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

一、回顾与规划

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

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

我们看一下我们的目前的架构图片:

初步架构通信图

我们已经实现了:

  • 抽象父类WebDelegate,用来管理webView的生命周期,以及初始化,保证不会内存泄漏,提供了一些get方法

  • 接口回调完成了,子类作为具体的实现类,要给我实现这个接口,也就是要完成三个方法,分别是初始化settings,设置client,以及chromeClientwebView设置的三部曲)

  • 创建js交互的本地对象类LatteWebInterface,算是为以后和js交互预留了地方,目前没用

本节我们想实现:

  • BaseDelegate
    先来对基类fragment封装一下下,让我们所有的fragment都继承于BaseDelegate,我们给它个新统称名字叫Delegate。(上一节中你可能已经发现,WebDelegate就已经继承于一个Delegate)

  • WebDelegateDefault
    父基类有了,我们想要一个默认的子Delegate,叫WebDelegateDefault吧!让它能够可以直接使用,也就是要实现接口,包括了初始化settings,设置client,以及chromeClient(webView设置的三部曲)。同时,别忘了还要实现它作为一个Delegate(fragment)应该实现的一些回调方法。

  • WebDelegateImpl
    默认的实现子类有了之后,其实已经可以使用,使用 方法类似于Android sdk中自带的WebViewFragment类。但是,我们还能建立一些特殊的子类继承于WebDelegateDefault,比如下图中的WebDelegateImpl,和具体业务有关,属于业务层,不想要默认的某些设置,就可以在这个类中override方法。由于是业务相关,我们放到第三篇来说,暂且不表。

高级架构通信图

二、基类fragment封装

这里说一下,由于本人使用了非常好用的fragmentation第三方库,所以基类BaseDelegate继承自SwipeBackFragment。如果不用这个吊炸天的开源库,直接继承原生的Fragment也是一样写的。BTW,还顺便封装了一下butterknife~~

public abstract class BaseDelegate extends SwipeBackFragment {

    @SuppressWarnings("SpellCheckingInspection")
    private Unbinder mUnbinder = null;

    //子类必须实现,可以返回一个layout的资源id,或一个view
    public abstract Object setLayout();

    //子类初始化时回调
    public abstract void onBindView(@Nullable Bundle savedInstanceState, View rootView);

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View rootView = null;
        Object mLayout = setLayout();
        if (mLayout instanceof Integer) {
            rootView = inflater.inflate((Integer) mLayout, container, false);
        } else if (mLayout instanceof View) {
            rootView = (View) mLayout;
        }

        if (rootView != null) {
            mUnbinder = ButterKnife.bind(this, rootView);
            onBindView(savedInstanceState, rootView);
        }

        return rootView;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        if (mUnbinder != null) {
            mUnbinder.unbind();
        }
    }

}
  • 这样一来,在子类中,我们只要在setLayout()方法里返回一个view或者layout布局,在onBindView()方法里做一些控件的初始化即可。另外,butterKnife已经在基类里和视图绑定,子类中不用再初始化啦!
  • 写完之后,记得让我们的WebDelegate继承它哦~

三、WebView初始化三部曲

还记得三部曲吗?包括了初始化settings,设置client,以及chromeClient。它们都应该在默认的子类WebDelegateDefault中实现,我们先来实现它们!但是有些设置代码太多,单独写几个文件比较好。

3.1 各种settings

注释写的很详细,也没啥好说的:

/**
 * @function 对传入的webView进行各种settings,返回setting好的webView
 * Created by 尤晟 on 2017-07-30.
 */

public class WebViewSettingsInitializer {

    @SuppressLint("SetJavaScriptEnabled")
    public WebView createWebView(final WebView webView) {
      //api>=21时才能开启
//        WebView.setWebContentsDebuggingEnabled(true);
        //不能横向滚动
        webView.setHorizontalScrollBarEnabled(false);
        //不能纵向滚动
        webView.setVerticalScrollBarEnabled(false);
        //允许截图
        webView.setDrawingCacheEnabled(true);
        //屏蔽长按事件
        webView.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                return true;
            }
        });
        //初始化WebSettings
        final WebSettings settings = webView.getSettings();
        settings.setJavaScriptEnabled(true);
        final String ua = settings.getUserAgentString();
        settings.setUserAgentString(ua + "Latte");
        //隐藏缩放控件
        settings.setBuiltInZoomControls(false);
        settings.setDisplayZoomControls(false);
        //禁止缩放
        settings.setSupportZoom(false);
        //文件权限
        settings.setAllowFileAccess(true);
        settings.setAllowFileAccessFromFileURLs(true);
        settings.setAllowUniversalAccessFromFileURLs(true);
        settings.setAllowContentAccess(true);
        //缓存相关
        settings.setAppCacheEnabled(true);
        settings.setDomStorageEnabled(true);
        settings.setDatabaseEnabled(true);
        settings.setCacheMode(WebSettings.LOAD_DEFAULT);

        return webView;
    }
}

3.2 ChromeClient实现

与页面的js交互有关,暂时不管。

/**
 * @function 不做处理,为了以后的扩展
 * Created by 尤晟 on 2017-07-30.
 */

public class WebChromeClientImpl extends WebChromeClient {

    @Override
    public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
        return super.onJsAlert(view, url, message, result);
    }
}

3.3 WebViewClient实现

里面有一系列常用的重写方法,不过由于我们只想让页面由此webView处理,所以只需重写shouldOverrideUrlLoading方法,返回一个false。由于代码过少,我们就在子类里用匿名类方式实现吧。

3.4 Router路由转发与加载类

这个类负责路由的截断处理,以及页面的一些重载加载,用单例模式。以后还会往里面添加方法。

/**
 * @function 路由截断, 线程安全的惰性单例模式
 * Created by 尤晟 on 2017-07-30.
 */

public class Router {
    private Router() {
    }

    private static class Holder {
        private static final Router INSTANCE = new Router();
    }

    public static Router getInstance() {
        return Holder.INSTANCE;
    }

    private void loadWebPage(WebView webView, String url) {
        if (webView != null) {
            webView.loadUrl(url);
        } else {
            throw new NullPointerException("WebView is null!");
        }
    }

    //在assets文件夹中的本地页面(和res文件夹同级)
    private void loadLocalPage(WebView webView, String url) {
        loadWebPage(webView, "file:///android_asset/" + url);
    }

    private void loadPage(WebView webView, String url) {
        if (URLUtil.isNetworkUrl(url) || URLUtil.isAssetUrl(url)) {
            loadWebPage(webView, url);
        } else {
            loadLocalPage(webView, url);
        }
    }

    public final void loadPage(WebDelegate delegate, String url) {
        loadPage(delegate.getWebView(), url);
    }
}

四、WebDelegateDefault默认子类的实现

  • 写了这么多基类,零件也初始化好了,终于要写一个默认的子类了!由于有了很多的封装,现在写起来非常简单。
  • 另外,此类实现了IWebViewInitializer 接口,setInitializer()方法里返回自身,而非在setInitializer()方法里返回一个新的接口实例,这是有理由的:方便在下一个子类中,只用override部分需要重写的方法就行了(比如只需要改变子类的WebViewClient,那我们只需重写initWebViewClient()方法),不然要重写整个setInitializer()方法了。

/**
 * @function WebDelegate的默认子类,点击链接会在webView内部跳转
 * Created by 尤晟 on 2017-07-31.
 */

public class WebDelegateDefault extends WebDelegate implements IWebViewInitializer {

    //必须用这种方式创建WebDelegateDefault 类
    public static WebDelegateDefault create(String url) {
        final Bundle bundle = new Bundle();
      //RouteKeys是个枚举类罢了,里面只有一个值URL
        bundle.putString(RouteKeys.URL.name(), url);
        final WebDelegateDefault delegate = new WebDelegateDefault();
        delegate.setArguments(bundle);
        return delegate;
    }

    @Override
    public WebView initWebViewSettings(WebView webView) {
        return new WebViewSettingsInitializer().createWebView(webView);
    }

    //匿名内部类方式实现WebViewClient
    @Override
    public WebViewClient initWebViewClient() {
        return new WebViewClient() {
            //谷歌已经不推荐用这个方法,而推荐用另一个重载方法。但是那个方法必须要api>=21。
            //为了兼容性和简单性,我们继续使用这个方法。你也可以判断一下api,我偷懒了。
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                //将页面内的点击链接,交给此webView自己处理
                return false;
            }
        };
    }

    @Override
    public WebChromeClient initWebChromeClient() {
        return new WebChromeClientImpl();
    }

    //基类Delegate中封装的方法,Fragment会加载这个方法返回的view或者layout布局
    @Override
    public Object setLayout() {
        return getWebView();
    }

    @Override
    public void onBindView(@Nullable Bundle savedInstanceState, View rootView) {
        if (getUrl() != null) {
            //进行页面加载
            Router.getInstance().loadPage(this, getUrl());
        }
    }

    @Override
    public IWebViewInitializer setInitializer() {
        //自身实现接口,向上转型返回自身给父类,父类获取到了初始化三部曲后进行初始化
        return this;
    }

    //这是第三方库fragmentation自带的方法,用来重写返回键,表示返回上一个页面而非退出webView。
    //若没用这个第三方库,可以在webView的三部曲之一settings时调用 webView.setOnKeyListener来设置。
    @Override
    public boolean onBackPressedSupport() {
        if (getWebView().canGoBack()) {  //表示按返回键时的操作
            getWebView().goBack();   //后退
            //webview.goForward();//前进
            return true;    //已处理
        }
        return false;
    }
}

五、总结

  • 至此,基本的类已经实现完毕,可以实例化默认的子类,然后当做Fragment随意使用了。但是,下一篇中,我将继续对client也做一个默认的封装,实现网页的loading界面。同时,还将针对某个具体的业务案例,实现一个特殊化的子类,配合我们的默认子类食用,其中包括了一些有关shouldOverrideUrlLoading重定向的深坑。
  • 欢迎大家点击关注,以及喜欢~~~

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

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

推荐阅读更多精彩内容