您的位置:首页 > 移动开发

说说webView与JS的交互方式

2018-01-28 18:49 281 查看
大家对于Hybrid App应该都不会很陌生,它主要就是指native和HTML5混合开发。Hybrid App能让前端,Android和iOS都同用一套界面和逻辑,大大降低了开发和维护的成本,因此Hybrid App在移动应用开发领域占有了一席之地。

而在Android端里面,开发Hybrid App主要是使用webView控件,webView加载的HTML5网页里面有很多逻辑都是放在JS里面执行了,因此要开发一个Hybrid App,就必须知道WebView怎么和JS进行交互。

Android端通过WebView调用JavaScript代码

在Android端里面,
WebView
调用JavaScript代码的方法有2种方法:

1.使用
WebView.loadUrl(String url)


2.使用
WebView.evaluateJavascript(String script, ValueCallback<String> resultCallback)


这两个方法在使用方式上大致相同,但是第二个方法只能在Android 4.4 后才可使用,是专门用于异步调用JavaScript方法并且能够得到一个回调结果,而且效率要比第一个方法高。因此日常开发中建议是混合使用,怎么个混合使用呢?先看一段HTML代码吧:

<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="user-scalable=no">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<div id="input" contenteditable="true">我是测试文本</div>
<script>
var re = document.getElementById("input");

function setTextColor(color){
re.style.color=color;
}

function setBackgroundColor(){
re.style.backgroundColor="#CCC";
}

function setUnderline(){
re.style.textDecoration = "underline";
}
</script>
</body>
</html>


上面这段代码很简单,没前端基础看那几个function的方法名字应该也看明白了。接着在Android端里通过
WebView
设置调用JS代码

private WebView mWebView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWebView = (WebView) findViewById(R.id.webView);
mWebView.setVerticalScrollBarEnabled(false);
mWebView.setHorizontalScrollBarEnabled(false);
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.loadUrl("file:///android_asset/input.html");
}

public void red(View view) {
String red = "#F00";
execute("setTextColor('" + red + "');");
}

public void black(View view) {
String black = "#000";
execute("setTextColor('" + black + "');");
}

public void bold(View view) {
execute("setBackgroundColor();");
}

public void underline(View view) {
execute("setUnderline();");
}

private void execute(String url) {
//调用js的方法需要一个javascript:的前缀
String trigger = "javascript:" + url;
// 因为evaluateJavascript()该方法在 Android 4.4 版本才可使用
// 所以使用时需进行版本判断
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
//如果不需要获取返回结果可以使用下面这个
//mWebView.evaluateJavascript(trigger, null);
mWebView.evaluateJavascript(trigger, new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
//此处为 JS返回的结果
}
});
} else {
mWebView.loadUrl(trigger);
}
}


这时就可以看到下面的运行结果了:



JS通过WebView调用Android端的代码

搞明白了怎么通过WebView调用JS代码后,就来看下JS是怎么通过WebView来调用Android端的代码吧。我们先从简单的方法开始吧

简单的JavaScriptInterface

这是最普遍的webView和JS的交互方式,就是使用Android的
@JavascriptInterface
注解来实现JS和WebView的交互。

先创建进行交互的HTML5文件:

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<script type="text/javascript">
function sayHello(world) {
//这里让html5调用Android的代码,要与代码设置的名称一致
//也就是说Android代码需要设置WebView这个名字
webView.sayHello(world);
}
</script>
</head>
<body>
<input type="button" value="Js调用Android代码" style="height: 30px;font-size:20px;"
onClick="sayHello('Js调用Android代码')" />
</body>
</html>


上面代码很简单,一个button,点击就执行JavaScrip脚本中的
sayHello
方法。接着就是通过
WebView
addJavascriptInterface
方法去注入一个我们自己写的JavaScriptInterface:

public class JsInterfaceActivity extends AppCompatActivity {

private WebView mWebView;

@SuppressLint("SetJavaScriptEnabled")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//布局很简单,就一个webView
setContentView(R.layout.activity_js_interface);
mWebView = (WebView) findViewById(R.id.js_webView);
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.loadUrl("file:///android_asset/JsInterface.html");
//注入JsInterface,这里注意映射的名字需要一致
mWebView.addJavascriptInterface(new JsInterface(), "webView");
}

public class JsInterface {

@JavascriptInterface
public void sayHello(String word) {
AlertDialog.Builder builder = new AlertDialog.Builder(JsInterfaceActivity.this);
builder.setMessage(word);
builder.create().show();
}
}
}


这样子运行就可以看下面的效果了:



不过方法虽然简单,但实际上这个方法是存在一定的风险的,如果你使用的是AndroidStudio,在你的
setJavaScriptEnabled(true);
这句方法中,AndroidStudio会给你一个提示:



这个提示的意思呢,就是如果你使用了这种方式去开启JavaScript通道,你就要小心XSS攻击了。具体可以到提示里面这个网站去查看,需要合理翻墙的。

不过这个漏洞已经在Android 4.2上面修复了,如果你不用兼容到这个以下的版本,就可以不用管了。如果需要兼容,那么为了确保安全,可以用下面这个有点复杂的方法。

有点复杂的JavaScriptBridge

JavaScriptBridge
顾名思义就是WebView和JavaScript沟通的通道,原理是利用WebView中的WebChromeClient类的onJsAlert()、onJsConfirm()、onJsPrompt()这3个方法回调拦截JS对话框alert()、confirm()、prompt() 消息

比如我们可以在JavaScript脚本中调用alert方法,这样对应的就会走到
WebChromeClient
类的
onJsAlert()
方法中,我们就可以拿到其中的信息去解析,并且做Java层的事情。

不过这并不意味着我们可以任意用一个方法来用。因为在JavaScript中,
alert()
confirm()
的使用概率还是很高的,所以剩下一个选项了。

不过还有一个细节需要处理,那就是怎么样才能让Java层知道JavaScript脚本需要调用的哪一个方法呢?怎么把JavaScript脚本的参数传递进来呢?这时候就需要自定义协议了。这里我们来自定义一个协议如下:

hybrid://JSBridge/method?arg1=我是prompt消息&arg2=Toast


然后本地的HTML5文件使用它:

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<script type="text/javascript">
function clickPrompt(){
// 调用prompt()
var result=prompt("hybrid://JSBridge/method?arg1=我是prompt消息&arg2=Toast");
}
</script>
</head>
<body>
<input type="button" value="Prompt调用Android代码" style="height: 30px;font-size:20px;"
onClick="clickPrompt()"/>
</body>
</html>


在Android通过
WebChromeClient
复写
onJsPrompt()
,拿到传递的数据:

public class MyWebChromeClient extends WebChromeClient {

@Override
public boolean onJsPrompt(WebView view, String url, String message,
String defaultValue, JsPromptResult result) {
// 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数)
//假定传入进来的 url = "hybrid://JSBridge/method?arg1=我是prompt消息&arg2=Toast"
//(同时也是约定好的需要拦截的)
Uri uri = Uri.parse(message);
// 如果url的协议 = 预先约定的 js 协议, 就解析往下解析参数
if ( uri.getScheme().equals("hybrid")) {
// 如果 authority  = 预先约定协议里的 webview,即代表都符合约定的协议
// 所以拦截url,下面JS开始调用Android需要的方法
if (uri.getAuthority().equals("JSBridge")) {
// 可以在协议上带有参数并传递到Android上
String query = uri.getQuery().replace("arg1=","")
.replace("&arg2=",",");
// 吐司一下好了,实际开发应该是依据参数执行不同的方法
Toast.makeText(view.getContext(),query,Toast.LENGTH_SHORT).show();
//参数result:代表消息框的返回值(输入值)
result.confirm("js成功调用了Android的方法了");
}
return true;
}
return super.onJsPrompt(view, url, message, defaultValue, result);
}
}


这时只要给
WebView
设置设置上面的
WebChromeClient
就好了,现在看下运行效果:



很好用的拦截Url

通过
WebViewClient
复写
shouldOverrideUrlLoading ()
的方法,拦截前端同学写的url,可以让前端唤起Android的native页面。这个方法用好了,可以很方便地让前端和移动端有个统一的调度。

而且只有重写
WebViewClient
shouldOverrideUrlLoading
方法,才不会跳转到系统浏览器

我们还是先来一个HTML5代码:

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<script type="text/javascript">
function callLogin(){
//约定一个协议,启动一个页面
document.location = "http://login.test.gradle.com/";
}

</script>
</head>
<body>
<input type="button" value="login" style="height: 30px;font-size:20px;"
onClick="callLogin()"/>
</body>
</html>


这里多说一句,这里是练习,所以可以随便定,但是实际开发中最好和前端与iOS端的同学协商好,保证前端逻辑的清晰

下面来看看WebViewClient类:

public class MyWebClient extends WebViewClient {

@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url == null) {
return false;
}
if (url.startsWith("http") || url.startsWith("https")) {
view.loadUrl(url);
return false;
} else {
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri parse = Uri.parse(url);
//设置intent的Data
intent.setData(parse);
try {
//设置包名
intent.setPackage(view.getContext().getPackageName());
PackageManager pm = view.getContext().getPackageManager();
ResolveInfo info = pm.resolveActivity(intent,
PackageManager.MATCH_DEFAULT_ONLY);
if (info == null) {
throw new ActivityNotFoundException("No Activity found : "
+ intent);
} else {
intent.setClassName(info.activityInfo.packageName,
info.activityInfo.name);
}
view.getContext().startActivity(intent);
return true;
} catch (ActivityNotFoundException e) {
return false;
}
}


然后我们去gradle里面写一下配置,这样子方便以后的维护和修改:

defaultConfig {
applicationId "test.gradle.com"
minSdkVersion 16
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
manifestPlaceholders = ["URL_SCHEMA": "http",
"URL_HOST": "login.test.gradle.com",
"URL_CATEGORY": "gradle.com"]
}


最后就是到清单文件去配置一下需要启动的Activity了:

<activity android:name=".LoginActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="${URL_CATEGORY}"/>
<data android:scheme="${URL_SCHEMA}"/>
<data android:host="${URL_HOST}"/>
</intent-filter>
</activity>


ok,这样子我们的拦截Url唤醒就完成了,我们运行一下看下效果:



总结

Android中的WebView和JavaScript的交互方式到此就说完了,看到这儿可能大家肯定会觉得这么简单啊?

是的,大体的原理就这么简单,但是如果你想真正的用好它来进行开发,还需要做很多工作。

与君共勉。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: