├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── gradle.xml ├── misc.xml ├── modules.xml └── runConfigurations.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── jswithnative │ │ └── com │ │ └── jsinteractswithnative │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ └── web.html │ ├── java │ │ └── jswithnative │ │ │ └── com │ │ │ └── jsinteractswithnative │ │ │ └── MainActivity.java │ └── res │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── jswithnative │ └── com │ └── jsinteractswithnative │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── imgs ├── GIF.gif └── 微信截图_20160920211757.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ##js与native的交互 2 | ####概述 3 | 目前所常用的native与js交互有两种方式,分别为 下面提到的方法1与方法2,这两种方式各有利弊,在4.2之前使用方法1存在安全问题, 4 | 类似与sql的注入漏洞,这是运行时虚拟机的漏洞,暂且这样理解吧。另外无论哪种方式,都要与页面开发人员定要协议。 5 | ###sample运行效果图 6 |
7 | 8 | ###方法1(webClient.loadUrl()): 9 | ####native:加载web页面并进行相关setting; 10 | ```javascript 11 | mWebView = (WebView) findViewById(R.id.webview); 12 | //支持JavaScript脚本 13 | mWebView.getSettings().setJavaScriptEnabled(true); 14 | // 加载html 15 | mWebView.loadUrl("file:///android_asset/web.html"); 16 | //android为flag 17 | mWebView.addJavascriptInterface(MainActivity.this, "android"); 18 | ``` 19 | ####native中调用(带参&不带参)js方法实现 20 | ```javascript 21 | //调用js函数 22 | findViewById(R.id.button).setOnClickListener(new View.OnClickListener() { 23 | @Override 24 | public void onClick(View v) { 25 | mWebView.loadUrl("javascript:javaCallJs()"); 26 | } 27 | }); 28 | //调用js函数并携带参数 29 | final String param = "'这是参数,注意这个参数的格式'"; 30 | findViewById(R.id.button2).setOnClickListener(new View.OnClickListener() { 31 | @Override 32 | public void onClick(View v) { 33 | // 传递参数调用 34 | mWebView.loadUrl("javascript:javaCallJswithParam(" + param + ")"); 35 | } 36 | }); 37 | ``` 38 | 39 | ####js响应native方法实现: 40 | ```javascript 41 | 53 | ``` 54 | ####web页面中,调用native方法具体实现 55 | ```javascript 56 | 58 |

