1. Summary of Interactive Ways
Android Calling methods with JS through WebView is actually:
- Android calls JS code
- JS calls Android code
The bridge between them is WebView
There are two ways for Android to call JS code:
1. Load Url () through WebView
2. Evaluate Javascript () via WebView
There are three ways for JS to call Android code:
1. Object mapping via WebView's addJavascriptInterface ()
2. Should Override Url Loading via WebViewClient
(method callback intercepts url)
3. Intercept JS dialog alert(), confirm(), prompt () messages by calling back the onJsAlert(), onJsConfirm(), onJsPrompt () methods of WebChromeClient
2. Specific analysis
2.1 Android calls JS code through WebView
There are two ways for Android to call JS code:
- loadUrl () through WebView
- Evaluate Javascript () via WebView
Way 1: Load Url () through WebView
- Examples: Click on the Android button to call JS () in WebView JS (text name is javascript)
- Specific use:
Step 1: Put the JS code you need to call into the src/main/assets folder in. html format
- For the convenience of demonstration, this paper uses Andorid to call local JS code description.
- In fact, Android calls more remote JS code, which is to change the path of loaded JS code to url.
JS code needs to be loaded: JavaScript.html
// Text name: javascript
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Carson_Ho</title>
// JS code
<script>
// The method Android needs to call
function callJS(){
alert("Android Called JS Of callJS Method");
}
</script>
</head>
</html>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
Step 2: Call JS code through WebView settings in Android
Android code: MainActivity. Java
The annotations are very clear.
public class MainActivity extends AppCompatActivity {
WebView mWebView;
Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWebView =(WebView) findViewById(R.id.webview);
WebSettings webSettings = mWebView.getSettings();
// Setting permissions to interact with Js
webSettings.setJavaScriptEnabled(true);
// Setting Allowed JS Bullet Window
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
// Load the JS code first
// The format is: file://android_asset/file name.html
mWebView.loadUrl("file:///android_asset/javascript.html");
button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// JS method calls must be made on a separate thread (otherwise they cannot be called)
mWebView.post(new Runnable() {
@Override
public void run() {
// Note that the JS method name to be invoked corresponds to
// Call the callJS() method of javascript
mWebView.loadUrl("javascript:callJS()");
}
});
}
});
// Because the pop-up window check call result is set up, the js dialog box needs to be supported.
// Web view is just a carrier, and the rendering of content needs to be implemented using the webview ChromClient class.
// Processing JavaScript dialogs by setting WebChromeClient objects
//Setting the Alert() function that responds to js
mWebView.setWebChromeClient(new WebChromeClient() {
@Override
public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
AlertDialog.Builder b = new AlertDialog.Builder(MainActivity.this);
b.setTitle("Alert");
b.setMessage(message);
b.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
result.confirm();
}
});
b.setCancelable(false);
b.create().show();
return true;
}
});
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
Special note: JS code calls must be invoked after onPageFinished () callback, otherwise they will not be invoked.
onPageFinished() is a method of the WebViewClient class, which is called mainly at the end of page loading.
Way 2: Evaluate Javascript () via WebView
-
Advantages: This method is more efficient and concise than the first one.
- Because the execution of this method will not refresh the page, while the execution of the first method (loadUrl) will.
- Android 4.4 will only be available
-
Specific use
// Just replace the loadUrl() of the first method with the following method
mWebView.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
//Here is the result returned by js
}
});
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
2.1.2 method comparison
2.1.3 Suggestions for Use
Mixed use of the two methods, that is, Android 4.4 below the use of method 1, Android 4.4 above the use of method 2
// Android version variable
final int version = Build.VERSION.SDK_INT;
// Because this method can only be used in Android version 4.4, version judgment is needed when using it.
if (version < 18) {
mWebView.loadUrl("javascript:callJS()");
} else {
mWebView.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
//Here is the result returned by js
}
});
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
2.2 JS calls Android code through WebView
There are three ways for JS to call Android code:
1. Object mapping via WebView's addJavascriptInterface ()
2. Should Override Url Loading via WebViewClient
(method callback intercepts url)
3. Intercept JS dialog alert(), confirm(), prompt () messages by calling back the onJsAlert(), onJsConfirm(), onJsPrompt () methods of WebChromeClient
2.2.1 Method Analysis
Way 1: Object mapping through WebView's addJavascriptInterface ()
Step 1: Define an Android class that maps to JS objects: Android to Js
Android to Js. Java (the annotations are very clear)
// Inheritance from Object Class
public class AndroidtoJs extends Object {
// Define the method that JS needs to call
// Method invoked by JS must be annotated with @JavascriptInterface
@JavascriptInterface
public void hello(String msg) {
System.out.println("JS Called Android Of hello Method");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
Step 2: Put the JS code you need to call into the src/main/assets folder in. html format
Need to load JS code: javascript.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Carson</title>
<script>
function callAndroid(){
// Because of object mapping, calling test objects is equivalent to calling Android mapped objects
test.hello("js Called android Medium hello Method");
}
</script>
</head>
<body>
//Click on the button to call the callAndroid function
<button type="button" id="button1" onclick="callAndroid()"></button>
</body>
</html>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
Step 3: Set up the mapping between Android class and JS code through WebView in Android
See the notes for details.
public class MainActivity extends AppCompatActivity {
WebView mWebView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWebView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = mWebView.getSettings();
// Setting permissions to interact with Js
webSettings.setJavaScriptEnabled(true);
// Mapping Java objects to JS objects by addJavascriptInterface()
//Parametric 1: Javascript object name
//Parametric 2: Java object name
mWebView.addJavascriptInterface(new AndroidtoJs(), "test");//Androidto js class object maps to js test object
// Loading JS code
// The format is: file://android_asset/file name.html
mWebView.loadUrl("file:///android_asset/javascript.html");
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
Characteristic
-
Advantages: Easy to use
Just map Android objects to JS objects
-
Disadvantage: There are serious loopholes, see the article specifically: Android WebView Vulnerabilities You Don't Know
Way 2: Should Override Url Loading via WebViewClient () callback interception url
-
Specific principles:
- Android should Override Url Loading via the WebViewClient callback method () intercepting url
- Parsing the protocol of the url
- If a pre-agreed protocol is detected, the corresponding method is invoked.
That is, JS needs to call Android's method
-
Specific use:
Step 1: Url protocol required in JS Convention
JS code: javascript.htmlPlace it in. html in the src/main/assets folder
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Carson_Ho</title>
<script>
function callAndroid(){
/*The agreed url protocol is: js://webview?Arg1=111&arg2=222*/
document.location = "js://webview?arg1=111&arg2=222";
}
</script>
</head>
<!-- Click on the button and call callAndroid()Method -->
<body>
<button type="button" id="button1" onclick="callAndroid()">Click Call Android Code</button>
</body>
</html>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
When the JS is loaded through Android's mWebView. loadUrl ("file://android_asset/javascript.html"), it calls back shouldOverrideUrlLoading. Next, look at step 2:
Step 2: Copy shouldOverrideUrlLoading () from WebViewClient in Android
MainActivity.java
public class MainActivity extends AppCompatActivity {
WebView mWebView;
// Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWebView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = mWebView.getSettings();
// Setting permissions to interact with Js
webSettings.setJavaScriptEnabled(true);
// Setting Allowed JS Bullet Window
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
// Step 1: Load the JS code
// The format is: file://android_asset/file name.html
mWebView.loadUrl("file:///android_asset/javascript.html");
// ShouOverrideUrlLoading Method for Overwriting the WebViewClient Class
mWebView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// Step 2: According to the parameters of the protocol, determine whether the url is needed?
// Generally, it is judged by scheme (protocol format) & authority (protocol name) (the first two parameters)
//Assume that the incoming url = js://webview?Arg1 = 111 & arg2 = 222 (also agreed to be intercepted)
Uri uri = Uri.parse(url);
// If the url protocol = the pre-agreed js protocol
// Analyse the parameters from the bottom
if ( uri.getScheme().equals("js")) {
// If authority = pre-agreed webview in the protocol, that is, the agreement that represents all conforms to the agreement
// So intercepting url, the following JS starts calling the method Android needs
if (uri.getAuthority().equals("webview")) {
// Step 3:
// Logic to be invoked to execute JS
System.out.println("js Called Android Method");
// You can take parameters on the protocol and pass them on to Android
HashMap<String, String> params = new HashMap<>();
Set<String> collection = uri.getQueryParameterNames();
}
return true;
}
return super.shouldOverrideUrlLoading(view, url);
}
}
);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
Characteristic
- Advantages: There is no loophole in Mode 1;
- Disadvantage: The return value of the Android method obtained by JS is complex.
If JS wants to get the return value of the Android method, it can only pass the return value back through the loadUrl () of WebView. The relevant code is as follows:
// Android: MainActivity.java
mWebView.loadUrl("javascript:returnResult(" + result + ")");
// JS: javascript.html
function returnResult(result){
alert("result is" + result);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
Way 3: Intercept JS dialog alert(), confirm(), prompt () messages via WebChromeClient onJsAlert(), onJsConfirm(), onJsPrompt () callbacks
In JS, there are three commonly used dialog box methods:
Principle of Mode 3: Android intercepts JS dialog boxes through onJsAlert(), onJsConfirm(), onJsPrompt () callbacks of WebChromeClient.
(that is, the three methods mentioned above), get their message content, and then parse it.
The following example will be illustrated by intercepting the JS input box (prompt () method):
- Commonly used interception is to intercept JS input box (prompt () method)
- Because only prompt () can return any type of value, the operation is the most comprehensive, convenient and flexible; alert () dialog box does not return value; confirm () dialog box can only return two states (confirm / cancel) two values.
Step 1: Load the JS code as follows:
javascript.html
Place it in. html in the src/main/assets folder
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Carson_Ho</title>
<script>
function clickprompt(){
// Call prompt ()
var result=prompt("js://demo?arg1=111&arg2=222");
alert("demo " + result);
}
</script>
</head>
<!-- Click on the button and call clickprompt() -->
<body>
<button type="button" id="button1" onclick="clickprompt()">Click Call Android Code</button>
</body>
</html>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
When the above JS code is loaded using mWebView.loadUrl("file:///android_asset/javascript.html"), the callback onJsPrompt () is triggered, as follows:
- If the alert() is intercepted, the callback onJsAlert () is triggered.
- If it is an interception confirmation box (confirm()), the callback onJsConfirm () is triggered.
Step 2: Replicate onJsPrompt () from WebChromeClient in Android
public class MainActivity extends AppCompatActivity {
WebView mWebView;
// Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWebView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = mWebView.getSettings();
// Setting permissions to interact with Js
webSettings.setJavaScriptEnabled(true);
// Setting Allowed JS Bullet Window
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
// Load the JS code first
// The format is: file://android_asset/file name.html
mWebView.loadUrl("file:///android_asset/javascript.html");
mWebView.setWebChromeClient(new WebChromeClient() {
// Intercept input box (same principle, mode 2)
// Parametric message: Represents promt () content (not url)
// Parameter result: Represents the return value of the input box
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
// According to the parameters of the protocol, can we judge whether it is the required URL (principle in the same way 2)?
// Generally, it is judged by scheme (protocol format) & authority (protocol name) (the first two parameters)
//Assume that the incoming url = js://webview?Arg1 = 111 & arg2 = 222 (also agreed to be intercepted)
Uri uri = Uri.parse(message);
// If the url protocol = the pre-agreed js protocol
// Analyse the parameters from the bottom
if ( uri.getScheme().equals("js")) {
// If authority = pre-agreed webview in the protocol, that is, the agreement that represents all conforms to the agreement
// So intercepting url, the following JS starts calling the method Android needs
if (uri.getAuthority().equals("webview")) {
//
// Logic to be invoked to execute JS
System.out.println("js Called Android Method");
// You can take parameters on the protocol and pass them on to Android
HashMap<String, String> params = new HashMap<>();
Set<String> collection = uri.getQueryParameterNames();
//Parameter result: Represents the return value of the message box (input value)
result.confirm("js Called Android The method succeeded.");
}
return true;
}
return super.onJsPrompt(view, url, message, defaultValue, result);
}
// The principle of interception by alert() and confirm() is the same, so we won't talk too much about it here.
// Warning box for intercepting JS
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
return super.onJsAlert(view, url, message, result);
}
// Confirmation box for intercepting JS
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
return super.onJsConfirm(view, url, message, result);
}
}
);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
2.2.2 Contrast of Three Ways-Use Scenarios
2.2.3 WebView and JavaScript Mutual Call Confusion
If the js in webview calls the local method, the debug package js calls normally will be no problem, but the commercial version of the apk is usually released through confusing steps. At this time, it will be found that the normal js calls before can not normally call the local method.
This is because the reference to the local code has been scrambled when confusing, resulting in the code in js can not find the address of the local method.
The solution to this problem is simple: add some code to the proguard.cfg file to declare that the code invoked locally by js is not confused. The following examples are given:
In Section 5, the package name of the DemoJavaScriptInterface class called by js is com.test.webview, so add in the proguard.cfg file:
-keep public class com.test.webview.DemoJavaScriptInterface{ public <methods>; }
If it is an internal class, it is roughly written in the following form:
-keep public class com.test.webview.DemoJavaScriptInterface$InnerClass{ public <methods>; }
If the android version is relatively new, you may need to add the following code:
-keepattributes *Annotation* -keepattributes *JavascriptInterface*