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
- 4.Android与JS的交互(兼容API 17以下版本)
- Android-Material学习(可以兼容5.0以下的版本)
- Android实现一键复制粘贴,兼容低版本系统(api 11以下)
- SL API 与 JS API的交互
- SL API 与 JS API的交互
- android webview js交互 调取本地相册(兼容各个版本)并且上传到服务器端。
- Selendroid在Appium中的使用(兼容Android api 17 以下)
- 关于android指纹识别兼容6.0以下版本
- html与iOS/Android的兼容交互
- 兼容ie10以下版本的placeholder属性
- Android与JS的交互(上)
- Android与JS的交互(下)
- android与js(JavaScript)的交互
- 【Android】高低API版本兼容之@TargetApi与@SuppressLint("NewApi")
- Android 与 js 的交互
- Android 与 js 的交互
- android与js的交互
- js 与 Android 的交互
- 2016蓝桥杯 凑算式
- X86上的Android:性能与兼容可否兼得?
- 共轭先验
- [LeetCode][数论]Odd Even Linked List
- java final 关键字
- 4.Android与JS的交互(兼容API 17以下版本)
- 204. Count Primes
- 单链表
- JVM调优总结
- C和C++中函数传参常见方式总结
- ScrollView问题(ScrollView中的LinearLayout的height属性match_parent 无效的解决办法)
- Android 开发打包时我们应该如何注意平台的兼容(x86,arm,arm-v7a)
- 定时器Android
- ftp连接不上的问题