標籤:直接 async index eid context try script 傳遞 activity
最近研究webview與js互動,看了幾個開源庫實現,感覺不盡如人意,存在主要問題是,耦合較高,使用不夠簡潔,後來參考Uri設定規則,格局Uri類似協議自訂了類似的js互動協議
比較簡潔,自訂協議內容樣式如:jsbridge://android-app/method123?a=123&b=345#jsMethod1(p1,p2)
協議說明:
scheme定義為jsbridge,用於區分別的網路請求(http),
authority定義為android-app,區分不同平台處理
path定義為 調用本地方法名稱 method123
Query定義為調用本地方法參數 a=123&b=345
Fragment定義為回調js方法 #jsMethod1(p1,p2)
這樣就可以做到精簡,靈活,良好的可擴充,靈活使用的特點。再看解析實現·
public boolean parseJsBridge(String url,WebAppInterface webNative){//WebAppInterface為被調用本地方法類執行個體 if(!isProtocol(url)) return false; int i = -1; Uri uri = Uri.parse(url);//藉助URI解析協議 String methodName = uri.getPath();//調用本地方法,method123 methodName = methodName.replace("/", ""); String params = uri.getQuery();//調用本地方法參數。a=123&b=345 String callback = uri.getFragment();//解析js回調方法,#jsMethod1(p1,p2) i = callback.lastIndexOf(‘(‘); String jsMethod = callback.substring(0,i); String jsParams = callback.substring(i+1,callback.length()-1);//將解析的結果封裝為JsResponse,便於後續使用 mJsRes = new JsResponse(methodName,jsMethod); mJsRes.parseJsCallbackParams(jsParams); mJsRes.parseNativeParams(params);
//這裡通過反射方式調用本地方法 String[] args = mJsRes.getMethodArgs(); if(null==args){//使用反射調用無參數方法 jsCallNoParamMethod(webNative,methodName); }else{//調用反射有參數方法 int count = args.length; Class[] javaParamsType = new Class[count]; for(int t=0;t<count;t++){ javaParamsType[t] = String.class; } jsCallParamMethod(webNative,methodName,javaParamsType,mJsRes.getMethodArgs()); } return true; }
協議解析完整實作類別:
public class JsProcessor {// private JsProcessor(){} private JsResponse mJsRes; public JsResponse getJsResponse(){ return mJsRes; } public static boolean isProtocol(String url) { return !TextUtils.isEmpty(url) && url.startsWith("jsbridge://"); }// jsbridge://android-app/method123?a=123&b=345#jsMethod1(p1,p2)" public boolean parseJsBridge(String url,WebAppInterface webNative){ if(!isProtocol(url)) return false; int i = -1; Uri uri = Uri.parse(url); String methodName = uri.getPath();//method1 methodName = methodName.replace("/", ""); String params = uri.getQuery();//a=123&b=345 String callback = uri.getFragment();//#jsMethod1(p1,p2) i = callback.lastIndexOf(‘(‘); String jsMethod = callback.substring(0,i); String jsParams = callback.substring(i+1,callback.length()-1); mJsRes = new JsResponse(methodName,jsMethod); mJsRes.parseJsCallbackParams(jsParams); mJsRes.parseNativeParams(params); String[] args = mJsRes.getMethodArgs(); if(null==args){ jsCallNoParamMethod(webNative,methodName); }else{ int count = args.length; Class[] javaParamsType = new Class[count]; for(int t=0;t<count;t++){ javaParamsType[t] = String.class; } jsCallParamMethod(webNative,methodName,javaParamsType,mJsRes.getMethodArgs()); } return true; } public boolean jsCallNoParamMethod(Object obj,String mName){ boolean success = false; try { Method method=obj.getClass().getMethod(mName); method.invoke(obj); success = true; } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); }catch (Exception ex){ ex.printStackTrace(); } return success; } //getMethod("sayHello", String.class,int.class); public boolean jsCallParamMethod(Object obj,String mName,Class<?>[] parameterTypes,Object[] args){ if(parameterTypes.length!=args.length) return false; boolean success = false; try { Method method = obj.getClass().getMethod(mName,parameterTypes); method.invoke(obj, args); success = true; } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); }catch (Exception ex){ ex.printStackTrace(); } return success; }}
JSResponse解析後的封裝實體類
public class JsResponse { //call native public String methodName; private Map<String,String> nativeParams = new HashMap<>(); //callback js method public String callJsMethod;// public Map<String,String> jsParams = new HashMap<>(); private int jsParamCount; public JsResponse(String javaMethod,String jsMethod){ this.methodName = javaMethod; this.callJsMethod = jsMethod; } public String[] getMethodArgs(){ if(0==nativeParams.size()) return null; String[] params = new String[nativeParams.size()]; int n = 0; for(Map.Entry<String,String> entry:nativeParams.entrySet()){ params[n] = entry.getValue(); n++; } return params; } public void parseNativeParams(String params){// int i = 0,len = params.length(); if(!nativeParams.isEmpty()) nativeParams.clear(); String[] eles = params.split("&"); for(String ele:eles){ int eq = ele.indexOf(‘=‘); String value = ele.substring(eq + 1); String name = ele.substring(0,eq); nativeParams.put(name,value); } } public void parseJsCallbackParams(String jsparams){ String[] eles = jsparams.split(","); jsParamCount = eles.length; }}
js使用規則協議,觸發調用本地方法如下,即可
function startRequest() { var iframe = document.createElement(‘iframe‘); iframe.setAttribute(‘src‘, ‘jsbridge://android-app/showHello?a=hello-&b=i_from_js&#callbackHello(p1,p2)‘); document.body.appendChild(iframe); iframe.parentNode.removeChild(iframe); iframe = null; }
本地調用js回調方法
void executeJsCmd(String jsCmd){ Log.d(TAG,"pending execute js command="+jsCmd); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { //async execute mWebView.evaluateJavascript(jsCmd, new ValueCallback<String>() { @Override public void onReceiveValue(String value) { Log.d(TAG,"async execute result="+value); } }); }else { // This code is BAD and will block the UI thread mWebView.loadUrl(jsCmd); } }
下面就是js與本地通訊了,通過WebViewClient的shouldOverrideUrlLoading攔截內容判斷處理
private class MonitorWebClient extends WebViewClient { WebAppInterface webCallapp = new WebAppInterface(activity); ....... @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { boolean handle = super.shouldOverrideUrlLoading(view, url); JsProcessor jsPro = null; if (!handle) { jsPro = new JsProcessor(); handle = jsPro.parseJsBridge(url,webCallapp); } if(handle){ handleJsCallback(jsPro,"hello","callback js method"); } return handle; } @Override public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { //如果需要傳檔案,攔截WebView的資源請求,將檔案以流的形式進行通訊 //return new WebResourceResponse("image/jpeg", "UTF-8", new FileInputStream(new File("xxxx"); return super.shouldInterceptRequest(view, request); } }
void handleJsCallback(JsProcessor jsPro,String... args){
//javascript:showJavaMessage(‘javaResult‘)"
//拼接js要執行方法名字與參數規則,如上範例 String jsM = jsPro.getJsResponse().callJsMethod; if(!TextUtils.isEmpty(jsM)){// String jsCmd = new StringBuilder().append("javascript:").append(jsM).// append(‘(‘).append("\‘java\‘").append(‘,‘).append("\‘hello\‘").append(‘)‘).toString(); StringBuilder buf = new StringBuilder().append("javascript:").append(jsM).append(‘(‘); if(args!=null && args.length>0){// int pos = buf.length()-1; for(String p:args) { buf.append(‘\‘‘).append(p).append(‘\‘‘).append(‘,‘); } buf.deleteCharAt(buf.length()-1); } buf.append(‘)‘); executeJsCmd(buf.toString()); } }
除了上面shouldOverrideUrlLoading方法攔截處理外,js也可以通過調用 alert,prompt 方法的方式傳遞資料 ,我們需要重寫WebChromeClient 通過回調本地方法onJsAlert(), onJsPrompt()方法即可收到對應函數回調,進行資料處理
private class AppWebChromeClient extends WebChromeClient { @Override public void onReachedMaxAppCacheSize(long spaceNeeded, long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) { quotaUpdater.updateQuota(spaceNeeded * 2); } @Override public boolean onConsoleMessage(ConsoleMessage cm) { Log.d("MyApplication", cm.message() + " -- From line " + cm.lineNumber() + " of " + cm.sourceId() ); return true; } @Override public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { if("1001".equals(message)){ //解析參數defaultValue,調用java方法並得到結果 //textexeJs(); } //給js返回處理結果 result.confirm("result"); return false; } @Override public boolean onJsAlert(WebView view, String url, String message, JsResult result) { Log.d("MyApplication",url); AlertDialog alertDialog = new AlertDialog.Builder(activity).create(); // Setting Dialog Title alertDialog.setTitle("JS come message"); // Setting Dialog Message alertDialog.setMessage(message); alertDialog.show(); return false; } }
調用本地方法類對象
public class WebAppInterface { Context mContext; /** Instantiate the interface and set the context */ WebAppInterface(Context c) { mContext = c; } /** Show a toast from the web page */ @JavascriptInterface public void showToast(String toast) { Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show(); } @JavascriptInterface public void showDialog(String dialogMsg){ AlertDialog alertDialog = new AlertDialog.Builder(mContext).create(); // Setting Dialog Title alertDialog.setTitle("JS triggered Dialog"); // Setting Dialog Message alertDialog.setMessage(dialogMsg); alertDialog.show(); } public void showHello(String title,String msg){ ToastUtils.showLong(mContext,title+msg); }}
js樣本內容
<html><head> <style> body{ background-color: #FA5858;color:#fff; }input{background-color: #F7D358;width: 300px;padding:10px;color: #000; } div#content{ padding:20px; background-color: #F7D358; color: #000; } </style> <script type="text/javascript"> function showAndroidToast(toastmsg) { Android.showToast(toastmsg); } function showAndroidDialog(dialogmsg) { Android.showDialog(dialogmsg); } function moveToScreenTwo() { Android.moveToNextScreen(); } function showPromptToJava() { var ret = prompt( "1001", "defaultMessage001" ); //ret值即為java傳回的”result” //根據返回內容作相應處理 } function showJavaMessage(result) { alert("hello!"+result); } function startRequest() { var iframe = document.createElement(‘iframe‘); iframe.setAttribute(‘src‘, ‘jsbridge://android-app/showHello?a=hello-&b=i_from_js&#callbackHello(p1,p2)‘); document.body.appendChild(iframe); iframe.parentNode.removeChild(iframe); iframe = null; } function callbackHello(p1,p2) { console.log("receive callback from app"); alert("receive callback "+p1+p2); } </script></head><body><center> <h3>Binding JavaScript code to Android code</h3> <div id="content"> //some content here </div> <div> Here are few examples: </div> <div> <input type="button" value="Make Toast" onClick="showAndroidToast(‘Toast made by Javascript‘)" /><br/> <input type="button" value="Trigger Dialog" onClick="showAndroidDialog(‘This dialog is triggered by Javascript ‘)" /><br/> <input type="button" value="Take me to Next Screen" onClick="showPromptToJava()" /><br/> <input type="button" value="jsBridgeHandle" onClick="startRequest()" /><br/> </div></center></body></html>
本地顯示
mWebView.loadUrl("file:///android_asset/sample1.html");
以上就是整個js與本地方法調用整體流程
備忘問題:
js與本地調用會產生的問題:
1,調用js函數參數注意
string 使用‘‘ 需要單引號拼接,直接value或"value"則無法正常匹配
直接value為整數類型
2,onJsPrompt,onJsAlert,返回true,js預設不處理,導致下次無法調用(即無反應事件)
android webview與js簡單的互動方案