android webview与js简单的交互方案

最近研究webview与js交互,看了几个开源库实现,感觉不尽如人意,存在主要问题是,耦合较高,使用不够简洁,后来参考Uri设定规则,格局Uri类似协议自定义了类似的js交互协议

比较简洁,自定义协议内容样式如:jsbridge://android-app/method123?a=123&b=345#jsMethod1(p1,p2)

协议说明:

scheme定义为jsbridge,用于区分别的网络请求(http),
authority定义为android-app,区分不同平台处理
path定义为 调用本地方法名称    method123
Query定义为调用本地方法参数    a=123&b=345
Fragment定义为回调js方法    #jsMethod1(p1,p2)

 

这样就可以做到精简,灵活,良好的可扩展,灵活使用的特点。再看解析实现·

 public boolean parseJsBridge(String url,WebAppInterface webNative){//WebAppInterface为被调用本地方法类实例
        if(!isProtocol(url)) return false;
        int i = -1;
        Uri uri = Uri.parse(url);//借助URI解析协议
        String methodName = uri.getPath();//调用本地方法,method123
        methodName = methodName.replace("/", "");

        String params = uri.getQuery();//调用本地方法参数。a=123&b=345
        String callback = uri.getFragment();//解析js回调方法,#jsMethod1(p1,p2)

        i = callback.lastIndexOf('(');
        String jsMethod = callback.substring(0,i);
        String jsParams = callback.substring(i+1,callback.length()-1);
//将解析的结果封装为JsResponse,便于后续使用
        mJsRes = new JsResponse(methodName,jsMethod);
        mJsRes.parseJsCallbackParams(jsParams);
        mJsRes.parseNativeParams(params);
//这里通过反射方式调用本地方法 String[] args = mJsRes.getMethodArgs(); if(null==args){//使用反射调用无参数方法 jsCallNoParamMethod(webNative,methodName); }else{//调用反射有参数方法 int count = args.length; Class[] javaParamsType = new Class[count]; for(int t=0;t<count;t++){ javaParamsType[t] = String.class; } jsCallParamMethod(webNative,methodName,javaParamsType,mJsRes.getMethodArgs()); } return true; }

 

协议解析完整实现类:

public class JsProcessor {

//    private JsProcessor(){}

    private JsResponse  mJsRes;

    public  JsResponse getJsResponse(){
        return mJsRes;
    }

    public static boolean isProtocol(String url) {
        return !TextUtils.isEmpty(url) && url.startsWith("jsbridge://");
    }

//    jsbridge://android-app/method123?a=123&b=345#jsMethod1(p1,p2)"
    public boolean parseJsBridge(String url,WebAppInterface webNative){
        if(!isProtocol(url)) return false;
        int i = -1;
        Uri uri = Uri.parse(url);
        String methodName = uri.getPath();//method1
        methodName = methodName.replace("/", "");

        String params = uri.getQuery();//a=123&b=345
        String callback = uri.getFragment();//#jsMethod1(p1,p2)

        i = callback.lastIndexOf('(');
        String jsMethod = callback.substring(0,i);
        String jsParams = callback.substring(i+1,callback.length()-1);

        mJsRes = new JsResponse(methodName,jsMethod);
        mJsRes.parseJsCallbackParams(jsParams);
        mJsRes.parseNativeParams(params);

        String[] args = mJsRes.getMethodArgs();
        if(null==args){
            jsCallNoParamMethod(webNative,methodName);
        }else{
            int count = args.length;
            Class[] javaParamsType = new Class[count];
            for(int t=0;t<count;t++){
                javaParamsType[t] = String.class;
            }
            jsCallParamMethod(webNative,methodName,javaParamsType,mJsRes.getMethodArgs());
        }
        return true;
    }

    public boolean jsCallNoParamMethod(Object obj,String mName){
        boolean success = false;
        try {
            Method method=obj.getClass().getMethod(mName);
            method.invoke(obj);
            success = true;
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }catch (Exception ex){
            ex.printStackTrace();
        }
        return success;
    }

    //getMethod("sayHello", String.class,int.class);
    public boolean jsCallParamMethod(Object obj,String mName,Class<?>[] parameterTypes,Object[] args){
        if(parameterTypes.length!=args.length) return false;
        boolean success = false;
        try {
            Method method = obj.getClass().getMethod(mName,parameterTypes);
            method.invoke(obj, args);
            success = true;
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }catch (Exception ex){
            ex.printStackTrace();
        }
        return success;
    }

}

 

JSResponse解析后的封装实体类

public class JsResponse {
    //call native
    public String methodName;
    private Map<String,String> nativeParams = new HashMap<>();
    //callback js method
    public String callJsMethod;
//    public Map<String,String> jsParams = new HashMap<>();
    private int jsParamCount;

    public JsResponse(String javaMethod,String jsMethod){
        this.methodName = javaMethod;
        this.callJsMethod = jsMethod;
    }

    public String[] getMethodArgs(){
        if(0==nativeParams.size()) return null;
        String[] params = new String[nativeParams.size()];
        int n = 0;
        for(Map.Entry<String,String> entry:nativeParams.entrySet()){
            params[n] = entry.getValue();
            n++;
        }
        return params;
    }

    public void parseNativeParams(String params){
//        int i = 0,len = params.length();
        if(!nativeParams.isEmpty())
            nativeParams.clear();

        String[] eles = params.split("&");
        for(String ele:eles){
            int eq = ele.indexOf('=');
            String value = ele.substring(eq + 1);
            String name = ele.substring(0,eq);
            nativeParams.put(name,value);
        }

    }

    public void parseJsCallbackParams(String jsparams){
        String[] eles = jsparams.split(",");
        jsParamCount = eles.length;
    }

}

 

js使用规则协议,触发调用本地方法如下,即可

function startRequest()
    {
        var iframe = document.createElement('iframe');
        iframe.setAttribute('src', 'jsbridge://android-app/showHello?a=hello-&b=i_from_js&#callbackHello(p1,p2)');
        document.body.appendChild(iframe);
        iframe.parentNode.removeChild(iframe);
        iframe = null;
    }

 本地调用js回调方法

  void executeJsCmd(String jsCmd){
        Log.d(TAG,"pending execute js command="+jsCmd);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            //async execute
            mWebView.evaluateJavascript(jsCmd, new ValueCallback<String>() {
                @Override
                public void onReceiveValue(String value) {
                    Log.d(TAG,"async execute result="+value);
                }
            });
        }else {
            // This code is BAD and will block the UI thread
            mWebView.loadUrl(jsCmd);
        }
    }

 

