Android開罐頭——WebView高可擴展性封裝(二)

閱讀之前推薦閱讀博客大佬的這2篇
Android開發(fā):最全面、最易懂的Webview使用詳解
最全面總結(jié) Android WebView與 JS 的交互方式

本文作者: @youyuge
個人博客站點: https://youyuge.cn
參考自imooc實戰(zhàn)課程,感謝猿猿老師,另外猿猿老師讓我發(fā)一下課程鏈接~~(笑,非廣告)http://coding.imooc.com/learn/list/116.html

一、回顧與規(guī)劃

回顧一下,我們在第一章中已經(jīng)完成了一些封裝:

Android開罐頭——WebView高可擴展性封裝(一)

我們看一下我們的目前的架構(gòu)圖片:

初步架構(gòu)通信圖

我們已經(jīng)實現(xiàn)了:

  • 抽象父類WebDelegate,用來管理webView的生命周期,以及初始化,保證不會內(nèi)存泄漏,提供了一些get方法

  • 接口回調(diào)完成了,子類作為具體的實現(xiàn)類,要給我實現(xiàn)這個接口,也就是要完成三個方法,分別是初始化settings,設(shè)置client,以及chromeClientwebView設(shè)置的三部曲)

  • 創(chuàng)建js交互的本地對象類LatteWebInterface,算是為以后和js交互預(yù)留了地方,目前沒用

本節(jié)我們想實現(xiàn):

  • BaseDelegate
    先來對基類fragment封裝一下下,讓我們所有的fragment都繼承于BaseDelegate,我們給它個新統(tǒng)稱名字叫Delegate。(上一節(jié)中你可能已經(jīng)發(fā)現(xiàn),WebDelegate就已經(jīng)繼承于一個Delegate)

  • WebDelegateDefault
    父基類有了,我們想要一個默認的子Delegate,叫WebDelegateDefault吧!讓它能夠可以直接使用,也就是要實現(xiàn)接口,包括了初始化settings,設(shè)置client,以及chromeClient(webView設(shè)置的三部曲)。同時,別忘了還要實現(xiàn)它作為一個Delegate(fragment)應(yīng)該實現(xiàn)的一些回調(diào)方法。

  • WebDelegateImpl
    默認的實現(xiàn)子類有了之后,其實已經(jīng)可以使用,使用 方法類似于Android sdk中自帶的WebViewFragment類。但是,我們還能建立一些特殊的子類繼承于WebDelegateDefault,比如下圖中的WebDelegateImpl,和具體業(yè)務(wù)有關(guān),屬于業(yè)務(wù)層,不想要默認的某些設(shè)置,就可以在這個類中override方法。由于是業(yè)務(wù)相關(guān),我們放到第三篇來說,暫且不表。

高級架構(gòu)通信圖

二、基類fragment封裝

這里說一下,由于本人使用了非常好用的fragmentation第三方庫,所以基類BaseDelegate繼承自SwipeBackFragment。如果不用這個吊炸天的開源庫,直接繼承原生的Fragment也是一樣寫的。BTW,還順便封裝了一下butterknife~~

public abstract class BaseDelegate extends SwipeBackFragment {

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

    //子類必須實現(xiàn),可以返回一個layout的資源id,或一個view
    public abstract Object setLayout();

    //子類初始化時回調(diào)
    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已經(jīng)在基類里和視圖綁定,子類中不用再初始化啦!
  • 寫完之后,記得讓我們的WebDelegate繼承它哦~

三、WebView初始化三部曲

還記得三部曲嗎?包括了初始化settings,設(shè)置client,以及chromeClient。它們都應(yīng)該在默認的子類WebDelegateDefault中實現(xiàn),我們先來實現(xiàn)它們!但是有些設(shè)置代碼太多,單獨寫幾個文件比較好。

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);
        //文件權(quán)限
        settings.setAllowFileAccess(true);
        settings.setAllowFileAccessFromFileURLs(true);
        settings.setAllowUniversalAccessFromFileURLs(true);
        settings.setAllowContentAccess(true);
        //緩存相關(guān)
        settings.setAppCacheEnabled(true);
        settings.setDomStorageEnabled(true);
        settings.setDatabaseEnabled(true);
        settings.setCacheMode(WebSettings.LOAD_DEFAULT);

        return webView;
    }
}

3.2 ChromeClient實現(xiàn)

與頁面的js交互有關(guān),暫時不管。

