webView的简单使用

###

是基于Android使用了WebKit内核的浏览器框架,和其他组件没有什么区别,主要作用就是加载一些基于HTML页面的基本信息

为什么使用WebView

优点:
  • 1、兼容已有的项目 例如淘宝
  • 2、可以动态更新 BUG的处理

基于以上好处,人们开始思考是否可以通过WebView去替代原有的Native原生开发。于是FaceBook去研究通过WebView去代替原有的Native开发,但是好景不长,facebook发现了WebView不是想象当中的那么好用,发现了如下缺点。

缺点:
  • 1、使用webView的App统一耗电量都比较庞大
  • 2、使用WebView的APP的加载速度也比较卡,
  • 3、由于耗电量比较大导致手机容特别容易发热

WebView基本去使用

其实Google把相应的方式都封装在相应的WebKit内核当中所以对于客户端的我们来说我们只需要创建WebView的对象,通过该对象的loadUrl(String url)方法去加载即可。

1、在XMl布局文件当中引入WebView

activity_main.xml

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<WebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent" />

</LinearLayout>

2、在MainActivity当中对WebView控件进行设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private WebView mWebView;
private String mUrl = "http://www.baidu.com";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}

/**
* 初始化控件
*/
private void initView() {
mWebView = (WebView) findViewById(R.id.webView);
mWebView.loadUrl(mUrl);
}

效果就是当我们的应用程序跑起来以后我们的WebView回去通过浏览器去加载我们的网页,所以我们去对WebView进行设置防止通过浏览器去进行加载

这个时候我们就需要自己去定义一个WebViewClient

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 mWebView.setWebViewClient(new WebViewClient(){
/**
* 覆盖原有的加载方式
* 如果没有给WebView设置WebViewClient,默认情况下WebView会要求Activity Manager选择URL的正确处理程序(浏览器)
* 如果给WebView设置WebViewClient,
* 注意:使用POST“方法”的请求不会调用此方法。
* @param view : WebView
* @param url :请求地址
* @return :返回true,表示主机应用程序处理url,而return false则表示当前WebView处理URL。
*/
// @Override
// public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
// view.loadUrl(request.getUrl().toString());
// return super.shouldOverrideUrlLoading(view, request);
// }
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return super.shouldOverrideUrlLoading(view, url);
}
});

WebView自定义Title

我们手机在显示WebView的时候会在标题的位置去显示当前加载的网页的地址之类的信息。

1、我们去修改XML布局,添加要显示地址的View

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="返回"/>

<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"/>

<Button
android:id="@+id/refresh"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text="刷新"/>
</RelativeLayout>

<WebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent" />

</LinearLayout>

2、对WebView控件进行设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package pengchengliu.imooc.webview;

import android.graphics.Bitmap;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

private String TAG = "WebViewTag";

private WebView mWebView;
Button mBackButton, mRefreshButton;
private TextView mTitleTextView;
String mUrl = "https://www.baidu.com";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.i(TAG ,"Activity创建," + "线程Id为" + Thread.currentThread().getId());
initView();
}

/**
* 初始化控件
*/
private void initView() {
findView();
mWebView.setWebViewClient(new WebViewClient(){
/**
* 页面可以试加载的时候进行回调。
*/
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
Log.i(TAG ,"页面开始加载," + "线程Id为" + Thread.currentThread().getId());
super.onPageStarted(view, url, favicon);
}
/**
* 页面用户可见的时候进行回调
*/
@Override
public void onPageCommitVisible(WebView view, String url) {
Log.i(TAG ,"页面开始加载," + "线程Id为" + Thread.currentThread().getId());
super.onPageCommitVisible(view, url);
}
/**
* 页面加载完成的时候进行回调
*/
@Override
public void onPageFinished(WebView view, String url) {
Log.i(TAG ,"页面加载完成," + "线程Id为" + Thread.currentThread().getId());
super.onPageFinished(view, url);
}
/**
* 覆盖原有的加载方式
* 如果没有给WebView设置WebViewClient,默认情况下WebView会要求Activity Manager选择URL的正确处理程序(浏览器)
* 如果给WebView设置WebViewClient,
* 注意:使用POST“方法”的请求不会调用此方法。
* @param view : WebView
* @param url :请求地址
* @return :返回true,表示主机应用程序处理url,而return false则表示当前WebView处理URL。
*/
// @Override
// public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
// view.loadUrl(request.getUrl().toString());
// return super.shouldOverrideUrlLoading(view, request);
// }
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return super.shouldOverrideUrlLoading(view, url);
}
});
mWebView.setWebChromeClient(new WebChromeClient(){
/**
* 通知主机应用程序更改文档标题。
* @param view 启动回调的WebView。
* @param title 包含文档的新标题的字符串。
*/
@Override
public void onReceivedTitle(WebView view, String title) {
//在主线程当中,可以直接更新UI
mTitleTextView.setText(title);
super.onReceivedTitle(view, title);
}
});
mWebView.loadUrl(mUrl);
}

