WebView全面学习(二)-- Native与js双方通信

WebView全面学习(二)-- Native与js双方通信

Native与js通信的本质

Native与js通信的核心在于WebView.

两端的通信主要还是单向的。假如要完成js->Native->js那么就需要把这两种单向的通信结合起来使用。

两种通信的处理依旧是在Native端来完成

Native调用js的代码:(两种方式)

  1. WebView.loadUrl()

    优点:调用方式简单
    缺点:获取返回值麻烦,效率低

  2. WebView.evaluateJavascript()

    优点:效率高
    缺点:仅Android4.4以上版本适用

总结:目前市面上基本都会要求Android4.4以上包括许多大厂,所以最好采用WebView.evaluateJavascript方式进行Native->js调用,
如果考虑兼容性到4.4以下可以使用WebView.loadUrl方式

loadUrl方式实现

第一步:为了方便测试,这里把页面放在assets目录中,页面命名native2js.html,下面是页面的内容





// JS代码

第二部:loadUrl调用js的方法

public class MainActivity extends AppCompatActivity {

private WebView mWebview;

private WebSettings mWebSettings;

private Button btn;

private FrameLayout container;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);



FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);


mWebview = new WebView(getApplicationContext());
//不要在xml中用标签!会引起内存泄漏


container = (FrameLayout) findViewById(R.id.webView1);

container.addView(mWebview);
 


btn = (Button) findViewById(R.id.btn);


btn.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

mWebview.loadUrl("javascript:callJs()");

}
 });


mWebSettings = mWebview.getSettings();

mWebSettings.setJavaScriptEnabled(true);

mWebSettings.setJavaScriptCanOpenWindowsAutomatically(true);


mWebview.loadUrl("file:///android_asset/native2js.html");



//设置不用系统浏览器打开,直接显示在当前Webview

mWebview.setWebViewClient(new WebViewClient() {

@Override

public boolean shouldOverrideUrlLoading(WebView view, String url) {

view.loadUrl(url);

return true;
 }
 });


//设置WebChromeClient类

mWebview.setWebChromeClient(new WebChromeClient() {



@Override

public boolean onJsAlert(WebView view, String url, String message, JsResult result) {

//这里不采取弹窗,让用户从原生无感调用到js

Log.d("mainActivity", url + " msg " + message);

return true;
 }
 });

}

@Override

protected void onPause() {

mWebview.onPause();

super.onPause();

}

 

//销毁Webview

@Override
 protected void onDestroy() {

if (mWebview != null) {

mWebview.loadDataWithBaseURL(null, "", "text/html", "utf-8", null);

mWebview.clearHistory();

((ViewGroup) mWebview.getParent()).removeView(mWebview);

mWebview.removeAllViews();

mWebview.destroy();

mWebview = null;

}

super.onDestroy();
 }

}

特别注意:1.这种方式Native调用js方法一定要在 onPageFinished() 回调之后才能调用,否则不会调用到。

2.webView生成不要使用XML中标签的形式,并且在onDestory中进行上述销毁工作。否则会引起内存泄漏:

Activity ..... has leaked IntentReceiver org.chromium.content.browser.accessibility.LollipopBrowserAccessibilityManager$1@9a08817 that was originally registered here. Are you missing a call to unregisterReceiver()?

3.loadUrl这种方式来做native->js的调用会让页面每次都会刷新,如果页面加载较慢很容易引起屏幕闪一下或者白屏,效果较差

WebView.evaluateJavascript方式

// 只需将第一种的loadUrl()换成下面该方法即可 mWebView.evaluateJavascript("javascript:callJs()",
new ValueCallback() {
@Override
public void onReceiveValue(String value) {
//此处为 js 返回的结果
}});

总结:综上使用WebView.evaluateJavascript方式实现Native->js的调用最佳!考虑兼容性可以在4.4以下使用loadUrl方式!

final int version = Build.VERSION.SDK_INT;
if (version >= 19) {

mWebView.evaluateJavascript("javascript:callJS()", new ValueCallback() {
@Override
public void onReceiveValue(String value) {
//此处为 js 返回的结果 }
});

} else {
mWebView.loadUrl("javascript:callJS()");
}

js调动Native的代码:(三种方式)

  1. WebView.addJavascriptInterface()进行对象映射

    优点:方式简单
    缺点:Android4.2以下有漏洞

  2. WebViewClient.shouldOverrideUrlLoading ()方法回调中拦截 url对url中采取和前端
    商定协议,然后在收到带有协议的url时进行拦截,并调用原生的代码处理

    优点:可以和IOS统一调用方式,让前端调用方便,没有漏洞问题
    缺点:需要提前和前端商定协议,从Native->js比较繁琐

  3. WebChromeClient 在onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截js对话框alert()、confirm()、prompt() 中的消息

    优点:可以和IOS统一调用方式,让前端调用方便,没有漏洞问题
    缺点:需要提前和前端商定协议,用户感知较明显,会弹出对话框(当然可以在Native层选择不弹框,但这样会让代码复杂性上升)

总结: 目前市面上基本都会要求Android4.4以上包括许多大厂,所以第一种方式也可以选择。但是在4.2以下版本中会存在安全漏洞,不建议使用

目前主流采用的还是第二种和第三种方式,我们Hybrid方案考虑到Android和IOS两端一致性采用的是WebViewClient.shouldOverrideUrlLoading回调中拦截url的方式

WebView.addJavascriptInterface()方式

第一步:定义一个记录Native与js对象映射关系的类:Native2Js

public class Native2Js extends Object {
// 定义JS需要调用的方法
// 被JS调用的方法必须加入@JavascriptInterface注解
@JavascriptInterface
public void callNativeMethod1(String msg) {
System.out.println("JS调用了Native的callNativeMethod1方法");
}
}

第二步:js调用Native方法

//把js2native.html放到Android工程assets目录下,内容如下:







//点击按钮则调用native端方法


第三步:Native端准备接受js的调用

@Override protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWebView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = mWebView.getSettings();
// 允许Js交互
webSettings.setJavaScriptEnabled(true);
// 通过addJavascriptInterface()将Java对象映射到JS对象

//参数分别为native和js中的映射关系对象,通过这个方法的调用做到js和native方法互通
mWebView.addJavascriptInterface(new Native2Js(), "jsCallNative");

//AndroidtoJS类对象映射到js的test对象
// 加载JS代码
// 格式规定为:file:///android_asset/文件名.html
mWebView.loadUrl("file:///android_asset/js2native.html");
}

注意: 这中方式在4.2以下会有安全漏洞,所以不建议使用这种方式

WebViewClient.shouldOverrideUrlLoading()回调拦截

这种方式的主要流程如下:

1. WebViewClient 的回调方法shouldOverrideUrlLoading ()拦截 url
2. 检测url,看是否是和前端确定好的协议
3. 如果是就调用相应原生方法

第一步:js端准备








第二步:native端准备接收js调用

mWebview.setWebViewClient(new WebViewClient() {

@Override

public boolean shouldOverrideUrlLoading(WebView view, String url) {

Uri uri = Uri.parse(url);

if (!uri.getScheme().equals("company")) {

return true;

}


switch (uri.getScheme()) {

case "company":

if (!uri.getAuthority().equals("hybrid")) {

super.shouldOverrideUrlLoading(view, url);

break;

}


//走到这里则认为如何我们的私有协议

Log.d("Hybrid","js调用了Native");

// 可以在协议上带有参数拿到

Set params = uri.getQueryParameterNames();

//...后面可以针对相应的params做处理

break;

default:

view.loadUrl(url);

break;

}

return true;
 }
});

注意: 这种方式的一个问题是js调用了native,但是没有返回到js.为了给js端回复,还需要结合native->js的调用

WebChromeClient中onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截js中对话框alert() confirm() prompt()的消息

注意:这种方式其实利用的是js调用alert confirm prompt方法时, WebChromeClient中相应的方法会有回调,其中alert没有返回值,confirm返回布尔值,true为确认,false为取消,prompt可以返回任何值, 所以一般都会利用prompt来做为通信手段

第一步:js端调用alert confirm prompt方法




 

 







第二步:Native端准备

mWebView.setWebChromeClient(new WebChromeClient() {

// 参数message:promt()的内容(不是url),从js传来
// 参数result:输入框的返回值,回到js中去
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {

Uri uri = Uri.parse(message);

if ( uri.getScheme().equals("conpany")) {

if (uri.getAuthority().equals("hybrid")) {

//拿到来自js的参数 Set collection = uri.getQueryParameterNames();

//参数result:代表消息框的返回值(输入值),把结果ret返回给js端
String ret = "js调用了native,这是返回结果";
result.confirm(ret);
}
return true;
}

return super.onJsPrompt(view, url, message, defaultValue, result);
}});

总结

native->js

native->js最好的办法还是envaluateJavaScript的方式,如果要考虑4.2以下的兼容性,可以结合上loadUrl

js->native

多数情况下hybrid都是js->native的。因为js要用native的一些本地能力
针对目前的双端调用方式来看,js->native的调用采用协议的方式最合适,不论是shouldOverrideUrlLoading回调拦截(这种方式要结合native->js的方式返回给js端数据),还是onJsPrompt回调拦截都比较灵活

posted @ 2017-12-24 03:37  zharma  阅读(1445)  评论(0)    收藏  举报