在 Android 和 Web 混淆开辟中,免不了 Java 与 JavaScript 代码相互调用,而 WebView 就给我们提供了这样一个接口:JavascriptInterface
public abstract @interface JavascriptInterface implements Annotation
Annotation that allows exposing methods to JavaScript. Starting from API level Build.VERSION_CODES.JELLY_BEAN_MR1 and above, only methods explicitly marked with this annotation are available to the Javascript code.
简单来说,在 Android 4.2 Jelly Bean(API 17)后,应用需要在方法中声明 @JavascriptInterface 注解,并将其地点类添加到 WebView 中,答应应用内启用了 JavaScript 的 WebView 直接调用其类成员方法。
MainActivity
mWebView.addJavascriptInterface(new JavaScriptBridge(), "Android"); // Export class JavaScriptBridge to WebView and map it to window.Android object in JavaScript
mWebView.loadUrl("file:///android_asset/www/index.html"); // You can directly use file://android_asset/ to access the assets folder, or use file://android_res/ to access the res folder
HTML
上述示例代码将答应 JavaScript 通过 window.Android 对象,调用 JavaScriptBridge 类中声明白 @JavascriptInterface 注解的 makeToast 方法。运行后表现一个内容为 Hello world 的 Toast。 链接访问拦截
WebViewClient 提供了 shouldOverrideUrlLoading 事件,可以让我们在 URL 加载时做一些变乱,好比拦截某个链接。
public boolean shouldOverrideUrlLoading (WebView view, WebResourceRequest request)
Give the host application a chance to take control when a URL is about to be loaded in the current WebView. If a WebViewClient is not provided, by default WebView will ask Activity Manager to choose the proper handler for the URL. If a WebViewClient is provided, returning true causes the current WebView to abort loading the URL, while returning false causes the WebView to continue loading the URL as usual.
MainActivity
mWebView.loadUrl("file:///android_asset/www/index.html"); // You can directly use file://android_asset/ to access the assets folder, or use file://android_res/ to access the res folder
}
...
复制代码
上述示例代码将在加载 https://www.google.cn/ 时跳转到 https://www.google.com/ncr*1,或在链接为 meowcat://open_settings 时打开体系设置。
除示例代码外,也可以直接 return true; 来中断页面加载。
注:该方法不适用于 POST 请求,页面在举行表单提交等 POST 请求时不会调用。 在页面内执行外部 JavaScript 代码
出于调试需求,我们大概需要通过 Java 代码在页面内执行一些 JavaScript 代码,利用 loadUrl(String) 或 evaluateJavascript(String, ValueCallback<String>) 方法即可轻松实现该需求。若代码需要在页面加载完毕后执行,WebViewClient 也为我们提供了 onPageFinished 事件。
public void loadUrl (String url)
Loads the given URL.
Also see compatibility note on evaluateJavascript(String, ValueCallback).
public void evaluateJavascript (String script, ValueCallback resultCallback)
Asynchronously evaluates JavaScript in the context of the currently displayed page. If non-null, resultCallback will be invoked with any result returned from that execution. This method must be called on the UI thread and the callback will be made on the UI thread.
Compatibility note. Applications targeting Build.VERSION_CODES.N or later, JavaScript state from an empty WebView is no longer persisted across navigations like loadUrl(java.lang.String). For example, global variables and functions defined before calling loadUrl(java.lang.String) will not exist in the loaded page. Applications should use addJavascriptInterface(Object, String) instead to persist JavaScript objects across navigations.
public void onPageFinished (WebView view, String url)
Notify the host application that a page has finished loading. This method is called only for main frame. Receiving an onPageFinished() callback does not guarantee that the next frame drawn by WebView will reflect the state of the DOM at this point. In order to be notified that the current DOM state is ready to be rendered, request a visual state callback with WebView#postVisualStateCallback and wait for the supplied callback to be triggered.
MainActivity
mWebView.loadUrl("file:///android_asset/www/index.html"); // You can directly use file://android_asset/ to access the assets folder, or use file://android_res/ to access the res folder
在上面的示例代码中,我们利用了 file:///android_asset/ 来直接加载 assets 资源文件夹中的资源。但由于一些逼迫执行的安全战略(Content Security Policy)限制,使得该非同源 URL 无法正常被加载,这时间就可以利用 WebViewClient 提供的 shouldInterceptRequest 事件来辅助加载。
public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request)
Notify the host application of a resource request and allow the application to return the data. If the return value is null, the WebView will continue to load the resource as usual. Otherwise, the return response and data will be used.
This callback is invoked for a variety of URL schemes (e.g., http(s):, data:, file:, etc.), not only those schemes which send requests over the network. This is not called for javascript: URLs, blob: URLs, or for assets accessed via file:///android_asset/ or file:///android_res/ URLs.
In the case of redirects, this is only called for the initial resource URL, not any subsequent redirect URLs. MainActivity
mWebView.addJavascriptInterface(new JavaScriptBridge(), "Android"); // Export class JavaScriptBridge to WebView and map it to window.Android object in JavaScript
mWebView.setWebViewClient(new WebViewClient() {
@Override
public void onPageFinished(WebView view, String url) {
mWebView.loadUrl("file:///android_asset/www/index.html"); // You can directly use file://android_asset/ to access the assets folder, or use file://android_res/ to access the res folder
}
private enum DialogType {
ALERT,
CONFIRM,
PROMPT
}
private static void onJsDialog(DialogType type, WebView view, String url, String message, final JsResult result, String defaultValue, final JsPromptResult promptResult) {
AlertDialog.Builder builder = new AlertDialog.Builder(view.getContext());
confirm("Confirm Title:This is a confirm") ? alert("Alert Title (Confirm):You confirmed the dialog") : alert("Alert Title (Confirm):You canceled the dialog");
alert("Alert Title (Prompt):Prompt content is " + prompt("Prompt Title:This is a prompt", "Hello world"));
mWebView.addJavascriptInterface(new JavaScriptBridge(), "Android"); // Export class JavaScriptBridge to WebView and map it to window.Android object in JavaScript
mWebView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
if (request.getUrl().toString().equalsIgnoreCase("https://www.google.cn/")) {
view.loadUrl("https://www.google.com/ncr");
return true;
} else if (request.getUrl().toString().startsWith("meowcat://open_settings")) {
final Intent intent = mContext.getPackageManager().getLaunchIntentForPackage("com.android.settings");
confirm("Confirm Title:This is a confirm") ? alert("Alert Title (Confirm):You confirmed the dialog") : alert("Alert Title (Confirm):You canceled the dialog");
alert("Alert Title (Prompt):Prompt content is " + prompt("Prompt Title:This is a prompt", "Hello world"));