/**
* 通过ID查找View
*/
private void findView() {
mWebView = (WebView) findViewById(R.id.webView);
mBackButton = (Button) findViewById(R.id.back);
mRefreshButton = (Button) findViewById(R.id.refresh);
mTitleTextView = (TextView) findViewById(R.id.title);
MyListener myListener = new MyListener();
mBackButton.setOnClickListener(myListener);
mRefreshButton.setOnClickListener(myListener);
}

/**
* 按钮监听事件
*/
private class MyListener implements View.OnClickListener {
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.back:
//可以通过这个方法去重新加载
mWebView.goBack();
Log.i(TAG, mWebView.getUrl());
break;
case R.id.refresh:
mWebView.reload();
//mWebView.goForward();//前进
//mWebView.reload(); //刷新
Log.i(TAG, mWebView.getUrl());
break;
}
}
}
}

使用WebView去下载文件

在下载之前必须要获取到文件的地址,需要DownloadListener接口的实现类,重写他的方法,而下载的具体url遍会出现在它的参数当中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* WebView下载的监听器
*/
private class WebViewDownload implements DownloadListener{
/**
* 通过WebView去下载文件时所需要的监听器
* 通知主机应用程序应该下载文件
     * @param url应该下载的内容的完整网址
     * @param userAgent要用于下载的用户代理。
     * @param contentDisposition Content-disposition http header,if当下。
     * @param mimetype服务器报告的内容的mimetype
     * @param contentLength服务器报告的文件大小
*/
@Override
public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
Log.i("haha", "这是一个下载地址, url ------ > " + url);
if(!url.endsWith(".apk")){
Log.i("haha", "下载文件不是apk文件");
return ;
}
new DownloadThread(url).start();
}
}

主要就是两种方式,

1、通过WebView获取将要下载的文件地址,然后通过Intent将要下载的Uri地址传过去,让系统自己为我们下载。
1
2
3
Uri uri = Uri.parse(url);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
2、通过WebView获取将要下载的文件地址,然后自己新建一个线程去进行下载。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class DownloadThread extends Thread {

private String url ;

DownloadThread(String url){
this.url = url;
}

@Override
public void run() {
try {
Log.i("haha", "开始下载");
URL url = new URL(this.url);
HttpURLConnection connect = (HttpURLConnection) url.openConnection();
connect.setDoInput(true);
connect.setDoOutput(true);
InputStream inputStream = connect.getInputStream();
if(!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
Log.e("haha", "不存在SD卡,或者外部存储空间不足");
return;
}
File descFile = Environment.getExternalStorageDirectory();
File srcFile = new File(descFile, "temp.apk");
OutputStream outputStream = new FileOutputStream(srcFile);
byte[] bytes = new byte[6 * 1024];
int len ;
while((len = inputStream.read(bytes)) != -1){
outputStream.write(bytes, 0, len);
Log.i("haha", "0,~~~" + len);
}
Log.i("haha", "下载成功");
} catch (IOException e) {
Log.i("haha", "下载失败");
e.printStackTrace();
}
}
}

WebView对错误页面的处理

在手机没有网络的情况下,WebView会默认给我们展示一个断网页面,但是对于一个商业项目来说,展示这个页面略显粗糙,我们如何自定义错误页面呢?有以下两种方法

  • 1、还在本地错误页面,但是还属于HTML页面
1
2
3
4
5
6
@Override
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
//加载本地资源
mWebView.loadData(file://xxx.xxx.xxx);
super.onReceivedError(view, request, error);
}
  • 2、加载一个本地的Native页面,在相应的WebViewClient内部重写监听WebView加载错误的回调,然后在错误回调当中去做相应的处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
mWebView.setWebViewClient(new WebViewClient(){
/**
* 当WebView出现错误的时候进行回调。一般用来进行错误页面的重写,
* 向主机应用程序报告Web资源加载错误。 这些错误通常表示无法连接到服务器。 请注意,与回拨版本不同的是,
* 新版本将被调用任何资源(iframe,image等),而不仅仅是为主页面。 因此,建议在此回调中执行最低要求的工作。
* @param view 启动回调的WebView。
* @param request 请求发起请求。
* @param error 错误有关错误的信息。
*/
@Override
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
Log.i("xixi" ,"onReceivedError,");
mErrorTextView .setText("404有错误");
mWebView.setVisibility(View.GONE);
super.onReceivedError(view, request, error);
}
}

WebView如何同步Cookie问题

开发过程中如果我们已经让用户登陆过了,在另外的页面是WebView,在刷到最新的WebView的时候会提示用户重新登录,但是如果我们把登录信息保存在Cookie当中,在刷WebView的时候一起传递给服务器,这样遍不会提示用户重新登录了。

这种问题就是客户端和服务器去同步Cookie问题。

流程就是:客户端先去登录,登录以后可以拿到相应的Cookie信息,当我们去第二次进行登陆的时候,拿着我们第一次登录Cookie信息,去设置相应的Cookie,已达到免登录的状态

问题 : 如何通过JavaAPI去进行POST请求,然后根据相应的POST请求拿到相应的Cookie,然后如何通过设置Cookie给WebView进行相应的设置,以至于达到免登录的状态

如何进行POST请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CookieStore cookieStore = new BasicCookieStore();
CloseableHttpClient client = HttpClientBuilder.create().setDefaultCookieStore(cookieStore).build();
HttpPost httpPost = new HttpPost(requestUrl);

List<NameValuePair> params = new ArrayList<>();
NameValuePair name = new BasicNameValuePair("","");
NameValuePair password = new BasicNameValuePair("","");
params.add(name);params.add(password);

if(params.size() <= 0){
return ;
}

UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params, charset);
httpPost.setEntity(entity);
CloseableHttpResponse execute = client.execute(httpPost);

如何获取Cookie

1
2
3
4
5
6
7
if (execute.getStatusLine().getStatusCode() == 200){
List<Cookie> cookies = cookieStore.getCookies();
for (Cookie tempCookie : cookies) {
Log.i("cookies", tempCookie.getName() + ": " + tempCookie.getValue() );
//使用Handler
}
}

如何给WebView设置Cookie

1
2
3
4
5
6
7
8
9
10
11
12
13
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
CookieSyncManager.createInstance(MainActivity.this);
CookieManager manager = CookieManager.getInstance();
manager.setAcceptCookie(true);
manager.setCookie(mUrl, msg.obj.toString());
CookieSyncManager.getInstance().sync();

mWebView.loadUrl(mUrl);
super.handleMessage(msg);
}
};

WebView和JS混淆回调

首先我们应该明白,在正常的情况下WebView和JS是可以相互调用的通过@JavaScriptIntface注解,但是在我们的apk上线的时候我们需要给我们的apk打入一个replase包,让我们的apk当中的代码混淆,这样就会使我们原有的JS调不到我们的android端的接口。

解决方案:就是在打入混淆的时候,对我们要给Js回调的类和方法进行保护,这样当前类就不能混淆了。

在当前项目里面的proguard.cfg文件当中加入如下代码

1
2
3
-keep public class com.example.webview_01.WebHost{
public <methods>
}

所涉及到的知识点:

WebHost.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 当前类为暴露给JS用户的接口
* Created by pengchengliu on 2017/8/13.
*/

public class WebHost {

private Context mContext;

public WebHost(Context context){
this.mContext = context;
}
public void callJs(){
Toast.makeText(mContext, "12334", Toast.LENGTH_LONG).show();
}
}

MainActivity.java

1
2
3
4
5
6
7
8
9
/**
* Java 暴露给 Js 回调接口
*/
@SuppressLint("AddJavascriptInterface")
private void setWebViewJavaScriptInterface() {
WebSettings settings = mWebView.getSettings();
settings.setJavaScriptEnabled(true);
mWebView.addJavascriptInterface(new WebHost(MainActivity.this),"callJs");
}

WebView导致的远程注入的问题

在android本地和js交互的途中会出现种种的注入问题,大体的流程就是我们把客户端的对象暴露给了服务器,服务器当中可以拿到我们暴露给它的对象,拿到这个对象,服务器就可以拿到我们本地的Runtime对象,就哭执行种种命令来获取我们本地的信息。Google也是意识到这样的危险性,所以在版本4.2之前就已经被修复,浏览器厂商也是做了相应的BUG修复,但是在开发者做WebView的时候,需要注意。

解决办法:尽可能少的去做Java和JS之间的交互,最好的就是通过自定义协议来处理相应的问题。


WebView自定义协议拦截问题

就是客户端人员和Web前端人员进行商议得到一个协议,然后就是通过Url上面带有不同的参数在Android里面的回调根据不同的参数进行不同的参数,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 覆盖原有的加载方式
* 如果没有给WebView设置WebViewClient,默认情况下WebView会要求Activity Manager选择URL的正确处理程序(浏览器)
* 如果给WebView设置WebViewClient,
* 注意:使用POST“方法”的请求不会调用此方法。
* @param view : WebView
* @param url :请求地址
* @return :返回true,表示主机应用程序处理url,而return false则表示当前WebView处理URL。
*/
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
//xxxxx就是约定的协议
if(url.endsWith("xxxxxxx")){
//做相应的操作
return true
}
view.loadUrl(url);
return super.shouldOverrideUrlLoading(view, url);
}