Android JS通过WebView相互调用详解

一.简介 

 

交互方式总结

Android与JS通过WebView互相调用方法,实际上是


Android 调用 JS 的代码。JS 调用 Android 的代码。二者沟通的桥梁是WebView

 

 

 

 

 

 

 

 

二.Android 调用 JS

对于Android 调用 JS 代码的方法有2种
 


1.通过 WebView 的 loadUrl()方法。

 

2.通过 WebView 的 evaluateJavascript()方法。此方法专门用于异步调用JavaScript方法,并且能够得到一个回调结果

 

 

 

通过WebView的loadUrl()代码详解

 

1.将需要调用的JS代码以.html格式放到src/main/assets文件夹里

 

 

 

 

2.abc.html代码

<script language="JavaScript" type="text/javascript">

    var nowDate = new Date();

    var nH=nowDate.getHours();

    var nM=nowDate.getMinutes();

    var nS=nowDate.getSeconds();

    function MyOnclick() {

        document.write("当前时间:" + nH+ ":" + nM + ":" + nS);

   }

</script>

 

 

3.Android代码

package com.wjn.customwebviewjs;

import android.os.Build;
import android.os.Bundle;
import android.support.annotation.RequiresApi;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.webkit.WebView;
import android.widget.TextView;

public class WebViewLoadUrlActivity extends AppCompatActivity {

    private WebView webView;
    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_webviewloadurl);
        initView();
    }

    /**
     * 初始化各种View
     */

    private void initView() {
        webView = findViewById(R.id.activity_webviewloadurl_webview);
        //设置WebSettings
        WebViewSettingsUtils.setWebSettings(webView);
        //webview加载网页
        webView.loadUrl("file:///android_asset/abc.html");
        //点击事件
        textView = findViewById(R.id.activity_webviewloadurl_textview);
        textView.setOnClickListener(new View.OnClickListener() {
            @RequiresApi(api = Build.VERSION_CODES.KITKAT)
            @Override
            public void onClick(View v) {
                webView.loadUrl("javascript:MyOnclick()");
            }
        });
    }

}

 

 

4.结果

点击按钮前

 

 

点击按钮后

 

 

 

5.总结

通过WebView的loadUrl方法可以实现简单的Android调用JS的方法

 

 

 

 

 

 

通过WebView的evaluateJavascript()代码详解

 

优点:该方法比第一种方法效率更高、使用更简洁。   

因为该方法的执行不会使页面刷新,而第一种方法(loadUrl )的执行则会。Android 4.4 后才可使用。

 

1.将需要调用的JS代码以.html格式放到src/main/assets文件夹里

 

 

2.def.html代码

<script language="JavaScript" type="text/javascript">

    var nowDate = new Date();

    var nH=nowDate.getHours();

    var nM=nowDate.getMinutes();

    var nS=nowDate.getSeconds();

    function MyOnclick() {

        return "当前时间:" + nH+ ":" + nM + ":" + nS;

   }

</script>

 

 

3.Android代码

package com.wjn.customwebviewjs;

import android.os.Build;
import android.os.Bundle;
import android.support.annotation.RequiresApi;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.webkit.ValueCallback;
import android.webkit.WebView;
import android.widget.TextView;
import android.widget.Toast;

public class WebViewEvaluateJavaScriptActivity extends AppCompatActivity {

    private WebView webView;
    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_webviewevaluatejavascript);
        initView();
    }

    /**
     * 初始化各种View
     */

    private void initView() {
        webView = findViewById(R.id.activity_webviewevaluatejavascript_webview);
        //设置WebSettings
        WebViewSettingsUtils.setWebSettings(webView);
        //webview加载网页
        webView.loadUrl("file:///android_asset/def.html");
        //点击事件
        textView = findViewById(R.id.activity_webviewevaluatejavascript_textview);
        textView.setOnClickListener(new View.OnClickListener() {
            @RequiresApi(api = Build.VERSION_CODES.KITKAT)
            @Override
            public void onClick(View v) {
                evaluateJavascriptMethod();
            }
        });
    }

    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    public void evaluateJavascriptMethod() {
        webView.evaluateJavascript("javascript:MyOnclick()", new ValueCallback<String>() {
            @Override
            public void onReceiveValue(String value) {
                //此处为 js 返回的结果
                Toast.makeText(WebViewEvaluateJavaScriptActivity.this, value, Toast.LENGTH_LONG).show();
            }
        });
    }


}

 

 

