• WebView全面学习(二)-- Native与js双方通信


    WebView全面学习(二)-- Native与js双方通信

    Native与js通信的本质

    Native与js通信的核心在于WebView.

    两端的通信主要还是单向的。假如要完成js->Native->js那么就需要把这两种单向的通信结合起来使用。

    两种通信的处理依旧是在Native端来完成

    Native调用js的代码:(两种方式)

    1. WebView.loadUrl()

       优点:调用方式简单
       缺点:获取返回值麻烦,效率低
      
    2. WebView.evaluateJavascript()

       优点:效率高
       缺点:仅Android4.4以上版本适用
      

    总结:目前市面上基本都会要求Android4.4以上包括许多大厂,所以最好采用WebView.evaluateJavascript方式进行Native->js调用,
    如果考虑兼容性到4.4以下可以使用WebView.loadUrl方式

    loadUrl方式实现

    第一步:为了方便测试,这里把页面放在assets目录中,页面命名native2js.html,下面是页面的内容

    <!DOCTYPE html> 
    <html> 
    	<head> 
    		<meta charset="utf-8">  
    		// JS代码 
    		
    		<script> 
    			// Android需要调用的方法 
    			function callJs(){ 
    				alert("Android调用了JS的callJs方法"); 
    			} 
    		</script> 
    		
    	</head> 
    	
    </html>
    

    第二部:loadUrl调用js的方法

    public class MainActivity extends AppCompatActivity {
    
            private WebView mWebview;
    
            private WebSettings mWebSettings;
    
            private Button btn;
    
            private FrameLayout container;

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


        
                FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);

       
                 mWebview = new WebView(getApplicationContext());
        //不要在xml中用标签!会引起内存泄漏

        
                container = (FrameLayout) findViewById(R.id.webView1);
        
                container.addView(mWebview);
       

        
                btn = (Button) findViewById(R.id.btn);

        
                btn.setOnClickListener(new View.OnClickListener() {
           
                     @Override
            
                        public void onClick(View v) {
                
                            mWebview.loadUrl("javascript:callJs()");
            
                        }
   });

       
                 mWebSettings = mWebview.getSettings();
        
                 mWebSettings.setJavaScriptEnabled(true);
        
                 mWebSettings.setJavaScriptCanOpenWindowsAutomatically(true);

        
                    mWebview.loadUrl("file:///android_asset/native2js.html");


        
                //设置不用系统浏览器打开,直接显示在当前Webview
        
                 mWebview.setWebViewClient(new WebViewClient() {
            
                        @Override
            
                        public boolean shouldOverrideUrlLoading(WebView view, String url) {
                
                                view.loadUrl(url);
                
                                return true;
            }
        });

        
                //设置WebChromeClient类
       
                 mWebview.setWebChromeClient(new WebChromeClient() {


            
                        @Override
            
                        public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
					 
                        //这里不采取弹窗,让用户从原生无感调用到js
                
                        Log.d("mainActivity", url + " msg " + message);
                
                        return true;
            }
        });
    
            }

    
            
            @Override
    
            protected void onPause() {
       
                 mWebview.onPause();
        
                super.onPause();
    
            }

 
    
            //销毁Webview
    
            @Override
    protected void onDestroy() {
       
                     if (mWebview != null) {
            
                            mWebview.loadDataWithBaseURL(null, "", "text/html", "utf-8", null);
            
                            mWebview.clearHistory();
           
                             ((ViewGroup) mWebview.getParent()).removeView(mWebview);
           
                             mWebview.removeAllViews();
            
                            mWebview.destroy();
            
                            mWebview = null;
        
                      }
        
                    super.onDestroy();
    }
            
}

    

    特别注意:1.这种方式Native调用js方法一定要在 onPageFinished() 回调之后才能调用,否则不会调用到。

    2.webView生成不要使用XML中标签的形式,并且在onDestory中进行上述销毁工作。否则会引起内存泄漏:

    Activity ..... has leaked IntentReceiver org.chromium.content.browser.accessibility.LollipopBrowserAccessibilityManager$1@9a08817 that was originally registered here. Are you missing a call to unregisterReceiver()?
    

    3.loadUrl这种方式来做native->js的调用会让页面每次都会刷新,如果页面加载较慢很容易引起屏幕闪一下或者白屏,效果较差

    WebView.evaluateJavascript方式

    // 只需将第一种的loadUrl()换成下面该方法即可 mWebView.evaluateJavascript("javascript:callJs()", 
    new ValueCallback<String>() { 
    	@Override 
    	public void onReceiveValue(String value) {
    		 //此处为 js 返回的结果 
    	}});
    

    总结:综上使用WebView.evaluateJavascript方式实现Native->js的调用最佳!考虑兼容性可以在4.4以下使用loadUrl方式!

    final int version = Build.VERSION.SDK_INT; 
    if (version >= 19) { 
    
    	mWebView.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() { 
    	@Override 
    	public void onReceiveValue(String value) {
    	 //此处为 js 返回的结果 }
    	}); 
    	
    } else { 
    	mWebView.loadUrl("javascript:callJS()"); 
    }
    

    js调动Native的代码:(三种方式)

    1. WebView.addJavascriptInterface()进行对象映射

       优点:方式简单
       缺点:Android4.2以下有漏洞
      
    2. WebViewClient.shouldOverrideUrlLoading ()方法回调中拦截 url对url中采取和前端
      商定协议,然后在收到带有协议的url时进行拦截,并调用原生的代码处理

       优点:可以和IOS统一调用方式,让前端调用方便,没有漏洞问题
       缺点:需要提前和前端商定协议,从Native->js比较繁琐
      
    3. WebChromeClient 在onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截js对话框alert()、confirm()、prompt() 中的消息

       优点:可以和IOS统一调用方式,让前端调用方便,没有漏洞问题
       缺点:需要提前和前端商定协议,用户感知较明显,会弹出对话框(当然可以在Native层选择不弹框,但这样会让代码复杂性上升)
      

    总结: 目前市面上基本都会要求Android4.4以上包括许多大厂,所以第一种方式也可以选择。但是在4.2以下版本中会存在安全漏洞,不建议使用

    目前主流采用的还是第二种和第三种方式,我们Hybrid方案考虑到Android和IOS两端一致性采用的是WebViewClient.shouldOverrideUrlLoading回调中拦截url的方式

    WebView.addJavascriptInterface()方式

    第一步:定义一个记录Native与js对象映射关系的类:Native2Js

    public class Native2Js extends Object { 
    	// 定义JS需要调用的方法 
    	// 被JS调用的方法必须加入@JavascriptInterface注解 
    	@JavascriptInterface 
    	public void callNativeMethod1(String msg) { 
    		System.out.println("JS调用了Native的callNativeMethod1方法"); 
    	} 
    }
    

    第二步:js调用Native方法

    //把js2native.html放到Android工程assets目录下,内容如下:
    
    <!DOCTYPE html> 
    	<html> 
    		<head> 
    			<meta charset="utf-8"> 
    			<script> 
    				function callNative(){ 
    					// 由于对象映射,所以调用test对象等于调用Native映射的对象 						jsCallNative.callNativeMethod1("js->native调用native的方法callNativeMethod1"); 
    				}
    			</script> 
    		</head> 
    		<body> 
    		
    			//点击按钮则调用native端方法
    			<button type="button" id="button1" onclick="callNative()"></button> 		
    		</body> 
    	</html>
    

    第三步:Native端准备接受js的调用

    @Override protected void onCreate(Bundle savedInstanceState) { 
    
    	super.onCreate(savedInstanceState); 		
                setContentView(R.layout.activity_main); 
    	mWebView = (WebView) findViewById(R.id.webview); 
    	WebSettings webSettings = mWebView.getSettings(); 
    	// 允许Js交互 
    	webSettings.setJavaScriptEnabled(true); 
    	// 通过addJavascriptInterface()将Java对象映射到JS对象 
    	
    	//参数分别为native和js中的映射关系对象,通过这个方法的调用做到js和native方法互通		
    	mWebView.addJavascriptInterface(new Native2Js(), "jsCallNative");
    	
    	//AndroidtoJS类对象映射到js的test对象 
    	// 加载JS代码 
    	// 格式规定为:file:///android_asset/文件名.html 		
                mWebView.loadUrl("file:///android_asset/js2native.html"); 
        }
    

    注意: 这中方式在4.2以下会有安全漏洞,所以不建议使用这种方式

    WebViewClient.shouldOverrideUrlLoading()回调拦截

    这种方式的主要流程如下:

    	1.	WebViewClient 的回调方法shouldOverrideUrlLoading ()拦截 url
    	2.	检测url,看是否是和前端确定好的协议
    	3.	如果是就调用相应原生方法
    

    第一步:js端准备

    <!DOCTYPE html>
    <html>
      
      <head>
        <meta charset="utf-8">
        <script>function callAndroid() {
            /*约定的url协议为:company://hybrid?arg1=111&arg2=222*/
            document.location = "company://hybrid?arg1=111&arg2=222";
          }</script>
      </head>	  
      <body>
        <button type="button" id="btn" onclick="callNative()">点击调用Native代码</button>
       </body>
    
    </html>
    

    第二步:native端准备接收js调用

    mWebview.setWebViewClient(new WebViewClient() {
    
                @Override
    
                public boolean shouldOverrideUrlLoading(WebView view, String url) {
        
                    Uri uri = Uri.parse(url);
        
                    if (!uri.getScheme().equals("company")) {
           
                         return true;
        
                    }

        
                    switch (uri.getScheme()) {
            
                        case "company":
                
                                if (!uri.getAuthority().equals("hybrid")) {
                   
                                        super.shouldOverrideUrlLoading(view, url);
                  
                                         break;
               
                                 }

                
                                //走到这里则认为如何我们的私有协议
               
                                 Log.d("Hybrid","js调用了Native");
                
                                // 可以在协议上带有参数拿到
               
                                 Set<String> params = uri.getQueryParameterNames();
                
                                    //...后面可以针对相应的params做处理
                
                                break;
            
                         default:
                
                                view.loadUrl(url);
                
                                break;
        
                            }
       
                     return true;
    }
});

    

    注意: 这种方式的一个问题是js调用了native,但是没有返回到js.为了给js端回复,还需要结合native->js的调用

    WebChromeClient中onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截js中对话框alert() confirm() prompt()的消息

    注意:这种方式其实利用的是js调用alert confirm prompt方法时, WebChromeClient中相应的方法会有回调,其中alert没有返回值,confirm返回布尔值,true为确认,false为取消,prompt可以返回任何值, 所以一般都会利用prompt来做为通信手段

    第一步:js端调用alert confirm prompt方法

    <!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">

    <script>
        
    function click(){
    // 调用prompt()
    var result=prompt("company://hybrid?arg1=111&arg2=222");
    alert("demo " + result);
}

      </script>
