WebView開發(fā)詳解

目錄

大綱_02.png
一.前言

現(xiàn)在很多app并不是純原生開發(fā),而是會嵌套網(wǎng)頁,比如一些經(jīng)常會變動的頁面往往會采用嵌套h5網(wǎng)頁的形式展現(xiàn)。Android中就有一個專門用來加載html網(wǎng)頁的組件,這個組件就是Webview。

二.概述

webview是Android開發(fā)中常見的控件,內(nèi)部實(shí)現(xiàn)是采用渲染引擎來展示內(nèi)容,Android4.4以前采用Webkit渲染引擎,4.4版本及以后開始采用chromium渲染引擎來渲染。

三.使用介紹
3.1 基本使用

(1)xml布局

<WebView
            android:id="@+id/webview"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

(2)activity或fragment中加載webview控件

WebView webView =(WebView) findViewById(R.id.webview);
//或者
WebView webView =new WebView(this);

(3)添加網(wǎng)絡(luò)權(quán)限

<uses-permission android:name="android.permission.INTERNET"/>

(4)訪問網(wǎng)頁

webview.loadurl("https://feibendexiaoma.github.io");

Tip : 如果為了避免內(nèi)存泄漏可以在xml中定義一個viewgroup,如linearLayout,然后在代碼中動態(tài)創(chuàng)建webView

3.2 常用方法詳解

3.2.1 加載url,這個url可以是網(wǎng)絡(luò)的url,可以是手機(jī)里的html網(wǎng)頁url,也可以是assets文件夾下的網(wǎng)頁url

webview.loadurl("https://feibendexiaoma.github.io");
webview.loadurl("content://com.android.htmlfileprovider/sdcard/index.html");
webview.loadurl("file:///android_asset/index.html");
//加載url,并添加HTTP headers
webview.loadUrl(String url, Map<String, String> additionalHttpHeaders);

3.2.2 加載網(wǎng)頁中的一部分代碼

/**
* data:要加載的內(nèi)容 e.g. 'text/html'. If null,defaults to 'text/html'.
  mimeType:data數(shù)據(jù)類型的mime類型
  encoding:data的編碼格式  如果data是base64編碼,則encoding必須為"base64"
*/
webview.loadData(String data, String mimeType, String encoding)

 /**
 * baseUrl 加載的base url的網(wǎng)頁內(nèi)容
   historyUrl 歷史記錄條目的url 如果為空默認(rèn)跳轉(zhuǎn)到“about :blank”,如果非null,則必須是有效的url
 */
webview.loadDataWithBaseURL(String baseUrl, String data,
            String mimeType, String encoding, String historyUrl)

3.2.3 前進(jìn)后退

webview.canGoBack()//是否能后退
webview.goBack();//后退
webview.canGoForward();//是否能前進(jìn)
webview.goForward();//前進(jìn)
webview.goBackOrForward(int steps);//以當(dāng)前的index為起始點(diǎn)前進(jìn)或后退到歷史記錄中指定的steps,steps為正則為前進(jìn),steps為負(fù)則為后退

Tip: 一般常用的是后退的方法

@Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            clickBtnReback();
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }

   public void clickBtnReback() {
      if (mCustomWebView.canGoBack()) {//能回退時回退
            mCustomWebView.goBack();
        }else {
            ActivityStack.getInstance().closeActivity(mActivity);
        }
    }

3.2.4 清除緩存

//清除資源緩存,由于內(nèi)核緩存是全局的 ,因此這里針對的是整個應(yīng)用程序
public void clearCache(boolean includeDiskFiles)
//清除自動完成填充的表單數(shù)據(jù),不會清除WebView存儲到本地的數(shù)據(jù)
public void clearFormData() 
 //清除webview訪問的歷史記錄,清除其前進(jìn)后退列表里的緩存,除了當(dāng)前訪問記錄
public void clearHistory ()

3.2.5 WebView的狀態(tài)

