14,086

现在业务稍微大一点的公司,基本上都会引入android与H5交互的方式开发,或者是引入Hybrid框架,更有甚者直接全部采用Js开发成Web App形式,就是看中其开发成本更低(跨平台),更新风险更小的优势。目前移动端开发市场的遇冷,除了android初级人才过多之外,还有就是前端技术的崛起,挤占了native开发的空间,不过仔细想想,在互联网的意义上,移动端的App其实也属于前端。。。所以顺应技术的浪潮,拥抱变化才能使自己立于不败之地。附上 源码地址

图中的上半部分是android原生界面,下半部分是webview加载html的页面,可以看到,两边可以相互传递参数,并且调用对方的代码块了。下面我把完成的代码先贴出来,有基础的同学可以直接copy源码然后自己调试看看,没基础的同学别急,听我一个一个解析。

Android调用Js

通过WebView有 loadUrl() evaluateJavascript() 两种方法调用Js方法。

这里采用加载本地assets中的html文件进行调试

1、loadUrl() 方式

JsMethod.html

< meta http-equiv = "Content-Type" charset = "UTF-8" /> < script type = "text/javascript" > var s = '我来自Js方法' ; function javatojscallback ( param ) { document .getElementById( "textshow" ).innerHTML = (param); //window.android.JsToJavaInterface(s) </ script > </ head > < h3 > Js Method </ h3 > < h3 id = "textshow" > 调用结果 </ h3 > </ body > </ html >

window.android.JsToJavaInterface(s) 是Js调用android的方法,由于loadUrl()不能从Js返回数据,可以让Js回调android的方法回传参数。

MainActivity.java

private void initView () { javaMethod = new JavaMethod( this ); webView = new WebView( this ); WebSettings settings = webView.getSettings(); settings.setDomStorageEnabled( true ); settings.setJavaScriptEnabled( true ); settings.setBlockNetworkImage( false ); frameLayout.addView(webView); webView.loadUrl( "file:///android_asset/JsMethod.html" );
webView.loadUrl("javascript:javatojscallback('我来自Java')");

2、evaluateJavascript()

< meta http-equiv = "Content-Type" charset = "UTF-8" /> < script type = "text/javascript" > var s = '我来自Js方法' ; function javatojswith ( param ) { document .getElementById( "textshow" ).innerHTML = (param); return s; </ script > </ head > < h3 > Js Method </ h3 > < h3 id = "textshow" > 调用结果 </ h3 > </ body > </ html >
    webView.evaluateJavascript("javascript:javatojswith('我来自Java')",
            new ValueCallback<String>() {
        @Override
        public void onReceiveValue(String s) {
            textShow.setText(s);

相信已经大家已经注意到,被调用的Js方法是有返回值的,如果是采用 loadUrl() 调用,返回值也会用 loadUrl() 载入,直接显示在WebView上,这显然是不对的,我们只想隐形的接收返回值,而 evaluateJavascript() 就提供了这样的隐形接收方式,不会调用到 loadUrl()

需要注意的是, evaluateJavascript() 只能在android 4.4之后才能调用。

Js调用Android

Js通过WebView有三种方式调用android方法

1、addJavascriptInterface

< meta http-equiv = "Content-Type" charset = "UTF-8" /> < script type = "text/javascript" > </ script > </ head > < h3 > Js Method </ h3 > < h3 id = "textshow" > 调用结果 </ h3 > < input type = "button" value = "JavascriptInterface" onclick = "window.android.JsToJavaInterface('我来自Js')" /> </ body > </ html >

JavaMethod.java

public class JavaMethod {
    private MainActivity mainActivity;
    private Handler uiHandler;
    public JavaMethod(MainActivity mainActivity) {
        this.mainActivity = mainActivity;
        uiHandler = new Handler(Looper.getMainLooper());
    @JavascriptInterface
    public void JsToJavaInterface(final String param) {
        uiHandler.post(new Runnable() {
            @Override
            public void run() {
                mainActivity.setTextShow("from JavaInterface: " + param);

这里我把Js调用Java的方法分离出来到一个JavaMethod类中,然后通过 Looper.getMainLooper() 获取主线程 Handler ,统一采用接口形式更新界面。

MainActivity.java

private void initView () { settings.setJavaScriptEnabled( true ); webView.addJavascriptInterface(javaMethod, "android" ); frameLayout.addView(webView); webView.loadUrl( "file:///android_asset/JsMethod.html" ); public void setTextShow (String str) { textShow.setText(str);

在android4.2之前有个严重漏洞,Js通过webview获取android对象后,可以调用到其他系统方法,为了避免这个漏洞,在4.2之后,只能调用到 @JavascriptInterface 注释过的方法。

2、shouldOverrideUrlLoading

通过 WebViewClient 中的 shouldOverrideUrlLoading 拦截url,制定一个对应协议。

< meta http-equiv = "Content-Type" charset = "UTF-8" /> < script type = "text/javascript" > </ script > </ head > < h3 > Js Method </ h3 > < h3 id = "textshow" > 调用结果 </ h3 > < input type = "button" value = "shouldOverrideUrlLoading" onclick = "document.location = 'js://jstojava?arg1=1号参数&arg2=2号参数'" /> </ body > </ html >

JavaMethod.java

public WebViewClient getWebViewClient () { WebViewClient webViewClient = new WebViewClient(){ @Override public boolean shouldOverrideUrlLoading (WebView view, String url) { Uri uri = Uri.parse(url); // 一般根据scheme(协议格式) & authority(协议名)判断 // url = "js://jstojava?arg1=1&arg2=2" if (uri.getScheme().equals( "js" )) { if (uri.getAuthority().equals( "jstojava" )) { final String param1 = uri.getQueryParameter( "arg1" ); final String param2 = uri.getQueryParameter( "arg2" ); uiHandler.post( new Runnable() { @Override public void run () { mainActivity.setTextShow( "arg1=" +param1+ " arg2=" +param2); return true ; return super .shouldOverrideUrlLoading(view, url); return webViewClient;

MainActivity.java

private void initView () { javaMethod = new JavaMethod( this ); webView = new WebView( this ); WebSettings settings = webView.getSettings(); settings.setDomStorageEnabled( true ); settings.setJavaScriptEnabled( true ); settings.setBlockNetworkImage( false ); webView.setWebViewClient(javaMethod.getWebViewClient()); webView.addJavascriptInterface(javaMethod, "android" ); frameLayout.addView(webView); webView.loadUrl( "file:///android_asset/JsMethod.html" );

这种方式没有版本限制和漏洞,不过没有返回值,如果Js调用后需要android返回就得使用 loadUrl() 或者 evaluateJavascript() 回传对应的接收方法了。值得一提的是,这种方式便于和IOS通用一套协议,简便Js端的代码量。

3、onJsAlert()、onJsConfirm()、onJsPrompt()

通过 WebChromeClient 中的onJsAlert()、onJsConfirm()、onJsPrompt()拦截Js中的alert()、confirm()、prompt() 消息。而 alert confirm prompt 代表Js中三种常用提示框,第一种没有返回值,第二种返回布尔值,第三种可返回任意值。由于考虑到灵活性,所以我们可以直接实现对 prompt 的拦截即可。

< meta http-equiv = "Content-Type" charset = "UTF-8" /> < script type = "text/javascript" > function jstojavaprompt ( param ) { result = prompt(param); document .getElementById( "textshow" ).innerHTML = (result); </ script > </ head > < h3 > Js Method </ h3 > < h3 id = "textshow" > 调用结果 </ h3 > < input type = "button" value = "onJsPrompt" onclick = "jstojavaprompt('js://jstojava?arg3=3号参数&arg4=4号参数')" /> </ body > </ html >

JavaMethod.java

public WebChromeClient getWebChromeClient () { WebChromeClient webChromeClient = new WebChromeClient(){ @Override public boolean onJsPrompt (WebView view, String url, String message, String defaultValue, final JsPromptResult result) { Uri uri = Uri.parse(message); if (uri.getScheme().equals( "js" )) { if (uri.getAuthority().equals( "jstojava" )) { final String param3 = uri.getQueryParameter( "arg3" ); final String param4 = uri.getQueryParameter( "arg4" ); uiHandler.post( new Runnable() { @Override public void run () { mainActivity.setTextShow( "arg3=" +param3+ " arg4=" +param4); result.confirm( "我来自onJsPrompt" ); return true ; return super .onJsPrompt(view, url, message, defaultValue, result); return webChromeClient;

MainActivity.java

private void initView () { javaMethod = new JavaMethod( this ); webView = new WebView( this ); WebSettings settings = webView.getSettings(); settings.setDomStorageEnabled( true ); settings.setJavaScriptEnabled( true ); settings.setBlockNetworkImage( false ); webView.setWebChromeClient(javaMethod.getWebChromeClient()); webView.addJavascriptInterface(javaMethod, "android" ); frameLayout.addView(webView); webView.loadUrl( "file:///android_asset/JsMethod.html" );

协议的方式与 shouldOverrideUrlLoading 拦截url时类似,在对应线程处理完业务后,可将结果通过 result.confirm() 返回给Js。

以上的交互方法各有利弊,主要是由于android版本的限制,没有版本限制的方法稍显麻烦,但是通用,一劳永逸,大家可以从业务覆盖的机型来考虑引入哪种方式来与Js交互。

源码已经集成文中的回调方式,感兴趣的同学可以看下。

最后附上一句“鸡汤”,希望大家能时刻让自己保持坚强,晚安。

生活不会为谁放慢节奏,我们只能提起精神,抹着泪,踉跄着追上生活的步伐。

LeviWGG Android
粉丝