上篇文章我们讲述了 React Native 的原理,其中讲到 React Native 与 原生模端的数据通信。那么脱离 React Native,JavaScrip 是怎样和原生端的数据通信的呢?作为补充,我们需要了解它们的工作原理。下面我们仅以 Android 原生端为例进行讲解。
概述
JavaScript 和 Android 之间的通信可以通过以下方式实现:
- 使用 WebView 的
loadUrl()
方法直接调用 JavaScript 端函数。 - 使用 WebView 的
evaluateJavascript
方法,将 JavaScript 代码传递给 WebView 执行,并通过回调函数获取执行结果。 - 使用
JavaScriptInterface
注解,JavaScript 端调用 Android 端暴露的对象。 - 为 WebViewClient 或 WebChromeClient 设置处理程序,通过拦截 JavaScrip 端请求调用 Android 端处理程序。
- 使用 Android 的 Intent 机制,通过 Intent 向其他应用程序发送消息,从而实现 JavaScript 端调用 Android 端方法的功能。
WebView 工作原理
不难看出,JavaScript Android 原生端的通信通常是通过 WebView 来实现的。下面我们来谈谈 WebView 工作原理。
- 加载网页:当应用程序调用 WebView 的 loadUrl() 方法时,WebView 会向指定的 URL 发送 HTTP 请求,并将响应内容显示在 WebView 中。
- 渲染网页:WebView 会将 HTML、CSS 和 JavaScript 代码解析成 DOM 树、CSS 树和 JavaScript 引擎执行的代码,并将它们渲染成可视化的网页。
- 处理用户交互:WebView 可以处理用户的点击、滑动、缩放等交互操作,并将这些操作转换成相应的 JavaScript 事件,以便网页可以响应用户的操作。
- 与应用程序交互:WebView 可以通过 JavaScript 接口与应用程序进行交互,例如调用应用程序的方法、获取应用程序的数据等。
- 缓存网页:WebView 可以缓存网页,以便在下次访问相同的 URL 时可以更快地加载网页。
总的来说,WebView 将网页的 HTML、CSS 和 JavaScript 代码解析成可视化的网页,并处理用户的交互操作,同时还可以与应用程序进行交互和缓存网页。
Android 调用 JavaScript
Android 调用 JavaScript 有两种方式:
- 通过调用 WebView 的
loadUrl ()
- 通过调用 WebView 的
evaluateJavascript ()
在 Android 中,可以通过 WebView 控件来调用 JavaScript。以下是一些示例代码:
调用 loadUrl
loadUrl()
方法可以直接调用 JavaScript 函数,例如:
- 调用 JavaScript 函数:
WebView webView = findViewById(R.id.webView);
webView.loadUrl("javascript:jsFunc()");
对应 JavaScript 函数:
function jsFunc() {
document.getElementById("result").innerHTML = "Android 调用了 JS 函数"
}
其中,jsFunc()
是 JavaScript 中的函数名。
- 传递参数给 JavaScript 函数代码:
WebView webView = findViewById(R.id.webView);
String param = "Hello, world!";
webView.loadUrl("jsFuncWithParam('" + param + "')");
对应 JavaScript 函数代码:
function jsFuncWithParam(msg) {
document.getElementById("result").innerHTML = msg
}
其中,jsFuncWithParam()
是 JavaScript 中的函数名,参数名为msg
。
如果 JavaScript 函数有返回值,执行 loadUrl
方法时会刷新 WebView,可以使用 evaluateJavascript()
方法来规避这一问题。
调用 evaluateJavascript
evaluateJavascript()
方法可以调用 JavaScript 函数并获取返回值,例如:
WebView webView = findViewById(R.id.android_web);
webView.getSettings().setJavaScriptEnabled(true);
findViewById(R.id.android_btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
webView.evaluateJavascript("jsFunc()", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
// value 是 JavaScript 函数的返回值
System.out.println(value);
}
});
}
});
其中,ValueCallback 是回调接口,用于异步接收 JavaScript 方法的返回值 value
。
两种方式的区别:
loadUrl()
使用起来方便简洁,效率比较低,如果 JavaScript 函数有返回值会刷新 WebView。evaluateJavascript ()
效率比loadUrl ()
高很多,在获取返回值时候很方便,也不刷新 WebView。但该方法只支持 Android 4.2 或更高版本。
我们可以根据当前项目开发的需求选择相应的使用方式,可以判断 Andriod SKD 版本号来区分使用:
if (Build.VERSION.SDK_INT < 18) {
webView.loadUrl("funcName()")
} else {
webView.evaluateJavascript("funcName()", new ValueCallback<String>() {
// ...
});
JavaScript 调用 Android
JavaScript 调用 Android 有三种方式:
- 通过调用 WebView 的
addJavascriptInterface ()
注入对象 - 通过调用WebViewClient 的
shouldOverrideUrlLoading()
拦截 URL - 使用 Intent机制,结合拦截 URL
addJavascriptInterface 注入
以下是在 JavaScript 中调用 Android 原生端的步骤:
- 在 Android 原生端代码中创建一个 Java 类,该类将包含要在 JavaScript 中调用的方法。
- 在 Android 原生端代码中将提供的Java对象注入到这个 WebView 中。
- 在 JavaScript 中使用
window.android
对象调用 Android 原生端代码中的方法。
下面是一个简单的示例,演示如何在 JavaScript 中调用 Android Native:
在 Android 原生端代码中创建一个 Java 类:
public class JavaScriptInterface {
Context mContext;
JavaScriptInterface(Context c) {
mContext = c;
}
@JavascriptInterface
public void showToast(String toast) {
Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
}
}
接下来需要注入 Java 对象
WebView webView = (WebView) findViewById(R.id.webview);
webView.getSettings().setJavaScriptEnabled(true);
// "android" 是在 JavaScript 中用于暴露对象的名称
webView.addJavascriptInterface(new JavaScriptInterface(this), "android");
webView.loadUrl("file:///android_asset/index.html");
在 JavaScript 中使用 window.android
对象调用 Android 原生端代码中的方法:
function callAndroidMethod() {
window.android.showToast("JS 调用 Android 方法");
}
在这个例子中,使用 window.android
对象调用 Android 原生端代码中的 showToast
方法。这将在 Android 应用程序中显示一个 Toast 消息。
请注意,为了确保安全性,应该仅允许调用受信任的方法,并且应该对传递给方法的参数进行验证。
拦截请求
使用过程中可能会接触到 WebViewClient 与 WebChromeClient。
- 使用 WebViewClient
通过 setWebViewClient
设置 WebViewClient 处理程序,它将接收各种通知和请求。
webView.setWebViewClient(new WebViewClient(){
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// 在当前的 WebView 中跳转到新的 URL
view.loadUrl(url);
return true;
}
});
对应的 JavaScript 代码:
function callAndroidByURL() {
document.location = "file:///android_asset/about.html";
}
在本示例中,我们给 WebView 加一个事件监听对象(WebViewClient)并重写其中的一些方法,比如使用 shouldOverrideUrlLoading
响应网页中超链接。当按下某个链接时 WebViewClient 会调用这个方法,并将按下的链接作为 url
参数传递。
加载网页时提示 NET::ERR_CACHE_MISS 的错误,就是没有添加网络访问的权限,在AndroidManifest.xml中配置<uses-permission android:name="android.permission.INTERNET" />
。如果提示 NET::ERR_ACCESS_DENIED 的错误,需要卸载并重新安装 App。
如果拦截请求使用了自定义协议,WebView 可能无法识别这个链接而提示错误:net::ERR_UNKNOWN_URL_SCHEME。解决方法是重写 WebViewClient 里面的 shouldOverrideUrlLoading
方法并使用 Intent。
- 使用 WebChromeClient
通过 setWebChromeClient
设置 WebChromeClient 处理程序。这是一个 WebChromeClient 的实现,用于处理 JavaScript 对话框、网站图标、标题和进度。
webView.setWebChromeClient(new WebChromeClient() {
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
return super.onJsAlert(view, url, message, result);
}
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
return super.onJsConfirm(view, url, message, result);
}
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
return super.onJsPrompt(view, url, message, defaultValue, result);
}
});
相应的调用代码:
<p>
<button onclick="callAndroidByURL()"> 跳转到新的 URL </button>
</p>
<p>
<button onclick="alert('Request failed!')">警告框</button>
</p>
<p>
<button onclick="confirm('Are you sure?')">确认框</button>
</p>
<p>
<button onclick="prompt('Your answer')">输入框</button>
</p>
在本示例中,我们给 WebView 设置 WebChromeClient 处理程序来响应 JavaScript 对话框操作。当按下某类对话框按钮时会调用 WebChromeClient 对应的 onJS 方法(onJsAlert()、onJsConfirm()、onJsPrompt()),并将对话框 url
、message
、result
作为参数传递。
结合使用 Intent 机制
通过拦截请求,结合使用 Android 的 Intent 机制向其他应用程序发送消息,从而实现 JavaScript 和 Android 之间的通信。
webView.setWebViewClient(new WebViewClient(){
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Uri uri = Uri.parse(url);
System.out.println(uri.getScheme());
if (uri.getScheme() == "file") {
// 在当前 WebView 中打开
view.loadUrl(url);
} else if(uri.getScheme() == "https") {
// 启动跳转打开浏览器
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
}
return super.shouldOverrideUrlLoading(view, url);
}
});
如果当前协议是 file
,在当前 WebView 中打开,如果当前协议是 https
,则启动跳转打开浏览器。
总结
如你所见,无论是 JavaScript 调用 Android 原生端,还是 Android 原生端调用 JavaScript,主要都是通过 WebView 这个“中介”来实现的——这也是混合 App 的精髓。
✌️Happy coding!
评论 (0)