//激活webview為活躍狀態(tài),能正常執(zhí)行網(wǎng)頁的響應(yīng) 在前一個頁面執(zhí)行完onPause()后會執(zhí)行這個方法
public void onResume () 
//當(dāng)前頁面失去焦點(diǎn)被切換為不可見狀態(tài),會執(zhí)行onPause,會嘗試暫停所有可以安全暫停的動作,如DOM的解析、plugin的執(zhí)行,注意不會暫停javascript。要想暫停javascript可以用pauseTimers
public void onPause()
//當(dāng)應(yīng)用程序被切換到后臺時,我們使用了webview,這個方法不僅針對當(dāng)前webview,而是全局應(yīng)用程序的Webview,會暫停所有webview的layout,解析和JavaScript
public void pauseTimers ()
//恢復(fù)所有Webview的所有布局,解析和JavaScript計(jì)時器    
public void resumeTimers()
 //調(diào)用destroy時,必須保證Webview已經(jīng)從view tree中被刪除了,可以在之前調(diào)用根布局的removeView方法將webview移除
public void destroy () 
3.3 相關(guān)設(shè)置介紹

3.3.1.WebSettings設(shè)置

WebSettings mSettings = this.getSettings();
mSettings.setJavaScriptEnabled(true);// 開啟javascript 如果訪問的頁面中有與js交互,則必須開啟
mSettings.setJavaScriptCanOpenWindowsAutomatically(true);////支持通過JS打開新窗口
mSettings.setDomStorageEnabled(true);// 開啟DOM
mSettings.setDatabaseEnabled(true);//開啟database
mSettings.setDefaultTextEncodingName("utf-8");// 設(shè)置字符編碼
mSettings.setAllowFileAccess(true);// 設(shè)置支持文件流

//LOAD_DEFAULT默認(rèn)緩存模式 根據(jù)cache-control決定是否從網(wǎng)絡(luò)上取數(shù)據(jù)
//LOAD_CACHE_ONLY: 不使用網(wǎng)絡(luò),只讀取本地緩存數(shù)據(jù)
//LOAD_NO_CACHE: 不使用緩存,只從網(wǎng)絡(luò)獲取數(shù)據(jù).
////LOAD_CACHE_ELSE_NETWORK,只要本地有,無論是否過期,或者no-cache,都使用緩存中的數(shù)據(jù)。
mSettings.setCacheMode(WebSettings.LOAD_DEFAULT);// 設(shè)置緩存模式

mSettings.setSupportZoom(false);// 支持縮放
mSettings.setBuiltInZoomControls(false);// 設(shè)置內(nèi)置的縮放控件

//以下兩個結(jié)合使用
mSettings.setUseWideViewPort(true);// 調(diào)整圖片適合webview大小
mSettings.setLoadWithOverviewMode(true);// 調(diào)整到屏幕大小

mSettings.setDefaultZoom(ZoomDensity.FAR);// 屏幕自適應(yīng)網(wǎng)頁,如果沒有這個,在低分辨率的        手機(jī)上顯示可能會異常
mSettings.setRenderPriority(RenderPriority.HIGH);
// 提高網(wǎng)頁加載速度,暫時阻塞圖片加載,然后網(wǎng)頁加載好了,在進(jìn)行加載圖片
mSettings.setBlockNetworkImage(true);
        mSettings.setAppCacheEnabled(true);// 開啟緩存機(jī)制

3.3.2 WebViewClient
用于處理請求事件,加載設(shè)置。
常用方法:

webview.setWebViewClient(new WebViewClient(){
    /**
    * 重寫此方法表明點(diǎn)擊網(wǎng)頁里面的鏈接還是在當(dāng)前的webview里跳轉(zhuǎn),不跳到瀏覽器那邊
    */
      @Override
     public boolean shouldOverrideUrlLoading(WebView view, String url) {
     if (url.endsWith(".apk")) {//添加下載
          WebActivity.this.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
     } else {
         view.loadUrl(url);
     } 
     /**
         * 頁面開始加載調(diào)用的方法
         *
         */
        @Override
        public void onPageStarted(WebView view, String url, Bitmap favicon) {
            super.onPageStarted(view, url, favicon);
        }
         
       /**
         * 頁面加載完成回調(diào)的方法
         *
         */
        @Override
        public void onPageFinished(WebView view, String url) {
            super.onPageFinished(view, url);

        }
        /**
         * 頁面加載過程中,加載資源回調(diào)的方法,每一個資源(比如圖片)的加載都會調(diào)用一次
         * 
         */
        @Override
        public void onLoadResource(WebView view, String url) {
            super.onLoadResource(view, url);
        }
         /**
         * 頁面加載出錯的時候調(diào)用 如404
           在這里可以自定義錯誤頁面
         */
       @Override
        public void onReceivedError(WebView view, WebResourceRequest request,WebResourceError error) {
            super.onReceivedError(view, request, error);
        }
         /**
         *加載網(wǎng)頁發(fā)生證書認(rèn)證錯誤時
         */
         @Override    
        public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {       
          handler.cancel();      //取消加載
         // handler.proceed();    //如果要上傳到google play ,回調(diào)中使用此方法是無法通過審核的
        // handler.handleMessage(null);    //可做其他處理
        }    
 }
});