/**
 * @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實現(xiàn)

里面有一系列常用的重寫方法,不過由于我們只想讓頁面由此webView處理,所以只需重寫shouldOverrideUrlLoading方法,返回一個false。由于代碼過少,我們就在子類里用匿名類方式實現(xiàn)吧。

3.4 Router路由轉(zhuǎn)發(fā)與加載類

這個類負責(zé)路由的截斷處理,以及頁面的一些重載加載,用單例模式。以后還會往里面添加方法。

/**
 * @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默認子類的實現(xiàn)

  • 寫了這么多基類,零件也初始化好了,終于要寫一個默認的子類了!由于有了很多的封裝,現(xiàn)在寫起來非常簡單。
  • 另外,此類實現(xiàn)了IWebViewInitializer 接口,setInitializer()方法里返回自身,而非在setInitializer()方法里返回一個新的接口實例,這是有理由的:方便在下一個子類中,只用override部分需要重寫的方法就行了(比如只需要改變子類的WebViewClient,那我們只需重寫initWebViewClient()方法),不然要重寫整個setInitializer()方法了。

/**
 * @function WebDelegate的默認子類,點擊鏈接會在webView內(nèi)部跳轉(zhuǎn)
 * Created by 尤晟 on 2017-07-31.
 */

public class WebDelegateDefault extends WebDelegate implements IWebViewInitializer {

    //必須用這種方式創(chuàng)建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);
    }

    //匿名內(nèi)部類方式實現(xiàn)WebViewClient
    @Override
    public WebViewClient initWebViewClient() {
        return new WebViewClient() {
            //谷歌已經(jīng)不推薦用這個方法,而推薦用另一個重載方法。但是那個方法必須要api>=21。
            //為了兼容性和簡單性,我們繼續(xù)使用這個方法。你也可以判斷一下api,我偷懶了。
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                //將頁面內(nèi)的點擊鏈接,交給此webView自己處理
                return false;
            }
        };
    }

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

    //基類Delegate中封裝的方法,F(xiàn)ragment會加載這個方法返回的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() {
        //自身實現(xiàn)接口,向上轉(zhuǎn)型返回自身給父類,父類獲取到了初始化三部曲后進行初始化
        return this;
    }

    //這是第三方庫fragmentation自帶的方法,用來重寫返回鍵,表示返回上一個頁面而非退出webView。
    //若沒用這個第三方庫,可以在webView的三部曲之一settings時調(diào)用 webView.setOnKeyListener來設(shè)置。
    @Override
    public boolean onBackPressedSupport() {
        if (getWebView().canGoBack()) {  //表示按返回鍵時的操作
            getWebView().goBack();   //后退
            //webview.goForward();//前進
            return true;    //已處理
        }
        return false;
    }
}

五、總結(jié)

  • 至此,基本的類已經(jīng)實現(xiàn)完畢,可以實例化默認的子類,然后當(dāng)做Fragment隨意使用了。但是,下一篇中,我將繼續(xù)對client也做一個默認的封裝,實現(xiàn)網(wǎng)頁的loading界面。同時,還將針對某個具體的業(yè)務(wù)案例,實現(xiàn)一個特殊化的子類,配合我們的默認子類食用,其中包括了一些有關(guān)shouldOverrideUrlLoading重定向的深坑。
  • 歡迎大家點擊關(guān)注,以及喜歡~~~

下一篇:Android開罐頭——WebView高可擴展性封裝(三)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,323評論 25 708
  • 閱讀之前推薦閱讀博客大佬的這2篇Android開發(fā):最全面、最易懂的Webview使用詳解最全面總結(jié) Androi...
    youyuge閱讀 3,340評論 2 15
  • 閱讀之前推薦閱讀博客大佬的這2篇Android開發(fā):最全面、最易懂的Webview使用詳解最全面總結(jié) Androi...
    youyuge閱讀 4,506評論 7 20
  • 官方文檔 初始化 Initialization是為準備使用類,結(jié)構(gòu)體或者枚舉實例的一個過程。這個過程涉及了在實例里...
    hrscy閱讀 1,205評論 0 1
  • 昨天遇到一個遠房親戚的孩子。按照輩分算是我弟弟,相差正好12歲。小時候記得看到過,現(xiàn)在已經(jīng)長那么大了。孩子非常懂禮...
    liyinkan閱讀 490評論 0 1

友情鏈接更多精彩內(nèi)容