WebView使用解析(一)之基本用法

来源:互联网 发布:淘宝优惠券怎么用不了 编辑:程序博客网 时间:2024/05/23 14:34

WebView基本用法

加载在线URL

void loadUrl(String url)

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

url = "http://www.baidu.com";...mLoad.setOnClickListener(new View.OnClickListener() {            @Override            public 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;    @Override    public 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是http://img.my.csdn.net,data中有个Img标签,它的内容是:<\img src=’/uploads/201309/01/1378037151_7904.jpg’>,很明显src的地址不是本地地址也不是在线地址,那它就是一个相对地址,所以加上baseUrl以后才是它的完整地址:http://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;    @Override    public 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 = "http://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;    @Override    protected 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() {            @Override            public void onClick(View v) {                mWebView.clearCache(true); //清除缓存            }        });        initWebView();        mWebView.setWebViewClient(new WebViewClient() {            @Override            public void onLoadResource(WebView view, String url) {                Log.i(TAG, "onLoadResource url="+url); // 开始加载                super.onLoadResource(view, url);            }            @Override            public boolean shouldOverrideUrlLoading(WebView webview, String url) {                Log.i(TAG, "intercept url="+url);                // 重写此方法表明点击网页里面的链接还是在当前的webview里跳转,不跳到浏览器那边                webview.loadUrl(url);                return true;            }            @Override            public void onPageStarted(WebView view, String url, Bitmap favicon) {                Log.e(TAG, "onPageStarted");            }            @Override            public void onPageFinished(WebView view, String url) {                String title = view.getTitle(); //得到网页标题                Log.e(TAG, "onPageFinished WebView title=" + title);                mTitle.setText(title);                mTitle.setVisibility(View.VISIBLE);            }            @Override            public 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加载内存泄漏     */    @Override    protected 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); //5MmWebView.getSettings().setAllowFileAccess(true); //使manifest生效

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

mWebView.setWebChromeClient(new WebChromeClient(){            //扩充缓存的容量            @Override            public 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() {            @Override            public 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() {            @Override            public 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); //替换成从本地获取image                htmlCode = replaceStyle(document,"link","href",htmlCode); //替换成从本地获取css                htmlCode = 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注解,不然无效        @JavascriptInterface        public 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注解,不然无效        @JavascriptInterface        public void toastMessage(String message) {            Toast.makeText(getApplicationContext(), "JS--->Natvie:" + message, Toast.LENGTH_LONG).show();        }        @JavascriptInterface        public 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>() {                    @Override                    public 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下载地址

原创粉丝点击