1. 程式人生 > >Android WebView填坑記錄

Android WebView填坑記錄

前言

       在應用程式開發過程中,經常會採用webview來展現某些介面,這樣就可以不受釋出版本控制,實時更新,遇到問題可以快速修復。

       但是在Android開發中,由於android版本分化嚴重,每一個版本針對webview都有部分更改,因此在開發過程中會遇到各種各樣的坑,因此在此總結一下在開發過程中遇到的一些坑!

樣例

       這裡不是講解怎麼進行webview開發,而是隻羅列其中遇到的一些坑!為了展示這些問題,我們還是寫一個樣例來進行展開。

       樣例程式碼:

/**
 * WebView demo
 */
public class WebViewActivity
extends AppCompatActivity {
public static void start(Context context) { Intent intent = new Intent(); intent.setClass(context, WebViewActivity.class); context.startActivity(intent); } private static final String TAG = "WebViewActivity"; public static final
String URL = "http://www.163.com"; private static final int REQUEST_CODE_GET_LOCAL_FILE = 0; private static final int REQUEST_CODE_GET_LOCAL_FILE_NEW = 1; private WebView webView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_web_view); findViews(); initWebView(); } /** * 查詢介面view */
private void findViews() { webView = (WebView) findViewById(R.id.web_view); } /** * 初始化webview引數 */ private void initWebView() { initWebSetting(); setAcceptThirdPartyCookies(); initWebClient(); loadUrl(); } /** * 載入地址 */ private void loadUrl() { webView.loadUrl(URL); } /** * 設定WebSetting */ private void initWebSetting() { WebSettings webSettings = webView.getSettings(); webSettings.setJavaScriptEnabled(true); webSettings.setDomStorageEnabled(true); webSettings.setLoadWithOverviewMode(true); webSettings.setUseWideViewPort(true); webSettings.setRenderPriority(WebSettings.RenderPriority.HIGH); webSettings.setUserAgentString(webSettings.getUserAgentString() + " VersionCode/" + InstallUtil .getVersionCode(this)); webSettings.setAppCacheMaxSize(1024 * 1024 * 8); webSettings.setAllowFileAccess(true); webSettings.setAppCacheEnabled(true); webSettings.setCacheMode(WebSettings.LOAD_DEFAULT); webSettings.setSupportZoom(true); webSettings.setGeolocationEnabled(true); webSettings.setDatabaseEnabled(true); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); } } /** * 設定跨域cookie讀取 */ public final void setAcceptThirdPartyCookies() { //target 23 default false, so manual set true if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { CookieManager.getInstance().setAcceptThirdPartyCookies(webView, true); } } /** * 設定client */ private void initWebClient() { webView.setWebChromeClient(new WebChromeClient() { @Override public void onProgressChanged(WebView view, int newProgress) { super.onProgressChanged(view, newProgress); Log.e(TAG, "onProgressChanged newProgress=" + newProgress); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) { uploadFileNew = filePathCallback; Intent intent = fileChooserParams.createIntent(); try { startActivityForResult(intent, REQUEST_CODE_GET_LOCAL_FILE_NEW); } catch (Exception e) { ToastUtil.showLong(getString(R.string.choose_fail)); return false; } return true; } // // FILE UPLOAD <3.0 // public void openFileChooser(ValueCallback<Uri> uploadFile) { chooseFile(uploadFile, null, null); } public void openFileChooser(ValueCallback<Uri> uploadFile, String acceptType) { chooseFile(uploadFile, acceptType, null); } /** * 4.x * @param uploadFile * @param acceptType * @param capture */ public void openFileChooser(ValueCallback<Uri> uploadFile, String acceptType, String capture) { chooseFile(uploadFile, acceptType, capture); } }); webView.setWebViewClient(new WebViewClient() { @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { super.onPageStarted(view, url, favicon); Log.e(TAG, "onPageStarted"); } @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); Log.e(TAG, "onPageFinished"); } @Override public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) { super.onReceivedError(view, request, error); Log.e(TAG, "onReceivedError"); } @Override public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { super.onReceivedSslError(view, handler, error); Log.e(TAG, "onReceivedSslError"); } @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { Log.e(TAG, "shouldOverrideUrlLoading url=" + url); return super.shouldOverrideUrlLoading(view, url); } }); } private ValueCallback<Uri[]> uploadFileNew; private ValueCallback<Uri> uploadFile; /** * 檔案選擇 * * @param uploadFile * @param acceptType * @param capture */ private void chooseFile(ValueCallback<Uri> uploadFile, String acceptType, String capture) { Intent intent = new Intent(Intent.ACTION_GET_CONTENT); if (TextUtils.isEmpty(acceptType)) { acceptType = "*/*"; } intent.setType(acceptType); this.uploadFile = uploadFile; try { startActivityForResult(Intent.createChooser(intent, capture), REQUEST_CODE_GET_LOCAL_FILE); } catch (Throwable tr) { tr.printStackTrace(); } } @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case REQUEST_CODE_GET_LOCAL_FILE: if (uploadFile != null) { uploadFile.onReceiveValue((resultCode == Activity.RESULT_OK && data != null) ? data.getData() : null); uploadFile = null; } break; case REQUEST_CODE_GET_LOCAL_FILE_NEW: if (uploadFileNew != null) { uploadFileNew.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, data)); uploadFileNew = null; } break; } } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213

       佈局程式碼

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.sunny.demo.webview.WebViewActivity">

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

</RelativeLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

填坑

1,回撥不成對

       在WebView載入地址時,在載入過程中回撥次數不一定相同,比如這裡我們我們載入http://www.163.com

E/WebViewActivity: onProgressChanged newProgress=10
E/WebViewActivity: onPageStarted
E/WebViewActivity: shouldOverrideUrlLoading url=http://3g.163.com/
E/WebViewActivity: onPageStarted
E/WebViewActivity: onPageStarted
E/WebViewActivity: shouldOverrideUrlLoading url=http://3g.163.com/touch/
E/WebViewActivity: onProgressChanged newProgress=50
E/WebViewActivity: onPageFinished
E/WebViewActivity: onProgressChanged newProgress=52
E/WebViewActivity: onProgressChanged newProgress=90
E/WebViewActivity: onProgressChanged newProgress=95
E/WebViewActivity: onProgressChanged newProgress=100
E/WebViewActivity: onPageFinished
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

       從上面的日誌中可以看到onPageStarted與onPageFinished次數不一致,因此如果你在start中進行進度條載入處理,finish中結束,會導致進度條一直不消失。因此可以在onProgressChanged進行處理。當newProgress為100時表示頁面載入完成。

2,檔案選擇回撥函式更改

       如果你是web頁面開發人員,如果要選取本地檔案,可以採用input標籤,如下一個簡單的html頁面:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <input type="file" accept="image/*;" capture="camera" >
    <a href="test1.html">url替換為test1頁面</a>
    <script>
    var a = function() {
        //alert(1);
    };
    document.addEventListener('JSBridgeReady',a);
    </script>
</body>
</html>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

附錄 
如果你不是專業的web開發人員,但是又想進行web頁面除錯,可以自己搭建一個web服務,如果你採用的是MAC可以採用如下方案進行web服務搭建

1.用node裝一個http-server

2.brew install node

3.npm install -g http-server 安裝

4.在一個目錄下 http-server啟動就是一個簡單http服務裡 port 8080

        上面的程式碼載入後效果如下: 
這裡寫圖片描述

       很簡單吧!是不是想說so easy!!!那我們就點選一下選擇檔案吧!

咦沒效果 
換手機,好,有效果 
再換,咦,又沒有效果

       是不是感覺很抓狂?這是由於Android的不同版本,WebView的回撥函式都不一致。怎麼解決這個問題?可以複寫不同的回撥函式,這裡主要有以下四個回撥函式,分別處理不同的版本:

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
                                 FileChooserParams fileChooserParams) {
    Intent intent = fileChooserParams.createIntent();
    try {
        startActivityForResult(intent, REQUEST_CODE_GET_LOCAL_FILE_NEW);
    } catch (Exception e) {
        ToastUtil.showLong(getString(R.string.choose_fail));
        return false;
    }
    return true;
}

//
// FILE UPLOAD <3.0
//
public void openFileChooser(ValueCallback<Uri> uploadFile) {
    chooseFile(uploadFile, null, null);
}

public void openFileChooser(ValueCallback<Uri> uploadFile, String acceptType) {
    chooseFile(uploadFile, acceptType, null);
}

/**
 * 4.x
 * @param uploadFile
 * @param acceptType
 * @param capture
 */
public void openFileChooser(ValueCallback<Uri> uploadFile, String acceptType, String capture) {
    chooseFile(uploadFile, acceptType, capture);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

       複寫了上述四個函式,同時記得onActivityResult進行返回結果處理。就算是這樣,我也不敢保證所有的手機都能正確的進行處理。 
       剛才在html中是否看到了一個一段document.addEventListener(‘JSBridgeReady’,a)這樣的程式碼。這裡是想說給出另外一種解決方案,可以採用JSBridge方式進行web頁面與native頁面互動,這樣就可以遮蔽版本中間的差異。

3,跨域cookie讀取

       什麼是跨域,簡單的說就是不同的域名,我們都知道在pc上我們用瀏覽器訪問網址,不同的網址都會在本地儲存一些cookie資訊,這樣就可以實現比如自動登入等功能,在pc上不同域名是不能相互讀取其他域下的cookie資訊的(非web專業開發人員,如果理解有誤,歡迎指出)。

       但是在Android上在api 23之前,是可以跨域讀取cookie的,比如A域寫入一個userId的cookie,B域可以讀取該值。但是在23時,系統將該值設定成了false,不再讓跨域讀取了。如果你的應用有跨域讀取需求,怎麼辦?可以採用如下方式進行開啟:

    /**
     * 設定跨域cookie讀取
     */
    public final void setAcceptThirdPartyCookies() {
        //target 23 default false, so manual set true
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            CookieManager.getInstance().setAcceptThirdPartyCookies(webView, true);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

       這裡我們來看看setAcceptThirdPartyCookies的註釋:

Sets whether the {@link WebView} should allow third party cookies to be set. 
Allowing third party cookies is a per WebView policy and can be set 
differently on different WebView instances. 


Apps that target {@link android.os.Build.VERSION_CODES#KITKAT} or below 
default to allowing third party cookies. Apps targeting 
{@link android.os.Build.VERSION_CODES#LOLLIPOP} or later default to disallowing 
third party cookies.

4,http/https混合載入

       在現階段,很多網站都改成了https進行訪問,https可以提升訪問網站的安全性,防止資訊被竊取,如果所有的網頁都是https且網頁內的連結也是都是https,那就沒有混合載入的問題了。但是很多資源現階段還沒有改變成https訪問,往往頁面都嵌入了http的連結。這種混合網頁如果不進行處理,直接載入是會出現錯誤的。怎麼解決這個問題?

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

       這也是一個分版本的函式,在api 23之前,預設是可以混合載入的,但是在23時,預設值改成了MIXED_CONTENT_NEVER_ALLOW,因此如果你有混合載入的需求,設定setMixedContentMode為MIXED_CONTENT_ALWAYS_ALLOW。

5,花屏

       WebView如果開啟了硬體加速,多次開啟,會導致介面花屏,或者介面繪製有殘留。這個在5.0剛出來的時候發生過多次,但是當時忘記截圖了。當時也沒有打算要把這些給記錄下來。這裡我已經復現不了了。如果你遇到了。關閉硬體加速就好了。

6,資源釋放

       這裡我們先擷取幾張圖來看看載入前與載入後webview記憶體的佔用: 
       載入之前: 
這裡寫圖片描述
       載入之後: 
這裡寫圖片描述
       退出頁面: 
這裡寫圖片描述

       可以看到記憶體蹭蹭蹭的就上去了。就算退出去了也沒有減少多少,因此這裡必須手動進行資源釋放,可以呼叫如下程式碼:

@Override
protected void onDestroy() {
    super.onDestroy();
    if (webView != null) {
        root.removeView(webView);
        webView.removeAllViews();
        webView.destroy();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

       也可以採用在介面中動態建立webview,之後add到頁面中,甚至更有人會為該頁面單獨設定一個程序,之後退出後會呼叫exit函式進行資源釋放。這裡可以根據需求來進行設定。

總結

       WebView有這樣或者那樣的坑。這裡只是大致羅列自己遇到的一些,在知乎上也看到一個專門講WebView坑的檢視連結。大家可以去看看,有什麼新的發現也可以進行探討。