4.结果

 

 

 

 

 

5.两种方法对比

 

 

6.使用建议

两种方法混合使用,即Android 4.4以下使用方法1,Android 4.4以上方法2

 

 

 

 

 

 

 

 

 

三.JS 调用 Android

 

对于JS调用Android代码的方法有3种:



1.通过WebView的addJavascriptInterface()进行对象映射。


 

2.通过 WebViewClient 的shouldOverrideUrlLoading()方法回调拦截 url。


 

3.通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法拦截JS对话框alert()、confirm()、prompt() 消息

 

 

 

 

通过WebView的addJavascriptInterface()代码详解

 

通过 WebView的addJavascriptInterface()进行对象映射(从Android4.2开始。 只有添加 @JavascriptInterface 声明的Java方法才可以被JavaScript调用)

 

1.将需要调用的JS代码以.html格式放到src/main/assets文件夹里

 


 

2.hhh.html代码

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta http-equiv="Content-Language" content="zh-cn"/>

<title>Android WebView 与 Javascript 交互</title>
<head>
    <style>
  body {background-color:#e6e6e6}
 
  .rect
  {
    color:white;
    font-family:Verdana, Arial, Helvetica, sans-serif;
    font-size:16px;
    width:100px;
    padding:6px;
    background-color:#98bf21;
    text-decoration:none;
    text-align:center;
    border:none;
    cursor:pointer;
  }
 
  .inputStyle {font-size:16px;padding:6px}


    </style>
</head>

<body>
<p>测试Android WebView 与 Javascript 交互</p>
<input id="name_input" class="inputStyle" type="text"/>
<a class="rect" onclick="sendInfoToJava()">JS调用Java</a>

<script>

  function sendInfoToJava(){
    //调用android程序中的方法,并传递参数
    var name = document.getElementById("name_input").value;
    window.AndroidWebView.showInfoFromJs(name);
  }


</script>

</body>
</html>

 

 

3.Android代码

package com.wjn.customwebviewjs;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.webkit.WebView;

public class WebViewAddJavaScriptInterfaceActivity extends AppCompatActivity {

    private WebView webView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_webviewaddjavascriptinterface);
        initView();
    }

    /**
     * 初始化各种View
     */

    private void initView() {
        webView = findViewById(R.id.activity_webviewaddjavascriptinterface_webview);
        //设置WebSettings
        WebViewSettingsUtils.setWebSettings(webView);
        //webview加载网页
        webView.loadUrl("file:///android_asset/hhh.html");
        // 添加js交互接口类,并起别名 AndroidWebView
        webView.addJavascriptInterface(new JSAndroid(this), "AndroidWebView");
    }

}

 

 

4.JS类

package com.wjn.customwebviewjs;

import android.content.Context;
import android.widget.Toast;

public class JSAndroid {

    private Context context;

    public JSAndroid(Context context) {
        this.context = context;
    }

    @android.webkit.JavascriptInterface
    public void showInfoFromJs(String name) {
        Toast.makeText(context, "JS页面输入内容:" + name, Toast.LENGTH_LONG).show();
    }

}

 

 

5.结果

 

 

6.特点
 

优点:使用简单 仅将Android对象和JS对象映射即可。

缺点:存在严重的漏洞问题。

Google 在Android 4.2 版本中规定对被调用的函数以 @JavascriptInterface进行注解从而避免漏洞攻击

 

 

 

 

通过 WebViewClient 的方法shouldOverrideUrlLoading ()回调拦截 url 代码详解

 

1.具体原理

Android通过 WebViewClient 的回调方法shouldOverrideUrlLoading ()拦截 url,解析该 url 的协议,如果检测到是预先约定好的协议,就调用相应方法,即JS需要调用Android的方法。

 

 

2.将需要调用的JS代码以.html格式放到src/main/assets文件夹里

 

 

3.zzz.html代码

