JavaScript 和 Android 的数据通信

JavaScript 和 Android 的数据通信

Flying
2018-04-10 / 0 评论 / 180 阅读 / 正在检测是否收录...

上篇文章我们讲述了 React Native 的原理,其中讲到 React Native 与 原生模端的数据通信。那么脱离 React Native,JavaScrip 是怎样和原生端的数据通信的呢?作为补充,我们需要了解它们的工作原理。下面我们仅以 Android 原生端为例进行讲解。

connection-js-android.svg

概述

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
}

call-js.jpg></p><blockquote>其中,<code>jsFuncWithParam()</code> 是 JavaScript 中的函数名,参数名为 <code>msg</code>。</blockquote><p>如果 JavaScript 函数有返回值,执行 <code>loadUrl</code> 方法时会刷新 WebView,可以使用 evaluateJavascript() 方法来规避这一问题。</p><h3 id=调用 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 原生端的步骤:

  1. 在 Android 原生端代码中创建一个 Java 类,该类将包含要在 JavaScript 中调用的方法。
  2. 在 Android 原生端代码中将提供的Java对象注入到这个 WebView 中。
  3. 在 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 消息。

call-android.jpg

请注意,为了确保安全性,应该仅允许调用受信任的方法,并且应该对传递给方法的参数进行验证。

拦截请求

使用过程中可能会接触到 WebViewClient 与 WebChromeClient。

  1. 使用 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。

  1. 使用 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()),并将对话框 urlmessageresult 作为参数传递。

结合使用 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!

8

评论 (0)

取消