3.3.3 WebChromeClient
用于處理加載條,對話框,網(wǎng)站標(biāo)題等
常見方法:

webview.setWebChromeClient(new WebChromeClient(){
    /**
    * 處理進(jìn)度條
    */
      @Override
     public void onProgressChanged(WebView view, int newProgress) {
           if (newProgress >= 100) {
                 myProgressBar.setVisibility(View.GONE);
            } else {
                 if (View.GONE == myProgressBar.getVisibility()) {
                       myProgressBar.setVisibility(View.VISIBLE);
                 }
                 myProgressBar.setProgress(newProgress);
            }
            super.onProgressChanged(view, newProgress);
     }
    /**
    * 獲取網(wǎng)站的標(biāo)題
    */
     @Override
    public void onReceivedTitle(WebView view, String title) {
       titleview.setText(title);
    }
    /**
    * 支持js彈alert窗處理
    */
     @Override
        public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
            new AlertDialog.Builder(MainActivity.this)
                    .setTitle("JsAlert")
                    .setMessage(message)
                    .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            result.confirm();
                        }
                    })
                    .setCancelable(false)
                    .show();
            return true;
        }
    /**
    *支持js 確認(rèn)框處理
    */
      @Override
        public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
            return super.onJsConfirm(view, url, message, result);
        }
});
3.4 相關(guān)問題

3.4.1 內(nèi)存泄漏
Webview為什么會發(fā)生內(nèi)存泄漏?
Webview會關(guān)聯(lián)一個activity,webview內(nèi)部執(zhí)行的操作是在新的線程中,這個線程的生命周期和activity的生命周期是不一樣的,導(dǎo)致webview一直持有activity的引用不能回收。和匿名內(nèi)部類持有外部類的引用導(dǎo)致外部類無法回收原理一樣。

首先應(yīng)該做的就是不能在xml中定義webview節(jié)點(diǎn),而是在需要的時候動態(tài)生成。即:可以在使用WebView的地方放置一個LinearLayout類似ViewGroup的節(jié)點(diǎn),然后在要使用WebView的時候,動態(tài)生成. new WebView(getApplicationContext()) ;必須傳入ApplicationContext如果傳入Activity的Context的話,對內(nèi)存的引用會一直被保持著。

LinearLayout.LayoutParams params =new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);
WebView mWebView = new WebView(getApplicationContext());
mWebView.setLayoutParams(params);
mLayout.addView(mWebView);

在onDestroy()中將webview移除

  @Override
    protected void onDestroy() {
        if(mWebView!=null){
           ((ViewGroup) mWebView.getParent()).removeView(mWebView);
            mWebView.destroy();
            mWebView = null;
        }
        super.onDestroy();
    }

3.4.2 微信支付問題

我在開發(fā)時遇到這樣的問題:當(dāng)網(wǎng)頁中有支付時,android自帶的webview不能響應(yīng)支付的相關(guān)協(xié)議,而ios的就可以,搜索網(wǎng)上的方法及開發(fā)者文檔,解釋是ios內(nèi)置瀏覽器中只要輸入相關(guān)協(xié)議都可以調(diào)起相關(guān)app,比如輸入weixin://就可以調(diào)起微信,輸入alipay://可以調(diào)起支付寶。
查看微信支付的開發(fā)者文檔(https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=15_1)知道:
H5支付是指商戶在微信客戶端外的移動端網(wǎng)頁展示商品或服務(wù),用戶在前述頁面確認(rèn)使用微信支付時,商戶發(fā)起本服務(wù)呼起微信客戶端進(jìn)行支付。
主要用于觸屏版的手機(jī)瀏覽器請求微信支付的場景。可以方便的從外部瀏覽器喚起微信支付。
微信支付的案例介紹給出了一個體驗(yàn)鏈接:http://wxpay.wxutil.com/mch/pay/h5.v2.php,在微信外瀏覽器打開,是可以調(diào)起微信支付的,看來微信是支持瀏覽器調(diào)起微信支付的。