<!DOCTYPE html>
<html>


<head>
    <meta charset="utf-8">
    <title>shouldOverrideUrlLoading ()拦截 url</title>


    <script>
         function callAndroid1(){
            /*约定的url协议为:js://webview?arg1=111&arg2=222*/
            document.location = "js://webview?arg1=111&arg2=222";
         }

             function callAndroid2(){
            /*不拦截 直接跳转*/
            document.location = "https://blog.csdn.net/weixin_37730482/article/category/6876458";
         }
      </script>
</head>


<!-- 点击按钮则调用callAndroid()方法  -->
<body>
<button type="button" id="button1" onclick="callAndroid1()">点击调用Android代码 拦截url</button>

<button type="button" id="button2" onclick="callAndroid2()">点击调用Android代码 不拦截url</button>
</body>
</html>

 

 

4.Android代码

package com.wjn.customwebviewjs;

import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;

import java.util.Iterator;
import java.util.Set;

public class WebViewShouldOverrideUrlLoadingActivity extends AppCompatActivity {

    private WebView webView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_webviewshouldoverrideurlloading);
        initView();
    }

    /**
     * 初始化各种View
     */

    private void initView() {
        webView = findViewById(R.id.activity_webviewshouldoverrideurlloading_webview);
        //设置WebSettings
        WebViewSettingsUtils.setWebSettings(webView);
        //webview加载网页
        webView.loadUrl("file:///android_asset/zzz.html");
        //添加WebViewClient监听
        webView.setWebViewClient(new MyWebViewClient());
    }

    /**
     * WebViewClient 实现类
     */

    private class MyWebViewClient extends WebViewClient {

        /**
         * shouldOverrideUrlLoading方法 拦截url
         */

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            Uri uri = Uri.parse(url);
            if (uri.getScheme().equals("js")) {// 如果url的协议 = 预先约定的 js 协议 就解析往下解析参数
                if (uri.getAuthority().equals("webview")) {
                    // 可以在协议上带有参数并传递到Android上
                    Set<String> collection = uri.getQueryParameterNames();
                    Iterator<String> iterable = collection.iterator();
                    String value = "";
                    while (iterable.hasNext()) {
                        value = value + uri.getQueryParameter(iterable.next()) + ";";
                    }
                    Toast.makeText(WebViewShouldOverrideUrlLoadingActivity.this, value, Toast.LENGTH_LONG).show();
                }
                view.stopLoading();
                return true;
            } else {//不是 事先规定的url直接跳转url
                view.loadUrl(url);
                return false;
            }
        }
    }

}

 

 

5.结果

点击第一个按钮(拦截url) 获取协议的参数值

 

 

点击第二个按钮(不拦截url) 直接WebView跳转页面

 

 

 

 

6.特点

   优点:不存在方式1的漏洞。

   缺点:JS获取Android方法的返回值复杂。

 

 

7.补充 Uri  获取url各种信息

 

代码

private void uriMethod(){
    String url="http://api.svipmovie.com/front/columns/getVideoList.do?catalogId=402834815584e463015584e539330016&pnum=1";
    Uri uri=Uri.parse(url);
    Log.d("TAG","全部链接:"+url);
    Log.d("TAG","-----------------------------");

    String scheme=uri.getScheme();// http
    Log.d("TAG","scheme:"+scheme);
    Log.d("TAG","-----------------------------");

    String authority=uri.getAuthority();// api.svipmovie.com
    Log.d("TAG","authority:"+authority);
    Log.d("TAG","-----------------------------");

    String encodedpath=uri.getEncodedPath();// /front/columns/getVideoList.do
    Log.d("TAG","encodedpath:"+encodedpath);
    Log.d("TAG","-----------------------------");

    String encodedquery=uri.getEncodedQuery();// catalogId=402834815584e463015584e539330016&pnum=1
    Log.d("TAG","encodedquery:"+encodedquery);
    Log.d("TAG","-----------------------------");

    //全部参数Key
    Set<String> collection = uri.getQueryParameterNames();
    Iterator<String> iterable = collection.iterator();
    String key = "";
    while (iterable.hasNext()) {
        key = key + iterable.next() + "###";
    }
    Log.d("TAG","key:"+key);
    Log.d("TAG","-----------------------------");

    //全部参数Value
    Set<String> collections = uri.getQueryParameterNames();
    Iterator<String> iterables = collections.iterator();
    String value = "";
    while (iterables.hasNext()) {
        value = value + uri.getQueryParameter(iterables.next()) + "###";
    }
    Log.d("TAG","value:"+value);
    Log.d("TAG","-----------------------------");
}

 

