本文参考了两篇文章,修改了部分代码,添加很多注释,帮助新手理解
效果图
点击“调用alert”按钮,在Android中捕获JS alert,并用Android组件(AlertDialog)替换
点击“调用java方法”按钮,在JS中调用并传递参数到Java中的方法
代码部分
js_interact_demo.html
<html>
<head>
<title>JS交互</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<script type="text/javascript">
function invokedByJava(param) {
document.getElementById("content").innerHTML = "Java has invoked JS function and returnd the data:"+param;
}
</script>
</head>
<body>
<p id="content"></p>
<p>
<input type="button" value="调用Java方法" onclick="window.stub.jsMethod('来至JS的参数');" />
<input type="button" value="调用alert" onclick="alert('hello')" />
</p>
</body>
</html>
res/layout目录下:
web_view.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button android:id="@+id/web_view_invoke_js"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="调JS方法"/>
<LinearLayout android:layout_width="fill_parent"
android:layout_height="wrap_content">
<EditText android:id="@+id/web_view_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<Button android:id="@+id/web_view_search"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="浏览"/>
</LinearLayout>
<WebView android:id="@+id/web_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</LinearLayout>
WebViewDemo.java
package com.example.webviewdemo;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.webkit.JavascriptInterface;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.EditText;
import android.widget.Toast;
import android.graphics.Bitmap;
import android.webkit.JsResult;
@SuppressLint("JavascriptInterface")
public class WebViewDemo extends Activity {
private WebView mWebView;//创建私有WebView对象
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);//照抄就行了
setContentView(R.layout.web_view);//载入web_view布局文件
findViewById(R.id.web_view_invoke_js).setOnClickListener(new OnClickListener() {//创建点击监听器(调JS方法)
@Override
public void onClick(View v) {//重构点击方法
//调用JS方法,并传递参数
mWebView.loadUrl("javascript:invokedByJava('我来自Java')");//直接访问js中的invokeByJava方法
}
});
mWebView = (WebView)findViewById(R.id.web_view);//找到web_view
mWebView.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);//设置滚动条
mWebView.getSettings().setBuiltInZoomControls(true);//允许滚动条
mWebView.getSettings().setJavaScriptEnabled(true);//允许webview的js有效
/*
WebView默认用系统自带浏览器处理页面跳转。
为了让页面跳转在当前WebView中进行,重写WebViewClient。
但是按BACK键时,不会返回跳转前的页面,而是退出本Activity。重写onKeyDown()方法来解决此问题。
*/
mWebView.setWebViewClient(new WebViewClient() {
//setWebViewClient方法的作用是
//Sets the WebViewClient that will receive various notifications and requests.
//This will replace the current handler.
//重写WebViewClient,使得webview中页面的跳转是在内部进行,而不是跳转到系统浏览器
//WebViewClient主要帮助WebView处理各种通知、请求事件的
//WebChromeClient主要辅助WebView处理Javascript的对话框、网站图标、网站title、加载进度等
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);//使用当前WebView处理跳转
return true;//true表示此事件在此处被处理,不需要再广播
}
/*onPageStarted作用:
Notify the host application that a page has started loading.
通知宿主应用程序的页已开始加载。
This method is called once for each main frame load
为每个主frame调用一次该方法
so a page with iframes or framesets will call onPageStarted one time for the main frame.
因此有iframes或者framsets的页面将会为主frame调用一次onPageStarted方法
This also means that onPageStarted will not be called when the contents of an embedded frame changes,
这也意味着,当内部frame的内容改变时,onPageStarted将不会被调用。
i.e. clicking a link whose target is an iframe.
比如,单击是iframe的链接*/
/*参数:
view:The WebView that is initiating the callback.
url:The url to be loaded.
favicon(图标):The favicon for this page if it already exists in the database.*/
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
//有页面跳转时被回调
}
@Override
public void onPageFinished(WebView view, String url) {
//页面跳转结束后被回调
}
/*Report an error to the host application. These errors are unrecoverable
(i.e. the main resource is unavailable).
The errorCode parameter corresponds to one of the ERROR_* constants.*/
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
Toast.makeText(WebViewDemo.this, "Oh no! " + description, Toast.LENGTH_SHORT).show();
//当出现错误时,通知宿主程序,Toast显示的内容
//show()作用:Show the view for the specified duration.
}
});
/*
当WebView内容影响UI时调用WebChromeClient的方法
*/
mWebView.setWebChromeClient(new WebChromeClient() {
/**
* 处理JavaScript Alert事件
*/
@Override
public boolean onJsAlert(WebView view, String url,String message, final JsResult result) {
//用Android组件替换,构建一个Builder来显示网页中的alert对话框
new AlertDialog.Builder(WebViewDemo.this)
.setTitle("JS提示")
.setMessage(message)
.setPositiveButton(android.R.string.ok, new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
result.confirm();
}
})
.setCancelable(false)
.create().show();
return true;
}
});
/*
绑定Java对象到WebView,这样可以让JS与Java通信(JS访问Java方法)
第一个参数是自定义类对象,映射成JS对象
第二个参数是第一个参数的JS别名
调用示例:
mWebView.loadUrl("javascript:window.stub.jsMethod('param')");
*/
mWebView.addJavascriptInterface(new JsToJava(), "stub");//js访问java的关键方法
final EditText mEditText = (EditText)findViewById(R.id.web_view_text);//找到edittext
findViewById(R.id.web_view_search).setOnClickListener(new OnClickListener() {//为button设置点击监听器
@Override
public void onClick(View view) {//点击事件触发的方法
String url = mEditText.getText().toString();//取文本框输入的内容,并转换为字符串
if (url == null || "".equals(url)) {//判断,如果文本框中的内容为空值,或者url的网址为空?
Toast.makeText(WebViewDemo.this, "请输入URL", Toast.LENGTH_SHORT).show();//给出toast提示
} else {
if (!url.startsWith("http:") && !url.startsWith("file:")) {//如果内容以http:和file开头
url = "http://" + url;//为url赋值
}
mWebView.loadUrl(url);//传入赋值后的url并加载
}
}
});
//默认页面,本地html
mWebView.loadUrl("file:///android_asset/js_interact_demo.html");
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {//重写back退回方法
//处理WebView跳转返回
if ((keyCode == KeyEvent.KEYCODE_BACK) && mWebView.canGoBack()) {//如果
mWebView.goBack();//webview返回,而不是退出当前activity
return true;//返回真
}
return super.onKeyDown(keyCode, event);//
}
private class JsToJava {
@JavascriptInterface
//添加@JavascriptInterface注释
//解决Uncaught TypeError: Object [object Object] has no method 安全限制问题
public void jsMethod(String paramFromJS) {
Log.i("CDH", "paramFromJS");
Toast.makeText(WebViewDemo.this, "Oh Yeah!终于调用了java,感动 ", Toast.LENGTH_SHORT).show();
}
}
}
疑问解答
Alert无法弹出
你应该是没有设置WebChromeClient,按照以下代码设置
myWebView.setWebChromeClient(new WebChromeClient() {});
Uncaught ReferenceError: functionName is not defined
问题出现原因,网页的js代码没有加载完成,就调用了js方法。解决方法是在网页加载完成之后调用js方法
myWebView.setWebViewClient(new WebViewClient() {
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
//在这里执行你想调用的js函数
}});
Uncaught TypeError: Object [object Object] has no method
安全限制问题
如果只在4.2版本以上的机器出问题,那么就是系统处于安全限制的问题了。Android文档这样说的
Caution: If you’ve set your targetSdkVersion to 17 or higher, you must add the @JavascriptInterface annotation to any method that you want available your web page code (the method must also be public). If you do not provide the annotation, then the method will not accessible by your web page when running on Android 4.2 or higher.
中文大意为
警告:如果你的程序目标平台是17或者是更高,你必须要在暴露给网页可调用的方法(这个方法必须是公开的)加上@JavascriptInterface注释。如果你不这样做的话,在4.2以以后的平台上,网页无法访问到你的方法。
两种解决方法
将targetSdkVersion设置成17或更高,引入@JavascriptInterface注释
自己创建一个注释接口名字为@JavascriptInterface,然后将其引入。注意这个接口不能混淆。
注,创建@JavascriptInterface代码
public @interface JavascriptInterface {
}
代码混淆问题
如果在没有混淆的版本运行正常,在混淆后的版本的代码运行错误,并提示Uncaught TypeError: Object [object Object] has no method,那就是你没有做混淆例外处理。 在混淆文件加入类似这样的代码
-keep class com.example.javajsinteractiondemo$JsInteration {
*;
}
All WebView methods must be called on the same thread
过滤日志曾发现过这个问题。
E/StrictMode( 1546): java.lang.Throwable: A WebView method was called on thread 'JavaBridge'. All WebView methods must be called on the same thread. (Expected Looper Looper (main, tid 1) {528712d4} called on Looper (JavaBridge, tid 121) {52b6678c}, FYI main Looper is Looper (main, tid 1) {528712d4})
E/StrictMode( 1546): at android.webkit.WebView.checkThread(WebView.java:2063)
E/StrictMode( 1546): at android.webkit.WebView.loadUrl(WebView.java:794)
E/StrictMode( 1546): at com.xxx.xxxx.xxxx.xxxx.xxxxxxx$JavaScriptInterface.onCanGoBackResult(xxxx.java:96)
E/StrictMode( 1546): at com.android.org.chromium.base.SystemMessageHandler.nativeDoRunLoopOnce(Native Method)
E/StrictMode( 1546): at com.android.org.chromium.base.SystemMessageHandler.handleMessage(SystemMessageHandler.java:27)
E/StrictMode( 1546): at android.os.Handler.dispatchMessage(Handler.java:102)
E/StrictMode( 1546): at android.os.Looper.loop(Looper.java:136)
E/StrictMode( 1546): at android.os.HandlerThread.run(HandlerThread.java:61)
在js调用后的Java回调线程并不是主线程。如打印日志可验证
ThreadInfo=Thread[WebViewCoreThread,5,main]
解决上述的异常,将webview操作放在主线程中即可。
webView.post(new Runnable() {
@Override
public void run() {
webView.loadUrl(YOUR_URL).
}
});