android js调用java 4.2之下漏洞的解决方案

最近在做hybrid 开发 涉及js调用java的问题,android4.2之下有漏洞 所以4.2之下不能使用官方的addJavascriptInterface 方法实现。查了很多资料 找到一个比较好的解决方案,参考了github上以为大牛下的点击打开链接https://github.com/pedant/safe-java-js-webview-bridge,他是基于webView的WebChromeClient 的监听js.prompt方法实现的,但是他的方法有一点不够灵活,就是只能调用本地的静态方法,然后静态方法的第一个参数必须为WebView 然后反射调用。然而我们很多时候  需要在方法里面使用成员属性,这个时候就不方便 我在他的核心代码上做了一些修改!
<span style="font-size:14px;color:#333333;">public class JsCallJava<T> {
    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;
    //当前对象
    private T mInstance;

    public JsCallJava(String injectedName, T instance) {
        try {
            if (TextUtils.isEmpty(injectedName)) {
                throw new Exception("injected name can not be null");
            }
            mInstance = instance;
            mInjectedName = injectedName;
            mMethodsMap = new HashMap<String, Method>();
            //获取自身声明的所有方法(包括public private protected), getMethods会获得所有继承与非继承的方法
            Method[] methods = instance.getClass().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]}}};");
            for (Method method : methods) {
                String sign;
                // Log.e("print",method.getName()+"==isPublic:"+);
                if (!Modifier.isPublic(method.getModifiers()) || (sign = genJavaMethodSign(method)) == null) {
                    continue;
                }
                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());
        }
    }</span>



上面的构造函数中我把 当前的对象以泛型的方式传入了进来供反射调用的使用,注册方法的时候 我是使用了自定义的注解JsInterface,判断有此注解的方法才能被注入js回调
<strong><span style="font-size:14px;">private String genJavaMethodSign(Method method) {
        String sign = method.getName();
        Class[] argsTypes = method.getParameterTypes();
        int len = argsTypes.length;
        Log.e("print", len + "===" + method.toString());
        //包含该注解的方法才可以使用的
      </span><span style="font-size:18px;color:#ff0000;">  JsInterface jsMeta = method.getAnnotation(JsInterface.class);
        if (jsMeta == null) {
            Log.w(TAG, "method(" + sign + ") must have annotation @JsInterface ,or will be pass");
            return null;
        }</span><span style="font-size:14px;">
        //Log.e("print","meta:"+meta);

        // Log.e("print","isSameClass"+(argsTypes[0]==mInstance.getClass().getSuperclass()));
//        if (len < 1 || argsTypes[0] != mInstance.getClass()) {
//            Log.w(TAG, "method(" + sign + ") must use webview to be first parameter, will be pass");
//          //  return null;
//        }
//        if (len < 1 || argsTypes[0] != mInstance.getClass()) {
//            Log.w(TAG, "method(" + sign + ") must use webview to be first parameter, will be pass");
//            //  return null;
//        }
        for (int k = 0; 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;
    }</span></strong>

在反射调用的时候把当前对象传入,就可以调用成员方法了,这样的话整个架构就比较灵活,感谢safe-java-js-webview-bridge的作者提供了这么好的解决方案!
<span style="font-size:14px;">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");
                //Log.e("print",methodName+"="+argsTypes+"="+argsVals);

                String sign = methodName;
                int len = argsTypes.length();
                Object[] values = new Object[len];
                int numIndex = 0;
                String currType;
                for (int k = 0; k < len; k++) {
                    currType = argsTypes.optString(k);
                    if ("string".equals(currType)) {
                        sign += "_S";
                        values[k] = argsVals.isNull(k) ? null : argsVals.getString(k);
                    } else if ("number".equals(currType)) {
                        sign += "_N";
                        //为了标记for循环的判断 如果第一位就是number类型排除0
                        numIndex = numIndex * 10 + k + 1;
                        Log.e("print", "numIndex:" + numIndex);
                    } else if ("boolean".equals(currType)) {
                        sign += "_B";
                        values[k] = argsVals.getBoolean(k);
                    } else if ("object".equals(currType)) {
                        sign += "_O";
                        values[k] = argsVals.isNull(k) ? null : argsVals.getJSONObject(k);
                    } else if ("function".equals(currType)) {
                        sign += "_F";
                        values[k] = 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) - 1;
                        currCls = methodTypes[currIndex];
                        if (currCls == int.class) {
                            values[currIndex] = argsVals.getInt(currIndex);
                        } else if (currCls == long.class) {
                            //WARN: argsJson.getLong(k + defValue) will return a bigger incorrect number
                            values[currIndex] = Long.parseLong(argsVals.getString(currIndex));
                        } else {
                            values[currIndex] = argsVals.getDouble(currIndex);
                        }
                        // Log.e("print",currIndex+"=numIndex="+numIndex+" value:"+ values[currIndex]);
                        numIndex /= 10;
                    }
                }
                </span><span style="font-size:24px;color:#ff0000;">return getReturn(jsonStr, 200, currMethod.invoke(mInstance, values));</span><span style="font-size:14px;">
            } 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");
        }
    }</span>

最后上两张图片
最后资源地址:http://download.csdn.net/detail/issingleman/9540271
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android中,您可以使用WebView来加载一个网页,然后通过JavaScriptJava代码进行交互。下面是一个示例: 首先,在您的Java代码中,您需要定义一个类并将其公开给JavaScript使用。例如: ```java public class MyJavaScriptInterface { Context mContext; MyJavaScriptInterface(Context context) { mContext = context; } @JavascriptInterface public void showToast(String toast) { Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show(); } } ``` 在这里,我们定义了一个名为 `MyJavaScriptInterface` 的类,并将其传递给 `Context`,以便我们可以在其中显示Toast消息。我们还定义了一个名为 `showToast` 的方法,并使用 `@JavascriptInterface` 注释将其标记为可以从JavaScript调用。 接下来,在您的Activity中,您需要启用JavaScript,并将您的Java对象添加到WebView中。例如: ```java WebView webView = (WebView) findViewById(R.id.webview); webView.getSettings().setJavaScriptEnabled(true); webView.addJavascriptInterface(new MyJavaScriptInterface(this), "Android"); webView.loadUrl("file:///android_asset/index.html"); ``` 在这里,我们首先获取WebView实例,然后启用JavaScript。接下来,我们将 `MyJavaScriptInterface` 实例添加到WebView中,并将其命名为 `Android`,这是JavaScript中将使用的名称。最后,我们使用 `loadUrl` 方法将WebView加载到我们的HTML文件中。 最后,在您的JavaScript代码中,您可以使用以下方式调用Java方法: ```javascript Android.showToast("Hello World!"); ``` 在这里,我们使用我们在Java代码中定义的名称 `Android` 来调用 `showToast` 方法,并将消息传递给它。在这种情况下,我们将显示一个Toast消息,显示 "Hello World!"。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值