Android进阶之路 - 通过WebView与H5前端进行完美交互

此篇讲解的是Android通过WebView与H5进行基础交互,主要分为无参方法的调用 传参方法的调用 ~

我那些关于WebView的回忆 ~ 包含入门使用、优化加载样式、监听加载状态、各场景后退键处理、俩端交互流程、header、user-agent传值、交互常见问题、较全API整合

目录

        • 目录 一 :Android调用Js,传入Js需要的数据
          • 使用场景 - 在App端展示的H5界面
          • 交互方式 1 (我之常用)
            • H5前端 - 实现代码
            • Android端 - 实现流程
            • Android端 - 完整代码
          • 交互方式 2 (几乎未用)
            • H5前端 - 调用方法
            • H5前端 - 完整代码
            • Android端 - 调用代码
        • 目录 二 :Js调用Android,方法存在于Android端
            • H5前端实现
            • Andorid端实现
        • 目录 三 :交互期间常见需求
          • header传值
          • 拦截HTML页面中的点击事件
          • 弹框无效
        • 目录 四 :交互期间所见异常

目录 一 :Android调用Js,传入Js需要的数据

使用场景 - 在App端展示的H5界面

Android进阶之路 - 通过WebView与H5前端进行完美交互_第1张图片

交互方式 1 (我之常用)
H5前端 - 实现代码

Android进阶之路 - 通过WebView与H5前端进行完美交互_第2张图片
提醒:被Android调用的H5前端方法(PS:setVersion 为JS方法名)

<script type="text/javascript">
    function setVersion(version) {
    	document.getElementById("version").innerHTML = version
    }
</script>
Android端 - 实现流程

使用之前科普一波Android调用js方法有参方法与无参方法的不同

  • 无参方法
  mWebView.loadUrl("javascript: H5Method()");
  • 有参方法
  mWebView.loadUrl("javascript:H5Method('" + everyDay Up+ " ')");

正式实现

1.首先我们在onCreat生命周期内调用

    //加载H5地址
    mWebView.loadUrl("这里输入我们要调用的H5地址");

2.监听WebView的加载状态,一般和H5前端的方法交互都会在初始的url完全加载之后 !!!

     // 注意我们要调用的H5前端方法: mWebView.loadUrl("javascript:setVersion('" + versionName + " ');");
  
	 /**
         * 监听WebView的加载状态    分别为 : 加载的 前 中 后期
         * */
        mWebView.setWebViewClient(new WebViewClient() {
            @Override
            public void onPageStarted(WebView view, String url, Bitmap favicon) {
                super.onPageStarted(view, url, favicon);
            }

            @Override
            public void onPageFinished(WebView view, String url) {
                super.onPageFinished(view, url);
                //获取当前版本号
                String versionName = DeviceUtils.getVersionName(AboutUsActivity.this);
                //在加载完成之后,我们通过android的方法,去调用js的方法,设置对应的版本号
                mWebView.loadUrl("javascript:setVersion('" + versionName + " ');");
            }

            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                //本应该加载的H5静态界面
                mWebView.loadUrl(url);
                return true;
            }
        });
Android端 - 完整代码
package com.bakheet.garage.mine.activity;

import android.graphics.Bitmap;
import android.os.Bundle;
import android.view.View;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.ProgressBar;

import com.bakheet.garage.R;
import com.bakheet.garage.base.BaseActivity;
import com.bakheet.garage.http.HttpUrl;
import com.bakheet.garage.utils.DeviceUtils;

/**
 * @author Created by YongLiu on 2017/11/14.
 */

public class AboutUsActivity extends BaseActivity {

    private WebView mWebView;

    @Override
    protected int getLayoutId() {
        return R.layout.activity_about;
    }

    @Override
    protected void init(Bundle savedInstanceState) {
        setToolBarTitle(getString(R.string.title_about_us));
        mWebView = (WebView) findViewById(R.id.web_agreement);
        final ProgressBar mBar = (ProgressBar) findViewById(R.id.progress_Bar);
		//允许Js的语言执行
        mWebView.getSettings().setJavaScriptEnabled(true);
        //加载本地H5
//        mWebView.loadUrl("file:///android_asset/about.html");

        //加载H5地址
        mWebView.loadUrl(HttpUrl.ABOUT_H5 + "?timestamp=" + System.currentTimeMillis());

        /**
         * 监听WebView的加载状态    分别为 : 加载的 前 中 后期
         * */
        mWebView.setWebViewClient(new WebViewClient() {
            @Override
            public void onPageStarted(WebView view, String url, Bitmap favicon) {
                super.onPageStarted(view, url, favicon);
            }

            @Override
            public void onPageFinished(WebView view, String url) {
                super.onPageFinished(view, url);
                String versionName = DeviceUtils.getVersionName(AboutUsActivity.this);
                //在加载完成之后,我们通过android的方法,去调用js的方法,设置对应的版本号
                mWebView.loadUrl("javascript:setVersion('" + versionName + " ');");
            }

            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                //本应该加载的H5静态界面
                mWebView.loadUrl(url);
                return true;
            }
        });