下面就是js与本地通信了,通过WebViewClient的shouldOverrideUrlLoading拦截内容判断处理

 private class MonitorWebClient extends WebViewClient {
        WebAppInterface webCallapp = new WebAppInterface(activity);
        .......

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            boolean handle = super.shouldOverrideUrlLoading(view, url);
            JsProcessor jsPro = null;
            if (!handle) {
                jsPro = new JsProcessor();
                handle = jsPro.parseJsBridge(url,webCallapp);
            }
            if(handle){
                handleJsCallback(jsPro,"hello","callback js method");
            }
            return handle;
        }

        @Override
        public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
            //如果需要传文件,拦截WebView的资源请求,将文件以流的形式进行通讯
            //return new WebResourceResponse("image/jpeg", "UTF-8", new FileInputStream(new File("xxxx");
            return super.shouldInterceptRequest(view, request);
        }
    }

 

void handleJsCallback(JsProcessor jsPro,String... args){
//javascript:showJavaMessage('javaResult')"
//拼接js要执行方法名字与参数规则,如上样例 String jsM = jsPro.getJsResponse().callJsMethod; if(!TextUtils.isEmpty(jsM)){ // String jsCmd = new StringBuilder().append("javascript:").append(jsM). // append('(').append("\'java\'").append(',').append("\'hello\'").append(')').toString(); StringBuilder buf = new StringBuilder().append("javascript:").append(jsM).append('('); if(args!=null && args.length>0){ // int pos = buf.length()-1; for(String p:args) { buf.append('\'').append(p).append('\'').append(','); } buf.deleteCharAt(buf.length()-1); } buf.append(')'); executeJsCmd(buf.toString()); } }

 

除了上面shouldOverrideUrlLoading方法拦截处理外,js也可以通过调用 alert,prompt 方法的方式传递数据 ,我们需要重写WebChromeClient 通过回调本地方法onJsAlert(), onJsPrompt()方法即可收到对应函数回调,进行数据处理

private class AppWebChromeClient extends WebChromeClient {
        @Override
        public void onReachedMaxAppCacheSize(long spaceNeeded, long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
            quotaUpdater.updateQuota(spaceNeeded * 2);
        }

        @Override
        public boolean onConsoleMessage(ConsoleMessage cm) {
            Log.d("MyApplication", cm.message() + " -- From line "
                    + cm.lineNumber() + " of "
                    + cm.sourceId() );
            return true;
        }

        @Override
        public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
            if("1001".equals(message)){
                //解析参数defaultValue,调用java方法并得到结果
                //textexeJs();
            }
            //给js返回处理结果
            result.confirm("result");
            return false;
        }

        @Override
        public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
            Log.d("MyApplication",url);
            AlertDialog alertDialog = new AlertDialog.Builder(activity).create();
            // Setting Dialog Title
            alertDialog.setTitle("JS come message");
            // Setting Dialog Message
            alertDialog.setMessage(message);
            alertDialog.show();
            return false;
        }
    }

 


 

 

