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

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是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;

    @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 = "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;

    @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); //5M
mWebView.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下载地址

  • 6
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值