WebView基本用法

加载在线URL

void loadUrl(String url)

这个函数主要加载url所对应的网页地址,或者用于调用网页中的指定的JS方法(调用js方法的用法,后面会讲),但有一点必须注意的是:loadUrl()必须在主线程中执行!!!否则就会报错!!!。
注意:加载在线网页地址是会用到联网permission权限。

url = "http://www.baidu.com";
...
mLoad.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {mWebView.loadUrl(url);}});

代码很简单,就是在点击按钮的时候加载网址,但需要注意的是:网址必须完整即以http://或者ftp://等协议开头,不能省略!不然将加载不出来,这是因为webview是没有自动补全协议功能的,所以如果我们不加,它将识别不出来网址类型,也就加载不出来了。
但如果我们运行上面的代码,效果却是利用浏览器来打开网址,却不是使用webview打开网址:

如果我们想实现在webview中打开网址需要怎么做呢? 我们需要设置WebViewClient:

mWebView.setWebViewClient(new WebViewClient());

再来运行一下看看:

要在WebView中打开链接,就必须要设置WebViewClient。

加载本地URL

本地html文件可以放在assets文件夹下,也可以放在手机目录下。

public static final String OFFLINE_PATH = Environment.getExternalStorageDirectory().getAbsolutePath()  + "/offline/";url = "file:///android_asset/web.html"; //Assets目录下
url = "file://" + OFFLINE_PATH_DOC + "web.html"; //手机目录下
或者 url = "file:///storage/emulated/0/offline/web.html"

HTML内容:

<!DOCTYPE html>
<html lang="en">
<head>  <meta charset="UTF-8">  <title>Title</title>  <h1>WebView加载本地HTML</h1>
</head>
<body>
</body>
</html> 

对于加载URL的总结就是:
1、如果是在线网址记得添加网络访问权限
2、在线网址中,如果要使用webview打开,记得设置WebViewClient
3、打开本地html文件时,是不需要设置WebViewClient,对应的asstes目录的url为:file:///android_asset/xxxxx

加载HTML片段

上面讲了通过loadUrl()来加载本地页面和在线地址的方式,这里给大家再补充两个方法LoadData()与loadDataWithBaseURL(),它们不是用来加载整个页面文件的,而是用来加载一段代码片的。

1. LoadData()

public void loadData(String data, String mimeType, String encoding)
  • String data:代码片段内容
  • String mimeType:代码片段所对应的MIME类型,如果传null,则默认为text/html
  • String encoding:代码片段的编码方式
public class MyActivity extends Activity {private WebView mWebView;@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);mWebView = (WebView) findViewById(R.id.webview);mWebView.getSettings().setJavaScriptEnabled(true);mWebView.getSettings().setDefaultTextEncodingName("utf-8");String summary = "<html><body>A <b>android</b> developer.</body></html>";mWebView.loadData(summary, "text/html", "utf-8");}
}

在使用loadData时,在数据里面不能出现英文字符:’#’, ‘%’, ‘\’ , ‘?’ 这四个字符,如果有的话可以用 %23, %25, %27, %3f,这些字符来替换。直接使用这四个字符会造成的问题如下:

%:会报找不到页面错误,页面全是乱码。
#:会让你的goBack失效,但canGoBAck是可以使用的。于是就会产生返回按钮生效,但不能返回的情况。
\ 和? :在转换时,会报错,因为它会把\当作转义符来使用,如果用两级转义,也不生效。

其实,Android给我们提供了一个专门用来转码的函数:URLEncoder.encode(String s, String charsetName) ,它能将冲突的字符进行转义,然后再传给webview,这样webview在加载时就不会有冲突了,encode函数的声明如下:

/**
String s:代码段
String charsetName:编码类型
*/
public static String encode(String s, String charsetName);

使用data中,如果出现中文乱码问题,解决办法:参数传”utf-8”,页面的编码格式也必须是utf-8,这样编码统一就不会乱了。

注意:
1、loadData()应该是不能加载图片的,加载图片的内容我们后面会使用loadDataWithBaseURL来实现。
2、为了防止字符冲突,在传递loadData的数据时,必须使用URLEncoder.encode()函数来转义
3、页面的编码格式必须与代码中传参的编码格式一致,不然会导致乱码

1. loadDataWithBaseURL()
相比loadData,这个函数更常用,因为loadData能实现的功能,它都能实现,而且也不会出现字符冲突。其函数声明如下:

public void loadDataWithBaseURL(String baseUrl, String data,String mimeType, String encoding, String historyUrl)
  • String baseUrl:基准URL,不需要可以传null,它的意思是,如果data中的url是相对地址,则就会加上基准url来拼接出完整的地址,比如baseUrl是https://img-my.csdn.net,data中有个Img标签,它的内容是:<\img src=’/uploads/201309/01/1378037151_7904.jpg’>,很明显src的地址不是本地地址也不是在线地址,那它就是一个相对地址,所以加上baseUrl以后才是它的完整地址:https://img-my.csdn.net/uploads/201309/01/1378037151_7904.jpg。如果data中的url是绝对地址,则baseUrl不起作用。
  • String mimeType:MIME类型
  • String encoding:编码方式
  • String historyUrl:当前的历史记录所要存储的值。如果不需要可以传Null,loadDataWithBaseURL它本身并不会向历史记录中存储数据,要想实现历史记录,需要我们自己来实现;有关历史记录的实现方式是比较复杂的,历史记录是以Key/value的方式存储在一个historyList里的,当前进后退时,会用Key来取出对应的value值来加载进webview中。而Key就是这里的baseUrl,Value就是这里的historyUrl;history所指向的必须是一个页面,并且页面存在于SD卡中或程序中(assets);
public class MyActivity extends Activity {private WebView mWebView;@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);mWebView = (WebView) findViewById(R.id.webview);mWebView.getSettings().setJavaScriptEnabled(true);mWebView.getSettings().setDefaultTextEncodingName("utf-8");String baseURL = "https://img-my.csdn.net";String data = "风景优美 <img src='/uploads/201309/01/1378037151_7904.jpg'>";mWebView.loadDataWithBaseURL(baseURL, data, "text/html", "utf-8", null);}
}

总结:
loadData和loadDataWithBaseURL这两种方法,我建议使用后者,虽然loadData的历史记录不需要我们自己来实现,但在使用时,这就两个加载上后者比前者快一到两倍,且不会出现字符冲突。另外loadData不能加载图片,而loadDataWithBaseURL是可以加载图片的。

WebView基本设置

如果我们需要设置WebView的属性,是通过WebView.getSettings()获取设置WebView的WebSettings对象, 然后调用WebSettings中的方法来实现的。
WebSettings的方法及说明如下:

/*** 是否支持缩放,配合方法setBuiltInZoomControls使用,默认true*/
setSupportZoom(boolean support)/*** 是否需要用户手势来播放Media,默认true*/
setMediaPlaybackRequiresUserGesture(boolean require)/*** 是否使用WebView内置的缩放组件,由浮动在窗口上的缩放控制和手势缩放控制组成,默认false*/
setBuiltInZoomControls(boolean enabled)/*** 是否显示窗口悬浮的缩放控制,默认true*/
setDisplayZoomControls(boolean enabled)/*** 是否允许访问WebView内部文件,默认true*/
setAllowFileAccess(boolean allow)/*** 是否允许获取WebView的内容URL ,可以让WebView访问ContentPrivider存储的内容。 默认true*/
setAllowContentAccess(boolean allow)/*** 是否启动概述模式浏览界面,当页面宽度超过WebView显示宽度时,缩小页面适应WebView。默认false*/
setLoadWithOverviewMode(boolean overview)/*** 是否保存表单数据,默认false*/
setSaveFormData(boolean save)/*** 设置页面文字缩放百分比,默认100%*/
setTextZoom(int textZoom)/*** 是否支持ViewPort的meta tag属性,如果页面有ViewPort meta tag 指定的宽度,则使用meta tag指定的值,否则默认使用宽屏的视图窗口*/
setUseWideViewPort(boolean use)/*** 是否支持多窗口,如果设置为true ,WebChromeClient#onCreateWindow方法必须被主程序实现,默认false*/
setSupportMultipleWindows(boolean support)/*** 指定WebView的页面布局显示形式,调用该方法会引起页面重绘。默认LayoutAlgorithm#NARROW_COLUMNS*/
setLayoutAlgorithm(LayoutAlgorithm l)/*** 设置标准的字体族,默认”sans-serif”。font-family 规定元素的字体系列。* font-family 可以把多个字体名称作为一个“回退”系统来保存。如果浏览器不支持第一个字体,* 则会尝试下一个。也就是说,font-family 属性的值是用于某个元素的字体族名称或/及类族名称的一个* 优先表。浏览器会使用它可识别的第一个值。*/
setStandardFontFamily(String font)/*** 设置混合字体族。默认”monospace”*/
setFixedFontFamily(String font)/*** 设置SansSerif字体族。默认”sans-serif”*/
setSansSerifFontFamily(String font)/*** 设置SerifFont字体族,默认”sans-serif”*/
setSerifFontFamily(String font)/*** 设置CursiveFont字体族,默认”cursive”*/
setCursiveFontFamily(String font)/*** 设置FantasyFont字体族,默认”fantasy”*/
setFantasyFontFamily(String font)/*** 设置最小字体,默认8. 取值区间[1-72],超过范围,使用其上限值。*/
setMinimumFontSize(int size)/*** 设置最小逻辑字体,默认8. 取值区间[1-72],超过范围,使用其上限值。*/
setMinimumLogicalFontSize(int size)/*** 设置默认字体大小,默认16,取值区间[1-72],超过范围,使用其上限值。*/
setDefaultFontSize(int size)/*** 设置默认填充字体大小,默认16,取值区间[1-72],超过范围,使用其上限值。*/
setDefaultFixedFontSize(int size)/*** 设置是否加载图片资源,注意:方法控制所有的资源图片显示,包括嵌入的本地图片资源。* 使用方法setBlockNetworkImage则只限制网络资源图片的显示。值设置为true后,* webview会自动加载网络图片。默认true*/
setLoadsImagesAutomatically(boolean flag)/*** 是否加载网络图片资源。注意如果getLoadsImagesAutomatically返回false,则该方法没有效果。* 如果使用setBlockNetworkLoads设置为false,该方法设置为false,也不会显示网络图片。* 当值从true改为false时。WebView会自动加载网络图片。*/
setBlockNetworkImage(boolean flag)/*** 设置是否加载网络资源。注意如果值从true切换为false后,WebView不会自动加载,* 除非调用WebView#reload().如果没有android.Manifest.permission#INTERNET权限,* 值设为false,则会抛出java.lang.SecurityException异常。* 默认值:有android.Manifest.permission#INTERNET权限时为false,其他为true。*/
setBlockNetworkLoads(boolean flag)/*** 设置是否允许执行JS。*/
setJavaScriptEnabled(boolean flag)/*** 是否允许Js访问任何来源的内容。包括访问file scheme的URLs。考虑到安全性,* 限制Js访问范围默认禁用。注意:该方法只影响file scheme类型的资源,其他类型资源如图片类型的,* 不会受到影响。ICE_CREAM_SANDWICH_MR1版本以及以下默认为true,JELLY_BEAN版本* 以上默认为false*/
setAllowUniversalAccessFromFileURLs(boolean flag)/*** 是否允许Js访问其他file scheme的URLs。包括访问file scheme的资源。考虑到安全性,* 限制Js访问范围默认禁用。注意:该方法只影响file scheme类型的资源,其他类型资源如图片类型的,* 不会受到影响。如果getAllowUniversalAccessFromFileURLs为true,则该方法被忽略。* ICE_CREAM_SANDWICH_MR1版本以及以下默认为true,JELLY_BEAN版本以上默认为false*/
setAllowFileAccessFromFileURLs(boolean flag)/*** 设置存储定位数据库的位置,考虑到位置权限和持久化Cache缓存,Application需要拥有指定路径的* write权限*/
setGeolocationDatabasePath(String databasePath)/*** 是否允许Cache,默认false。考虑需要存储缓存,应该为缓存指定存储路径setAppCachePath*/
setAppCacheEnabled(boolean flag)/*** 设置Cache API缓存路径。为了保证可以访问Cache,Application需要拥有指定路径的write权限。* 该方法应该只调用一次,多次调用自动忽略。*/
setAppCachePath(String appCachePath)/*** 是否允许数据库存储。默认false。查看setDatabasePath API 如何正确设置数据库存储。* 该设置拥有全局特性,同一进程所有WebView实例共用同一配置。注意:保证在同一进程的任一WebView* 加载页面之前修改该属性,因为在这之后设置WebView可能会忽略该配置*/
setDatabaseEnabled(boolean flag)/*** 是否存储页面DOM结构,默认false。*/
setDomStorageEnabled(boolean flag)/*** 是否允许定位,默认true。注意:为了保证定位可以使用,要保证以下几点:* Application 需要有android.Manifest.permission#ACCESS_COARSE_LOCATION的权限* Application 需要实现WebChromeClient#onGeolocationPermissionsShowPrompt的回调,* 接收Js定位请求访问地理位置的通知*/
setGeolocationEnabled(boolean flag)/*** 是否允许JS自动打开窗口。默认false*/
setJavaScriptCanOpenWindowsAutomatically(boolean flag)/*** 设置页面的编码格式,默认UTF-8*/
setDefaultTextEncodingName(String encoding)/*** 设置WebView代理,默认使用默认值*/
setUserAgentString(String ua)/*** 通知WebView是否需要设置一个节点获取焦点当* WebView#requestFocus(int,android.graphics.Rect)被调用的时候,默认true*/
setNeedInitialFocus(boolean flag)/*** 基于WebView导航的类型使用缓存:正常页面加载会加载缓存并按需判断内容是否需要重新验证。* 如果是页面返回,页面内容不会重新加载,直接从缓存中恢复。setCacheMode允许客户端根据指定的模式来* 使用缓存。* LOAD_DEFAULT 默认加载方式* LOAD_CACHE_ELSE_NETWORK 按网络情况使用缓存* LOAD_NO_CACHE 不使用缓存* LOAD_CACHE_ONLY 只使用缓存*/
setCacheMode(int mode)/*** 设置加载不安全资源的WebView加载行为。KITKAT版本以及以下默认为MIXED_CONTENT_ALWAYS_ALLOW方* 式,LOLLIPOP默认MIXED_CONTENT_NEVER_ALLOW。强烈建议:使用MIXED_CONTENT_NEVER_ALLOW*/
setMixedContentMode(int mode)