解決方案
方案一
由于微信的H5支付協(xié)議可以在瀏覽器中調(diào)起微信,那么如果我們的app打開第三方網(wǎng)頁的是手機(jī)瀏覽器的話,就不用我們做什么,就可以直接調(diào)起微信支付了。但是我們一般是在webview中調(diào)起微信支付,而直接在瀏覽器中打開的非常少,所以這種方法作為參考

方案二
由于以前的微信支付的協(xié)議為weixin://wap/pay?,后來改版之后url中就沒有這個了,替換為https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?這個開頭的url了,其實(shí)方案二也是參考的方案一,可以通過判斷支付前一步的網(wǎng)頁地址,在支付的前一步就跳到手機(jī)瀏覽器內(nèi),通過瀏覽器調(diào)起微信支付

public boolean shouldOverrideUrlLoading(WebView view, String url) {
               try {
                    if (url.startsWith("http://dh.xxx.cn/index/order/pay")) {//在支付的前一個網(wǎng)頁就跳轉(zhuǎn)出去用瀏覽器調(diào)支付
                        Intent intent = new Intent();
                        intent.setAction(Intent.ACTION_VIEW);
                        intent.setData(Uri.parse(url));
                        startActivity(intent);
                        return true;
                    }

因涉及到公司網(wǎng)址,就以xxx代表了,親測這樣是可以通過手機(jī)瀏覽器調(diào)起微信支付的。

3.5 與Js交互

3.5.1 Android調(diào)用js
(1)首先要想調(diào)用網(wǎng)頁上js腳本,webview必須支持java script

mWebView.getSettings().setJavaScriptEnabled(true);//支持javascript
// 設(shè)置允許JS彈窗 測試使用
mWebView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);

(2)js代碼 test.html 放到src\main\assets下

<!DOCTYPE html>
<html>
    <script type="text/javascript">

      //android 要調(diào)用的方法
       function helloworld(){
         alert("helloworld")
       }
       
       function alertMsg(msg){
         alert(msg)
       }
       
       function toastMsg(msg){
          window.web2app.toastMsg(msg)
       }
       
       function add(num1,num2){
          window.web2app.add(num1+num2)
       }
    </script>
     java-js in Android
</html>

(3)java 調(diào)用js的方法,注意這里的方法名要和test.html里的名稱對應(yīng)

String method ="javascript:helloworld()";//無參數(shù)調(diào)用
      method ="javascript:alertMsg(\""+ "content" +"\")";//有參數(shù)調(diào)用 注意對于字符串作為參數(shù)值需要進(jìn)行轉(zhuǎn)義雙引號
   mWebView.loadUrl(method);

(4)調(diào)用setWebChromeClient處理彈窗,這一步只為測試時用

mWebView.setWebChromeClient(new WebChromeClient(){
            @Override
            public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
                AlertDialog.Builder ad = new AlertDialog.Builder(WebTestActivity.this);
                ad.setTitle("Java 調(diào)用了JS方法");
                ad.setMessage(message);
                ad.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        result.confirm();
                    }
                });
                ad.setCancelable(false);
                ad.create().show();
                return true;
            }
        });

java整體代碼

private void initView() {
        mWebView = (WebView)findViewById(R.id.webview);
        btnJava = (Button)findViewById(R.id.btn_java_to_js);
        WebSettings mWebSettings = mWebView.getSettings();
        mWebSettings.setJavaScriptEnabled(true);//開啟支持js
        mWebSettings.setJavaScriptCanOpenWindowsAutomatically(true);//支持彈窗
        mWebView.setWebChromeClient(new WebChromeClient(){
            @Override
            public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
                //處理彈窗后的樣式
                AlertDialog.Builder ad = new AlertDialog.Builder(WebTestActivity.this);
                ad.setTitle("Java 調(diào)用了JS方法");
                ad.setMessage(message);
                ad.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        result.confirm();
                    }
                });
                ad.setCancelable(false);
                ad.create().show();
                return true;
            }
        });
        btnJava.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //調(diào)用js方法
                        javaToJsMethod();
            }
        });
        mWebView.loadUrl("file:///android_asset/test.html");
    }

    private void javaToJsMethod() {
        String method ="javascript:helloworld()";
         method ="javascript:alertMsg(\""+ "content" +"\")";
        mWebView.loadUrl(method);
    }

