说说webView与JS的交互方式

大家对于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方法。接着就是通过WebViewaddJavascriptInterface方法去注入一个我们自己写的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页面。这个方法用好了,可以很方便地让前端和移动端有个统一的调度。

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

我们还是先来一个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的交互方式到此就说完了,看到这儿可能大家肯定会觉得这么简单啊?

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

与君共勉。

猜你喜欢

转载自blog.csdn.net/f409031mn/article/details/79188497
今日推荐