59 | 61 | ``` 62 | ####native中,被调用的方法实现 63 | ```javascript 64 | //由于安全原因 需要加 @JavascriptInterface 65 | @JavascriptInterface 66 | public void startFunction() { 67 | runOnUiThread(new Runnable() { 68 | @Override 69 | public void run() { 70 | new AlertDialog.Builder(MainActivity.this).setMessage("native方法触发").show(); 71 | } 72 | }); 73 | } 74 | @JavascriptInterface 75 | public void startFunction(final String text) { 76 | runOnUiThread(new Runnable() { 77 | @Override 78 | public void run() { 79 | new AlertDialog.Builder(MainActivity.this).setMessage(text).show(); 80 | } 81 | }); 82 | } 83 | ``` 84 | #####:red_circle:上段备注中提到“由于安全原因 需要加@JavascriptInterface”,是指在4.2版本之前的addjavascriptInterface接口引起的漏洞,可能导致恶意网页通过Js方法遍历刚刚通过addjavascriptInterface注入进来的类的所有方法从中获取到getClass方法,然后通过反射获取到Runtime对象,进而调用Runtime对象的exec方法执行一些操作,恶意的Js代码如下: 85 | ```javascript 86 | function execute(args) { 87 | for (var obj in window) { 88 | if ("getClass" in window[obj]) { 89 | alert(obj); 90 | return window[obj].getClass().forName("java.lang.Runtime") 91 | .getMethod("getRuntime",null).invoke(null,null).exec(args); 92 | } 93 | } 94 | } 95 | ``` 96 | 在Android API Level 17(Android 4.2)之后,可以通过添加@JavascriptInterface这个注解来避免该漏洞,在4.1及之前可以使用方法2 97 | 实现native与js之间的交互。 98 | 99 | ###方法2(prompt()$onJsPrompt()/confirm()$onConfirm()/alert()$onAlert()): 100 | ####继承WebChromeClient重写该方法的onJsPrompt()/onConfirm()/onAlert()方法,用到那个方法重写那个就行,然后设置webView的WebChromeClient为该重写的类 101 | ####具体实现 102 | ```javascript 103 | class HarlanWebChromeClient extends WebChromeClient { 104 | 105 | /*此处覆盖的是javascript中的alert方法。 106 | *当网页需要弹出alert窗口时,会执行onJsAlert中的方法 107 | * 网页自身的alert方法不会被调用。 108 | */ 109 | @Override 110 | public boolean onJsAlert(WebView view, String url, String message, 111 | JsResult result) { 112 | show("onJsAlert"); 113 | result.confirm(); 114 | return true; 115 | } 116 | 117 | /*此处覆盖的是javascript中的confirm方法。 118 | *当网页需要弹出confirm窗口时,会执行onJsConfirm中的方法 119 | * 网页自身的confirm方法不会被调用。 120 | */ 121 | @Override 122 | public boolean onJsConfirm(WebView view, String url, 123 | String message, JsResult result) { 124 | show("onJsConfirm"); 125 | result.confirm(); 126 | return true; 127 | } 128 | 129 | /*此处覆盖的是javascript中的confirm方法。 130 | *当网页需要弹出confirm窗口时,会执行onJsConfirm中的方法 131 | * 网页自身的confirm方法不会被调用。 132 | */ 133 | @Override 134 | public boolean onJsPrompt(WebView view, String url, 135 | String message, String defaultValue, 136 | JsPromptResult result) { 137 | show("onJsPrompt...."); 138 | result.confirm(); 139 | return true; 140 | } 141 | ``` 142 | 然后给webView设置该重写的类 143 | ```javascript 144 | //设置ChromeClient 145 | mWebView.setWebChromeClient(new HarlanWebChromeClient()); 146 | ``` 147 | ####相应的在JS中的具体实现代码如下 148 | ```javascript 149 | function cfm() { 150 | confirm("") 151 | } 152 | 153 | function pmt() { 154 | prompt("",""); 155 | } 156 | 157 | function onAlert() 158 | { 159 | alert("这是网页中的alert方法,如果重写了mWebView的onAlert方法,该方法不会执行"); 160 | } 161 | ``` 162 | ####触发页面中的js函数,例如: 163 | ```javascript 164 |

165 |

166 |

167 | ``` 168 | 实现原理就是在页面中触发的方法被webView中设置的WebChromeClient给拦截了,从而执行了WebChromeClient中重写的onXxx()方法, 169 | 没有执行页面中相应的onXxx()方法,这是方式相对简单,而且安全。 170 | 171 | 172 | 173 | 174 | 参考链接: 175 | https://github.com/Sunzxyong/RainbowBridge 176 | 177 | 178 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 24 5 | buildToolsVersion "24.0.2" 6 | defaultConfig { 7 | applicationId "jswithnative.com.jsinteractswithnative" 8 | minSdkVersion 15 9 | targetSdkVersion 24 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 25 | exclude group: 'com.android.support', module: 'support-annotations' 26 | }) 27 | compile 'com.android.support:appcompat-v7:24.2.0' 28 | testCompile 'junit:junit:4.12' 29 | } 30 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in E:\androidSdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/jswithnative/com/jsinteractswithnative/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package jswithnative.com.jsinteractswithnative; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("jswithnative.com.jsinteractswithnative", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/assets/web.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZQiang94/JSInteractsWithNative/b5cae234235c2bc9bac3ae357d36fdb6f46584b2/app/src/main/assets/web.html -------------------------------------------------------------------------------- /app/src/main/java/jswithnative/com/jsinteractswithnative/MainActivity.java: -------------------------------------------------------------------------------- 1 | package jswithnative.com.jsinteractswithnative; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AlertDialog; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.view.View; 7 | import android.webkit.JavascriptInterface; 8 | import android.webkit.JsPromptResult; 9 | import android.webkit.JsResult; 10 | import android.webkit.WebChromeClient; 11 | import android.webkit.WebView; 12 | import android.widget.Toast; 13 | 14 | public class MainActivity extends AppCompatActivity { 15 | private WebView mWebView = null; 16 | 17 | @Override 18 | protected void onCreate(Bundle savedInstanceState) { 19 | super.onCreate(savedInstanceState); 20 | setContentView(R.layout.activity_main); 21 | mWebView = (WebView) findViewById(R.id.webview); 22 | //支持JavaScript脚本 23 | mWebView.getSettings().setJavaScriptEnabled(true); 24 | // 加载html 25 | mWebView.loadUrl("file:///android_asset/web.html"); 26 | //android为flag 27 | mWebView.addJavascriptInterface(MainActivity.this, "android"); 28 | //设置ChromeClient 29 | mWebView.setWebChromeClient(new HarlanWebChromeClient()); 30 | 31 | //调用js函数 32 | findViewById(R.id.button).setOnClickListener(new View.OnClickListener() { 33 | @Override 34 | public void onClick(View v) { 35 | mWebView.loadUrl("javascript:javaCallJs()"); 36 | } 37 | }); 38 | //调用js函数并携带参数 39 | final String param = "'这是参数,注意这个参数的格式'"; 40 | findViewById(R.id.button2).setOnClickListener(new View.OnClickListener() { 41 | @Override 42 | public void onClick(View v) { 43 | // 传递参数调用 44 | mWebView.loadUrl("javascript:javaCallJswithParam(" + param + ")"); 45 | } 46 | }); 47 | } 48 | 49 | //由于安全原因 需要加 @JavascriptInterface 50 | @JavascriptInterface 51 | public void startFunction() { 52 | runOnUiThread(new Runnable() { 53 | @Override 54 | public void run() { 55 | new AlertDialog.Builder(MainActivity.this).setMessage("native方法触发").show(); 56 | } 57 | }); 58 | } 59 | 60 | @JavascriptInterface 61 | public void startFunction(final String text) { 62 | runOnUiThread(new Runnable() { 63 | @Override 64 | public void run() { 65 | new AlertDialog.Builder(MainActivity.this).setMessage(text).show(); 66 | } 67 | }); 68 | } 69 | 70 | 71 | /*** 72 | * webChromeClient主要是将javascript中相应的方法翻译成android本地方法 73 | *

74 | * 例如:我们重写了onJsAlert方法,那么当页面中需要弹出alert窗口时,便 75 | * 会执行我们的代码,按照我们的Toast的形式提示用户。 76 | */ 77 | class HarlanWebChromeClient extends WebChromeClient { 78 | 79 | /*此处覆盖的是javascript中的alert方法。 80 | *当网页需要弹出alert窗口时,会执行onJsAlert中的方法 81 | * 网页自身的alert方法不会被调用。 82 | */ 83 | @Override 84 | public boolean onJsAlert(WebView view, String url, String message, 85 | JsResult result) { 86 | show("onJsAlert"); 87 | result.confirm(); 88 | return true; 89 | } 90 | 91 | /*此处覆盖的是javascript中的confirm方法。 92 | *当网页需要弹出confirm窗口时,会执行onJsConfirm中的方法 93 | * 网页自身的confirm方法不会被调用。 94 | */ 95 | @Override 96 | public boolean onJsConfirm(WebView view, String url, 97 | String message, JsResult result) { 98 | show("onJsConfirm"); 99 | result.confirm(); 100 | return true; 101 | } 102 | 103 | /*此处覆盖的是javascript中的confirm方法。 104 | *当网页需要弹出confirm窗口时,会执行onJsConfirm中的方法 105 | * 网页自身的confirm方法不会被调用。 106 | */ 107 | @Override 108 | public boolean onJsPrompt(WebView view, String url, 109 | String message, String defaultValue, 110 | JsPromptResult result) { 111 | show("onJsPrompt...."); 112 | result.confirm(); 113 | return true; 114 | } 115 | } 116 | 117 | private void show(String warring) { 118 | Toast.makeText(this, warring, Toast.LENGTH_SHORT).show(); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 |