结果

D/TAG: 全部链接:http://api.svipmovie.com/front/columns/getVideoList.do?catalogId=402834815584e463015584e539330016&pnum=1

D/TAG: -----------------------------

D/TAG: scheme:http

D/TAG: -----------------------------

D/TAG: authority:api.svipmovie.com

D/TAG: -----------------------------

D/TAG: encodedpath:/front/columns/getVideoList.do

D/TAG: -----------------------------

D/TAG: encodedquery:catalogId=402834815584e463015584e539330016&pnum=1

D/TAG: -----------------------------

D/TAG: key:catalogId###pnum###

D/TAG: -----------------------------

D/TAG: value:402834815584e463015584e539330016###1###

D/TAG: -----------------------------

 

 

 

 

 

 

 

通过 WebChromeClient 的onJsAlert()+onJsConfirm()+onJsPrompt()方法分别拦截JS对话框alert()+confirm()+prompt()

 

在JS中,有三个常用的对话框方法

方式3的原理:Android通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调分别拦截JS对话框(即上述三个方法),得到他们的消息内容,然后解析即可。

 

 

 

1. 将需要调用的JS代码以.html格式放到src/main/assets文件夹里

 

 

 

2.kkk.html代码

<html>
    <head>
            <meta http-equiv = "Content-Type" content="text/html;charset=UTF-8"
            <title>测试Js的三种不同对话框</title>
            <script language="JavaScript">
                function alertFun()
                {
                    alert("Js的Alert警告对话框弹起!");
                }
                function confirmFun()
                {
                    if(confirm("访问个人主页?"))
                    {location.href = "https://blog.csdn.net/weixin_37730482/article/category/6876458";}
                    else alert("取消访问!");
                }
                function promptFun()
                {
                    var word = prompt("Prompt对话框","");
                    if(word)
                    {
                        alert("你输入了:"+word)
                    }else{alert("呵呵,你什么都没写!");}
                }
		
		       function showAndroidToJsWithParameter(msg)
			   {
                   alert("Android 调用 JS 并传递参数:"+msg);
               }
		
            </script>
    </head>
 
 
    <body>
        <p>三种对话框的使用</p>
 
 
        <p>Alert对话框</p>
        <p>
                <input type="submit" name = "Submit1" value="点击显示JS的Alert对话框" onclick="alertFun()"/>
        </p>
        <p>Confirm对话框</p>
        <p>
                <input type="submit" name = "Submit2" value="点击显示JS的Confirm对话框" onclick="confirmFun()"/>
        </p>
        <p>Prompt对话框</p>
        <p>
                <input type="submit" name = "Submit3" value="点击显示JS的Prompt对话框" onclick="promptFun()"/>
        </p>
    </body>
</html>

 

 

3.Android代码

package com.wjn.customwebviewjs;

import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.widget.EditText;
import android.widget.TextView;

public class WebViewJsDialogActivity extends AppCompatActivity {

