閱讀之前推薦閱讀博客大佬的這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)完成了一些封裝:
我們看一下我們的目前的架構(gòu)圖片:

我們已經(jīng)實現(xiàn)了:
抽象父類
WebDelegate,用來管理webView的生命周期,以及初始化,保證不會內(nèi)存泄漏,提供了一些get方法接口回調(diào)完成了,子類作為具體的實現(xiàn)類,要給我實現(xiàn)這個接口,也就是要完成三個方法,分別是初始化
settings,設(shè)置client,以及chromeClient(webView設(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),我們放到第三篇來說,暫且不表。

二、基類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)注,以及喜歡~~~