阅读之前推荐阅读博客大佬的这2篇
Android开发:最全面、最易懂的Webview使用详解
最全面总结 Android WebView与 JS 的交互方式
本文作者: @youyuge
个人博客站点: https://youyuge.cn
参考自imooc实战课程,感谢猿猿老师,另外猿猿老师让我发一下课程链接~~(笑,非广告)http://coding.imooc.com/learn/list/116.html
一、回顾与规划
回顾一下,我们在第一章中已经完成了一些封装:
我们看一下我们的目前的架构图片:
初步架构通信图我们已经实现了:
-
抽象父类
WebDelegate
,用来管理webView
的生命周期,以及初始化,保证不会内存泄漏,提供了一些get方法 -
接口回调完成了,子类作为具体的实现类,要给我实现这个接口,也就是要完成三个方法,分别是初始化
settings
,设置client
,以及chromeClient
(webView
设置的三部曲) -
创建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
重定向的深坑。 - 欢迎大家点击关注,以及喜欢~~~
网友评论