调用本地方法类对象

public class WebAppInterface {

    Context mContext;

    /** Instantiate the interface and set the context */
    WebAppInterface(Context c) {
        mContext = c;
    }

    /** Show a toast from the web page */
    @JavascriptInterface
    public void showToast(String toast) {
        Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
    }

    @JavascriptInterface
    public void showDialog(String dialogMsg){
        AlertDialog alertDialog = new AlertDialog.Builder(mContext).create();
        // Setting Dialog Title
        alertDialog.setTitle("JS triggered Dialog");
        // Setting Dialog Message
        alertDialog.setMessage(dialogMsg);
        alertDialog.show();
    }

    public void showHello(String title,String msg){
        ToastUtils.showLong(mContext,title+msg);
    }

}

 

js示例内容

<html>
<head>
    <style>
  body{
 background-color: #FA5858;
color:#fff;
  }
input{
background-color: #F7D358;
width: 300px;
padding:10px;
color: #000;
 }
 div#content{
 padding:20px;
 background-color: #F7D358;
  color: #000;
 }
 </style>
    <script type="text/javascript">
  function showAndroidToast(toastmsg) {
    Android.showToast(toastmsg);
   }
 function showAndroidDialog(dialogmsg) {
      Android.showDialog(dialogmsg);
   }
  function moveToScreenTwo() {
    Android.moveToNextScreen();
   }
   function showPromptToJava()
   {
      var ret = prompt( "1001", "defaultMessage001" );
     //ret值即为java传回的”result”
     //根据返回内容作相应处理
    }
    function showJavaMessage(result)
    {
        alert("hello!"+result);
    }
    function startRequest()
    {
        var iframe = document.createElement('iframe');
        iframe.setAttribute('src', 'jsbridge://android-app/showHello?a=hello-&b=i_from_js&#callbackHello(p1,p2)');
        document.body.appendChild(iframe);
        iframe.parentNode.removeChild(iframe);
        iframe = null;
    }
    function callbackHello(p1,p2)
    {
        console.log("receive callback from app");
        alert("receive callback "+p1+p2);
    }
 </script>
</head>
<body>
<center>
    <h3>Binding JavaScript code to Android code</h3>
    <div id="content">
        //some content here
    </div>
    <div>
        Here are few examples:
    </div>
    <div>
        <input type="button" value="Make Toast" onClick="showAndroidToast('Toast made by Javascript')" /><br/>
        <input type="button" value="Trigger Dialog" onClick="showAndroidDialog('This dialog is triggered by Javascript ')" /><br/>
        <input type="button" value="Take me to Next Screen" onClick="showPromptToJava()" /><br/>
        <input type="button" value="jsBridgeHandle" onClick="startRequest()" /><br/>
    </div>
</center>
</body>
</html>

 本地显示

mWebView.loadUrl("file:///android_asset/sample1.html");

以上就是整个js与本地方法调用整体流程

 

备注问题:

js与本地调用会产生的问题:

1,调用js函数参数注意
string 使用'' 需要单引号拼接,直接value或"value"则无法正常匹配
直接value为整数类型


2,onJsPrompt,onJsAlert,返回true,js默认不处理,导致下次无法调用(即无反应事件)

 

posted @ 2018-05-28 15:12  HappyCode002  阅读(497)  评论(0编辑  收藏  举报