目前开发android、ios客户端,为了保持可移植性(苹果、chrome、android浏览器都是用的webkit引擎),一般会采用原生与h5交互(新的方法Native React、Weex等已经流行)。WebView作为android重要组件,非常有必要了解下。那么,我们写个较为完整的例子。
1、布局文件webview.xml
<?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">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal">
<EditText
android:id="@+id/address"
android:layout_width="400dp"
android:layout_height="40dp"
android:layout_centerVertical="true"
android:hint="请输入地址"/>
<Button
android:id="@+id/cutGreenBt"
android:layout_width="80dp"
android:layout_height="35dp"
android:layout_centerVertical="true"
android:text="加载"
android:textColor="@android:color/white" />
<Button
android:id="@+id/captureWebView"
android:layout_width="80dp"
android:layout_height="35dp"
android:layout_centerVertical="true"
android:text="截图"
android:textColor="@android:color/white" />
</LinearLayout>
<WebView
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
2、主Activity
package com.example.test.activity;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.webkit.WebView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import com.example.test.R;
import com.example.test.utils.NativePlugin;
import com.example.test.utils.WebViewManager;
import org.apache.commons.lang.StringUtils;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* @author WiterFellA 2017-03-01
* @purpose
*/
public class WebViewActivity extends Activity implements View.OnClickListener, WebViewManager.WebViewManageListener {
private static String TAG = "WebviewActivity";
private WebView webView;
private Button cutGreenBt = null;
private Button captureWebView = null;
private EditText address = null;
private WebViewManager webViewManager;
private NativePlugin nativePlugin;
private final String nativePluginName = "WebCallNative";
@SuppressLint("NewApi")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().requestFeature(Window.FEATURE_PROGRESS);设置窗口风格为进度条
setContentView(R.layout.webview);
initGui();
initAction();
initData();
}
public void initGui() {
webView = (WebView) findViewById(R.id.webview);
cutGreenBt = (Button) findViewById(R.id.cutGreenBt);
captureWebView = (Button) findViewById(R.id.captureWebView);
address = (EditText) findViewById(R.id.address);
}
public void initAction() {
cutGreenBt.setOnClickListener(this);
captureWebView.setOnClickListener(this);
captureWebView.setEnabled(false);
}
public void initData() {
nativePlugin = new NativePlugin(this, webView);
webViewManager = new WebViewManager(this, webView);
webViewManager.setListener(this);
webViewManager.addJavascriptInterface(nativePlugin, nativePluginName);
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.cutGreenBt:
runOnUiThread(new Runnable() {
@Override
public void run() {
if (!StringUtils.isEmpty(address.getText().toString())) {
webViewManager.load(address.getText().toString().trim());
} else {
Toast.makeText(WebViewActivity.this, "请输入地址", Toast.LENGTH_SHORT).show();
}
}
});
//webViewManager.load("file:///android_asset/www/log.txt");
captureWebView.setEnabled(true);
break;
case R.id.captureWebView:
runOnUiThread(new Runnable() {
ByteArrayOutputStream bos = null;
FileOutputStream fos = null;
@Override
public void run() {
try {
fos = new FileOutputStream(new File("/mnt/sdcard/capture.png"));
Bitmap bitmapSource = webViewManager.captureWebView(webView);
Bitmap bitmap = webViewManager.getScaleBitmap(bitmapSource, 0.5f);
bos = webViewManager.compress(bitmap, 200, 100);
fos.write(bos.toByteArray());
fos.flush();
fos.close();
} catch (IOException e) {
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
}
}
}
}
});
break;
default:
break;
}
}
@Override
protected void onStop() {
super.onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
}
@Override
public void onClickErrorConfirm(int errorCode, String description) {
finish();
}
}
3、WebView实现类WebViewManager
package com.example.test.utils;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.net.http.SslError;
import android.os.Build;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.SslErrorHandler;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;
import org.apache.http.cookie.Cookie;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.util.List;
/**
* @author WiterFellA 2017-03-06
* @purpose WebView设置
*/
public class WebViewManager {
private Context context;
private WebView webView;
private WebSettings webSettings;
private static final String APP_CACAHE_DIRNAME = "/webcache";
private List<Cookie> cookies = null;
private WebViewManageListener webViewManageListener = null;
private AlertDialog.Builder mBuilder;
private AlertDialog mAlertDialog;
public WebViewManager(Context context, WebView webView) {
this.context = context;
this.webView = webView;
initWebView();
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public void initWebView() {
webSettings = webView.getSettings();
//webSettings.setSupportZoom(true);// 支持缩放
//webSettings.setBuiltInZoomControls(true);// 出现缩放工具
//webSettings.setUseWideViewPort(true);// 扩大比例的缩放
webSettings.setLoadWithOverviewMode(true);// 设置页面自适应屏幕
/**
* 用WebView显示图片,可使用这个参数 设置网页布局类型:
* 1、LayoutAlgorithm.NARROW_COLUMNS :适应内容大小
* 2、LayoutAlgorithm.SINGLE_COLUMN : 适应屏幕,内容将自动缩放
*/
webSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);// 设置布局方式-自适应屏幕
//webSettings.setRenderPriority(WebSettings.RenderPriority.HIGH);
//webSettings.setCacheMode(WebSettings.LOAD_CACHE_ONLY);//不使用网络,只读取本地缓存数据。
//webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);// 设置缓存模式,根据cache-control决定是否从网络上取数据。
//webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);//只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。
webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE); // 不使用webview缓存
//webSettings.setPluginState(WebSettings.PluginState.ON);// 支持flash,API18以后不用了
//webSettings.setAppCacheEnabled(true);//h5的应用缓存(可选择性缓存html、css、js等所有)
webSettings.setDomStorageEnabled(true);// 使用h5的localStorage(缓存生命周期无限制)、sessionStorage(生命周期为session存在时间,即浏览器窗口关闭,即删除)
webSettings.setDatabaseEnabled(true);// 开启数据库存储功能
webSettings.setNeedInitialFocus(false); // 阻止内部节点获取焦点
webSettings.setJavaScriptEnabled(true);// 可以调用javascript
// 可以使用File协议,这样才能加载本地文件包括h5页面(比如loadUrl或者file:///)
webSettings.setAllowFileAccess(true);
webSettings.setAllowContentAccess(true);
// 由于类似进行跨域请求读取其他文件;比如在读取一个html(里面有js)后,在可以通过//此js读取其他本地文件;是否开启file协议同源检查
webSettings.setAllowFileAccessFromFileURLs(true);
// 真正的跨域请求,可以通过https、http等访问其他服务器资源
webSettings.setAllowUniversalAccessFromFileURLs(true);
//webSettings.setBlockNetworkLoads(false);// 是否加载网络资源
//webSettings.setBlockNetworkImage(true);// 是否加载网络图像
//webSettings.setDefaultFontSize(16);//设置默认字体的大小
//webSettings.setFixedFontFamily("monospace");//设置默认使用的字体
//webSettings.setLightTouchEnabled(true);//设置用鼠标激活被选项
//webSettings.setDefaultTextEncodingName("uft-8");//设置编码格式,比如gbk
//webSettings.setSavePassword(true);//用户与密码明文保存在h5应用缓存中
/*
if (Build.VERSION.SDK_INT >= 19) {//不要自动加载图片,等页面finish后再发起图片加载。
webSettings.setLoadsImagesAutomatically(true);
} else {
webSettings.setLoadsImagesAutomatically(false);
}
*/
// SDK3.0以上开启硬件加速,部分机器
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
//webView.setDrawingCacheEnabled(true);// 开启图片cache
webView.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY);//滚动条
// 支持获取手势焦点
webView.requestFocusFromTouch();
webView.setFocusable(true);
webView.setLongClickable(true);// 支持长单击事件
webView.setOnLongClickListener(new WebView.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
return false;// WebView开启复制粘贴功能,如果要关闭,改为true
}
});
//点击后退按钮,让WebView后退一页(也可以覆写Activity的onKeyDown方法)
webView.setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (keyCode == KeyEvent.KEYCODE_BACK && webView.canGoBack()) {
webView.goBack(); //后退
return true; //已处理
}
}
return false;
}
});
webView.setWebViewClient(webViewClient);
}
//加载
public void load(String url) {
webView.loadUrl(url);
//webView.loadUrl("javascript:"+functionName+"('"+returnResult+"')");
//webView.loadUrl("file:///android_asset/www/index.html");
webView.requestFocus();
}
//截全屏
public Bitmap captureWebView(WebView webView) {
float scale = webView.getScale();//由于webView可能放大或缩小
int h = (int) (webView.getContentHeight() * scale);
Bitmap bmp = Bitmap.createBitmap(webView.getWidth(), h, Bitmap.Config.RGB_565);
Canvas canvas = new Canvas(bmp);
webView.draw(canvas);
return bmp;
}
//图片缩放
public Bitmap getScaleBitmap(Bitmap bitmap, float scaleXY) {
DisplayMetrics outMetrics = new DisplayMetrics();
((Activity) context).getWindow().getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
int w = outMetrics.widthPixels;
float scale = (float) ((w * scaleXY) / bitmap.getWidth());
int bW = bitmap.getWidth();
int bH = bitmap.getHeight();
Matrix matrix = new Matrix();
matrix.postScale(scale, scale);
Bitmap newBmp = Bitmap.createBitmap(bitmap, 0, 0, bW, bH, matrix, true);
return newBmp;
}
//压缩,图片大小不能超过maxSize(KB),压缩质量quality(100表示不压缩)
public ByteArrayOutputStream compress(Bitmap bitmap, double maxSize, int quality) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int options = 99;
if(baos.toByteArray().length / 1024 <= maxSize){
if (quality >= 0 && quality < 100) {
bitmap.compress(Bitmap.CompressFormat.PNG,
quality, baos);
} else if (quality <= 0) {
bitmap.compress(Bitmap.CompressFormat.PNG,
0, baos);
} else {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
}
}else{
while (baos.toByteArray().length / 1024 > maxSize) {
options -= 3;
if (options < 0) {
break;
}
baos.reset();
if (quality >= 0 && quality < 100) {
bitmap.compress(Bitmap.CompressFormat.PNG,
quality, baos);
} else if (quality <= 0) {
bitmap.compress(Bitmap.CompressFormat.PNG,
0, baos);
} else {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
}
}
}
return baos;
}
private void clearWebView() {
webView.pauseTimers();
webView.stopLoading();
webView.setDrawingCacheEnabled(false);
webView.removeAllViews();
webView.clearCache(true);
webView.clearHistory();
webView.clearFocus();
webView.destroy();
}
// 清除WebView缓存
public void clearWebViewCache() {
/*Webview缓存
/data/data/package_name/cache/webview/
/data/data/package_name/cache/webviewCache/
/data/data/package_name/databases/webview.db 缓存对应的索引
/data/data/package_name/databases/webviewCache.db 缓存对应的索引
*/
// 清理Webview缓存数据库
try {
context.deleteDatabase("webview.db");
context.deleteDatabase("webviewCache.db");
} catch (Exception e) {
e.printStackTrace();
}
// WebView 缓存文件 data/data/<application package>/files
File appCacheDir = new File(context.getFilesDir().getAbsolutePath()
+ APP_CACAHE_DIRNAME);
///data/data/<application package>/cache
File webviewCacheDir = new File(context.getCacheDir().getAbsolutePath()
+ "/webviewCache");
// 删除webview 缓存目录
if (webviewCacheDir.exists()) {
deleteFile(webviewCacheDir);
}
// 删除webview 缓存 缓存目录
if (appCacheDir.exists()) {
deleteFile(appCacheDir);
}
}
// 递归删除 文件/文件夹
public void deleteFile(File file) {
if (file.exists()) {
if (file.isFile()) {
file.delete();
} else if (file.isDirectory()) {
File files[] = file.listFiles();
for (int i = 0; i < files.length; i++) {
deleteFile(files[i]);
}
}
file.delete();
} else {
}
}
// 添加javascript插件,页面与android互动
@SuppressLint("JavascriptInterface")
public void addJavascriptInterface(Object plugin, String name) {
webView.addJavascriptInterface(plugin, name);
}
public void setListener(WebViewManageListener listener) {
this.webViewManageListener = listener;
}
// 添加需要同步的Cookie,并且做一次同步
public void setCookies(List<Cookie> cookies) {
removeCookie();
this.cookies = cookies;
syncCookies(this.cookies);
}
// 清除Cookie
private void removeCookie() {
CookieSyncManager.createInstance(context);
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.removeAllCookie();
CookieSyncManager.getInstance().sync();
}
// Cookie 同步
private void syncCookies(List<Cookie> cookies) {
CookieSyncManager.createInstance(context);
CookieManager cookieManager = CookieManager.getInstance();
if (cookies != null) {
for (int i = 0; i < cookies.size(); i++) {
String sessionString = cookies.get(i).getName() + "="
+ cookies.get(i).getValue() + ";domain="
+ cookies.get(i).getDomain();
// String sessionString = cookies.get(i).getName()
// + "=" + cookies.get(i).getValue()
// + ";path=" + cookies.get(i).getPath();
cookieManager.setCookie("http://192.168.1.200:8080/webview",
sessionString);
}
CookieSyncManager.getInstance().sync();
} else {
cookieManager.removeAllCookie();
cookieManager.removeSessionCookie();
CookieSyncManager.getInstance().sync();
}
}
// 是否支持JS alert 显示
public void setSupportJsAlert(boolean isSupport) {
if (!isSupport) {
webView.setWebChromeClient(null);
} else {
webView.setWebChromeClient(new WebChromeClient() {
@SuppressLint("NewApi")
@Override
public boolean onJsAlert(WebView view, String url,
String message, final JsResult result) {
AlertDialog.Builder b2 = new AlertDialog.Builder(
context,
AlertDialog.THEME_DEVICE_DEFAULT_LIGHT)
.setTitle("测试WebView")
.setMessage(message)
.setPositiveButton("确定",
new AlertDialog.OnClickListener() {
@Override
public void onClick(
DialogInterface dialog,
int which) {
result.confirm();
}
});
b2.setCancelable(false);
b2.create();
b2.show();
return true;
}
@Override
public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {
new AlertDialog.Builder(context)
.setTitle("confirm")
.setMessage(message)
.setPositiveButton("YES", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(context, "hello world", Toast.LENGTH_SHORT);
result.confirm();
}
})
.setNegativeButton("NO", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//处理结果为取消状态 同时唤醒WebCore线程
result.cancel();
}
}).create().show();
return true;
}
//当WebView进度改变时更新窗口进度
@Override
public void onProgressChanged(WebView view, int newProgress) {
//Activity的进度范围在0到10000之间,所以这里要乘以100
((Activity) context).setProgress(newProgress * 100);
}
});
}
}
private WebViewClient webViewClient = new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.requestFocus();
view.loadUrl(url);
return true;
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {//开始加载网页时,发生的的动作
super.onPageStarted(view, url, favicon);
}
// /**
// * 此方法从api 21开始引入
// */
// @SuppressLint("NewApi")
// public WebResourceResponse shouldInterceptRequest(WebView view,
// android.webkit.WebResourceRequest request) {
// String url = request.getUrl().getPath();
// WebResourceResponse response = super.shouldInterceptRequest(view,
// request);
// return getResourceResponse(url,response);
// };
//
// /**
// * 此方法从api 11开始引入,api 21弃用
// */
// @TargetApi(Build.VERSION_CODES.HONEYCOMB)
// @Override
// public WebResourceResponse shouldInterceptRequest(WebView view,
// String url) {
// WebResourceResponse response = super.shouldInterceptRequest(view,
// url);
// return getResourceResponse(url,response);
// }
@Override
public void onPageFinished(WebView view, String url) {//加载网页完成后,发生的的动作
super.onPageFinished(view, url);
/*
if (!webSettings.getLoadsImagesAutomatically()) {
webSettings.setLoadsImagesAutomatically(true);
}
*/
}
@SuppressLint("NewApi")
@Override
public void onReceivedError(WebView view, final int errorCode,
String description, String failingUrl) {//网页加载失败后,发生的动作
String errorFlagString = "";
switch (errorCode) {
// User authentication failed on server
case ERROR_AUTHENTICATION:
errorFlagString = "用户认证失败";
break;
// Failed to connect to the server
case ERROR_CONNECT:
errorFlagString = "连接服务器失败";
break;
// Connection timed out
case ERROR_TIMEOUT:
errorFlagString = "网络连接超时";
break;
case ERROR_PROXY_AUTHENTICATION:
errorFlagString = "用户代理验证失败";
break;
case ERROR_HOST_LOOKUP:
// errorFlagString = "服务器绑定或代理失败";
errorFlagString = "网络连接已断开,请稍后再试";
break;
case ERROR_BAD_URL:
errorFlagString = "URL 格式错误";
break;
default:
errorFlagString = "未知错误";
break;
}
final int err = errorCode;
final String msg = description;
if (mBuilder == null) {
mBuilder = new AlertDialog.Builder(context, AlertDialog.THEME_DEVICE_DEFAULT_LIGHT);
mBuilder.setTitle("网页错误提示");
mBuilder.setMessage(errorFlagString);
mBuilder.setPositiveButton("确定",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (webViewManageListener != null) {
webViewManageListener.onClickErrorConfirm(err,
msg);
}
mBuilder = null;
dialog.dismiss();
}
});
mAlertDialog = mBuilder.create();
mAlertDialog.setCancelable(false);
mAlertDialog.show();
}
if (view.canGoBack()) {
view.goBack();
}
}
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {//对https处理
handler.proceed(); // 接受信任所有网站的证书
// handler.cancel(); // 默认操作 不处理
// handler.handleMessage(null); // 可做其他处理
}
};
public interface WebViewManageListener {
// 联网错误弹出确认框,点击确认框监听
public void onClickErrorConfirm(int errorCode, String description);
}
}
4、插件实现类NativePlugin
package com.example.test.utils;
import android.app.Activity;
import android.webkit.JavascriptInterface;
import android.webkit.WebView;
import android.widget.Toast;
/**
* @author WinterFellA
* @purpose js与android交互插件
*/
public class NativePlugin {
private Activity activity;
private WebView webView;
public NativePlugin(Activity activity, WebView webView) {
this.activity = activity;
this.webView = webView;
}
@JavascriptInterface
public void showToast(final String msg){
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(activity,msg,Toast.LENGTH_SHORT);
}
});
}
}
5、说明
1>WebViewActivity实现了两个功能:加载url(包括file地址)、截图(图片压缩等)。
加载url,需要开启访问网络权限,即 <uses-permission android:name="android.permission.INTERNET" />
截图,需要添加<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
由于安全问题-跨域攻击(同源策略检测),特别注意下面设置
webSettings.setJavaScriptEnabled(true);// 可以调用javascript
// 可以使用File协议,这样才能加载本地文件包括h5页面(比如loadUrl或者file:///)
webSettings.setAllowFileAccess(true);
webSettings.setAllowContentAccess(true);
// 由于类似进行跨域请求读取其他文件;比如在读取一个html(里面有js)后,在可以通过//此js读取其他本地文件;是否开启file协议同源检查
webSettings.setAllowFileAccessFromFileURLs(true);
// 真正的跨域请求,可以通过https、http等访问其他服务器资源
webSettings.setAllowUniversalAccessFromFileURLs(true);
同源策略是浏览器最重要的一种安全机制。同源一般是指协议、域名、端口都相同才行,IE忽略端口的判断。比如以下例子都和
http://www.aa.com:80/index.html比较
http://www.aa.com:80/a/index.html 同源
http://cc.aa.com:80/index.html 域名不同,不同源
http://www.aa.com:9080/index.html 域名不同,不同源
ftp:///aa.com/a 协议和域名、端口不同,不同源
在webview中,当我们通过http加载js,在通过js调用本地文件的时候,就会触发跨域请求
2>js与android交互
高版本android如此定义
@JavascriptInterface
public void showToast(final String msg){
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(activity,msg,Toast.LENGTH_SHORT);
}
});
}
然后webview添加对应的插件类
webView.addJavascriptInterface(plugin, name);
这里的name即WebCallNative(js与之对应)
页面代码如下:
<script type="text/javascript">
function showToast(){
window.WebCallNative.showToast("hello,world");
}
</script>
2>android调用js
webView.loadUrl("javascript:test('hello,world')");
页面代码如下:
<script type="text/javascript">
function test(msg){
alert(msg);
}
</script>