    private TextView textView;
    private WebView webView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_webviewjsdialog);
        initView();
    }

    /**
     * 初始化各种View
     */

    private void initView() {
        webView = findViewById(R.id.activity_webviewjsdialog_webview);
        //设置WebSettings
        WebViewSettingsUtils.setWebSettings(webView);
        //webview加载网页
        webView.loadUrl("file:///android_asset/kkk.html");
        //添加WebChromeClient监听
        webView.setWebChromeClient(new MyWebChromeClient());
        //TextView点击事件
        textView = findViewById(R.id.activity_webviewjsdialog_textview);
        textView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String msg = "Android传递的参数666";
                webView.loadUrl("javascript:showAndroidToJsWithParameter('" + msg + "')");
            }
        });
    }

    /**
     * WebChromeClient 实现类
     */

    private class MyWebChromeClient extends WebChromeClient {

        /**
         * onJsAlert方法 拦截JS的alert对话框
         */

        @Override
        public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
            //创建一个Builder来显示网页中的对话框
            new AlertDialog.Builder(WebViewJsDialogActivity.this)
                    .setTitle("Alert对话框")
                    .setMessage(message)
                    .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            result.confirm();
                        }
                    }).setCancelable(false).show();
            return true;
        }

        /**
         * onJsConfirm方法 拦截JS的confirm对话框
         */

        @Override
        public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {
            new AlertDialog.Builder(WebViewJsDialogActivity.this)
                    .setTitle("Confirm对话框")
                    .setMessage(message)
                    .setPositiveButton("确定", new DialogInterface.OnClickListener() {

                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            result.confirm();
                        }
                    })
                    .setNegativeButton("取消", new DialogInterface.OnClickListener() {

                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            result.cancel();
                        }
                    }).setCancelable(false).show();
            return true;
        }

        /**
         * onJsPrompt方法 拦截JS的prompt对话框
         */

        @Override
        public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, final JsPromptResult result) {
            //①获得一个LayoutInflater对象factory,加载指定布局成相应对象
            final LayoutInflater inflater = LayoutInflater.from(WebViewJsDialogActivity.this);
            final View myview = inflater.inflate(R.layout.prompt_view, null);
            //设置TextView对应网页中的提示信息,edit设置来自于网页的默认文字
            ((TextView) myview.findViewById(R.id.textview)).setText(message);
            ((EditText) myview.findViewById(R.id.edittext)).setText(defaultValue);
            //定义对话框上的确定按钮
            new AlertDialog.Builder(WebViewJsDialogActivity.this).setTitle("Prompt对话框").setView(myview)
                    .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            //单击确定后取得输入的值,传给网页处理
                            String value = ((EditText) myview.findViewById(R.id.edittext)).getText().toString();
                            result.confirm(value);
                        }
                    })
                    .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            result.cancel();
                        }
                    }).show();
            return true;
        }
    }

}

 

 

4.结果

点击Android原生TextView

 

点击JS的Alert对话框

 

点击JS的Confirm对话框对话框

 

 

点击JS的Prompt对话框对话框

 

 

 

 

 

补充:Android调用相机相册,并将内容传递给JS

 

JS代码

index1.html

<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="text/javascript">
        function pickPhoto() {
            try {
                App.pickPhotoFromLibrary({"needCompress": false}, function (data) {
                    alert(data.message);
                    var elementById = document.getElementById('firstPhoto');
                    elementById.src = "data:image/jpg;base64," + data.image64;
                    elementById.width=240;
                    elementById.height = 320;
                    return false;
                });
            } catch (e) {
                alert(e);
            }
        }

        function takePhoto() {
            try {
                App.makePhotoFromCamera({}, function (data) {
                    var elementById = document.getElementById('secondPhoto');
                    elementById.src = "data:image/png;base64," + data.image64;
                    elementById.width=240;
                    elementById.height = 320;
                })
            } catch (e) {
                alert(e);
            }
        }
    </script>
    <script type="text/javascript" src="http://www.baidu.com/static/native-app.js"></script>
    <style type="text/css">
        photo {
            width: 240px;
            height: 400px;
        }
    </style>
</head>
<body>
<img id="firstPhoto" class="photo"/>
<br/>
<button onclick="pickPhoto()">系统相册</button>
<br/>
<img id="secondPhoto" class="photo"/>
<br/>
<button onclick="takePhoto()">去拍照</button>
</body>
</html>

 

local.js

//callback容器
var SDKNativeEvents = {}

/*
 * 存入传来的callback回调,加入唯一键名,使调用相同的功能可以并发
 * funcName native的方法名
 * data 参数
 * callback 回调匿名方法
 */
function sdk_launchFunc(funcName,data,callback){

    if(!data){
        alert("必须传入data");
        return;
    }

    if(!callback){
        alert("必须传入回调function");
        return;
    }
    var newName = funcName += getUniqueKey();
    SDKNativeEvents[newName] = callback;

    App.native_launchFunc(newName,JSON.stringify(data));
}

/**
 * native回调  本地做完事情后将funcName和data传过来,调用之前h5预留的匿名函数
 * funcName 对应的触发方法名
 * data 参数
 */
function sdk_nativeCallback(funcName,data){
    var obj= JSON.parse(data);
    try{
        if(SDKNativeEvents[funcName]){
            SDKNativeEvents[funcName](obj);
            SDKNativeEvents[funcName] = null;
        }
    }catch(e){
        alert(e);
    }
}

//工具--生成唯一键

var chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];

function randomStr() {
    var res = "";
    for(var i = 0; i < 5; i++) {
        var id = Math.ceil(Math.random() * 35);
        res += chars[id];
    }
    return res;
}

function getUniqueKey(){
    return randomStr()+""+(new Date()).getTime();
}

window.App.pickPhotoFromLibrary = function(data, callback) {
    sdk_launchFunc("pickPhotoFromLibrary", data, callback);
}

window.App.makePhotoFromCamera = function(data, callback) {
    sdk_launchFunc("makePhotoFromCamera", data, callback);
}

window.App.encrypt = function(data, callback) {
    sdk_launchFunc("encrypt", data, callback);
}

window.App.playSnake = function(data, callback) {
    sdk_launchFunc("playSnake", data, callback);
}

 

Android代码

package com.wjn.customwebviewjs;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.MediaStore;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;

import com.google.gson.JsonObject;
import com.tencent.smtt.sdk.CacheManager;

import java.io.File;
import java.io.IOException;

public class WebViewAndroidJSActivity extends AppCompatActivity {

    private WebView webView;
    private ToastUtils toast;
    private String pickPhotoFuncNameName;//手机相册 JS传递
    private String takePhotofuncName;//手机拍照 JS传递
    private String mPath;
    private Bitmap bitmap;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_webviewandroidjs);
        initView();
    }

    /**
     * 初始化各种View
     */

    private void initView() {
        toast=new ToastUtils(this);
        webView = findViewById(R.id.activity_webviewandroidjs_webview);
        //设置WebSettings
        WebViewSettingsUtils.setWebSettings(webView);
        //添加WebViewClient事件
        webView.setWebViewClient(new myWebViewClient());
        // 添加js交互接口类,并起别名 AndroidWebView
        webView.addJavascriptInterface(new JavascriptInterface(this), "App");
        //webview加载网页
        webView.loadUrl("file:///android_asset/index1.html");
    }

    /**
     * WebViewClient监听
     */

    private class myWebViewClient extends WebViewClient {

        @Override
        public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
            WebResourceResponse webResourceResponse = super.shouldInterceptRequest(view, url);
            if (url == null) {
                return webResourceResponse;
            }
            if (url.endsWith("native-app.js")) {
                try {
                    webResourceResponse = new WebResourceResponse("text/javascript", "UTF-8", WebViewAndroidJSActivity.this.getAssets().open("local.js"));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return webResourceResponse;
        }

        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        @Override
        public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
            WebResourceResponse webResourceResponse = super.shouldInterceptRequest(view, request);
            if (request == null) {
                return webResourceResponse;
            }
            String url = request.getUrl().toString();
            if (url != null && url.endsWith("native-app.js")) {
                try {
                    webResourceResponse = new WebResourceResponse("text/javascript", "UTF-8", WebViewAndroidJSActivity.this.getAssets().open("local.js"));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return webResourceResponse;
        }
    }

    /**
     * Native JS 接口
     */

    @SuppressLint("JavascriptInterface")
    public class JavascriptInterface {

        /**
         * 构造方法
         */

        private Context context;
        public JavascriptInterface(Context context) {
            this.context = context;
        }

        /**
         * JS 调用 Android
         */

        @android.webkit.JavascriptInterface
        public void native_launchFunc(final String funcName, final String jsonStr) {
            if (funcName.startsWith("pickPhotoFromLibrary")) {
                selectImageFromLocal(funcName);//手机相册
            } else if (funcName.startsWith("makePhotoFromCamera")) {
                takePhoto(funcName);//手机拍照
            }
        }

    }

    /**
     * 手机相册
     * */

    public void selectImageFromLocal(String funcName){
        pickPhotoFuncNameName = funcName;
        if(FileHelper.isSdCardExist()){
            Intent intent;
            if(Build.VERSION.SDK_INT<19){
                intent=new Intent(Intent.ACTION_GET_CONTENT);
                intent.setType("image/*");
            }else{
                intent=new Intent(Intent.ACTION_PICK,android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
            }
            startActivityForResult(intent, StringConstant.REQUEST_CODE_SELECT_PICTURE);
        }else{
            toast.showToast(StringConstant.Filestatus1);
        }
    }

    /**
     *手机拍照
     * */

    public void takePhoto(String funcName){
        takePhotofuncName = funcName;
        if(FileHelper.isSdCardExist()){
            Uri photoURI=null;
            String name=String.valueOf(System.currentTimeMillis());
            mPath=FileHelper.createAvatarPathPicture(name);
            Intent takePictureIntent=new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            if(takePictureIntent.resolveActivity(getPackageManager())!=null){
                //创建一个File
                File photoFile = new File(mPath);
                if(photoFile != null){
                    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
                        //如果是7.0及以上的系统使用FileProvider的方式创建一个Uri
                        photoURI= FileProvider.getUriForFile(WebViewAndroidJSActivity.this, "com.wjn.customwebviewjs.fileprovider", photoFile);
                        takePictureIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                        takePictureIntent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
                    } else {
                        //7.0以下使用这种方式创建一个Uri
                        photoURI = Uri.fromFile(photoFile);
                    }
                    //将Uri传递给系统相机
                    takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
                    startActivityForResult(takePictureIntent, StringConstant.REQUEST_CODE_TAKE_PHOTO);
                }
            }
        }else{
            toast.showToast(StringConstant.Filestatus1);
        }
    }

    /**
     * onActivityResult方法
     * */

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
            case StringConstant.REQUEST_CODE_SELECT_PICTURE://本地相册
                Uri uri = data.getData();
                if (uri != null) {
                    String filePath;
                    if (!TextUtils.isEmpty(uri.toString()) && uri.toString().startsWith("file")) {
                        filePath = uri.getPath();
                    } else {
                        filePath = RealPathUtil.getRealPathFromURI(this, uri);
                    }
                    bitmap=BitmapFactory.decodeFile(filePath);
                    String base64Image = Base64Util.bitmapToBase64(bitmap);
                    JsonObject jsonObject = new JsonObject();
                    jsonObject.addProperty("image64", base64Image);
                    jsonObject.addProperty("message", "图片获取成功");
                    webView.loadUrl("javascript:sdk_nativeCallback(\'" + pickPhotoFuncNameName + "\',\'" + jsonObject + "\')");
                }
                break;
            case StringConstant.REQUEST_CODE_TAKE_PHOTO://手机拍照
                bitmap=BitmapFactory.decodeFile(mPath);
                String base64Image = Base64Util.bitmapToBase64(bitmap);
                JsonObject jsonObject = new JsonObject();
                jsonObject.addProperty("image64", base64Image);
                webView.loadUrl("javascript:sdk_nativeCallback(\'" + takePhotofuncName + "\',\'" + jsonObject + "\')");
                break;
        }
    }

    /**
     * onDestroy方法
     * */

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Base64Util.recycleBitmap(bitmap);//释放Bitmap
        //删除剪切保存的图片
        File file = new File(FileHelper.getTheRootDirectory() + StringConstant.PICTURTemporary_Path);
        Base64Util.deleteFile(file);
        //删除WebView缓存
        destory();
    }

    /**
     * 删除WebView缓存
     * */

    private void destory() {
        File file = CacheManager.getCacheFileBaseDir();
        if (file !=null && file.exists() && file.isDirectory()){
            for (File item : file.listFiles()) {
                item.delete();
            }
            file.delete();
        }
        deleteDatabase("webview.db");
        deleteDatabase("webviewCache.db");
        webView.destroy();
    }

}

 

 

三种方式的对比 & 使用场景

 

 

 

 

 

 

四.总结
 

 

 

附:代码链接:https://github.com/wujianning/CustomWebViewJS

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值