現在很多的 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())");
這個問題比較難解決,但也不是不能解決。
首先,我們肯定不能再調用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引入到我們的工程
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 }