        mWebView.setWebChromeClient(new WebChromeClient() {
            @Override
            public void onProgressChanged(WebView view, int newProgress) {
                if (newProgress == 100) {
                    mBar.setVisibility(View.GONE);
                } else {
                    mBar.setVisibility(View.VISIBLE);
                    mBar.setProgress(newProgress);
                }
            }
        });
    }
}
交互方式 2 (几乎未用)

最低版本貌似要求19,但可尝试此方式下Android端代码部分的第二份代码(注解部分)

H5前端 - 调用方法
<script type="text/javascript">
    function sum(a,b){
    return a+b;
    }
    function do(){
    document.getElementById("p").innerHTML="hello world";
    }
</script>
H5前端 - 完整代码

<html>
<head>
<title>title>
<script type="text/javascript">
    function sum(a,b){
    return a+b;
    }
     function s(){
    var result =window.android.back();
    document.getElementById("p").innerHTML=result;
    }

    script>
head>
<body>
<button onclick="s()">调用本地方法button>
<a href="file:///android_asset/test2.html">点击a>
<p id="p">p>
body>
html>
Android端 - 调用代码
mWebView.evaluateJavascript("sum(1,2)", new ValueCallback<String>() {
        @Override
        public void onReceiveValue(String value) {
            Log.e(TAG, "onReceiveValue value=" + value);
       }
  });

//Android调用有返回值js方法
@TargetApi(Build.VERSION_CODES.KITKAT)
public void onClick(View v) {

    mWebView.evaluateJavascript("sum(1,2)", new ValueCallback<String>() {
        @Override
        public void onReceiveValue(String value) {
            Log.e(TAG, "onReceiveValue value=" + value);
        }
    });
}

目录 二 :Js调用Android,方法存在于Android端

触发android功能,如拍照 亦或 跳转Androi的一些界面

Effect(已禁止LoginData.onBack()方法,所以不会触发)
Android进阶之路 - 通过WebView与H5前端进行完美交互_第3张图片

H5前端实现

<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Documenttitle>
head>

<body>
<button onclick="LoginData.onBack()">注册方法button>
<script type="text/javascript">
    let button = document.querySelector('button')
    let register = button.addEventListener('click', function () {
       //LoginData.onBack();
       LoginData.setData('18888888888','111111');
    })
script>
body>

html>

图片讲解
Android进阶之路 - 通过WebView与H5前端进行完美交互_第4张图片

Andorid端实现
  • 创建交互类 (内部声明JS要调用的方法)

注:如果是要让H5前端调用的方法,请在方法上加 @JavascriptInterface 注解!!!如下:

	//想要让JS识别的话,必须在方法名上加 @JavascriptInterface  如:
   //这里我是用于监听用户在WebView中的点击事件,捕获此事件,如捕获成功关掉当前界面
   @JavascriptInterface
    public void onBack(){
        mContext.finish();
    }

因为我交互的场景是在登录注册的界面,故我创建了LoginData类 ; 这里需要注意一下类名、方法、内部实现都是根据自己的使用场景去创建的,并不是人人相同

LoginData 交互类

package com.bakheet.garage.mine.model;

import android.app.Activity;
import android.content.Intent;
import android.webkit.JavascriptInterface;

import com.alibaba.fastjson.JSON;
import com.bakheet.garage.base.MainActivity;
import com.bakheet.garage.http.HttpManager;
import com.bakheet.garage.http.ObjectResult;
import com.bakheet.garage.utils.SpUtil;
import com.bakheet.garage.utils.ToolUtil;

import java.io.IOException;
import java.util.HashMap;

import okhttp3.MediaType;
import okhttp3.RequestBody;
import retrofit2.Call;
import retrofit2.Response;

/**
 * author  yongliu
 * date  2018/4/19.
 * desc:
 */

public class LoginData {
    Activity mContext;

    public LoginData(Activity mContext) {
        this.mContext = mContext;
    }

