您的位置:首页 > 移动开发

【android学习】WebView详解

2017-08-11 10:10 295 查看
java8中加入了javascript引擎,有时间试试。

http://www.tuicool.com/articles/ne6b6fI

1,概念

WebView(网络视图)能加载显示网页,可以将其视为一个浏览器。它使用了WebKit渲染引擎加载显示网页。

2,方法

1)网页回退:goBack();

if (webView_guarantee.canGoBack()) {
webView_guarantee.goBack();
}


注意:

a.因为重定向而无法goBack

解决方案:

http://www.baidu.com网址改为https://www.baidu.com.

原因:

web服务器如:apache等,配置好https后,需要设置url重定向规则,使网站页面的http访问都自动转到https访问。所以造成网页的webView.canGoBack()返回永远为true。

http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。http的连接很简单,是无状态的。HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全。

2)网页重载:reload()

if (null != webView_guarantee) {
showMessage("正在刷新,请稍后");
webView.reload();
}


3)网页/JS加载

①loadUrl(url)

直接网页显示 :

webView.loadUrl("http://www.google.com");


直接网络图片显示 :

WebView
.loadUrl("http://www.gstatic.com/codesite/ph/images/code_small.png");


显示本地图片文件 :

// 本地文件处理(如果文件名中有空格需要用+来替代)
WebView.loadUrl("file:///android_asset/icon.png");


显示本地网页文件 :

// 本地文件处理(如果文件名中有空格需要用+来替代)
WebView.loadUrl("file:///android_asset/test.html");


注意:

webview.loadurl 的加载是在另一个线程中执行必须要在webview加载完毕执行。

比如:

webview.loadUrl("file:///android_asset/test.html");
webview.loadUrl("javascript:alert(test)");


第二句可能看不到或后发先至,因为loadUrl是异步执行的。

此时,避免出现这种问题的方式是:将第二句放在handle中,当第一句执行成功再执行第二句。

②中文显示:loadData()

String data = " 测试含有空格的Html 数据";
// 不对空格做处理
WebView.loadData(URLEncoder.encode(data, "utf-8"), "text/html", "utf-8" );
// 对空格做处理(在SDK1.5 版本中)
WebView.loadData(URLEncoder.encode(data, encoding).replaceAll(
"\+", " "), "text/html", "utf-8");


③loadDataWithBaseURL()

显示本地图片和文字混合的Html 内容 :

String data = "测试本地图片和文字混合显示,这是APK 里的图片";
WebView.loadDataWithBaseURL("about:blank", data, "text/html", "utf-8" "");


4)获取webview配置管理器:getSettings()

①支持javascript:

webView.getSettings().setJavaScriptEnabled(true);// 支持javascript


②控制JS跨域访问能力

setAllowFileAccess(true);//webview是否允许JS访问本地文件系统,默认允许
setAllowFileAccessFromFileURLs(true);//WebView是否运行运行在本地文件中的JS访问其它的本地文件。Android4.1之前默认允许,之后默认为禁止的。
setAllowUniversalAccessFromFileURLs(true);//WebView是否运行运行在本地文件中的JS访问其它的本地或远程文件。Android4.1之前默认运行,之后默认为禁止的。


为了避免文件跨域脚本漏洞,在不需要的情况下,将以上3个设置全部设置为false。

③其它

WebSettings ws = webView_guarantee.getSettings();
/**支持javascript*/
ws.setJavaScriptEnabled(true);
/**
* 允许访问文件
* 若html是一个文件框的话,就可以浏览本地文件
*/
ws.setAllowFileAccess(true);
/**设置显示缩放按钮*/
ws.setBuiltInZoomControls(false);
/**支持缩放*/
ws.setSupportZoom(false);
/**
* 开启DOM Storage
* 应该是Html 5中的localStorage
* (可以使用Android4.4手机和Chrome Inspcet Device联调),
* 用于持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的,
* 绝大多数的浏览器都是支持 localStorage 的,
* 但是鉴于它的安全特性(任何人都能读取到它,尽管有相应的限制,将敏感数据存储在这里依然不是明智之举),
* Android 默认是关闭该功能的。
*/
ws.setDomStorageEnabled(true);
/**开启缓存*/
ws.setAppCacheEnabled(true);
/**设置WebView是否使用其内置的变焦机制,
* 该机制结合屏幕缩放控件使用,默认是false,
* 不使用内置变焦机制。
*/
ws.setAllowContentAccess(true);
/**
* 标示可以通过javaScript访问file文件
*/
ws.setAllowFileAccessFromFileURLs(true);
/**
*  在使用webview显示网页或者图片、flash的时候,总会出现没有充满webview的现象,左右滑动,就可以使之充满全屏。
*/
ws.setLoadWithOverviewMode(true);
/**
* 设定支持viewport
*/
ws.setUseWideViewPort(true);


5)setWebChromeClient()

主要辅助WebView处理Javascript的对话框、网站图标、网站title、加载进度等。

webView.setWebChromeClient(new WebChromeClient() {
/**
* 获取网页title
*/
@Override
public void onReceivedTitle(WebView view, String title) {
super.onReceivedTitle(view, title);
mTitleBarView.setTitleText(title);
}
/**
* 回调函数:当加载的数据有所改变的时候,就会通知给你。
*/
@Override
public void onProgressChanged(WebView view, int newProgress) {

super.onProgressChanged(view, newProgress);
setTitleBarChange(view);
}
});


6)设置事件处理器:setWebViewClient()

主要帮助WebView处理各种通知、请求事.

①WebView导航能力控制:shouldOverrideUrlLoading(WebView view, String url)

决定当用户点击WebView加载的网页内的超链接时,Android应用是继续采用当前的WebView去加载超链接所指向的目标网页,还是启动用户设备的默认浏览器来显示新网页。

用法如下demo.

默认使用设备浏览器来加载网页。

如果WebView没有添加WebViewClient,那么这个WebView不可导航;否则,这个WebView的导航能力取决于所添加的WebViewClient的shouldOverrideUrlLoading方法。该方法没有重载,或重载的方法返回默认的super()或false值,或使用loadUrl()方法来显示新的url,那么该WebView是可导航的。

当WebView可导航的时候,可能存在跨站点脚本漏洞

②onReceivedError

如下demo.

/**
* 加载网页
*/
webView.setWebViewClient(new WebViewClient() {

/**
* 一般在onPageStarted之前开始调用
* 返回true:点击网页里面的链接在当前的webview里跳转;
* 返回false:调用系统浏览器或第三方浏览器打开链接。
*/
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {

view.loadUrl(url);
return true;
}

/**
* 捕获http error
*/
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
/**
* 用javascript隐藏系统定义的404页面信息,防止IP泄露
*/
String data = "当前网页无法访问,请检查网络状态!";
view.loadUrl("javascript:document.body.innerHTML=\"" + data + "\"");
}
});


③onPageFinished()

网页加载完成后执行。

注意:

当前正在加载的网页产生跳转的时候这个方法可能会被多次调用。所以当WebView需要加载各种各样的网页并且需要在页面加载完成时采取一些操作的话,可能WebChromeClient.onProgressChanged()比WebViewClient.onPageFinished()都要靠谱一些。

7)webView退出

当程序调用了WebView加载网页,WebView会自己开启一些线程,如果你没有正确地将WebView销毁的话,这些残余的线程会一直在后台运行,由此导致你的应用程序耗电量居高不下。

①destroy()

推荐的方法。

即使webView依赖的activity被销毁了,gc也不会立即回收webview内存,等回收时会自动调用destroy.

webView_guarantee.destroy();


②System.exit(0);

暴力方法(不推荐):Activity.onDestroy()中直接调用System.exit(0),使得应用程序完全被移出虚拟机。

@Override
protected void onDestroy() {

super.onDestroy();
/**
* 使Webview应用程序完全被移出虚拟机,节省耗电
*/
System.exit(0);
}


