现在很多的 APP中会嵌套HTML5的页面,比如经常变化的等等,有一部分页面需要原生Java与HTML5中的js进行交互操作,下面介绍一下android中HTML5的使用:
1、关于HTML5种cookie
网页中可能会用到 用户信息等很多参数,可以提前把这些信息放到cookie中,可以采用以下方法:
1 public static void addCookies(Context context, WebView webView, String url) {
2
3 String url=“https://www.xxxx.com/xx/xx/”
4 String protocol = "";
5 String authority = "";
6 try {
7 URL urlObj = new URL(url);
8 protocol = urlObj.getProtocol();
9 authority = urlObj.getAuthority();
10 } catch (Exception e1) {
11 e1.printStackTrace();
12 }
13
14 String ua = webView.getSettings().getUserAgentString();
15 webView.getSettings().setUserAgentString(Constant.PROJECT_NAME + "/" + ParamHandler.getVersion(context) + "(" + ua + "; HFWSH)");
16
17 if (!TextUtils.isEmpty(url) && !TextUtils.isEmpty(protocol) && !TextUtils.isEmpty(authority)) {
18 if (protocol.equals("https") && authority.indexOf("liepin.com") > -1) {
19 CookieSyncManager.createInstance(context);
20 CookieManager cookieManager = CookieManager.getInstance();
21 cookieManager.setAcceptCookie(true);
22 try {
23 List<String> data = getCookiesString();
24 if (!ListUtils.isEmpty(data)) {
25 for (String value : data) {
26 cookieManager.setCookie(url, value);
27 }
28 }
29 cookieManager.setCookie(url, "client_id=" + Constant.CLIENT_ID + ";path=/;domain=.XXXX.com");
30 cookieManager.setCookie(url, "appVersion=" + Constant .VERSION + ";path=/;domain=.XXXX.com");
31 CookieSyncManager.getInstance().sync();
32 } catch (Exception e) {
33 LogUtils.e("Exception:" + e.getMessage());
34 }
35 }
36 }
37 }
1 public List<String> getCookiesString() {
2 ArrayList data = new ArrayList();
3 this.clearExpired();
4 Collection values = this.mCookies.values();
5 Iterator var3 = values.iterator();
6
7 while(var3.hasNext()) {
8 SwiftCookie c = (SwiftCookie)var3.next();
9 data.add(c.toCookieString());
10 }
11
12 return data;
13 }
在 mWebView.loadUrl(Url)之前添加cookie,网页就可以通过cookie取到相应的参数值了。
2、关于js的安全问题
js在4.2以前有漏洞
通过JavaScript,可以访问当前设备的SD卡上面的任何东西,甚至是联系人信息,短信等。好,我们一起来看看是怎么出现这样的错误的。
1,WebView添加了JavaScript对象,并且当前应用具有读写SDCard的权限,也就是:android.permission.WRITE_EXTERNAL_STORAGE
2,JS中可以遍历window对象,找到存在“getClass”方法的对象的对象,然后再通过反射的机制,得到Runtime对象,然后调用静态方法来执行一些命令,比如访问文件的命令.
3,再从执行命令后返回的输入流中得到字符串,就可以得到文件名的信息了。然后想干什么就干什么,好危险。核心JS代码如下:
1 function execute(cmdArgs)
2 {
3 for (var obj in window) {
4 if ("getClass" in window[obj]) {
5 alert(obj);
6 return window[obj].getClass().forName("java.lang.Runtime") 7 .getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs); 8 } 9 } 10 }
解决方案:
1,Android 4.2以上的系统
在Android 4.2以上的,google作了修正,通过在Java的远程方法上面声明一个@JavascriptInterface,如下面代码:
-
1 class JsObject {
2 @JavascriptInterface
3 public String toString() { return "injectedObject"; }
4 }
5 webView.addJavascriptInterface(new JsObject(), "injectedObject");
6 webView.loadData("", "text/html", null); 7 webView.loadUrl("javascript:alert(injectedObject.toString())");
2,Android 4.2以下的系统
这个问题比较难解决,但也不是不能解决。
首先,我们肯定不能再调用addJavascriptInterface方法了。关于这个问题,最核心的就是要知道JS事件这一个动作,JS与Java进行交互我们知道,有以下几种,比prompt, alert等,
这样的动作都会对应到WebChromeClient类中相应的方法,对于prompt,它对应的方法是onJsPrompt方法,这个方法的声明如下:
- public boolean onJsPrompt(WebView view, String url, String message,
- String defaultValue, JsPromptResult result)
通过这个方法,JS能把信息(文本)传递到Java,而Java也能把信息(文本)传递到JS中,通知这个思路我们能不能找到解决方案呢?
经过一番尝试与分析,找到一种比较可行的方案,请看下面几个小点:
【1】让JS调用一个Javascript方法,这个方法中是调用prompt方法,通过prompt把JS中的信息传递过来,这些信息应该是我们组合成的一段有意义的文本,可能包含:特定标识,方法名称,参数等。
在onJsPrompt方法中,我们去解析传递过来的文本,得到方法名,参数等,再通过反射机制,调用指定的方法,从而调用到Java对象的方法。
【2】关于返回值,可以通过prompt返回回去,这样就可以把Java中方法的处理结果返回到Js中。
【3】我们需要动态生成一段声明Javascript方法的JS脚本,通过loadUrl来加载它,从而注册到html页面中,具体的代码如下:
1 javascript:(function JsAddJavascriptInterface_(){
2 if (typeof(window.jsInterface)!='undefined') {
3 console.log('window.jsInterface_js_interface_name is exist!!');}
4 else {
5 window.jsInterface = {
6 onButtonClick:function(arg0) { 7 return prompt('MyApp:'+JSON.stringify({obj:'jsInterface',func:'onButtonClick',args:[arg0]})); 8 }, 9 10 onImageClick:function(arg0,arg1,arg2) { 11 prompt('MyApp:'+JSON.stringify({obj:'jsInterface',func:'onImageClick',args:[arg0,arg1,arg2]})); 12 }, 13 }; 14 } 15 } 16 )()
说明:
1,上面代码中的jsInterface就是要注册的对象名,它注册了两个方法,onButtonClick(arg0)和onImageClick(arg0, arg1, arg2),如果有返回值,就添加上return。
2,prompt中是我们约定的字符串,它包含特定的标识符MyApp:,后面包含了一串JSON字符串,它包含了方法名,参数,对象名等。
3,当JS调用onButtonClick或onImageClick时,就会回调到Java层中的onJsPrompt方法,我们再解析出方法名,参数,对象名,再反射调用方法。
4,window.jsInterface这表示在window上声明了一个Js对象,声明方法的形式是:方法名:function(参数1,参数2)
3、在html5中进行java和js的交互
1)、方法一:
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.addJavascriptInterface(this, "xxx");
然后在当前类中实现以下方法:
@JavascriptInterface
public void callbackFromH5(final String j) {
//TODO
}
callbackFromH5的名字必须和网页中的js方法名一样
Java调用js方法:
mWebView.loadUrl(String.format("javascript:java2js(0)"));//这里是java端调用webview的JS
js方法名需要和网页端一直
2)方法二:
jsbridge方法(https://github.com/lzyzsd/JsBridge)
Android JsBridge 就是用来在 Android app的原生 java 代码与 javascript 代码中架设通信(调用)桥梁的辅助工具
1 将jsBridge.jar引入到我们的工程
Android Studio:
repositories {
// ...
maven { url "https://jitpack.io" }
}
dependencies {
compile 'com.github.lzyzsd:jsbridge:1.0.4' }
2、布局文件
1 <?xml version="1.0" encoding="utf-8"?>
2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 android:layout_width="match_parent"
4 android:layout_height="match_parent"
5 android:orientation="vertical" >
6
7 <!-- button 演示Java调用web -->
8 <Button
9 android:id="@+id/button"
10 android:layout_width="match_parent"
11 android:text="@string/button_name"
12 android:layout_height="48dp"
13 />
14
15 <!-- webview 演示web调用Java -->
16 <com.github.lzyzsd.jsbridge.BridgeWebView
17 android:id="@+id/webView"
18 android:layout_width="match_parent"
19 android:layout_height="match_parent" >
20 </com.github.lzyzsd.jsbridge.BridgeWebView>
21
22 </LinearLayout>
3、java代码
1 //加载服务器网页
2 webView.loadUrl("https://www.baidu.com");
3
4 //必须和js同名函数。
5 webView.registerHandler("submitFromWeb", new BridgeHandler() {
6
7 @Override
8 public void handler(String data, CallBackFunction function) {
9
10 String str ="html返回给java的数据:" + data;
11
12 makeText(MainActivity.this, str, LENGTH_SHORT).show();
13
14 Log.i(TAG, "handler = submitFromWeb, data from web = " + data);
15 function.onCallBack( str + ",Java经过处理后:"+ str.substring(0,5));
16 }
17
18 });
19 //模拟用户获取本地位置
20 User user = new User();
21 Location location = new Location();
22 location.address = "xxx";
23 user.location = location;
24 user.name = "Bruce";
25
26 webView.callHandler("functionInJs", new Gson().toJson(user), new CallBackFunction() {
27 @Override
28 public void onCallBack(String data) {
29 makeText(MainActivity.this, "网页在获取你的信息", LENGTH_SHORT).show();
30
31 }
32 });
33
34 webView.send("hello");
1 webView.callHandler("functionInJs", "data from Java", new CallBackFunction() {
2
3 @Override
4 public void onCallBack(String data) {
5 // TODO Auto-generated method stub
6 Log.i(TAG, "reponse data from js " + data);
7 }
8
9 });
js调用
1 var str1 = document.getElementById("text1").value;
2 var str2 = document.getElementById("text2").value;
3
4 //调用本地java方法
5 window.WebViewJavascriptBridge.callHandler(
6 'submitFromWeb'
7 , {'param': str1}
8 , function(responseData) {
9 document.getElementById("show").innerHTML = "send get responseData from java, data = " + responseData
10 }
11 );
12
13 //注册事件监听
14 document.addEventListener(
15 'WebViewJavascriptBridgeReady'
16 , function() {
17 callback(WebViewJavascriptBridge)
18 },
19 false
20 );
21
22 //注册回调函数,第一次连接时调用 初始化函数
23 connectWebViewJavascriptBridge(function(bridge) {
24 bridge.init(function(message, responseCallback) {
25 console.log('JS got a message', message);
26 var data = {
27 'Javascript Responds': 'Wee!'
28 };
29 console.log('JS responding with', data);
30 responseCallback(data);
31 });
32
33 bridge.registerHandler("functionInJs", function(data, responseCallback) {
34 document.getElementById("show").innerHTML = ("data from Java: = " + data);
35 var responseData = "Javascript Says Right back aka!";
36 responseCallback(responseData);
37 });
38 })
4、关于webView的优化
1、设置WebView 缓存模式
1 private void initWebView() {
2
3 mWebView.getSettings().setJavaScriptEnabled(true);
4 mWebView.getSettings().setRenderPriority(RenderPriority.HIGH);
5 mWebView.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT); //设置 缓存模式
6 // 开启 DOM storage API 功能
7 mWebView.getSettings().setDomStorageEnabled(true);
8 //开启 database storage API 功能
9 mWebView.getSettings().setDatabaseEnabled(true);
10 String cacheDirPath = getFilesDir().getAbsolutePath()+APP_CACAHE_DIRNAME;
11 // String cacheDirPath = getCacheDir().getAbsolutePath()+Constant.APP_DB_DIRNAME;
12 Log.i(TAG, "cacheDirPath="+cacheDirPath);
13 //设置数据库缓存路径
14 mWebView.getSettings().setDatabasePath(cacheDirPath);
15 //设置 Application Caches 缓存目录
16 mWebView.getSettings().setAppCachePath(cacheDirPath);
17 //开启 Application Caches 功能
18 mWebView.getSettings().setAppCacheEnabled(true);
2、清除缓存
1 /**
2 * 清除WebView缓存
3 */
4 public void clearWebViewCache(){
5
6 //清理Webview缓存数据库
7 try {
8 deleteDatabase("webview.db");
9 deleteDatabase("webviewCache.db");
10 } catch (Exception e) {
11 e.printStackTrace();
12 }
13
14 //WebView 缓存文件
15 File appCacheDir = new File(getFilesDir().getAbsolutePath()+APP_CACAHE_DIRNAME);
16 Log.e(TAG, "appCacheDir path="+appCacheDir.getAbsolutePath());
17
18 File webviewCacheDir = new File(getCacheDir().getAbsolutePath()+"/webviewCache");
19 Log.e(TAG, "webviewCacheDir path="+webviewCacheDir.getAbsolutePath());
20
21 //删除webview 缓存目录
22 if(webviewCacheDir.exists()){
23 deleteFile(webviewCacheDir);
24 }
25 //删除webview 缓存 缓存目录
26 if(appCacheDir.exists()){
27 deleteFile(appCacheDir);
28 }
29 }
3、在使用WebView加载网页的时候,有一些固定的资源文件如js/css/图片等资源会比较大,如果直接从网络加载会导致页面加载的比较慢,而且会消耗比较多的流量。所以这些文件应该放在assets里面同app打包。
解决这个问题用到API 11(HONEYCOMB)提供的shouldInterceptRequest(WebView view, String url) 函数来加载本地资源。
API 21又将这个方法弃用了,是重载一个新的shouldInterceptRequest,需要的参数中将url替换成了成了request。
比如有一个图片xxxxx.png,这个图片已经放在了assets中,现在加载了一个外部html,就需要直接把assets里面的图片拿出来加载而不需要重新从网络获取。当然可以在html里面将图片链接换成file:///android_asset/xxxxx.png,
但是这样这个html就不能在Android ,ios,WAP中公用了。
1 webView.setWebViewClient(new WebViewClient() {
2
3 @Override
4 public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
5 WebResourceResponse response = null;
6 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB){
7 response = super.shouldInterceptRequest(view,url);
8 if (url.contains("xxxxx.png")){
9 try {
10 response = new WebResourceResponse("image/png","UTF-8",getAssets().open("xxxxx.png"));
11 } catch (IOException e) {
12 e.printStackTrace();
13 }
14 }
15 }
16 // return super.shouldInterceptRequest(view, url);
17 return response;
18 }
19
20 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
21 @Override
22 public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
23 WebResourceResponse response = null;
24 response = super.shouldInterceptRequest(view, request);
25 if (url.contains("xxxxx.png")){
26 try {
27 response = new WebResourceResponse("image/png","UTF-8",getAssets().open("xxxxx.png"));
28 } catch (IOException e) {
29 e.printStackTrace();
30 }
31 }
32 return response;
33 }
34 }