    /**
     * 暴露给JS的方法
     */
    @JavascriptInterface
    public void setData(String account, String password) {
        //interLogin(account, password);
        ToastUtils.shortShow("account ="+account+"password ="+password);
    }

    @JavascriptInterface
    public void onBack(){
        mContext.finish();
    }

    /**
     * 登录请求 - 此条请忽略,这是我的项目需求
     * */
    private void interLogin(String account, String password) {
        String mdPassword = ToolUtil.md5(password);
        HashMap<String, String> map = new HashMap<>();
        map.put("acc", account);
        map.put("pwd", mdPassword);
        String jsonParams = JSON.toJSONString(map);
        RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), jsonParams);
        Call<ObjectResult> login = HttpManager.getHttpService().getLogin(body);
        HttpManager.enqueue(login, new HttpManager.OnResultListener<ObjectResult>() {
            @Override
            public void onSuccess(Call<ObjectResult> call, Response<ObjectResult> response) throws IOException {
                String data = (String) response.body().getData();
                SpUtil.putString("token", data);
                Intent intent = new Intent(mContext, MainActivity.class);
                mContext.startActivity(intent);
                mContext.finish();
            }

            @Override
            public void onError(Throwable t) {

            }
        });
    }
}

  • 承载WebView的Activity使用方式

注:核心配置

//基本配置:允许俩端交互 - 可识别JS代码
 mWebView.getSettings().setJavaScriptEnabled(true);

//核心配置:传入的参数包含实例化的交互类、我们给前端自定的API ~ 这里的“LoginData”并不需要和类名一致,见名知意点就好
//格外注意:前端只能识别我们给传的API,只有类似"LoginData"这样的API才能让他调到我们交互类内的方法
mWebView.addJavascriptInterface(new LoginData(this),"LoginData");

//随机加载:因为给大家的是测试效果,所以我加载的本地的html!真是使用场景的话,我们加载的url一般前端的同事都会给我们
mWebView.loadUrl("file:///android_asset/register.html");

这是我的使用场景 RegisterActivity 代码,只是一些WebView的简单使用,然后需要注意的还是上方提交的核心配置 ~

package com.bakheet.garage.home.activity;

import android.os.Bundle;
import android.view.View;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.widget.ProgressBar;

import com.bakheet.garage.R;
import com.bakheet.garage.base.BaseActivity;
import com.bakheet.garage.mine.model.LoginData;

/**
 * @author yongliu
 *         date  2018/4/16.
 *         desc:
 */

public class RegisterActivity extends BaseActivity {
    private WebView mWebView;

    @Override
    protected int getLayoutId() {
        return R.layout.activity_register;
    }

    @Override
    protected void init(Bundle savedInstanceState) {
        setToolBarTitle("注册");
        mWebView = (WebView) findViewById(R.id.web_register);
        final ProgressBar mBar = (ProgressBar) findViewById(R.id.progress_Bar);
        mWebView.getSettings().setJavaScriptEnabled(true);

        //加载H5地址
        mWebView.loadUrl("file:///android_asset/register.html");
        mWebView.addJavascriptInterface(new LoginData(this), "LoginData");

        mWebView.setWebChromeClient(new WebChromeClient() {
            @Override
            public void onProgressChanged(WebView view, int newProgress) {
                if (newProgress == 100) {
                    mBar.setVisibility(View.GONE);
                } else {
                    mBar.setVisibility(View.VISIBLE);
                    mBar.setProgress(newProgress);
                }
            }
        });
    }
}

目录 三 :交互期间常见需求

header传值

其实我们的loadUrl就是一个get网络请求,所以当我们需要在请求头中添加数据的时候,我们可以用到loadUrl的重载方法,如下图
Android进阶之路 - 通过WebView与H5前端进行完美交互_第5张图片
常规使用

 //关键API
 webView.loadUrl(url,header);

 请求头添加数据,是以Map的形式提交的,我们只需要写好对应的键值对,后台就可以通过Map的键名获取值了
 Map<String,String> header = new HashMap<String, String>();
 header.put("tick",postDate);
 webView.loadUrl(url,header);
拦截HTML页面中的点击事件
mWebView.setWebViewClient(new WebViewClient() {
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            //判断url拦截事件
            if (url.equals("file:///android_asset/test2.html")) {
                Log.e(TAG, "shouldOverrideUrlLoading: " + url);
                startActivity(new Intent(MainActivity.this,Main2Activity.class));
                return true;
            } else {
                mWebView.loadUrl(url);
                return false;
            }
        }
    });