效果:


java_01.png

java_02.png

Android4.4以后處理
另外在Android4.4之后引入了一個方法evaluateJavascript,因?yàn)樵摲椒ǖ膱?zhí)行不會使頁面刷新,比loadurl更高效,但是只能用于4.4以后版本。

private void testEvaluateJavascript(WebView webView) {
  webView.evaluateJavascript("helloworld()", new ValueCallback<String>() {

  @Override
  public void onReceiveValue(String value) {
      //處理js 返回的結(jié)果
  }});
}

在使用時可以通過判斷版本號,根據(jù)版本號是4.4以下的話使用loadurl,4.4以上版本可以使用evaluateJavascript

3.5.2 Js 調(diào)用Android的方法
(1)添加webview的addJavascriptInterface方法,并使用web2app作為注入接口名稱,這個名稱可以自定義,但是要和html中的名稱一致
(2)定義一個與js對象映射關(guān)系的android 類 jsToJavaInteration,里面定義js要調(diào)用的方法

public class jsToJavaInteration{
        @JavascriptInterface
        public void toastMsg(String msg){
            Toast.makeText(getApplicationContext(),msg,Toast.LENGTH_SHORT).show();
        }

        @JavascriptInterface
        public void add(String result){
            Toast.makeText(getApplicationContext(),result,Toast.LENGTH_SHORT).show();
        }

    }

(3)加載js方法 ,這里的方法要和test.html中的方法名一致

private void javaToJsMethod() {
        String method ="javascript:helloworld()";
//         method ="javascript:alertMsg(\""+ "content" +"\")";
        method = "javascript:toastMsg(\"" + "content" + "\")";//有參調(diào)用
        method = "javascript:add(2,3)";//
        mWebView.loadUrl(method);
    }

(4)同上第四步
整體java類

private void initView() {
        mWebView = (WebView)findViewById(R.id.webview);
        btnJava = (Button)findViewById(R.id.btn_java_to_js);
        WebSettings mWebSettings = mWebView.getSettings();
        mWebSettings.setJavaScriptEnabled(true);
        mWebView.addJavascriptInterface(new jsToJavaInteration(),"web2app");
        mWebSettings.setJavaScriptCanOpenWindowsAutomatically(true);
        mWebView.setWebChromeClient(new WebChromeClient(){
            @Override
            public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
                AlertDialog.Builder ad = new AlertDialog.Builder(WebTestActivity.this);
                ad.setTitle("Java 調(diào)用了JS方法");
                ad.setMessage(message);
                ad.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        result.confirm();
                    }
                });
                ad.setCancelable(false);
                ad.create().show();
                return true;
            }
        });
        btnJava.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                        javaToJsMethod();
            }
        });
        mWebView.loadUrl("file:///android_asset/test.html");
    }

    private void javaToJsMethod() {
         String method="";
 //        method ="javascript:helloworld()";
//         method ="javascript:alertMsg(\""+ "content" +"\")";
        method = "javascript:toastMsg(\"" + "content" + "\")";
        method = "javascript:add(2,3)";
        mWebView.loadUrl(method);
    }

    //js 調(diào)用java 類方法
    public class jsToJavaInteration{
        @JavascriptInterface
        public void toastMsg(String msg){
            Toast.makeText(getApplicationContext(),msg,Toast.LENGTH_SHORT).show();
        }

        @JavascriptInterface
        public void add(String result){
            Toast.makeText(getApplicationContext(),result,Toast.LENGTH_SHORT).show();
        }

    }

效果:


js_01.png

js_02.png
四.使用示例參照上面代碼
五.總結(jié)

本節(jié)寫了一些關(guān)于webview的知識,包括webview的概述,基本使用,常用方法介紹,相關(guān)設(shè)置,內(nèi)存泄漏,webview遇到的問題,java與js的交互,并用一些代碼做了說明,希望對你對我都有幫助,有不完善的后續(xù)補(bǔ)充哦。

頭條號申請者 :飛奔的小付

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容