例如:
示例1:打开页面时, 自适应屏幕:

webSettings.setUseWideViewPort(true);//设置此属性,可任意比例缩放
webSettings.setLoadWithOverviewMode(true);

效果图如下:(所使用的网址为:http://www.w3school.com.cn/)

注意:自己写的网页代码,也可以在HTML中做宽度100%自适应屏幕

示例2:使页面支持缩放:

//开启javascript支持
webSettings.setJavaScriptEnabled(true);
// 设置可以支持缩放
webSettings.setSupportZoom(true);
// 设置出现缩放工具
webSettings.setBuiltInZoomControls(true); 

示例3:如果webView中需要用户手动输入用户名、密码或其他,则webview必须设置支持获取手势焦点

webview.requestFocusFromTouch(); 

其他请自行摸索。

WebView缓存模式

WebView是Android中直接加载html页面的控件。当我们加载Html时候,会在我们data/应用package下生成database与cache两个文件夹:

我们请求的Url记录是保存在webviewCache.db里,而url的内容是保存在webviewCache文件夹下。

WebView中存在着两种缓存:网页数据缓存(存储打开过的页面及资源)、H5缓存(即AppCache)。

网页缓存

  1. 网页缓存的结构:
    /data/data/package_name/cache/
    /data/data/package_name/database/webview.db
    /data/data/package_name/database/webviewCache.db

  2. 缓存模式(5种)
    LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据
    LOAD_DEFAULT: 根据cache-control决定是否从网络上取数据。
    LOAD_CACHE_NORMAL: API level 17中已经废弃, 从API level 11开始作用同LOAD_DEFAULT模式
    LOAD_NO_CACHE: 不使用缓存,只从网络获取数据.
    LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。

如:www.taobao.com的cache-control为no-cache,在模式LOAD_DEFAULT下,无论如何都会从网络上取数据,如果没有网络,就会出现错误页面;在LOAD_CACHE_ELSE_NETWORK模式下,无论是否有网络,只要本地有缓存,都使用缓存。本地没有缓存时才从网络上获取。
www.360.com.cn的cache-control为max-age=60,在两种模式下都使用本地缓存数据。
总结:根据以上两种模式,建议缓存策略为:判断是否有网络,有的话,使用LOAD_DEFAULT,无网络时,使用LOAD_CACHE_ELSE_NETWORK。

3、清除缓存

webview.clearCache(boolean);

CacheManager.clear高版本中需要调用隐藏API。

4、控制大小
无系统API支持。
可选方式:定时统计缓存大小、按时间顺序删除缓存。

public class MainActivity extends AppCompatActivity {private static final String TAG = MainActivity.class.getSimpleName();private TextView mTitle;private WebView mWebView;private TextView mClear;private String url;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);url = "https://wap.baidu.com/";findView();}private void findView() {mTitle = (TextView) findViewById(R.id.tv_topbar_title);mWebView = (WebView) findViewById(R.id.mWebView);mClear = (TextView) findViewById(R.id.clear);mClear.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {mWebView.clearCache(true); //清除缓存}});initWebView();mWebView.setWebViewClient(new WebViewClient() {@Overridepublic void onLoadResource(WebView view, String url) {Log.i(TAG, "onLoadResource url="+url); // 开始加载super.onLoadResource(view, url);}@Overridepublic boolean shouldOverrideUrlLoading(WebView webview, String url) {Log.i(TAG, "intercept url="+url);// 重写此方法表明点击网页里面的链接还是在当前的webview里跳转,不跳到浏览器那边webview.loadUrl(url);return true;}@Overridepublic void onPageStarted(WebView view, String url, Bitmap favicon) {Log.e(TAG, "onPageStarted");}@Overridepublic void onPageFinished(WebView view, String url) {String title = view.getTitle(); //得到网页标题Log.e(TAG, "onPageFinished WebView title=" + title);mTitle.setText(title);mTitle.setVisibility(View.VISIBLE);}@Overridepublic void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {Toast.makeText(getApplicationContext(), description, Toast.LENGTH_LONG).show();}});mWebView.loadUrl(url);}private void initWebView() {mWebView.getSettings().setJavaScriptEnabled(true);mWebView.getSettings().setRenderPriority(WebSettings.RenderPriority.HIGH);// 设置缓存模式if (NetUtils.isNetworkAvailable(MainActivity.this)) {mWebView.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT);} else {mWebView.getSettings().setCacheMode( WebSettings.LOAD_CACHE_ELSE_NETWORK);}// webView.getSettings().setBlockNetworkImage(true);//把图片加载放在最后来加载渲染// 支持多窗口webView.getSettings().setSupportMultipleWindows(true);// 开启 DOM storage API 功能mWebView.getSettings().setDomStorageEnabled(true);//开启 database storage API 功能mWebView.getSettings().setDatabaseEnabled(true);// 开启 Application Caches 功能
//      webView.getSettings().setAppCacheEnabled(true);}@Override// 设置回退// 覆盖Activity类的onKeyDown(int keyCoder,KeyEvent event)方法public boolean onKeyDown(int keyCode, KeyEvent event) {if ((keyCode == KeyEvent.KEYCODE_BACK) && mWebView.canGoBack()) {mWebView.goBack(); // goBack()表示返回WebView的上一页面return true;} else {finish();}return super.onKeyDown(keyCode, event);}/**** 防止WebView加载内存泄漏*/@Overrideprotected void onDestroy() {super.onDestroy();mWebView.removeAllViews();mWebView.destroy();}
}

AndroidManifest.xml 中加权限

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

H5缓存

Application Cache(简称 AppCache)似乎是为支持 Web App 离线使用而开发的缓存机制。它的缓存机制类似于浏览器的缓存(Cache-Control 和 Last-Modified)机制,都是以文件为单位进行缓存,且文件有一定更新机制。但 AppCache 是对浏览器缓存机制的补充,不是替代。
如果你还不了解什么叫做H5缓存,推荐这篇文章:H5 缓存机制浅析 - 移动端 Web 加载性能优化

1、缓存构成
根据setAppCachePath(String appCachePath)提供的路径,在H5使用缓存过程中生成的缓存文件。

2、缓存模式
无模式选择,通过setAppCacheEnabled(boolean flag)设置是否打开。默认关闭,即,H5的缓存无法使用。

3、清除缓存
找到调用setAppCachePath(String appCachePath)设置缓存的路径,把它下面的文件全部删除就OK了。

4、控制大小
通过setAppCacheMaxSize(long appCacheMaxSize)设置缓存最大容量,默认为Max Integer。
同时,可能通过覆盖WebChromeClient.onReachedMaxAppCacheSize(long requiredStorage, long quota, WebStorage.QuotaUpdater quotaUpdater)来设置缓存超过先前设置的最大容量时的策略。

...
String cacheDirPath = getApplicationContext().getDir("cache", Context.MODE_PRIVATE).getPath();
Log.i(TAG, "cacheDirPath="+cacheDirPath);
/**设置Application Caches 缓存目录*/
mWebView.getSettings().setAppCachePath(cacheDirPath);
//开启 Application Caches 功能
mWebView.getSettings().setAppCacheEnabled(true);
mWebView.getSettings().setAppCacheMaxSize(5*1024*1024); //5M
mWebView.getSettings().setAllowFileAccess(true); //使manifest生效

webview可以设置一个WebChromeClient对象,在其onReachedMaxAppCacheSize函数对扩充缓冲做出响应。代码如下:

mWebView.setWebChromeClient(new WebChromeClient(){//扩充缓存的容量@Overridepublic void onReachedMaxAppCacheSize(long spaceNeeded, long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {quotaUpdater.updateQuota(spaceNeeded * 2);}

清除缓存:

    /*** 清除WebView缓存*/public void clearWebViewCache(){//清理Webview缓存数据库try {deleteDatabase("webview.db");deleteDatabase("webviewCache.db");} catch (Exception e) {e.printStackTrace();}//WebView 缓存文件File appCacheDir = new File(getApplicationContext().getDir("cache", Context.MODE_PRIVATE).getPath());Log.e(TAG, "appCacheDir path="+appCacheDir.getAbsolutePath());//删除webview 缓存 缓存目录if(appCacheDir.exists()){deleteFile(appCacheDir);}}

权限:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

其次要修改http服务器中的配置,使其支持text/cache-manifest,我使用的是apach服务器,是windows版本的,在apache的conf文件夹中找到mime.types文件,打开后在文件的最后加上

“text/cache-manifest  mf  manifest”,

重启服务器即可。

WebView离线阅读

WebView离线阅读就是使用WebView加载本地的html文档,上面说的H5缓存实质上已经实现了WebView离线阅读的功能,但是需要服务器的支持,本节我们自己来实现WebView的离线阅读而不依赖服务器。

首先来看看webview加载网络资源的情况:

url = "http://dfz.eastday.com/nanchang/u1ai17496_t11.html";
...
mOnline.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {mWebView.loadUrl(url);}});

我们的思路是把html文档下载到本地,然后用webview加载本地文档即可。

    private void DownloadArticle(String url){String htmlCode = "";try {Document document = Jsoup.parse(new URL(url), 10000);if (document != null){String filePath = OFFLINE_PATH_DOC + MD5Util.getMD5Str(url) + ".html"; //url进行MD5编码后作为文件名htmlCode = document.html().toString();HttpUtil.SaveTextToFile(filePath, htmlCode);}}  catch (Exception e) {e.printStackTrace();}}
        mOffline.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {String filePath = "file://" + OFFLINE_PATH_DOC + MD5Util.getMD5Str(url) + ".html";mWebView.loadUrl(filePath);}});

可以看到,确实加载到了本地的html文档,但是图片没有了,版式也有些不对。其实看html源码

我们可以看到图片,版式等都是在加载html时实时从网络获取的,我们现在在离线状态下,当然获取不到这些内容了。所以我们还需要把需要的js、css、图片等文件也下载下来,在我们执行html文档的时候可以选择从本地加载这些资源。有了想法,实施。

    /*** 下载稿件html以及包含的图片,cs,js* @param url*/private void DownloadArticle(String url){String htmlCode = "";try {Document document = Jsoup.parse(new URL(url), 10000);if (document != null){String filePath = OFFLINE_PATH_DOC + MD5Util.getMD5Str(url) + ".html";htmlCode = document.html().toString();htmlCode = replaceImage(document,"img","src",htmlCode); //替换成从本地获取imagehtmlCode = replaceStyle(document,"link","href",htmlCode); //替换成从本地获取csshtmlCode = htmlCode.replace("Common.js", ""); //这篇稿件特殊情况,Common.js会弹出一个alert,屏蔽它HttpUtil.SaveTextToFile(filePath, htmlCode);}}  catch (Exception e) {e.printStackTrace();}}.../*** 替换中间的图片* @param document* @param tag* @param attr* @param htmlCode*/private String replaceImage(Document document, String tag, String attr, String htmlCode){Elements imageDocuments = document.getElementsByTag(tag);String imgURL = "";Element element = null;for(int i = 0; i < imageDocuments.size(); i++){element = imageDocuments.get(i);imgURL = element.attr(attr);if (imgURL.isEmpty())continue;DownloadImage(getWholeURL(imgURL));htmlCode = htmlCode.replace(imgURL, "file://" + OFFLINE_PATH_IMG + MD5Util.getMD5Str(getWholeURL(imgURL)));}return htmlCode;}.../***替换中间的样式css* @param document* @param tag* @param attr* @param htmlCode* @return*/private String replaceStyle(Document document, String tag, String attr, String htmlCode){String initialUrl = "";String replaceUrl = "";String getBack = "";String fileJS = "";Element element = null;Elements elements = document.getElementsByTag(tag);for(int i = 0; i < elements.size(); i++){element = elements.get(i);initialUrl = element.attr(attr);if (initialUrl.isEmpty())continue;replaceUrl = getWholeURL(initialUrl);fileJS = OFFLINE_PATH_JS + MD5Util.getMD5Str(getWholeURL(replaceUrl));if (!new File(fileJS).exists()){getBack = HttpUtil.requestContentWithGet11(replaceUrl);HttpUtil.SaveTextToFile(fileJS, getBack);}htmlCode = htmlCode.replace(initialUrl, "file://" + fileJS);}return htmlCode;}

再运行一下

非常完美,成功实现了webview的离线阅读。

WebView与JS交互

JS调用Java代码

网页中需要通过JS代码来调用本地的Android代码,比如H5页面需要判断当前用户是否登录等。
利用JS代码调用JAVA代码,主要是用到WebView下面的一个函数:

public void addJavascriptInterface(Object obj, String interfaceName) 

这个函数有两个参数:

  • Object obj:interfaceName所绑定的对象
  • String interfaceName:所绑定的对象所对应的名称

它的意思就是向WebView注入一个obj对象,对象的别名为interfaceName,在JS中,我们就可以通过interfaceName这个别名来调用obj对象中的任何public方法。

我们实现这样一个效果,在上面的html中添加了一个按钮,当点击按钮时调用Android的Toast函数弹出一个toast消息。

先看android代码:

public class MyActivity extends Activity {   private WebView mWebView;  @Override  public void onCreate(Bundle savedInstanceState) {  super.onCreate(savedInstanceState);  setContentView(R.layout.main);  mWebView = (WebView) findViewById(R.id.webview);  WebSettings webSettings = mWebView.getSettings();  webSettings.setJavaScriptEnabled(true);  mWebView.addJavascriptInterface(new JSBridge(), "android");  mWebView.loadUrl("file:///android_asset/web.html");  }  public class JSBridge {//在android:targetSdkVersion数值为17(Android4.2)及以上的APP中,JS只能访问带有 @JavascriptInterface注解的Java函数,所以如果你的android:targetSdkVersion是17+,与JS交互的Native函数中,必须添加JavascriptInterface注解,不然无效@JavascriptInterfacepublic void toastMessage(String message) {Toast.makeText(getApplicationContext(), "JS--->Natvie:" + message, Toast.LENGTH_LONG).show();}}   

下面我们看看html代码:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><h1>WebView加载本地HTML</h1><input type="button" value="js调native" onclick="ok()">
</head>
<body>
<script type="text/javascript">function ok() {android.toastMessage("我是来自JS的消息!");}
</script>
</body>
</html> 

JAVA调用JS代码

前面给大家演示了如何通过JS调用Java代码,这里就反过来看看,如何在Native中调用JS的代码 。
本例的效果图如下:

在点击“求和”按钮时,调用webview中的JavaScript求和函数,将结果通过alert显示出来。
先看html代码:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><h1>WebView加载本地HTML</h1><input type="button" value="js调native" onclick="ok()">
</head>
<body>
<script type="text/javascript">function ok() {android.toastMessage("我是来自JS的消息!");}function sum(i,m){alert("Native--->JS  sum=" + (i + m));}
</script>
</body>
</html> 

在这里,我们写了一个求和函数sum(i,m) ,alert出求和结果
再来看看JAVA的调用代码:

public class MyActivity extends Activity {  private WebView mWebView;  private Button mBtn;  @Override  public void onCreate(Bundle savedInstanceState) {  super.onCreate(savedInstanceState);  setContentView(R.layout.main);  mWebView = (WebView) findViewById(R.id.webview);  mBtn = (Button) findViewById(R.id.btn);  WebSettings webSettings = mWebView.getSettings();  webSettings.setJavaScriptEnabled(true);  mWebView.loadUrl("file:///android_asset/web.html");  mBtn.setOnClickListener(new View.OnClickListener() {  @Override  public void onClick(View v) {  mWebView.loadUrl("javascript:sum(3,8)");  }  });  }
}     

看看在JAVA中调用JS函数的方法:

String url = "javascript:methodName(params……);"
webView.loadUrl(url);

javascript:伪协议让我们可以通过一个链接来调用JavaScript函数 ,中间methodName是JavaScript中实现的函数 ,jsonParams是传入的参数列表 。

JAVA中如何得到JS中的返回值呢?
相信看完了上面Native和JS的互相调用,你一定知道了。

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><h1>WebView加载本地HTML</h1><input type="button" value="js调native" onclick="ok()">
</head>
<body>
<script type="text/javascript">function ok() {android.toastMessage("我是来自JS的消息!");}function sum(i,m){   var result = i+m;alert("Native--->JS  sum=" + result);android.onSumResult(result);}
</script>
</body>
</html> 
    public class JSBridge {//在android:targetSdkVersion数值为17(Android4.2)及以上的APP中,JS只能访问带有 @JavascriptInterface注解的Java函数,所以如果你的android:targetSdkVersion是17+,与JS交互的Native函数中,必须添加JavascriptInterface注解,不然无效@JavascriptInterfacepublic void toastMessage(String message) {Toast.makeText(getApplicationContext(), "JS--->Natvie:" + message, Toast.LENGTH_LONG).show();}@JavascriptInterfacepublic void onSumResult(int result) {  Toast.makeText(this,"received result:"+result,Toast.LENGTH_SHORT).show();  }}  

Android4.4之后,我们有新的方法在JAVA中获取JS的返回值:
首先给html的求和函数一个返回值:

    function sum(i,m){var result = i+m;return result;}

其次java代码时用evaluateJavascript方法调用:

                mWebView.evaluateJavascript("sum(3,8)", new ValueCallback<String>() {@Overridepublic void onReceiveValue(String value) {Toast.makeText(getApplicationContext(),"Android 4.4 received result:"+value,Toast.LENGTH_SHORT).show();}});

注意:
1. evaluateJavascript须在html加载完毕后执行,否则返回的value为null。
2. 上面限定了结果返回结果为String,对于简单的类型会尝试转换成字符串返回,对于复杂的数据类型,建议以字符串形式的json返回。
3. evaluateJavascript方法必须在UI线程(主线程)调用,因此onReceiveValue也执行在主线程。

Demo下载地址

WebView使用解析(一)之基本用法相关推荐

  1. c语言组json包,json格式解析和libjson的用法介绍(关于cjson的使用方法)

    在阅读本文之前,请先阅读下<Rss Reader实例开发之系统设计>一文. Rss Reader实例开发中,进行网络数据交换时主要使用到了两种数据格式:JSON与XML.本文主要介绍JSO ...

  2. linux网络编程函数解析之——setsockopt / getsockopt用法

    linux网络编程函数解析之--setsockopt / getsockopt用法 工程中无线传输方面的东西用到了setsockopt(),getsockopt().网上相关博客很多,而且类似,原文出 ...

  3. php中的解析范围符,PHP 范围解析操作符(::)用法分析【访问静态成员和类常量】...

    本文实例讲述了PHP 范围解析操作符(::)用法.分享给大家供大家参考,具体如下: 范围解析操作符 (::) 范围解析操作符(也可称作 Paamayim Nekudotayim)或者更简单地说是一对冒 ...

  4. Qt解析XML及QTableWidget用法示例

    #include "widget.h" #include "ui_widget.h" #include <QFile> #include <Q ...

  5. 深入解析final关键字的用法

    夫陶公清风千古,余又何人,敢称庶几 个人博客地址:http://blog.breez.work 文章目录 介绍 解析 final属性 final方法 final参数 final类 介绍 final用于 ...

  6. 【ES6】什么是Promise?解析Promise的基本用法

    Promise介绍 Promise 是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更合理和更强大.它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise ...

  7. 获取解析后的html内容,Android通过WebView获取解析html内容

    混合开发 概述 在Android开发过程中,我们可能需要做一些模拟操作,可能需要获取HTML页面的一些信息,此时就需要借助重写WebViewClient的onPageFinished方法,在onPag ...

  8. 微信小程序JS中写Json解析for 数组的用法

    前言: 1 JS作为小程序的函数库,大家可以作为一个模块编写好自己的函数,然后就可以随心所欲的去创作了.Json的调用,比前面我写的更加简洁写,直接贴代码,应该更加容易懂了. 2 刚入手小程序有些语言 ...

  9. 【pytorch系列】卷积操作原理解析与nn.Conv2d用法详解

    参考: https://pytorch.org/docs/master/generated/torch.nn.Conv2d.html#torch.nn.Conv2d https://zhuanlan. ...

最新文章

  1. linux能修复根目录硬盘,Linux系统报错修复的方法
  2. golang 获取两个时间 相差多少 小时
  3. explain mysql 权限_explain命令为什么可能会修改MySQL数据
  4. OpenCV移植各向异性图像分割间隙流体的实例(附完整代码)
  5. 【java】Java 最坑爹的 10 大功能点
  6. 博客开张,小小的庆下生^_^
  7. paip.输入法编程----删除双字词简拼
  8. 山特UPS电源注意事项
  9. 高斯核与高斯核的卷积的结果还是一个高斯核的推导
  10. 文本串加密和解密程序
  11. java u0000_Java-从字符串中删除\ u0000
  12. 通过企业微信或者微信公众号发送小程序消息推送功能
  13. 网页版手游怎么选服务器,吃鸡怎么看自己选的哪个服务器 | 手游网游页游攻略大全...
  14. 当金融科技遇上云原生,蚂蚁金服是怎么做安全架构的?
  15. Python 第三方库大全看这一篇就够了(1000+工具包)
  16. 迅为RK3568开发板实现的NVR/XVR方案
  17. GitLab CI/CD系列教程(一)
  18. 顶加载天线(圆盘)的设计与仿真
  19. ZooKeeper面试题(2020最新版,springmvc源码分析pdf百度云
  20. 从浮华的管理驾驶舱,到务实的企业信息化

热门文章

  1. Neutron(一)网络实现模型篇
  2. 达梦物理备份与还原介绍
  3. Kali渗透测试:使用Metasploit对Web应用的攻击
  4. vue 简单的购物车页面案例
  5. 史上最易懂——ReactNative分组列表SectionList使用详情及示例详解
  6. ubuntu18.4 安装 wps 2019
  7. 利用机器学习(mediapipe),进行人手的21个3D手关节坐标检测
  8. 关于append函数的疑问
  9. 测试中Bug的管理流程和禅道
  10. 面部吸脂手术会反弹吗?面部吸脂会不会导致皮肤松弛