弹框无效

此功能,本人未亲测 ~

  • 方法一
WebSettings webSettings = webview.getSettings();
//允许js弹出窗口
setJavaScriptCanOpenWindowsAutomatically(true);
  • 方法二:重绘alert、confirm和prompt的弹出效果,并把用户具体的操作结果回调给JS

借鉴效果
Android进阶之路 - 通过WebView与H5前端进行完美交互_第6张图片
借鉴代码

  /**
             * Webview加载html中有alert()执行的时候,会回调这个方法
             * url:当前Webview显示的url
             * message:alert的参数值
             * JsResult:java将结果回传到js中
             */
            @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
            @Override
            public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
                AlertDialog.Builder builder = new AlertDialog.Builder(JSActivity.this);
                builder.setTitle("提示:看到这个,说明Java成功重写了Js的Alert方法");
                builder.setMessage(message);//这个message就是alert传递过来的值
                builder.setPositiveButton("确定", new OnClickListener() {

                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        //处理确定按钮,且通过jsresult传递,告诉js点击的是确定按钮
                        result.confirm();
                    }
                });
                builder.show();
                //自己处理
                result.cancel();
                return true;
            }

            /**
             * Webview加载html中有confirm执行的时候,会回调这个方法
             * url:当前Webview显示的url
             * message:alert的参数值
             * JsResult:java将结果回传到js中
             */
            @Override
            public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {
                AlertDialog.Builder builder = new AlertDialog.Builder(JSActivity.this);
                builder.setTitle("提示:看到这个,说明Java成功重写了Js的Confirm方法");
                builder.setMessage(message);//这个message就是alert传递过来的值
                builder.setPositiveButton("确定", new OnClickListener() {

                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        //处理确定按钮,且通过jsresult传递,告诉js点击的是确定按钮
                        result.confirm();
                    }
                });
                builder.setNegativeButton("取消", new OnClickListener() {

                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        //处理取消按钮,且通过jsresult传递,告诉js点击的是取消按钮
                        result.cancel();

                    }
                });
                builder.show();
                //自己处理
                result.cancel();
                return true;
            }

            /**
             * Webview加载html中有prompt()执行的时候,会回调这个方法
             * url:当前Webview显示的url
             * message:alert的参数值
             *defaultValue就是prompt的第二个参数值,输入框的默认值
             * JsPromptResult:java将结果重新回传到js中
             */
            @Override
            public boolean onJsPrompt(WebView view, String url, String message, String defaultValue,
                                      final JsPromptResult result) {
                AlertDialog.Builder builder = new AlertDialog.Builder(JSActivity.this);
                builder.setTitle("提示:看到这个,说明Java成功重写了Js的Prompt方法");
                builder.setMessage(message);//这个message就是alert传递过来的值
                //添加一个EditText
                final EditText editText = new EditText(JSActivity.this);
                editText.setText(defaultValue);//这个就是prompt 输入框的默认值
                //添加到对话框
                builder.setView(editText);
                builder.setPositiveButton("确定", new OnClickListener() {

                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        //获取edittext的新输入的值
                        String newValue = editText.getText().toString().trim();
                        //处理确定按钮了,且过jsresult传递,告诉js点击的是确定按钮(参数就是输入框新输入的值,我们需要回传到js中)
                        result.confirm(newValue);
                    }
                });
                builder.setNegativeButton("取消", new OnClickListener() {

                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        //处理取消按钮,且过jsresult传递,告诉js点击的是取消按钮
                        result.cancel();

                    }
                });
                builder.show();
                //自己处理
                result.cancel();
                return true;
            }
        });

目录 四 :交互期间所见异常

  • java.lang.RuntimeException: java.lang.Throwable: A WebView method was called on thread ‘JavaBridge’. 兼容性问题,有网友说开始在4.2的机器上没有这个问题,到4.4的机器上就出了这个问题

    解决方式 :webview的loadUrl方法写在runOnUiThread中即可解决问题

	runOnUiThread(new Runnable() {
       @Override
       public void run() {
		//我的场景是在调用交互方法时发生的错误,如果在正常加载也遇到这样的异常,那么尝试把loadUrl内部字符串换成需要加载的Url
         webView.loadUrl("javascript: H5Method()");
       }
	});

end:借鉴文章

  • Android和H5交互-基础篇
  • Android和H5、JS进行交互调用

你可能感兴趣的:(Android进阶之路,#,项目开发知识点归纳)