pedant / safe-java-js-webview-bridge

为WebView中的Java与JavaScript提供【安全可靠】的多样互通方案
http://pedant.github.io/2014/07/04/webview-js-java-interface-research/
2.43k stars 567 forks source link

我把你的JsCallJava的代码改了一下,用起来会更加灵活:(你可以参考一下) #12

Closed CLovinr closed 8 years ago

CLovinr commented 8 years ago

我把你的代码改了一下,用起来会更加灵活:(你可以参考一下) new MyInjectedChromeClient(new JsCallJava.InjectObj("myjs.ui", MyJs.class), new JsCallJava.InjectObj("myjs.ui2", MyJs2.class,MyJs3.class,MyJs4.class));

package cn.pedant.SafeWebViewBridge;

import android.text.TextUtils; import android.webkit.WebView; import android.util.Log;

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;

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 static class InjectObj
{
    String namespace;
    Class<?>[] interfaceClasses;

    public InjectObj(String namespace, Class<?>... interfaceClasses)
    {
        if (TextUtils.isEmpty(namespace))
        {
            throw new RuntimeException("namespace can not be null!");
        }
        this.namespace = namespace;
        this.interfaceClasses = interfaceClasses;
    }
}
/////>

public JsCallJava(InjectObj... injectObjs)
{
    try
    {

        mMethodsMap = new HashMap<String, Method>();
        StringBuilder sbuilder = new StringBuilder("javascript:");

        for (InjectObj injectObj : injectObjs)
        {
            injectOne(sbuilder, injectObj);
        }

        mPreloadInterfaceJS = sbuilder.toString();
    } catch (Exception e)
    {
        e.printStackTrace();
        throw new RuntimeException("init js error:" + e.getMessage());
    }
}

private void injectOne(StringBuilder sbuilder, InjectObj injectObj)
{
    String mInjectedName = injectObj.namespace;

    StringBuilder sb = new StringBuilder("(function(b){console.log(\"");/////去掉前面的javascript://////

    sb.append(mInjectedName);
    sb.append(
            " initialization begin\");var a={");

    sb.append("namespace:\"").append(mInjectedName).append("\",");//////添加一个namespace///////

    sb.append("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 (Class<?> c : injectObj.interfaceClasses)
    {
        searchClass(sb, c);
    }

    StringBuilder namespaces = new StringBuilder();

    {
        StringBuilder temp = new StringBuilder();
        String[] ss = injectObj.namespace.split("\\.");
        for (String s : ss)
        {
            if ("".equals(s))
            {
                continue;
            } else
            {
                temp.append(".").append(s);
                namespaces.append("b").append(temp).append("=").append("b").append(temp).append("||{};");
            }
        }
    }

    ///////////>

    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");

    sb.append(",namespace:a.namespace");/////////加入namespace/////////

    sb.append("})));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)))" +
                    "}}});");

    sb.append(namespaces);//////////加入,如:b.ui={};b.ui.abc={};

    sb.append("b.").append(mInjectedName);
    sb.append("=a;console.log(\"");
    sb.append(mInjectedName);
    sb.append(" initialization end\")})(window);");

    /////////
    sbuilder.append(sb);
    ////////
}

private void searchClass(StringBuilder sb, Class<?> c)
{
    /////个人建议还是用getMethods,这样可以不用把所有的static函数都挤在一个类里,而可以把一部分放在父类中.//////
    Method[] methods = c.getMethods();

    for (Method method : methods)
    {
        String sign;
        if (method.getModifiers() != (Modifier.PUBLIC | Modifier.STATIC) || (sign = genJavaMethodSign(
                method)) == null)
        {
            continue;
        }
        mMethodsMap.put(sign, method);
        sb.append(String.format("a.%s=", method.getName()));
    }
}

private String genJavaMethodSign(Method method)
{
    String sign = method.getName();
    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 mInjectedName = callJson.getString("namespace");//////得到namespace///////

            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;
}

}