</head>

<body>
<button type="button" id="button1" onclick="click()">点击调用Android代码</button>
</body>
</html>
    

    第二步:Native端准备

    mWebView.setWebChromeClient(new WebChromeClient() {
                                      
                                        // 参数message:promt()的内容(不是url),从js传来
                                        // 参数result:输入框的返回值,回到js中去
                                        @Override
                                        public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
                                           
        Uri uri = Uri.parse(message);
                                            
       	if ( uri.getScheme().equals("conpany")) {
    
                                                                                                				if (uri.getAuthority().equals("hybrid")) {
    
              //拿到来自js的参数                                                                                                                                              					Set<String> collection = uri.getQueryParameterNames();
    
              //参数result:代表消息框的返回值(输入值),把结果ret返回给js端
              String ret = "js调用了native,这是返回结果";
              result.confirm(ret);
            }
            return true;
        }
                                           
        return super.onJsPrompt(view, url, message, defaultValue, result);
    

    }});

    总结

    native->js

    native->js最好的办法还是envaluateJavaScript的方式,如果要考虑4.2以下的兼容性,可以结合上loadUrl

    js->native

    多数情况下hybrid都是js->native的。因为js要用native的一些本地能力
    针对目前的双端调用方式来看,js->native的调用采用协议的方式最合适,不论是shouldOverrideUrlLoading回调拦截(这种方式要结合native->js的方式返回给js端数据),还是onJsPrompt回调拦截都比较灵活

  • 相关阅读:
    BZOJ 4445 [Scoi2015]小凸想跑步:半平面交
    BZOJ 3931 [CQOI2015]网络吞吐量:最大流【拆点】
    BZOJ 3698 XWW的难题:有上下界的最大流
    AtCoder ARC097C Sorted and Sorted:dp
    BZOJ 1835 [ZJOI2010]base 基站选址:线段树优化dp
    BZOJ 3329 Xorequ:数位dp + 矩阵快速幂
    BZOJ 1492 [NOI2007]货币兑换Cash:斜率优化dp + cdq分治
    BZOJ 4726 [POI2017]Sabota?:树形dp
    BZOJ 1185 [HNOI2007]最小矩形覆盖:凸包 + 旋转卡壳
    存一些东西
  • 原文地址:https://www.cnblogs.com/zharma/p/8504873.html
Copyright © 2020-2023  润新知