package com.minggo.pluto.util; import android.text.TextUtils; import android.util.Log; import android.webkit.WebView; import com.google.gson.Gson; import org.json.JSONArray; import org.json.JSONObject; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.HashMap; /** * JS调用java * * @author minggo * @time 2015-5-8上午10:50:26 */ public class JsCallJava { private final static String TAG = "JsCallJava"; private final static String RETURN_RESULT_FORMAT = "{\"code\": %d, \"result\": %s}"; private HashMap<String, Method> mMethodsMap; private String mInjectedName; private String mPreloadInterfaceJS; private Gson mGson; public JsCallJava(String injectedName, Class injectedCls) { try { if (TextUtils.isEmpty(injectedName)) { throw new Exception("injected name can not be null"); } mInjectedName = injectedName; mMethodsMap = new HashMap<String, Method>(); // 获取自身声明的所有方法(包括public private protected), getMethods会获得所有继承与非继承的方法 Method[] methods = injectedCls.getDeclaredMethods(); StringBuilder sb = new StringBuilder("javascript:(function(b){console.log(\""); sb.append(mInjectedName); sb.append(" initialization begin\");var a={queue:[],callback:function(){var d=Array.prototype.slice.call(arguments,0);var c=d.shift();var e=d.shift();this.queue[c].apply(this,d);if(!e){delete this.queue[c]}}};"); //LogUtils.info("WebView", "methods有获取" + methods.length); for (Method method : methods) { method.setAccessible(true); String sign = null; // method.getModifiers() != (Modifier.PUBLIC | Modifier.STATIC) // || if (method.getModifiers() != (Modifier.PUBLIC | Modifier.STATIC) || (sign = genJavaMethodSign(method)) == null) { //LogUtils.info("WebView", "没有获取" + sign); continue; } //LogUtils.info("WebView", "获取" + sign); mMethodsMap.put(sign, method); sb.append(String.format("a.%s=", method.getName())); } sb.append("function(){var f=Array.prototype.slice.call(arguments,0);if(f.length<1){throw\""); sb.append(mInjectedName); sb.append(" call error, message:miss method name\"}var e=[];for(var h=1;h<f.length;h++){var c=f[h];var j=typeof c;e[e.length]=j;if(j==\"function\"){var d=a.queue.length;a.queue[d]=c;f[h]=d}}var g=JSON.parse(prompt(JSON.stringify({method:f.shift(),types:e,args:f})));if(g.code!=200){throw\""); sb.append(mInjectedName); sb.append(" call error, code:\"+g.code+\", message:\"+g.result}return g.result};Object.getOwnPropertyNames(a).forEach(function(d){var c=a[d];if(typeof c===\"function\"&&d!==\"callback\"){a[d]=function(){return c.apply(a,[d].concat(Array.prototype.slice.call(arguments,0)))}}});b."); sb.append(mInjectedName); sb.append("=a;console.log(\""); sb.append(mInjectedName); sb.append(" initialization end\")})(window);"); mPreloadInterfaceJS = sb.toString(); } catch (Exception e) { Log.e(TAG, "init js error:" + e.getMessage()); } } private String genJavaMethodSign(Method method) { String sign = method.getName(); method.setAccessible(true); Class[] argsTypes = method.getParameterTypes(); int len = argsTypes.length; if (len < 1 || argsTypes[0] != WebView.class) { Log.w(TAG, "method(" + sign + ") must use webview to be first parameter, will be pass"); return null; } for (int k = 1; k < len; k++) { Class cls = argsTypes[k]; if (cls == String.class) { sign += "_S"; } else if (cls == int.class || cls == long.class || cls == float.class || cls == double.class) { sign += "_N"; } else if (cls == boolean.class) { sign += "_B"; } else if (cls == JSONObject.class) { sign += "_O"; } else if (cls == JsCallback.class) { sign += "_F"; } else { sign += "_P"; } } return sign; } public String getPreloadInterfaceJS() { return mPreloadInterfaceJS; } public String call(WebView webView, String jsonStr) { if (!TextUtils.isEmpty(jsonStr)) { try { JSONObject callJson = new JSONObject(jsonStr); String methodName = callJson.getString("method"); JSONArray argsTypes = callJson.getJSONArray("types"); JSONArray argsVals = callJson.getJSONArray("args"); String sign = methodName; int len = argsTypes.length(); Object[] values = new Object[len + 1]; int numIndex = 0; String currType; values[0] = webView; for (int k = 0; k < len; k++) { currType = argsTypes.optString(k); if ("string".equals(currType)) { sign += "_S"; values[k + 1] = argsVals.isNull(k) ? null : argsVals.getString(k); } else if ("number".equals(currType)) { sign += "_N"; numIndex = numIndex * 10 + k + 1; } else if ("boolean".equals(currType)) { sign += "_B"; values[k + 1] = argsVals.getBoolean(k); } else if ("object".equals(currType)) { sign += "_O"; values[k + 1] = argsVals.isNull(k) ? null : argsVals.getJSONObject(k); } else if ("function".equals(currType)) { sign += "_F"; values[k + 1] = new JsCallback(webView, mInjectedName, argsVals.getInt(k)); } else { sign += "_P"; } } Method currMethod = mMethodsMap.get(sign); // 方法匹配失败 if (currMethod == null) { return getReturn(jsonStr, 500, "not found method(" + sign + ") with valid parameters"); } // 数字类型细分匹配 if (numIndex > 0) { Class[] methodTypes = currMethod.getParameterTypes(); int currIndex; Class currCls; while (numIndex > 0) { currIndex = numIndex - numIndex / 10 * 10; currCls = methodTypes[currIndex]; if (currCls == int.class) { values[currIndex] = argsVals.getInt(currIndex - 1); } else if (currCls == long.class) { // WARN: argsJson.getLong(k + defValue) will return // a bigger incorrect number values[currIndex] = Long.parseLong(argsVals.getString(currIndex - 1)); } else { values[currIndex] = argsVals.getDouble(currIndex - 1); } numIndex /= 10; } } return getReturn(jsonStr, 200, currMethod.invoke(null, values)); } catch (Exception e) { // 优先返回详细的错误信息 if (e.getCause() != null) { return getReturn(jsonStr, 500, "method execute error:" + e.getCause().getMessage()); } return getReturn(jsonStr, 500, "method execute error:" + e.getMessage()); } } else { return getReturn(jsonStr, 500, "call data empty"); } } private String getReturn(String reqJson, int stateCode, Object result) { String insertRes; if (result == null) { insertRes = "null"; } else if (result instanceof String) { result = ((String) result).replace("\"", "\\\""); insertRes = "\"" + result + "\""; } else if (!(result instanceof Integer) && !(result instanceof Long) && !(result instanceof Boolean) && !(result instanceof Float) && !(result instanceof Double) && !(result instanceof JSONObject)) { // 非数字或者非字符串的构造对象类型都要序列化后再拼接 if (mGson == null) { mGson = new Gson(); } insertRes = mGson.toJson(result); } else { // 数字直接转化 insertRes = String.valueOf(result); } String resStr = String.format(RETURN_RESULT_FORMAT, stateCode, insertRes); Log.d(TAG, mInjectedName + " call json: " + reqJson + " result:" + resStr); return resStr; } }