目錄
一.什麼是WebView?WebView能幹啥?
WebView 是一個用來顯示 Web 網頁的控件,它就是一個微型瀏覽器,包含一個瀏覽器該有的基本功能,例如:滾動、縮放、前進、後退下一頁、搜索、執行 Js等功能。在Android4.4之前使用的是WebKit的內核,之後使用的是Chrome內核。
二.玩一玩WebView
常用API
void loadUrl(String url):加載網絡鏈接 url
boolean canGoBack():判斷 WebView 當前是否可以返回上一頁
goBack():回退到上一頁
boolean canGoForward():判斷 WebView 當前是否可以向前一頁
goForward():回退到前一頁
onPause():類似 Activity 生命週期,頁面進入後臺不可見狀態
pauseTimers():該方法面向全局整個應用程序的webview,它會暫停所有webview的layout,parsing,JavaScript Timer。當程序進入後臺時,該方法的調用可以降低CPU功耗。
onResume():在調用 onPause()後,可以調用該方法來恢復 WebView 的運行。
resumeTimers():恢復pauseTimers時的所有操作。(注:pauseTimers和resumeTimers 方法必須一起使用,否則再使用其它場景下的 WebView 會有問題)
destroy():銷燬 WebView
clearHistory():清除當前 WebView 訪問的歷史記錄。
clearCache(boolean includeDiskFiles):清空網頁訪問留下的緩存數據。需要注意的時,由於緩存是全局的,所以只要是WebView用到的緩存都會被清空,即便其他地方也會使用到。該方法接受一個參數,從命名即可看出作用。若設爲false,則只清空內存裏的資源緩存,而不清空磁盤裏的。
reload():重新加載當前請求
setLayerType(int layerType, Paint paint):設置硬件加速、軟件加速
removeAllViews():清除子view。
clearSslPreferences():清除ssl信息。
clearMatches():清除網頁查找的高亮匹配字符。
removeJavascriptInterface(String interfaceName):刪除interfaceName 對應的注入對象
addJavascriptInterface(Object object,String interfaceName):注入 java 對象。
setVerticalScrollBarEnabled(boolean verticalScrollBarEnabled):設置垂直方向滾動條。
setHorizontalScrollBarEnabled(boolean horizontalScrollBarEnabled):設置橫向滾動條。
loadUrl(String url, Map<String, String> additionalHttpHeaders):加載制定url並攜帶http header數據。
evaluateJavascript(String script, ValueCallback resultCallback):Api 19 之後可以採用此方法之行 Js。
stopLoading():停止 WebView 當前加載。
clearView():在Android 4.3及其以上系統這個api被丟棄了, 並且這個api大多數情況下會有bug,經常不能清除掉之前的渲染數據。官方建議通過loadUrl(“about:blank”)來實現這個功能,陰雨需要重新加載一個頁面自然時間會收到影響。
freeMemory():釋放內存,不過貌似不好用。
clearFormData():清除自動完成填充的表單數據。需要注意的是,該方法僅僅清除當前表單域自動完成填充的表單數據,並不會清除WebView存儲到本地的數據。
基本使用
基本上是和WebSettings和WebClient、WebChromeClient搭配使用,還有和JS一些交互等
1.添加網絡權限
<uses-permission android:name="android.permission.INTERNET"/>
2.實例化WebView
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<com.seven.webview.SevenWebView
android:layout_weight="9"
android:id="@+id/seven_web"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:text="調用js方法"
android:layout_weight="1"
android:id="@+id/call_js"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
這裏的WebView是單獨封裝的,配合WebSetting給WebView設置一些參數,比如支持js等
package com.seven.webview;
import android.content.Context;
import android.util.AttributeSet;
import android.webkit.WebSettings;
import android.webkit.WebView;
/**
* Time:2020/3/13
* <p>
* Author:seven
* <p>
* Description:
*/
public class SevenWebView extends WebView {
public SevenWebView(Context context) {
super(context);
init(context);
}
public SevenWebView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
/**
* 初始化設置,和WebSettings搭配使用
* @param context
*/
private void init(Context context) {
WebSettings webSettings = getSettings();
if (webSettings == null) return;
// 支持 Js 使用
webSettings.setJavaScriptEnabled(true);
// 開啓DOM緩存,默認狀態下是不支持LocalStorage的
webSettings.setDomStorageEnabled(true);
// 開啓數據庫緩存
webSettings.setDatabaseEnabled(true);
// 設置 WebView 的緩存模式
webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
// 支持縮放
webSettings.setSupportZoom(true);
// 設置 UserAgent 屬性
webSettings.setUserAgentString("");
// 允許加載本地 html 文件/false
webSettings.setAllowFileAccess(true);
// 允許通過 file url 加載的 Javascript 讀取其他的本地文件,Android 4.1 之前默認是true,在 Android 4.1 及以後默認是false,也就是禁止
webSettings.setAllowFileAccessFromFileURLs(false);
// 允許通過 file url 加載的 Javascript 可以訪問其他的源,包括其他的文件和 http,https 等其他的源,
// Android 4.1 之前默認是true,在 Android 4.1 及以後默認是false,也就是禁止
// 如果此設置是允許,則 setAllowFileAccessFromFileURLs 不起做用
webSettings.setAllowUniversalAccessFromFileURLs(false);
//設置背景
setBackgroundColor(getResources().getColor(android.R.color.transparent));
}
}
3.設置WebClient
注意重寫shouldOverrideUrlLoading這個方法,需要調用webView去加載網頁,否則,會有系統瀏覽器去加載的,理解這句話的意思第一次加載還是用webView,但是網頁上如果有鏈接,就不會攔截了。
package com.seven.webview;
import android.graphics.Bitmap;
import android.net.http.SslError;
import android.util.Log;
import android.webkit.SslErrorHandler;
import android.webkit.WebResourceError;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;
/**
* Time:2020/3/13
* <p>
* Author:seven
* <p>
* Description:
*/
public class SevenWebClient extends WebViewClient {
private String TAG=getClass().getSimpleName();
/**
* 當WebView得頁面Scale值發生改變時回調
*/
@Override
public void onScaleChanged(WebView view, float oldScale, float newScale) {
super.onScaleChanged(view, oldScale, newScale);
Log.d(TAG, "onScaleChanged:oldScale="+oldScale+",newScale="+newScale);
}
/**
* 系統默認會通過手機瀏覽器打開網頁,
* 爲了能夠直接通過WebView顯示網頁,則必須設置
* 是否在 WebView 內加載頁面
*
* @param view
* @param url
* @return
*/
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Log.d(TAG, "shouldOverrideUrlLoading: "+url);
view.loadUrl(url);
return true;
}
/**
* WebView 開始加載頁面時回調,一次Frame加載對應一次回調
*
* @param view
* @param url
* @param favicon
*/
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
Log.d(TAG, "onPageStarted: "+url);
}
/**
* WebView 完成加載頁面時回調,一次Frame加載對應一次回調
*
* @param view
* @param url
*/
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
Log.d(TAG, "onPageFinished: "+url);
}
/**
* WebView 加載頁面資源時會回調,每一個資源產生的一次網絡加載,除非本地有當前 url 對應有緩存,否則就會加載。
*
* @param view WebView
* @param url url
*/
@Override
public void onLoadResource(WebView view, String url) {
super.onLoadResource(view, url);
Log.d(TAG, "onLoadResource: "+url);
}
/**
* WebView 可以攔截某一次的 request 來返回我們自己加載的數據,這個方法在後面緩存會有很大作用。
*
* @param view WebView
* @param request 當前產生 request 請求
* @return WebResourceResponse
*/
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
return super.shouldInterceptRequest(view, request);
}
/**
* WebView 訪問 url 出錯
*
* @param view
* @param request
* @param error
*/
@Override
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
super.onReceivedError(view, request, error);
Log.d(TAG, "onReceivedError: "+error);
}
/**
* WebView ssl 訪問證書出錯,handler.cancel()取消加載,handler.proceed()對然錯誤也繼續加載
*
* @param view
* @param handler
* @param error
*/
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
super.onReceivedSslError(view, handler, error);
Log.d(TAG, "onReceivedSslError: "+error);
}
}
4.設置WebChromeClient
onProgressChanged這個方法會回調網頁加載的進度,可以做一些進度條什麼的,顯示友好效果。
package com.seven.webview;
import android.content.Context;
import android.graphics.Bitmap;
import android.util.Log;
import android.webkit.ConsoleMessage;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
/**
* Time:2020/3/13
* <p>
* Author:seven
* <p>
* Description:
*/
public class SevenWebChromeClient extends WebChromeClient {
private String TAG=getClass().getSimpleName();
private Context mContenxt;
public SevenWebChromeClient(Context context) {
this.mContenxt = context;
}
/**
* Js的日誌打印
* @param consoleMessage
* @return
*/
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
Log.d(TAG, "onConsoleMessage: "+consoleMessage.message());
return super.onConsoleMessage(consoleMessage);
}
/**
* 當前 WebView 加載網頁進度
*
* @param view
* @param newProgress
*/
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
Log.d(TAG, "onProgressChanged: "+newProgress);
}
/**
* Js 中調用 alert() 函數,產生的對話框
*
* @param view
* @param url
* @param message
* @param result
* @return
*/
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
Log.d(TAG, "onJsAlert: "+message);
return super.onJsAlert(view, url, message, result);
}
/**
* 處理 Js 中的 Confirm 對話框
*
* @param view
* @param url
* @param message
* @param result
* @return
*/
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
Log.d(TAG, "onJsConfirm: "+message);
return super.onJsConfirm(view, url, message, result);
}
/**
* 處理 JS 中的 Prompt對話框
*
* @param view
* @param url
* @param message
* @param defaultValue
* @param result
* @return
*/
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
Log.d(TAG, "onJsPrompt: "+message);
return super.onJsPrompt(view, url, message, defaultValue, result);
}
/**
* 接收web頁面的icon
*
* @param view
* @param icon
*/
@Override
public void onReceivedIcon(WebView view, Bitmap icon) {
super.onReceivedIcon(view, icon);
}
/**
* 接收web頁面的 Title
*
* @param view
* @param title
*/
@Override
public void onReceivedTitle(WebView view, String title) {
super.onReceivedTitle(view, title);
Log.d(TAG, "onReceivedTitle: "+title);
}
}
5.如何和JS交互
在我自定義的WebView的時候,就已經設置過支持javascript了,所以這裏只要實例化一個java對象,並通過addJavascriptInterface這個方法將java對象注入進去。
實力惡化一個java對象,被js調用的方法上需要加上@JavascriptInterface這個註解
package com.seven.webview;
import android.content.Context;
import android.webkit.JavascriptInterface;
import android.widget.Toast;
/**
* Time:2020/3/13
* <p>
* Author:seven
* <p>
* Description:
*/
public class NativeJsInterface {
private Context mContext;
public NativeJsInterface(Context context) {
mContext = context;
}
@JavascriptInterface
public void helloAndroid() {
Toast.makeText(mContext, "Hello Android!", Toast.LENGTH_SHORT).show();
}
@JavascriptInterface
public String getAndroid() {
Toast.makeText(mContext, "getAndroid", Toast.LENGTH_SHORT).show();
return "Android data";
}
}
package com.seven.webview;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
public class MainActivity extends AppCompatActivity {
private String TAG=getClass().getSimpleName();
@BindView(R.id.seven_web)
WebView webView;
private WebViewClient webViewClient;
private WebChromeClient webChromeClient;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
loadUrl();
supportJs();
}
/**
* 加載網頁
*/
private void loadUrl() {
webViewClient = new SevenWebClient();
webChromeClient = new SevenWebChromeClient(this);
webView.setWebViewClient(webViewClient);
webView.setWebChromeClient(webChromeClient);
webView.loadUrl("file:///android_asset/html/ts.html");
}
/**
* 擴展JS對象,頁面可直接使用NativeObject對象調用Android提供的方法
*/
private void supportJs() {
NativeJsInterface nativeJsInterface = new NativeJsInterface(this);
webView.addJavascriptInterface(nativeJsInterface, "NativeObject");
}
/**
* 調用JS的方法
*/
@OnClick(R.id.call_js)
public void onViewClicked() {
String jsMethod="javascript:androidCallJs()";
if(Build.VERSION.SDK_INT< Build.VERSION_CODES.KITKAT){
webView.loadUrl(jsMethod);
}else {
webView.evaluateJavascript(jsMethod, new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
Log.d(TAG, "onReceiveValue: "+value);
}
});
}
}
}
6.寫一個簡單的網頁
</html>
<html>
<head>
<meta charset="utf-8">
<title>測試頁</title>
<script>
function callAndroid(){
NativeObject.helloAndroid();
}
function getAndroid(){
NativeObject.getAndroid();
}
function androidCallJs(){
alert('js方法被執行!!!')
}
function jumpApp(){
}
</script>
</head>
<body background="images/ts.png">
<center>
<button type="button" id="button" onclick="callAndroid()">helloAndroid</button>
</center>
<center>
<button type="button" id="button2" onclick="getAndroid()">getAndroid</button>
</center>
</center> <a href="scheme://host/path?query">重新打開自己</a></center>
</body>
</html>
通過以上6步就可以完成基本的加載網頁,支持java和js互相調用的功能了。
網頁通過Scheme方式跳轉其他應用
1.scheme是什麼?
android中的scheme是一種頁面內跳轉協議,是一種非常好的實現機制,通過定義自己的scheme協議,可以非常方便跳轉app中的各個頁面;通過scheme協議,服務器可以定製化告訴App跳轉那個頁面,可以通過通知欄消息定製化跳轉頁面,可以通過H5頁面跳轉頁面等。schema也是【隱示啓動】中的一種,在data屬性下的,其餘內容查看Android Intent的隱示啓動(啓動其餘APP界面並傳遞數據)
Scheme的格式:客戶端自定義的 URL 作爲從一個應用調用另一個的基礎,遵循 RFC 1808 (Relative Uniform Resource Locators) 標準。這跟我們常見的網頁內容 URL 格式一樣。一個普通的 URL 分爲幾個部分,scheme、host、relativePath、query。
2.網頁如果跳轉app?
1)首先,你自定義一個scheme,我在上面的頁面代碼中已經寫過了。
2)如果在webViewclient攔截了,在webView中使用shouldOverrideUrlLoading中去攔截
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Log.d(TAG, "shouldOverrideUrlLoading: "+url);
if (url.startsWith("scheme")) {
Log.d(TAG, "shouldOverrideUrlLoading: 處理自定義scheme-->" + url);
try {
// 以下固定寫法
final Intent intent = new Intent(Intent.ACTION_VIEW,
Uri.parse(url));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_SINGLE_TOP);
mContext.startActivity(intent);
} catch (Exception e) {
// 防止沒有安裝的情況
e.printStackTrace();
Toast.makeText(mContext,"您所打開的第三方App未安裝!",Toast.LENGTH_SHORT).show();
}
return true;
}
view.loadUrl(url);
return true;
}
3)如果沒有攔截,準確的來說是沒有設置過webViewClient,可以直接用頁面的方法直接打開。Android清單文件如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.seven.webview">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:usesCleartextTraffic="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="scheme"/>
</intent-filter>
</activity>
</application>
</manifest>
3.推薦做法
一般的做法都是攔截,在攔截出去啓動app。不過也可以單獨提供native方法給頁面使用,讓頁面去跳轉app。實現的方法可以有多種。
三.一些小坑
1.onPause() 盡力嘗試暫停可以暫停的任何處理,如動畫和地理位置。 不會暫停JavaScript。 要全局暫停JavaScript,可使用pauseTimers。onResume() 恢復onPause() 停掉的操作;pauseTimers() 暫停所有WebView的佈局,解析和JavaScript定時器。 這個是一個全局請求,不僅限於這個WebView。resumeTimers() 恢復所有WebView的所有佈局,解析和JavaScript計時器,將恢復調度所有計時器.另外注意 JS 端setTimeout()、setInterval() 方法使用,當不使用 pauseTimers() 和pauseTimers() ,從 Activity 返回上一個包含WebView 的Activity時,頁面裏的 setTimeout() 是不執行的,setInterval() 是可以恢復執行的。在適當的生命週期使用 pauseTimers() 和 pauseTimers() 既可以恢復setTimeout() 執行。
2.擴展的java方法是在子線程中執行的,更新UI需要切換至主線程,否則會報錯!
3.跨域的小坑,記得設置setAllowFileAccessFromFileURLs(true)這個方法
4.頁面和native保存賬號的坑
原生和頁面嵌套時,比如登錄時,如何同步賬號的問題,一般的解法都是使用cookieManage這個來保存原生登錄時的cookie,以確保在網頁操作時不至於需要重複登錄。
5.文件選擇,重寫WebChromeClient的這幾個方法
// For Android < 3.0
public void openFileChooser(ValueCallback<Uri> valueCallback) {
if(null!=valueCall){
valueCall.onValueSelect(valueCallback);
}
openImageChooserActivity();
}
// For Android >= 3.0
public void openFileChooser(ValueCallback valueCallback, String acceptType) {
if(null!=valueCall){
valueCall.onValueSelect(valueCallback);
}
openImageChooserActivity();
}
//For Android >= 4.1
public void openFileChooser(ValueCallback<Uri> valueCallback, String acceptType, String capture) {
// uploadMessage = valueCallback;
if(null!=valueCall){
valueCall.onValueSelect(valueCallback);
}
openImageChooserActivity();
}
// For Android >= 5.0
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) {
if(null!=valueCall){
valueCall.onValuesSelectAboveL(filePathCallback);
}
openImageChooserActivity();
return true;
}
private void openImageChooserActivity() {
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("image/*");
mContenxt.startActivityForResult(Intent.createChooser(i, "Image Chooser"), FILE_CHOOSER_RESULT_CODE);
}
四.發散學習
安全漏洞·:Android 4.2 以下不要在使用 JavascriptInterface方式,4.2 以上需要添加註解 @JavascriptInterface 才能調用。(這部分和JsBrige 有關,更詳細的內容後面會介紹)
當系統輔助功能服務被開啓時,在 Android 4.4 以下的系統中,由系統提供的 WebView 組件都默認導出 ”accessibility” 和 ”accessibilityTraversal” 這兩個接口,這兩個接口同樣存在遠程任意代碼執行的威脅,同樣的需要通過 removeJavascriptInterface 方法將這兩個對象刪除。
super.removeJavascriptInterface("searchBoxJavaBridge_");
super.removeJavascriptInterface("accessibility");
super.removeJavascriptInterface("accessibilityTraversal");
webView緩存問題
參考:WebView緩存原理分析和應用
如何優化加載速度
參考:WebView性能、體驗分析與優化
五.總結
本文整理了一些webView常見用法、和js交互以及一些小坑。希望能對你有所幫助!如果覺得對你有用,我也覺得很高興,畢竟技術是拿來分享的。