WebChromeClient

設計思想理解

在WebView的設計中,不是什麼事都要WebView類乾的,有相當多的雜事是分給其他類做的,這樣WebView專心幹好自己的解析、渲染工作就行了。比如我們最熟知的,所有針對WebView的設置都封裝到了WebSettings裏。我們知道,在使用WebView加載資源過程中,可能會有大量各種類型事件的回調,爲了方便開發組處理這些回調,針對不同的事件回調,google將這些回調進行了分類集合,於是就產生了WebViewClient、WebChromeClient這兩個大類。

很多同學一看到WebChromeClient類裏有Chrome,立馬就會想到google的Chrome瀏覽器,其實這裏並不是"特指"Chrome瀏覽器的意思,而是"泛指"瀏覽器的意思。

爲什麼叫WebChromeClient呢?這是因爲WebChromeClient中集合了影響瀏覽器的事件到來時的回調方法,所以這裏需要突出瀏覽器的概念,而Chrome則是google自家的瀏覽器名稱,也是目前市面上最受歡迎的瀏覽器,所以就採用了WebChromeClient來做爲名稱吧,純屬臆想……

簡單來說就是
  • WebViewClient:在影響【View】的事件到來時,會通過WebViewClient中的方法回調通知用戶
  • WebChromeClient:當影響【瀏覽器】的事件到來時,就會通過WebChromeClient中的方法回調通知用法。

實際使用的話,如果你的WebView只是用來處理一些html的頁面內容,只用WebViewClient就行了,如果需要更豐富的處理效果,比如JS、進度條等,就要用到WebChromeClient。

API

1、獲取網頁的加載進度

  • void  onProgressChanged(WebView view, int newProgress)  Tell the host application the current progress of loading a page. 
    • newProgress: Current page loading progress, represented by an integer between 0 and 100.
    • 大家一定要注意,底層實現時,是利用handler來定時輪循當前進度的,每隔一定時間查詢一次,所以每次拿到的進度數據是不一樣的。
    • 也就是說如果頁面較簡單,可能會直接返回100,而跳過中間的各個數據。也就是說,除了100,其它任何一個數值不是一定返回的。
    • 所以大家如果要用到進度,除了數值100可以用等號來判斷,其它一定要用大於號或小於號,如果用了等號,可能永遠也不會執行到。
  • void  onReachedMaxAppCacheSize(long requiredStorage, long quota, WebStorage.QuotaUpdater quotaUpdater)  This method was deprecated in API level 19. This method is no longer called; WebView now uses the HTML5 / JavaScript Quota Management API. 

2、獲取網頁中的基本信息

  • void  onReceivedIcon(WebView view, Bitmap icon)  Notify the host application of a new favicon圖標 for the current page. 
    • icon: A Bitmap containing the favicon for the current page.
  • void  onReceivedTitle(WebView view, String title)  Notify the host application of a change in the document title 文檔標題中的更改
    • title: A String containing the new title of the document.
    • 獲取標題的時間主要取決於網頁前段設置標題的位置,一般設置在頁面加載前面,可以較早調用到這個函數
  • void  onReceivedTouchIconUrl(WebView view, String url, boolean precomposed)  Notify the host application of the url for an apple-touch-icon(蘋果圖標). 
    • 蘋果爲iOS設備配備了apple-touch-icon私有屬性,添加該屬性,在iPhone,iPad,iTouch的safari瀏覽器上可以使用添加到主屏按鈕將網站添加到主屏幕上,方便用戶以後訪問。apple-touch-icon 標籤支持sizes屬性,可以用來放置對應不同的設備。
    • url: The icon url.
    • precomposed: True if the url is for a precomposed touch icon. 

3、攔截網頁中JS控制檯消息

當html中調用console相關輸出的時候,就會通過onConsoleMessage進行通知
和alert,prompt,confirm不同,我們不需要強制設置WebChromeClient(但是仍需要setJavaScriptEnabled爲true),當點擊log按鈕時,也會調用console相應的函數把日誌打印出來。
在我們logcat中也可以看到如下日誌: 
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
    Log.i("bqt", "【onConsoleMessage】" + "\nmessage=" + consoleMessage.message()
            + "\nlineNumber=" + consoleMessage.lineNumber()
            + "\nmessageLevel=" + consoleMessage.messageLevel() + "\nsourceId=" + consoleMessage.sourceId());
    return super.onConsoleMessage(consoleMessage);
}
I/chromium: [INFO:CONSOLE(18)] "log日誌", source: file:///android_asset/h5/JS%E5%BC%B9%E6%A1%86%E6%BC%94%E7%A4%BA.html (18)
I/chromium: [INFO:CONSOLE(19)] "warn日誌", source: file:///android_asset/h5/JS%E5%BC%B9%E6%A1%86%E6%BC%94%E7%A4%BA.html (19)
I/chromium: [INFO:CONSOLE(20)] "error日誌", source: file:///android_asset/h5/JS%E5%BC%B9%E6%A1%86%E6%BC%94%E7%A4%BA.html (20)
可以獲取到的信息有:
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
    Log.i("bqt", "【onConsoleMessage】" + "\nmessage=" + consoleMessage.message()
            + "\nlineNumber=" + consoleMessage.lineNumber()
            + "\nmessageLevel=" + consoleMessage.messageLevel() + "\nsourceId=" + consoleMessage.sourceId());
    return super.onConsoleMessage(consoleMessage);
}
  • boolean onConsoleMessage(ConsoleMessage consoleMessage) Report a JavaScript console message to the host application. 向主機應用程序報告JavaScript控制檯消息。
    • The ChromeClient should override this to process the log message as they see fit 處理他們認爲合適的日誌消息.
    • consoleMessage ConsoleMessage: Object containing details of the console message.
    • 返回值:如果返回true時,就表示攔截了console輸出,系統就不再通過console輸出出來了;如果返回false(默認值),則表示沒有攔截console輸出,調用系統默認處理。
  • void onConsoleMessage(String message, int lineNumber, String sourceID) This method was deprecated in API level 8. Use onConsoleMessage(ConsoleMessage) instead.

4、攔截網頁中JS彈框

1、如何使HTML中的彈框生效
雖然我們啓用了JavaScript(前提條件),但是點擊網頁中的confrim()、alert()、prompt()卻沒有效果! 這是因爲我們還需要設置WebChromClient!
在程序中,我們只需要給WebView加上.setWebChromeClient(new WebChromeClient())就可以實現confrim()、alert()、prompt()的彈出效果了。

2、JsResult的兩個函數的作用
JsResult有兩個函數:JsResult.confirm()和JsResult.cancel()。JsResult.confirm()表示點擊了彈出框的確定按鈕,JsResult.cancel()則表示點擊了彈出框的取消按鈕。
如果我們是return false,此時不需要我們手動調用JsResult的.confirm()或.cancel()方法,因爲當我們點擊彈框中的確認或取消按鈕時JS會調用相應的方法。
如果我們是return true,此時我們必須手動調用JsResult的.confirm()或.cancel()方法,因爲如果沒有調用JsResult的confirm()或cancel()來告訴WebView你的處理結果,則WebView就會認爲這個彈出框還一直彈在那裏(雖然此時根本沒有彈框彈出),所以之後你再點擊alert按鈕時,將會無效。這一點一定要注意。

3、方法返回值的意義
如果return true,WebView就會認爲我們已經攔截了alert()函數,並且需要以自己的邏輯處理彈框,所以就不彈出alert對話框了。
否則,WebView就會認爲我們沒有攔截alert()函數,會繼續彈出alert對話框(當然我們的吐司仍會彈出來)。

4、一共三種類型的彈框
  • onJsAlert:當網頁調用alert()來彈出alert彈出框前回調,用以攔截alert()函數
  • onJsConfirm:當網頁調用confirm()來彈出confirm彈出框前回調,用以攔截confirm()函數
  • onJsPrompt:當網頁調用prompt()來彈出prompt彈出框前回調,用以攔截prompt()函數

5、相關API
  • boolean onJsAlert(WebView view, String url, String message, JsResult result) Tell the client to display a javascript alert dialog. 告訴客戶端顯示一個javascript警報對話框。
    • If the client returns true, WebView will assume假定 that the client will handle the dialog. If the client returns false, it will continue execution 繼續執行.
    • The default behavior is to return false.(下面幾個方法都是如此)
    • 參數 url: The url of the page requesting the dialog.(下面幾個方法都是如此)
    • 參數 message: Message to be displayed in the window.(下面幾個方法都是如此)
    • 參數 result: A JsResult to confirm that the user hit enter. 一個用以確認用戶點擊進入的JsResult對象
    • Returns:boolean Whether the client will handle the alert dialog. 如果return true,不彈出對話框了;否則,會繼續彈出對話框(下面幾個方法都是如此)
  • boolean onJsConfirm(WebView view, String url, String message, JsResult result) Tell the client to display a confirm dialog to the user. 
    • If the client returns true, WebView will assume假定 that the client will handle the confirm dialog and call the appropriate相應的 JsResult method.If the client returns false, a default value of false will be returned to javascript.
    • Returns:boolean Whether the client will handle the confirm dialog. 如果return true,不彈出對話框了;否則,會繼續彈出對話框
  • boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) Tell the client to display a prompt dialog to the user. 
    • If the client returns true, WebView will assume假定 that the client will handle the prompt dialog and call the appropriate相應的 JsPromptResult methodIf the client returns false, a default value of false will be returned to to javascript.
    • 參數 defaultValue: The default value displayed in the prompt dialog.
    • 參數 result: A JsPromptResult used to send the user's reponse to javascript.
  • boolean onJsBeforeUnload(WebView view, String url, String message, JsResult result) Tell the client to display a dialog to confirm navigation導航 away from離開 the current page.
    • This is the result of the onbeforeunload javascript event. If the client returns true, WebView will assume假定 that the client will handle the confirm dialog and call the appropriate相應的 JsResult method. If the client returns false, a default value of true will be returned to javascript to accept navigation away from the current page.
    • Setting the JsResult to true will navigate away from the current page, false will cancel the navigation.
  • boolean onJsTimeout() This method was deprecated in API level 17. This method is no longer supported and will not be invoked. 

5、打開和關閉Window(多窗口)

1、onCreateWindow的回調邏輯
我們都知道,超級鏈接標籤a裏有一個target屬性,其意義是決定"是否在新窗口/標籤頁中打開鏈接",如果不寫target=”_blank”那麼就是在相同的標籤頁打開,如果寫了,就是在新的空白標籤頁中打開。比如:
<a href="https://www.taobao.com/" title="淘寶" target="_blank">新窗口打開鏈接</a>
而我們WebView默認是不支持target這個屬性的,默認情況下,點擊上面的鏈接會在當前WebView中打開此鏈接。

那麼,如何如何在一個新窗口打開一個具有target="_blank"屬性的超鏈接呢?
要實現這個功能,我們必須設置如下屬性:
webSettings.setSupportMultipleWindows(true);//支持多窗口。如果設置爲true,主程序要實現onCreateWindow
完整邏輯如下
  • 沒有設置setSupportMultipleWindows屬性爲true
    • 沒有setWebChromeClient:點擊此鏈接會在當前WebView中打開此鏈接
    • setWebChromeClient:點擊此鏈接會在當前WebView中打開此鏈接,不會回調onCreateWindow方法
  • 有設置setSupportMultipleWindows屬性爲true
    • 沒有setWebChromeClient點擊此鏈接不會在當前WebView中打開此鏈接
    • setWebChromeClient
      • 沒有重寫onCreateWindow方法:點擊此鏈接不會在當前WebView中打開此鏈接會回調onCreateWindow方法
      • 有重寫onCreateWindow方法:點擊此鏈接不會在當前WebView中打開此鏈接,會回調onCreateWindow方法,會在你新創建的WebView中打開此鏈接

2、案例:在新窗口打開一個具有target="_blank"屬性的超鏈接
這裏有一個案例 http://blog.csdn.net/asdfghgw/article/details/76066769 ,但是試驗後發現根本不行!網上也找了很多資料,都是不行!
可供參考的代碼段:
@Override
public boolean onCreateWindow(WebView webView, boolean isDialog, boolean isUserGesture, Message resultMsg) {
    Log.i("bqt", "【onCreateWindow】 " + isDialog + "  " + isUserGesture + "\n詳細信息" + resultMsg.toString());
    if (activity != null) {
        WebView childView = new WebView(activity);//Parent WebView cannot host it's own popup window.
        childView.setBackgroundColor(Color.GREEN);
        childView.setWebViewClient(new WebViewClient() {
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                Log.i("bqt", "【shouldOverrideUrlLoading-子】");
                activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
                return true;
            }
        });
        WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj;
        transport.setWebView(childView);//setWebView和getWebView兩個方法
        resultMsg.sendToTarget();
        return true;
    } else return super.onCreateWindow(webView, isDialog, isUserGesture, resultMsg);//默認是returns false
}

3、相關API
  • boolean  onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg)  Request the host application to create a new window. 請求主機應用程序創建一個新窗口。
    • If the host application chooses to honor this request遵守、尊敬此請求, it should return true from this method, create a new WebView to host託管 the window, insert it into the View system and send the supplied提供的 resultMsg message to its target with the new WebView as an argument. If the host application chooses not to honor the request, it should return false from this method. 
    • The default implementation of this method does nothing and hence因此、因而 returns false.
    • 參數 view: The WebView from which the request for a new window originated 發起新窗口的請求的.
    • 參數 isDialog: True if the new window should be a dialog, rather than而不是 a full-size window.
    • 參數 isUserGesture: True if the request was initiated by發起 a user gesture用戶手勢, such as the user clicking a link單擊鏈接.
    • 參數 esultMsg: The message to send when once a new WebView has been created. resultMsg.obj is a WebView.WebViewTransport object. This should be used to transport the new WebView, by calling WebView.WebViewTransport.setWebView(WebView).
    • 返回值:This method should return true if the host application will create a new window, in which case在這種情況下 resultMsg should be sent to its target. Otherwise, this method should return false. Returning false from this method but also sending resultMsg will result in導致 undefined behavior未定義的行爲.
  • void  onCloseWindow(WebView window)  Notify the host application to close the given WebView and remove it from the view system if necessary. 通知主機主機應用WebView關閉了,並在需要的時候從view系統中移除它。
    • 此時,WebCore已經停止窗口中的所有加載進度,並在JavaScript中移除了所有cross-scripting的功能。
    • 實驗發現,在JS調用window.close()方法時會回調此方法【<button onclick="window.close()">關閉窗口</button>】
    • 參數 window: The WebView that needs to be closed.

6、視頻(全屏)播放

  • Bitmap getDefaultVideoPoster() When not playing, video elements are represented by a 'poster' image. 當不播放時,視頻元素由“poster”圖像表示。
    • The image to use can be specified指定 by the poster attribute of the video tag in HTML. If the attribute is absent不存在, then a default poster will be used. This method allows the ChromeClient to provide that default image.
    • 返回值:Bitmap The image to use as a default poster, or null if no such image is available.
  • View getVideoLoadingProgressView() Obtains a View to be displayed while buffering of full screen video is taking place. 獲取在全屏幕視頻緩衝期間顯示的視圖。
    • The host application can override this method to provide a View containing a spinner微調器、旋轉器 or similar.
    • 播放視頻時,在第一幀呈現之前,需要花一定的時間來進行數據緩衝。ChromeClient可以使用這個函數來提供一個在數據緩衝時顯示的視圖。 
    • 返回值:The View to be displayed whilst the video is loading.
  • void  onShowCustomView(View view, WebChromeClient.CustomViewCallback callback)  Notify the host application that the current page has entered full screen mode 全屏模式
    • The host application must show the custom View which contains the web contents — video or other HTML content — in full screen mode. Also see "Full screen support" documentation on WebView.
    • 網頁中有H5播放flash video的時候按下全屏按鈕將會調用到這個方法,一般用作設置網頁播放全屏操作。
    • view: the View object to be shown.
    • callback: invoke this callback to request the page to exit full screen mode.
  • void onShowCustomView(View view, int requestedOrientation, WebChromeClient.CustomViewCallback callback) This method was deprecated in API level 18. This method supports the obsolete plugin mechanism, and will not be invoked in future. 
  • void onHideCustomView() Notify the host application that the current page hasexited full screen mode. 
    • The host application must hide the custom View, ie.即 the View passed to傳遞給 onShowCustomView(view, callback) when the content entered fullscreen. Also see "Full screen support" documentation on WebView.

7、文件選擇器

  • boolean  onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams)  Tell the client to show a file chooser 文件選擇器. added in API level 21
    • This is called to handle HTML forms with 'file' input type, in response to the user pressing the "Select File" button. 這是爲了響應用戶按下“選擇文件”按鈕來處理帶有“file”輸入類型的HTML表單
    • To cancel the request, call filePathCallback.onReceiveValue(null) and return true.
    • 參數 filePathCallback: Invoke this callback to supply the list of paths to files to upload 提供要上傳的文件的路徑列表, or NULL to cancel. Must only be called if the showFileChooser implementations returns true.
    • 參數 fileChooserParams: Describes the mode of file chooser to be opened, and options to be used with it. 描述要打開的文件選擇器的模式,以及與之一起使用的選項。
    • 返回值:Returns true if filePathCallback will be invoked, false to use default handling.

8、請求權限/獲取地理位置

  • void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) Notify the host application that web content from the specified origin指定源 is attempting to use the Geolocation地理定位 API, but no permission state is currently set for that origin源. 
    • The host application should invoke調用 the specified callback with the desired所需 permission state. See GeolocationPermissions for details 有關詳細信息.
    • Note that for applications targeting Android N and later更高版本的 SDKs (API level > M) this method is only called for requests originating from源自 secure origins安全起始點 such as https. On non-secure origins geolocation requests are automatically denied拒絕.
    • 參數 origin: The origin of the web content attempting to use the Geolocation API.
    • 參數 callback: The callback to use to set the permission state for the origin.
GeolocationPermissions.Callback接口只有一個invoke方法
public void invoke (String origin, boolean allow, boolean retain)
	Sets the Geolocation permission state for the supplied origin.
Parameters
	origin	String: the origin for which permissions are set
	allow	boolean: whether or not the origin should be allowed to use the Geolocation API
	retain	boolean: whether the permission should be retained保留 beyond在...之內 the lifetime of a page currently being displayed by a WebView
  • void onGeolocationPermissionsHidePrompt() Notify the host application that a request for Geolocation地理定位 permissions, made with a previous call to onGeolocationPermissionsShowPrompt() has been canceled. 通知主機應用程序,以前調用**的地理位置權限請求已被取消。
    • Any related相關 UI should therefore因此 be hidden.
  • void  onPermissionRequest(PermissionRequest request)  Notify the host application that web content is requesting permission to access the specified resources訪問指定資源 and the permission currently isn't granted授予 or denied拒絕.
    • The host application must invoke調用 grant(String[]) or deny(). If this method isn't overridden, the permission is denied拒絕.
    • 參數 request: the PermissionRequest from current web content.
  • void  onPermissionRequestCanceled(PermissionRequest request)  Notify the host application that the given permission request has been canceled. 
    • Any related相關 UI should therefore因此 be hidden.

9、其他方法

  • void  getVisitedHistory(ValueCallback<String[]> callback)  Obtains獲得 a list of all visited history items, used for link coloring. 獲得所有訪問歷史項目的列表,用於鏈接着色。
  • void  onExceededDatabaseQuota(String url, String databaseIdentifier, long quota, long estimatedDatabaseSize, long totalQuota, WebStorage.QuotaUpdater quotaUpdater)  This method was deprecated in API level 19. This method is no longer called; WebView now uses the HTML5 / JavaScript Quota Management API. 
  • void onRequestFocus(WebView view) Request display and focus for this WebView. 
    • This may happen due to由於 another WebView opening a link in this WebView and requesting that this WebView be displayed.
    • 參數view: The WebView that needs to be focused.

全屏播放視頻案例

1、清單文件設置
必要的權限
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
播放視頻的Activity必須設置以下屬性
android:configChanges="keyboardHidden|orientation|screenSize"
android:hardwareAccelerated="true"
2、自定義WebChromeClient
private View mCustomView;//onShowCustomView傳過來的view,其實就是我們構造的View
private CustomViewCallback mCustomViewCallback;//onShowCustomView傳過來的callback
private FullscreenHolder videoFullView;//全屏播放視頻時的根佈局

// 播放網絡視頻時全屏會被調用的方法
@Override
public void onShowCustomView(View view, CustomViewCallback callback) {
	Log.i("bqt", "【onShowCustomView】" + view.getClass().getSimpleName());//FrameLayout
	if (view instanceof ViewGroup) {
		ViewGroup vp = (ViewGroup) view;
		Log.i("bqt", "【onShowCustomView】count=" + vp.getChildCount()
				+ "  type=" + vp.getChildAt(0).getClass().getSimpleName());//count=1  type=FullScreenView
	}
	activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
	if (mCustomView == null) {
		mCustomView = view;
		mCustomViewCallback = callback;
		videoFullView = new FullscreenHolder(activity);
		videoFullView.addView(mCustomView);
		((FrameLayout) activity.getWindow().getDecorView()).addView(videoFullView);
	} else callback.onCustomViewHidden();
}

// 視頻播放退出全屏會被調用的
@Override
public void onHideCustomView() {
	Log.i("bqt", "【onHideCustomView】");
	// 是全屏播放狀態
	if (videoFullView != null && mCustomView != null && mCustomViewCallback != null) {
		activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
		videoFullView.removeView(mCustomView);
		videoFullView.setVisibility(View.GONE);//這個不能省
		mCustomView = null;
		videoFullView = null;
		mCustomViewCallback.onCustomViewHidden();
	}
}
	
public boolean isOnShowCustomView() {
	return mCustomView != null;
}
3、Activity中設置重寫後退按鍵事件
@Override
//設置當點擊後退按鈕時不是退出Activity,而是讓WebView後退一頁。也可以通過webview.setOnKeyListener設置
public boolean onKeyDown(int keyCode, KeyEvent event) {
	if (keyCode == KeyEvent.KEYCODE_BACK) {
		if (mWebChromeClient != null && mWebChromeClient.isOnShowCustomView()) {
			mWebChromeClient.onHideCustomView();
			setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
			return true;
		} else if (webview.canGoBack()) {//返回網頁上一頁
			webview.goBack(); //後退,goForward() 前進
			return true;
		} else {//退出網頁
			webview.loadUrl("about:blank");
			finish();
		}
	}
	return super.onKeyDown(keyCode, event);
}
4、參考HTML代碼
<video width="100%p" height="150" controls="controls" poster="../icon.jpg">
    <source src="http://qnfile.ogod.xin/_F-UBAMKr_AID.mp4" type="video/mp4"/>
</video>

文件選擇器案例

1、重寫WebChromeClient
//********************************************以下爲5.0以上,上傳文件相關代碼*******************************************
private ValueCallback<Uri[]> mUploadMessageForAndroid5;
public static int FILECHOOSER_RESULTCODE_FOR_ANDROID_5 = 2;

@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> uploadMsg, FileChooserParams params) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        Log.i("bqt", "【onShowFileChooser】 5.0+" + "   " + params.getMode() + "  " + params.getTitle() + "  "//Mode爲0
                + params.isCaptureEnabled() + "  " + params.getFilenameHint() + "  " + Arrays.toString(params.getAcceptTypes()));
    }
    mUploadMessageForAndroid5 = uploadMsg;
    Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
    contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
    contentSelectionIntent.setType("image/*");//文件類型
    
    Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
    chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
    chooserIntent.putExtra(Intent.EXTRA_TITLE, "圖片選擇");
    
    activity.startActivityForResult(chooserIntent, FILECHOOSER_RESULTCODE_FOR_ANDROID_5);
    return true;
}

//5.0以上,上傳圖片成功後的回調
public void mUploadMessageForAndroid5(Intent intent, int resultCode) {
    Log.i("bqt", "【上傳圖片成功後的回調】 5.0+");
    if (mUploadMessageForAndroid5 != null) {
        Uri result = (intent == null || resultCode != RESULT_OK) ? null : intent.getData();
        if (result != null) mUploadMessageForAndroid5.onReceiveValue(new Uri[]{result});
        else mUploadMessageForAndroid5.onReceiveValue(new Uri[]{});
        mUploadMessageForAndroid5 = null;
    }
}

//*******************************************以下爲5.0以下,上傳文件相關代碼***********************************************
private ValueCallback<Uri> mUploadMessage;
public static int FILECHOOSER_RESULTCODE = 1;

//The undocumented magic method override Eclipse will swear at you if you try to put @Override here
//undocumented:無正式文件的,無事實證明的;與…不協調,咒罵,發誓
// For Android 3.0-
@SuppressWarnings("unused")
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
    openFileChooserImpl(uploadMsg);
}

// For Android 3.0+
@SuppressWarnings("unused")
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
    openFileChooserImpl(uploadMsg);
}

//For Android 4.1
@SuppressWarnings("unused")
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
    openFileChooserImpl(uploadMsg);
}

//低版本上傳文件代碼
private void openFileChooserImpl(ValueCallback<Uri> uploadMsg) {
    Log.i("bqt", "【openFileChooser】");
    mUploadMessage = uploadMsg;
    Intent i = new Intent(Intent.ACTION_GET_CONTENT);
    i.addCategory(Intent.CATEGORY_OPENABLE);
    i.setType("image/*");
    activity.startActivityForResult(Intent.createChooser(i, "文件選擇"), FILECHOOSER_RESULTCODE);
}

//5.0以下,上傳圖片成功後的回調
public void mUploadMessage(Intent intent, int resultCode) {
    Log.i("bqt", "【上傳圖片成功後的回調】 5.0-");
    if (mUploadMessage != null) {
        if (intent != null && resultCode == RESULT_OK) mUploadMessage.onReceiveValue(intent.getData());
        mUploadMessage = null;
    }
}
2、Activity中處理上傳圖片之後的回調
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
    Log.i("bqt", "【onActivityResult】 requestCode=" + requestCode + "  resultCode=" + resultCode);
    //上傳圖片之後的回調
    if (requestCode == MyWebChromeClient.FILECHOOSER_RESULTCODE) {
        mWebChromeClient.mUploadMessage(intent, resultCode);
    } else if (requestCode == MyWebChromeClient.FILECHOOSER_RESULTCODE_FOR_ANDROID_5) {
        mWebChromeClient.mUploadMessageForAndroid5(intent, resultCode);
    }
}
3、參考HTML代碼
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>圖片上傳預覽</title>
    <script type="text/javascript">
    function imgPreview(fileDom){
        //判斷是否支持FileReader
        if (window.FileReader) var reader = new FileReader();
         else alert("您的設備不支持圖片預覽功能,如需該功能請升級您的設備!");

        //獲取文件
        var file = fileDom.files[0];
        var imageType = /^image\//;
        //是否是圖片
        if (!imageType.test(file.type)) {
            alert("請選擇圖片!");
            return;
        }
        //讀取完成
        reader.onload = function(e) {
            //獲取圖片dom
            var img = document.getElementById("preview");
            //圖片路徑設置爲讀取的圖片
            img.src = e.target.result;
        };
        reader.readAsDataURL(file);
    }
    </script>
</head>
<body>
<img id="preview" width="100%P"/>
<br/>
<input type="file" name="file" onchange="imgPreview(this)"/>
</body>
</html>

完整的WebChromeClient

public class MyWebChromeClient extends WebChromeClient {
	private WebViewActivity activity;//控件的顯示和隱藏應該都由WebViewClient控制
	
	public MyWebChromeClient(WebViewActivity activity) {
		super();
		this.activity = activity;
	}
	
	@Override
	public void onProgressChanged(WebView view, int newProgress) {
		Log.i("bqt", "【onProgressChanged】  " + newProgress);
		if (activity.getProgress_bar() != null) activity.getProgress_bar().setProgress(newProgress);
		super.onProgressChanged(view, newProgress);
	}
	
	@Override
	public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
		//If the client returns true, WebView will assume that the client will handle the dialog. Otherwise, it will continue execution執行.
		boolean b = new Random().nextBoolean();
		Log.i("bqt", "【onJsAlert】" + b + "  " + url + "  " + message);
		if (b) return super.onJsAlert(view, url, message, result);//默認爲return false,有彈窗
		else {//攔截html中alert函數之後,我們可以在這裏做任何自己的操作。我們還可以根據message來做不同的操作
			Toast.makeText(view.getContext(), "onJsAlert,message=" + message, Toast.LENGTH_SHORT).show();
			result.confirm();//confirm()表示點擊了彈出框的確定按鈕,cancel()則表示點擊了彈出框的取消按鈕。兩者必須調用一個
			return true;//如果return true,不彈出對話框了;否則,會繼續彈出對話框
		}
	}
	
	@Override
	public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
		boolean b = new Random().nextBoolean();
		Log.i("bqt", "【onJsConfirm】" + b + "  " + url + "  " + message);
		//return super.onJsConfirm(view, url, message, result);//默認爲return false,有彈窗
		Toast.makeText(view.getContext(), "onJsConfirm,message=" + message, Toast.LENGTH_SHORT).show();
		result.confirm();
		return b;//如果return true,不彈出對話框了;否則,會繼續彈出對話框
	}
	
	@Override
	public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
		boolean b = new Random().nextBoolean();
		Log.i("bqt", "【onJsPrompt】" + b + "  " + url + "  " + message + "  " + defaultValue);
		Toast.makeText(view.getContext(), "親,請輸入你的暱稱", Toast.LENGTH_SHORT).show();
		return super.onJsPrompt(view, url, message, defaultValue, result);//默認爲return false,有彈窗
	}
	
	@Override
	public boolean onJsBeforeUnload(WebView view, String url, String message, JsResult result) {
		boolean b = new Random().nextBoolean();
		Log.i("bqt", "【onJsBeforeUnload】" + b + "  " + url + "  " + message);
		return super.onJsBeforeUnload(view, url, message, result);
	}
	
	@Override
	public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
		Log.i("bqt", "【onConsoleMessage】" + "\nmessage=" + consoleMessage.message()
				+ "\nlineNumber=" + consoleMessage.lineNumber()
				+ "\nmessageLevel=" + consoleMessage.messageLevel() + "\nsourceId=" + consoleMessage.sourceId());
		return super.onConsoleMessage(consoleMessage);
	}
	
	@Override
	public void onReceivedTitle(WebView view, String title) {
		Log.i("bqt", "【onReceivedTitle 標題】" + title);
		activity.getTv_title().setText(title);
		super.onReceivedTitle(view, title);
	}
	
	@Override
	public void onReceivedIcon(WebView view, Bitmap icon) {
		Log.i("bqt", "【onReceivedIcon 圖標】");
		activity.getIv_icon().setVisibility(View.VISIBLE);
		activity.getIv_icon().setImageBitmap(icon);
		super.onReceivedIcon(view, icon);
	}
	
	@Override
	public void onReceivedTouchIconUrl(WebView view, String url, boolean precomposed) {
		//【url】The icon url.   【precomposed】True if the url is for a precomposed touch icon.
		Log.i("bqt", "【onReceivedTouchIconUrl 蘋果圖標】" + precomposed + "  " + url);
		super.onReceivedTouchIconUrl(view, url, precomposed);
	}
	//獲得所有訪問歷史項目的列表,用於鏈接着色。
	
	@Override
	public void getVisitedHistory(ValueCallback<String[]> callback) {
		Log.i("bqt", "【getVisitedHistory 不知道怎麼用】" + callback.toString());
		super.getVisitedHistory(callback);
	}
	
	@Override
	public boolean onCreateWindow(WebView webView, boolean isDialog, boolean isUserGesture, Message resultMsg) {
		Log.i("bqt", "【onCreateWindow】 " + isDialog + "  " + isUserGesture + "\n詳細信息" + resultMsg.toString());
		return super.onCreateWindow(webView, isDialog, isUserGesture, resultMsg);//默認是returns false
	}
	
	@Override
	public void onCloseWindow(WebView window) {
		Log.i("bqt", "【onCloseWindow】");
		super.onCloseWindow(window);
	}
	
	@Override
	public void onPermissionRequest(PermissionRequest request) {
		Log.i("bqt", "【onPermissionRequest】");
		//The host application must invoke grant(String[]) or deny(). If this method isn't overridden, the permission is denied拒絕.
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
			Log.i("bqt", "Origin=" + request.getOrigin().toString() + "   Resources=" + Arrays.toString(request.getResources()));
		}
		super.onPermissionRequest(request);
	}
	
	@Override
	public void onPermissionRequestCanceled(PermissionRequest request) {
		Log.i("bqt", "【onPermissionRequestCanceled】");
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
			Log.i("bqt", "Origin=" + request.getOrigin().toString() + "   Resources=" + Arrays.toString(request.getResources()));
		}
		super.onPermissionRequestCanceled(request);
	}
	
	@Override
	public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
		Log.i("bqt", "【onGeolocationPermissionsShowPrompt】" + origin);
		//callback只有一個方法【public void invoke(String origin, boolean allow, boolean retain)】
		super.onGeolocationPermissionsShowPrompt(origin, callback);
	}
	
	@Override
	public void onGeolocationPermissionsHidePrompt() {
		Log.i("bqt", "【onGeolocationPermissionsHidePrompt】");
		super.onGeolocationPermissionsHidePrompt();
	}
	
	@Override
	public void onRequestFocus(WebView view) {
		Log.i("bqt", "【onRequestFocus】" + (view == activity.getWebview()));
		super.onRequestFocus(view);
	}
	
	@Override
	public Bitmap getDefaultVideoPoster() {
		Log.i("bqt", "【getDefaultVideoPoster】");
		return BitmapFactory.decodeResource(activity.getResources(), R.drawable.ic_launcher);
		//return super.getDefaultVideoPoster(); //return null;
	}
	
	private ImageView loadingView;
	
	// 視頻加載時進程loading
	@Override
	public View getVideoLoadingProgressView() {
		Log.i("bqt", "【getVideoLoadingProgressView】");
		if (loadingView != null) return loadingView;
		else {
			loadingView = new ImageView(activity);
			loadingView.setImageResource(R.drawable.icon);
			return loadingView;
		}
		//else return super.getVideoLoadingProgressView();//return null;
	}
	
	//*****************                ↓↓↓↓↓↓↓↓↓↓↓↓↓↓      以下爲全屏播放視頻相關代碼      ↓↓↓↓↓↓↓↓↓↓↓↓↓             *******************
	private View mCustomView;//onShowCustomView傳過來的view,其實就是我們構造的View
	private CustomViewCallback mCustomViewCallback;//onShowCustomView傳過來的callback
	private FullscreenHolder videoFullView;//全屏播放視頻時的根佈局
	
	// 播放網絡視頻時全屏會被調用的方法
	@Override
	public void onShowCustomView(View view, CustomViewCallback callback) {
		Log.i("bqt", "【onShowCustomView】" + view.getClass().getSimpleName());//FrameLayout
		if (view instanceof ViewGroup) {
			ViewGroup vp = (ViewGroup) view;
			Log.i("bqt", "【onShowCustomView】count=" + vp.getChildCount()
					+ "  type=" + vp.getChildAt(0).getClass().getSimpleName());//count=1  type=FullScreenView
		}
		activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
		if (mCustomView == null) {
			mCustomView = view;
			mCustomViewCallback = callback;
			videoFullView = new FullscreenHolder(activity);
			videoFullView.addView(mCustomView);
			((FrameLayout) activity.getWindow().getDecorView()).addView(videoFullView);
		} else callback.onCustomViewHidden();
	}
	
	// 視頻播放退出全屏會被調用的
	@Override
	public void onHideCustomView() {
		Log.i("bqt", "【onHideCustomView】");
		// 是全屏播放狀態
		if (videoFullView != null && mCustomView != null && mCustomViewCallback != null) {
			activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
			videoFullView.removeView(mCustomView);
			videoFullView.setVisibility(View.GONE);//這個不能省
			mCustomView = null;
			videoFullView = null;
			mCustomViewCallback.onCustomViewHidden();
		}
	}
	
	public boolean isOnShowCustomView() {
		return mCustomView != null;
	}
	
	//*****************                ↑↑↑↑↑↑↑↑↑↑↑↑↑↑      以上爲全屏播放視頻相關代碼      ↑↑↑↑↑↑↑↑↑↑↑↑↑             *******************
	
	//********************************************以下爲5.0以上,上傳文件相關代碼*******************************************
	private ValueCallback<Uri[]> mUploadMessageForAndroid5;
	public static int FILECHOOSER_RESULTCODE_FOR_ANDROID_5 = 2;
	
	@Override
	public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> uploadMsg, FileChooserParams params) {
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
			Log.i("bqt", "【onShowFileChooser】 5.0+" + "   " + params.getMode() + "  " + params.getTitle() + "  "//Mode爲0
					+ params.isCaptureEnabled() + "  " + params.getFilenameHint() + "  " + Arrays.toString(params.getAcceptTypes()));
		}
		mUploadMessageForAndroid5 = uploadMsg;
		Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
		contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
		contentSelectionIntent.setType("image/*");//文件類型
		
		Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
		chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
		chooserIntent.putExtra(Intent.EXTRA_TITLE, "圖片選擇");
		
		activity.startActivityForResult(chooserIntent, FILECHOOSER_RESULTCODE_FOR_ANDROID_5);
		return true;
	}
	
	//5.0以上,上傳圖片成功後的回調
	public void mUploadMessageForAndroid5(Intent intent, int resultCode) {
		Log.i("bqt", "【上傳圖片成功後的回調】 5.0+");
		if (mUploadMessageForAndroid5 != null) {
			Uri result = (intent == null || resultCode != Activity.RESULT_OK) ? null : intent.getData();
			if (result != null) mUploadMessageForAndroid5.onReceiveValue(new Uri[]{result});
			else mUploadMessageForAndroid5.onReceiveValue(new Uri[]{});
			mUploadMessageForAndroid5 = null;
		}
	}
	
	//*******************************************以下爲5.0以下,上傳文件相關代碼***********************************************
	private ValueCallback<Uri> mUploadMessage;
	public static int FILECHOOSER_RESULTCODE = 1;
	
	//The undocumented magic method override Eclipse will swear at you if you try to put @Override here
	//undocumented:無正式文件的,無事實證明的;與…不協調,咒罵,發誓
	// For Android 3.0-
	@SuppressWarnings("unused")
	public void openFileChooser(ValueCallback<Uri> uploadMsg) {
		openFileChooserImpl(uploadMsg);
	}
	
	// For Android 3.0+
	@SuppressWarnings("unused")
	public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
		openFileChooserImpl(uploadMsg);
	}
	
	//For Android 4.1
	@SuppressWarnings("unused")
	public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
		openFileChooserImpl(uploadMsg);
	}
	
	//低版本上傳文件代碼
	private void openFileChooserImpl(ValueCallback<Uri> uploadMsg) {
		Log.i("bqt", "【openFileChooser】");
		mUploadMessage = uploadMsg;
		Intent i = new Intent(Intent.ACTION_GET_CONTENT);
		i.addCategory(Intent.CATEGORY_OPENABLE);
		i.setType("image/*");
		activity.startActivityForResult(Intent.createChooser(i, "文件選擇"), FILECHOOSER_RESULTCODE);
	}
	
	//5.0以下,上傳圖片成功後的回調
	public void mUploadMessage(Intent intent, int resultCode) {
		Log.i("bqt", "【上傳圖片成功後的回調】 5.0-");
		if (mUploadMessage != null) {
			if (intent != null && resultCode == Activity.RESULT_OK) mUploadMessage.onReceiveValue(intent.getData());
			mUploadMessage = null;
		}
	}
}

請求參數

WebViewActivity.start(this, WebSettingsModel.newBuilder()
		.url("file:///android_asset/h5/WebChromeClient演示.html")
		.setJavaScriptEnabled(true)
		.setDomStorageEnabled(true)//這句話必須保留,否則無法播放優酷網頁視頻,其他的可以
		.setSupportMultipleWindows(true)
		.setMixedContentMode(WebSettingsModel.MIXED_CONTENT_ALWAYS_ALLOW)//我覺得這個設置一般都很有必要
		.setGeolocationEnabled(true)
		.build());

參考的HTML代碼

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<button onclick="alert('alert彈框')">alert</button>
&nbsp;
<button onclick="confirm('confirm彈框')">confirm</button>
&nbsp;
<button onclick="prompt('prompt彈框','包青天')">prompt</button>

<p></p>

<button onclick="log()">調用console打印日誌</button>
&nbsp;
<button onclick="window.close()">關閉窗口</button>

<h2>鏈接</h2>
<a href="file:///android_asset/h5/test.html" title="新窗口" target="_blank">有target屬性的鏈接</a>
<p></p>
<a href="http://v.youku.com/v_show/id_XODkxMjcyNzAw.html?spm=a2h1n.8251845.0.0" title="網頁視頻">沒有target屬性的鏈接,網頁視頻</a>
<p></p>
<a href="file:///android_asset/h5/uploadPic.html" title="圖片上傳預覽">圖片上傳預覽</a>

<h2>視頻</h2>
ogod視頻,沒有poster屬性
<p></p>
<video width="100%p" controls="controls">
    <source src="http://qnfile.ogod.xin/_F-UBALyBRgEC.mp4" type="video/mp4"/>
</video>
ogod視頻,有poster屬性
<p></p>
<video width="100%p" controls="controls" poster="../icon.jpg">
    <source src="http://qnfile.ogod.xin/_F-UBAMKr_AID.mp4"
            type="video/mp4"/>
</video>

</body>

<script type="text/javascript">
function log(){
  console.log("log日誌");
  console.warn("warn日誌");
  console.error("error日誌");
}


</script>
</html>
2017-8-18
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章