4.Android与JS的交互(兼容API 17以下版本)

来源:互联网 发布:send to kindle mac 编辑:程序博客网 时间:2024/06/05 18:52

转载请注明出处 http://blog.csdn.net/qq_31715429/article/details/50972559
本文出自:猴菇先生的博客

(1).MainActivity.java:

public class MainActivity extends Activity {    private WebView webView;    @SuppressLint({ "SetJavaScriptEnabled", "AddJavascriptInterface" })    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        webView = (WebView) findViewById(R.id.webView);        // 取消Scrollbar        webView.setVerticalScrollbarOverlay(true);        // 设置WebView支持JavaScript        webView.getSettings().setJavaScriptEnabled(true);        String url = "file:///android_asset/html.html";        webView.loadUrl(url);        // 在js中调用本地java方法        webView.addJavascriptInterface(new JsInterface(this), "AndroidWebView");        // 添加客户端支持       webView.setWebChromeClient(new WebChromeClient());    }    private class JsInterface {        private Context mContext;         public JsInterface(Context context) {                 this.mContext = context;         }         // 在js中调用window.AndroidWebView.showInfoFromJs(name),便会触发此方法。         @JavascriptInterface //API17以及以上才有这个注解的jar包JavascriptInterface.class                                            //更换API版本:工程右键 --> Perpertise --> Android -->右边选择         public void showInfoFromJs(String name) {                 Toast.makeText(mContext, name, Toast.LENGTH_SHORT).show();         }    }    // 在java中调用js代码    public void sendInfoToJs(View view) {        String msg = ((EditText) findViewById(R.id.input_et)).getText().toString();        // 调用js中的函数:showInfoFromJava(msg)        webView.loadUrl("javascript:showInfoFromJava('" + msg + "')");    }}

(2).html.html:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><meta http-equiv="Content-Language" content="zh-cn" /><title>Android WebView 与 Javascript 交互</title><head>  <style>  body {background-color:#e6e6e6}  .rect  {    color:white;    font-family:Verdana, Arial, Helvetica, sans-serif;    font-size:16px;    width:100px;    padding:6px;    background-color:#98bf21;    text-decoration:none;    text-align:center;    border:none;    cursor:pointer;  }  .inputStyle {font-size:16px;padding:6px}  </style></head><body>  <p>测试Android WebView 与 Javascript 交互</p>  <input id = "name_input" class = "inputStyle" type="text"/>  <a class = "rect" onclick="sendInfoToJava()">JS调用Java</a>  <script>  function sendInfoToJava(){    //调用android程序中的方法,并传递参数    var name = document.getElementById("name_input").value;    window.AndroidWebView.showInfoFromJs(name);  }  //在android代码中调用此方法  function showInfoFromJava(msg){    alert("来自客户端的信息:"+msg);  }  </script></body></html>

—————————————————————————————-
以上是在API 17以上没有问题,因为@JavascriptInterface注解是在17以上为了增加与js交互的安全性才有的,但是在17以下如何与js交互呢?尝试使用了一下第三方控件WebViewEx

/** * 这个类解析了Android 4.0以下的WebView注入Javascript对象引发的安全漏洞。 */public class WebViewEx extends WebView {    private OnSizeChangedListener mOnSizeChangedListener;    private static final boolean DEBUG = true;    private static final String VAR_ARG_PREFIX = "arg";    private static final String MSG_PROMPT_HEADER = "MyApp:";    private static final String KEY_INTERFACE_NAME = "obj";    private static final String KEY_FUNCTION_NAME = "func";    private static final String KEY_ARG_ARRAY = "args";    private static final String[] mFilterMethods = {"getClass", "hashCode", "notify", "notifyAll", "equals","toString", "wait",};    private HashMap<String, Object> mJsInterfaceMap = new HashMap<String, Object>();    private String mJsStringCache = null;    public WebViewEx(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);        init(context);    }    public WebViewEx(Context context, AttributeSet attrs) {        super(context, attrs);        init(context);    }    public WebViewEx(Context context) {        super(context);        init(context);    }    private void init(Context context) {        // 添加默认的Client        super.setWebChromeClient(new WebChromeClientEx());        super.setWebViewClient(new WebViewClientEx());        // 删除掉Android默认注册的JS接口        removeSearchBoxImpl();    }    @Override    public void addJavascriptInterface(Object obj, String interfaceName) {        if (TextUtils.isEmpty(interfaceName)) {            return;        }        // 如果在4.2以上,直接调用基类的方法来注册        if (hasJellyBeanMR1()) {            super.addJavascriptInterface(obj, interfaceName);        } else {            mJsInterfaceMap.put(interfaceName, obj);        }    }    @Override    public void removeJavascriptInterface(String interfaceName) {        if (hasJellyBeanMR1()) {            super.removeJavascriptInterface(interfaceName);        } else {            mJsInterfaceMap.remove(interfaceName);            mJsStringCache = null;            injectJavascriptInterfaces();        }    }    private boolean removeSearchBoxImpl() {        if (hasHoneycomb() && !hasJellyBeanMR1()) {            super.removeJavascriptInterface("searchBoxJavaBridge_");            return true;        }        return false;    }    private void injectJavascriptInterfaces() {        if (!TextUtils.isEmpty(mJsStringCache)) {            loadJavascriptInterfaces();            return;        }        String jsString = genJavascriptInterfacesString();        mJsStringCache = jsString;        loadJavascriptInterfaces();    }    private void injectJavascriptInterfaces(WebView webView) {        if (webView instanceof WebViewEx) {            injectJavascriptInterfaces();        }    }    private void loadJavascriptInterfaces() {        this.loadUrl(mJsStringCache);    }    private String genJavascriptInterfacesString() {        if (mJsInterfaceMap.size() == 0) {            mJsStringCache = null;            return null;        }        /*         * 要注入的JS的格式,其中XXX为注入的对象的方法名,例如注入的对象中有一个方法A,那么这个XXX就是A         * 如果这个对象中有多个方法,则会注册多个window.XXX_js_interface_name块,我们是用反射的方法遍历         * 注入对象中的所有带有@JavaScripterInterface标注的方法         *          * javascript:(function JsAddJavascriptInterface_(){         * if(typeof(window.XXX_js_interface_name)!='undefined'){         * console.log('window.XXX_js_interface_name is exist!!'); }else{         * window.XXX_js_interface_name={ XXX:function(arg0,arg1){ return         * prompt(         * 'MyApp:'+JSON.stringify({obj:'XXX_js_interface_name',func:'XXX_',args:[arg0,arg1]}));         * }, }; } })()         */        Iterator<Entry<String, Object>> iterator = mJsInterfaceMap.entrySet().iterator();        // Head        StringBuilder script = new StringBuilder();        script.append("javascript:(function JsAddJavascriptInterface_(){");        // Add methods        try {            while (iterator.hasNext()) {                Entry<String, Object> entry = iterator.next();                String interfaceName = entry.getKey();                Object obj = entry.getValue();                createJsMethod(interfaceName, obj, script);            }        } catch (Exception e) {            e.printStackTrace();        }        // End        script.append("})()");        return script.toString();    }    private void createJsMethod(String interfaceName, Object obj, StringBuilder script) {        if (TextUtils.isEmpty(interfaceName) || (null == obj) || (null == script)) {            return;        }        Class<? extends Object> objClass = obj.getClass();        script.append("if(typeof(window.").append(interfaceName).append(")!='undefined'){");        if (DEBUG) {            script.append("    console.log('window." + interfaceName + "_js_interface_name is exist!!');");        }        script.append("}else {");        script.append("    window.").append(interfaceName).append("={");        // Add methods        Method[] methods = objClass.getMethods();        for (Method method : methods) {            String methodName = method.getName();            // 过滤掉Object类的方法,包括getClass()方法,因为在Js中就是通过getClass()方法来得到Runtime实例            if (filterMethods(methodName)) {                continue;            }            script.append("        ").append(methodName).append(":function(");            // 添加方法的参数            int argCount = method.getParameterTypes().length;            if (argCount > 0) {                int maxCount = argCount - 1;                for (int i = 0; i < maxCount; ++i) {                    script.append(VAR_ARG_PREFIX).append(i).append(",");                }                script.append(VAR_ARG_PREFIX).append(argCount - 1);            }            script.append(") {");            // Add implementation            if (method.getReturnType() != void.class) {                script.append("            return ").append("prompt('").append(MSG_PROMPT_HEADER).append("'+");            } else {                script.append("            prompt('").append(MSG_PROMPT_HEADER).append("'+");            }            // Begin JSON            script.append("JSON.stringify({");            script.append(KEY_INTERFACE_NAME).append(":'").append(interfaceName).append("',");            script.append(KEY_FUNCTION_NAME).append(":'").append(methodName).append("',");            script.append(KEY_ARG_ARRAY).append(":[");            // 添加参数到JSON串中            if (argCount > 0) {                int max = argCount - 1;                for (int i = 0; i < max; i++) {                    script.append(VAR_ARG_PREFIX).append(i).append(",");                }                script.append(VAR_ARG_PREFIX).append(max);            }            // End JSON            script.append("]})");            // End prompt            script.append(");");            // End function            script.append("        }, ");        }        // End of obj        script.append("    };");        // End of if or else        script.append("}");    }    private boolean handleJsInterface(WebView view, String url, String message, String defaultValue,                                      JsPromptResult result) {        String prefix = MSG_PROMPT_HEADER;        if (!message.startsWith(prefix)) {            return false;        }        String jsonStr = message.substring(prefix.length());        try {            JSONObject jsonObj = new JSONObject(jsonStr);            String interfaceName = jsonObj.getString(KEY_INTERFACE_NAME);            String methodName = jsonObj.getString(KEY_FUNCTION_NAME);            JSONArray argsArray = jsonObj.getJSONArray(KEY_ARG_ARRAY);            Object[] args = null;            if (null != argsArray) {                int count = argsArray.length();                if (count > 0) {                    args = new Object[count];                    for (int i = 0; i < count; ++i) {                        args[i] = argsArray.get(i);                    }                }            }            if (invokeJSInterfaceMethod(result, interfaceName, methodName, args)) {                return true;            }        } catch (Exception e) {            e.printStackTrace();        }        result.cancel();        return false;    }    private boolean invokeJSInterfaceMethod(JsPromptResult result, String interfaceName, String methodName, Object[] args) {        boolean succeed = false;        final Object obj = mJsInterfaceMap.get(interfaceName);        if (null == obj) {            result.cancel();            return false;        }        Class<?>[] parameterTypes = null;        int count = 0;        if (args != null) {            count = args.length;        }        if (count > 0) {            parameterTypes = new Class[count];            for (int i = 0; i < count; ++i) {                parameterTypes[i] = getClassFromJsonObject(args[i]);            }        }        try {            Method method = obj.getClass().getMethod(methodName, parameterTypes);            Object returnObj = method.invoke(obj, args); // 执行接口调用            boolean isVoid = returnObj == null || returnObj.getClass() == void.class;            String returnValue = isVoid ? "" : returnObj.toString();            result.confirm(returnValue); // 通过prompt返回调用结果            succeed = true;        } catch (NoSuchMethodException e) {            e.printStackTrace();        } catch (Exception e) {            e.printStackTrace();        }        result.cancel();        return succeed;    }    private Class<?> getClassFromJsonObject(Object obj) {        Class<?> cls = obj.getClass();        // js对象只支持int boolean string三种类型        if (cls == Integer.class) {            cls = Integer.TYPE;        } else if (cls == Boolean.class) {            cls = Boolean.TYPE;        } else {            cls = String.class;        }        return cls;    }    private boolean filterMethods(String methodName) {        for (String method : mFilterMethods) {            if (method.equals(methodName)) {                return true;            }        }        return false;    }    private boolean hasHoneycomb() {        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;    }    private boolean hasJellyBeanMR1() {        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1;    }    private class WebChromeClientEx extends WebChromeClient {        @Override        public final void onProgressChanged(WebView view, int newProgress) {            injectJavascriptInterfaces(view);            super.onProgressChanged(view, newProgress);        }        @Override        public final boolean onJsPrompt(WebView view, String url, String message, String defaultValue,                                        JsPromptResult result) {            if (view instanceof WebViewEx) {                if (handleJsInterface(view, url, message, defaultValue, result)) {                    return true;                }            }            return super.onJsPrompt(view, url, message, defaultValue, result);        }        @Override        public final void onReceivedTitle(WebView view, String title) {            injectJavascriptInterfaces(view);        }    }    private class WebViewClientEx extends WebViewClient {        @Override        public void onLoadResource(WebView view, String url) {            injectJavascriptInterfaces(view);            super.onLoadResource(view, url);        }        @Override        public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) {            injectJavascriptInterfaces(view);            super.doUpdateVisitedHistory(view, url, isReload);        }        @Override        public void onPageStarted(WebView view, String url, Bitmap favicon) {            injectJavascriptInterfaces(view);            super.onPageStarted(view, url, favicon);        }        @Override        public void onPageFinished(WebView view, String url) {            injectJavascriptInterfaces(view);            super.onPageFinished(view, url);        }    }    @Override    protected void onSizeChanged(int w, int h, int ow, int oh) {        super.onSizeChanged(w, h, ow, oh);        if (mOnSizeChangedListener != null) {            if (h != 0) {                mOnSizeChangedListener.getSize(h);            }        }    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        // 阻止ViewPager调用onInterceptTouchEvent拦截        getParent().requestDisallowInterceptTouchEvent(true);        return true;    }    public void setOnSizeChangedListener(OnSizeChangedListener mOnSizeChangedListener) {        this.mOnSizeChangedListener = mOnSizeChangedListener;    }    public interface OnSizeChangedListener {        public void getSize(int height);    }}

使用方法与WebView类似:

        WebViewEx wv = (WebViewEx) rootView.findViewById(R.id.wv);        wv.setVerticalScrollbarOverlay(true);        // 设置WebView支持JavaScript        wv.getSettings().setJavaScriptEnabled(true);        // 在js中调用本地java方法        wv.addJavascriptInterface(new JsInterface(getActivity()), "AndroidWebView");        // 一定注意不要以下代码,因为WebViewEx已经重写过已处理不兼容问题//        wv.setWebChromeClient(new WebChromeClient());        wv.loadUrl(url);        private class JsInterface {        private Context mContext;        public JsInterface(Context context) {            this.mContext = context;        }        // window.AndroidWebView.itemClickFromJs()        // 当点击条目的时候,触发此方法        @JavascriptInterface        public void itemClickFromJs() {            setUserPermission(mMsg + "");        }        @JavascriptInterface        public void showInfoFromJs(String msg) {            LogMessage.e("JS", "showInfoFromJs === in ");            // ToastUtil.show(mContext, msg);        }    // 在java中调用js代码    public void setUserPermission(final String msg) {        // 调用js中的函数:setUserPermission(msg)        // ToastUtil.show(getActivity(), mMsg + "");        reference_table_wv.post(new Runnable() {            @Override            public void run() {                LogMessage.e("JS", "setUserPermission === in ");                wv.loadUrl("javascript:setUserPermission('" + msg + "')");            }        });    }

————————————————————————————————
然而在使用这个自定义WebViewEx之后,发现并不能很好的兼容17版本一下js交互,时好时不好,最终只能:

    if(Build.Version.INT >= 17){  // 判断版本号        使用@JavaScriptInterface注解    }else{        不使用@JavaScriptInterface注解    }

还要将编译版本改为17以上,否则17以下代码报错,因为没有JavascriptInterface注解
如果不用@JavascriptInterface注解,API17以下版本没问题,但17及以上版本貌似不能交互。。
大家有什么好办法么?

1 0
原创粉丝点击