8)WebView加载带有Input的输入框时点击无法弹出软键盘

添加代码:

webView.requestFocusFromTouch() ;


9)支持JS-java交互

addJavascriptInterface()


3,demo

1)简单demo

<!-- 允许应用程序完全使用网络 -->
<uses-permission android:name="android.permission.INTERNET" />


public class MainActivity extends Activity {

private static String Tag = "MainActivity";
private WebView webView;

private final String url_WebView =
"https://www.baidu.com";

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

findView();
init();
}

@SuppressLint("SetJavaScriptEnabled")
private void findView() {

try {

webView = (WebView) findViewById(R.id.webView);
webView.getSettings().setJavaScriptEnabled(true);// 支持javascript

} catch (Exception e) {

System.out.println(Tag + "findView()");
}
}

/**
* 弹窗消息提示
*
* @param s
*/
private void showMessage(String s) {

Toast.makeText(MainActivity.this, s, Toast.LENGTH_SHORT).show();
}

/**
* 获取网页内容
*/
private void getwebView() {
try {
showMessage("正在刷新,请稍后");
webView.loadUrl(url_WebView);

/**
* 加载网页
*/
webView.setWebViewClient(new WebViewClient() {

@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// 返回值是true的时候控制去WebView打开,为false调用系统浏览器或第三方浏览器
view.loadUrl(url);
return true;
}

@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
// 用javascript隐藏系统定义的404页面信息
String data = "当前网页无法访问,请检查网络状态!";
view.loadUrl("javascript:document.body.innerHTML=\"" + data + "\"");
}
});
} catch (Exception e) {

System.out.println(Tag + "getwebView()");
}

}
private void init() {

try {
getwebView();
} catch (Exception e) {

System.out.println(Tag + "init()");
}
}

@Override
protected void onDestroy() {

super.onDestroy();
/**
* 使Webview应用程序完全被移出虚拟机,节省耗电
*/
System.exit(0);
}
}


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


2)改进

问题:

因为添加了回退按钮、刷新按钮。title根据网页加载的不同而不同,产生了一系列问题:

1)点击进入新的链接后,回退之后,title未及时更新。

2)点击刷新后,会有一瞬间闪过网址(title获取结果为网址)

解决方案:

1)建立一个栈来存储每次获得的标题,每次点击回退时读取栈内信息。

private List<String> List_titles = new ArrayList<String>();


2)回退按钮点击事件处理:

if (webView_guarantee.canGoBack()) {

webView_guarantee.goBack();
if (null != List_titles && !List_titles.isEmpty()) {
mTitleBarView.setTitleText(List_titles.get(List_titles.size() - 2));
List_titles.remove(List_titles.size() - 1);
}
}


3)每次获得新标题入栈

webView_guarantee.setWebChromeClient(new WebChromeClient() {
/**
* 获取网页title
*/
@Override
public void onReceivedTitle(WebView view, String title) {
super.onReceivedTitle(view, title);

/**
* 去掉title为网址的情况
* 去掉title相同的情况
*/
if (null != title && !title.contains(".html")) {

if (List_titles.isEmpty() || !List_titles.get(List_titles.size()-1).equals(title)) {

List_titles.add(title);
mTitleBarView.setTitleText(title);
}
}
}


3)基于2的修改

再次修改后又遇到一些问题:

1)通过onReceivedError可以处理网页出错(如下),但是无法处理404错误。

/**
* 捕获http error
*/
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
/**
* 用javascript隐藏系统定义的404页面信息,防止IP泄露
*/
super.onReceivedError(view, errorCode, description, failingUrl);
String Str_data = "当前网页无法访问,请检查网络状态!";
view.loadUrl("javascript:document.body.innerHTML=\"" + Str_data + "\"");

}


2)因为加了title,当出现404错误时,会获取title为“找不到网页”。这样并不好。

解决方案:

1)通过getRespStatus(String url)方法,检测网址返回的IIS状态码,主要目的是检测出404进行处理。因为是耗时操作,所以开一个线程,在线程里面检测IIS状态。

2)添加网络出错标志:htmlError。只要触发onReceivedTitle方法,就将htmlError置为true。每次onReceivedTitle的时候,httmError为true不获取标题,并再次把htmlError置为false.

demo(只添加新加入的部分):

/**
* 判断网络是否出错,出错为true,则不更新title
*/
private boolean htmlError = false;

private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == 404) {// 主页不存在
/**
* 移除最近title:找不到网页
*/
setHtmlError(webView_guarantee);
} else {
webView_guarantee.loadUrl(Str_url);
}
}
};


刷新:

@Override
public void onClick(View v) {
// getWebview_Guarantee();
if (null != webView_guarantee) {
showMessage("正在刷新,请稍后");
//                  webView_guarantee.reload();
load(webView_guarantee.getUrl());
}
}


private String Str_url = "";
private void load(String url) {
Str_url = url;

new Thread(new Runnable() {
@Override
public void run() {
Message msg = new Message();
// 此处判断主页是否存在,因为主页是通过loadUrl加载的,
// 此时不会执行shouldOverrideUrlLoading进行页面是否存在的判断
// 进入主页后,点主页里面的链接,链接到其他页面就一定会执行shouldOverrideUrlLoading方法了
if (getRespStatus(url_WebView) == 404) {
msg.what = 404;
}
handler.sendMessage(msg);
}
}).start();
}


在oncreate中初次加载网页:

load(url_WebView);


webView_guarantee.setWebChromeClient(new WebChromeClient() {
/**
* 获取网页title
*/
@Override
public void onReceivedTitle(WebView view, String title) {
super.onReceivedTitle(view, title);

/**
* 去掉title为网址的情况 去掉title相同的情况
*/
if (null != title && !title.contains(".html") && !htmlError) {

set_titles.add(title);
mTitleBarView.setTitleText(title);
}
htmlError = false;
}
});


/**
* 加载网页
*/
webView_guarantee.setWebViewClient(new WebViewClient() {

/**
* 一般在onPageStarted之前开始调用 返回true:点击网页里面的链接在当前的webview里跳转;
* 返回false:调用系统浏览器或第三方浏览器打开链接。
*/
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {

load(url);
return true;
}

/**
* 捕获http error
*/
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
/**
* 用javascript隐藏系统定义的404页面信息,防止IP泄露
*/
super.onReceivedError(view, errorCode, description, failingUrl);
/**
* 移除最近title:找不到网页
*/
htmlError = true;
setHtmlError(view);
}
});


private void setHtmlError(WebView view) {
String Str_data = "当前网页无法访问,请检查网络状态!";
view.loadUrl("javascript:document.body.innerHTML=\"" + Str_data + "\"");
}


private int getRespStatus(String url) {
int status = -1;
try {
HttpHead head = new HttpHead(url);
HttpClient client = new DefaultHttpClient();
HttpResponse resp = client.execute(head);
status = resp.getStatusLine().getStatusCode();
} catch (IOException e) {
status = 404;
}
return status;
}


4,交互性能

业界衡量web app交互性能的优劣主要通过监测webView渲染页面的四个指标:

1)白屏时间( firstPaint)

指浏览器开始显示内容的时间,即开始解析DOM耗时,用户在没有滚动时候看到的内容渲染完成并且可以交互的时间。

如:在低网速的环境中,页面由上至下缓慢显示完、或者先显示文本内容后再重绘成有格式的页面内容。

2)DOM树构建时间(domReady Time)

指浏览器开始对基础页文本内容进行解析到文本中构建出一个内部数据结构(DOM树)的时间。DOM树构建后,CSS等才开始渲染。

DOM树加载后,用户才可以渲染。

3)整页时间(FirstScreen Time)

指用户看到第一屏(整个网页顶部大小未当前窗口)时的时间。

4)首屏时间(Page Load Time)

指页面完成整个加载过此的时间。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: