最基礎的使用方法
最簡單的布局:
<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=".MainActivity">
<WebView
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout>
在Activity中使用WebView:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
WebView webView = (WebView) findViewById(R.id.webview);
webView.loadUrl(baiduUrl);
}
但只是這樣的話,在模擬器上是會直接調到系統瀏覽器去的,在手機上(我用的三星N9002,5.0系統)貌似可以直接加載而且點擊頁面的超鏈接也可以在當前頁面完成跳轉。事實上, WebView的默認行為是將鏈接點擊事件作為 Intent 發送給系統,由系統決定如何處理(通常的行為是使用瀏覽器打開或是彈出瀏覽器選擇對話框),我猜我用的三星測試機的WebView可能被廠商做了一些處理。
所以我們開發使用webView時一般都會給它設置WebViewClient。
If WebViewClient is not provided, by default WebView will ask Activity Manager to choose the proper handler for the url.
/**
* Sets the WebViewClient that will receive various notifications and
* requests. This will replace the current handler.
*
* @param client an implementation of WebViewClient
*/
public void setWebViewClient(WebViewClient client) {
checkThread();
mProvider.setWebViewClient(client);
}
WebViewClient主要幫助WebView處理各種通知、請求事件的,它的方法有:
shouldOverrideUrlLoading
onPageStarted
onPageFinished
onLoadResource
onReceivedError
……
添加如下一行代碼:
//WebViewClient中的方法大都是空實現,如果需要處理,則寫一個它的子類傳入即可
webView.setWebViewClient(new WebViewClient());
另外一個看起來跟WebViewClient很像的類是WebChromeClient,它主要輔助WebView處理Javascript的對話框、網站圖標、網站title、加載進度等,它的方法有:
onProgressChanged
onReceivedTitle
onReceivedIcon
onJsAlert
openFileChooser
……
設置方法也差不多:
//傳入WebChromeClient或其子類
webView.setWebChromeClient(new WebChromeClient());
運行結果如下:
但會發現怎么頁面的百度樣式貌似很老很老,跟瀏覽器加載出來的百度首頁怎么不一樣呢。那是因為我們百度頁面是使用了JS的,所以我們需要對WebView設置JS可執行:
webView.getSettings().setJavaScriptEnabled(true);
另外百度新聞的頁面始終只轉菊花而加載不出來,試驗發現需要設置另外一個屬性,使DOM storage API可用:
webView.getSettings().setDomStorageEnabled(true);
這是整個頁面就跟瀏覽器一致了:
當點擊進入下一級頁面如新聞時,發現點返回鍵就直接退出了桌面,這里需要我們自己去復寫當前Activity的onKeyDown方法實現WebView的返回邏輯:
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && webView.canGoBack()) {
webView.goBack();
return true;
}
return super.onKeyDown(keyCode, event);
}
自定義url
- shouldOverrideUrlLoading
我的項目中為了使從WebView中點擊特定鏈接(比如我們的自定義鏈接:yxy://abc?id=1)可以跳轉到一個指定的native頁面,需要進行特殊處理,這個需求就可以使用shouldOverrideUrlLoading方法來實現。
/**
* Give the host application a chance to take over the control when a new
* url is about to be loaded in the current WebView. If WebViewClient is not
* provided, by default WebView will ask Activity Manager to choose the
* proper handler for the url. If WebViewClient is provided, return true
* means the host application handles the url, while return false means the
* current WebView handles the url.
* This method is not called for requests using the POST "method".
*
* @param view The WebView that is initiating the callback.
* @param url The url to be loaded.
* @return True if the host application wants to leave the current WebView
* and handle the url itself, otherwise return false.
*/
public boolean shouldOverrideUrlLoading(WebView view, String url) {
return false;
}
從代碼注釋中可以看出這個方法的作用是,當一個新的url將要在當前webView中加載時,給當前應用程序一個機會,去決定如何處理。有三種情況:
- 當前webView沒有設置WebViewClient,webView將請求系統去選擇合適的處理程序(比如系統瀏覽器);
- 當前webView設置了WebViewClient:
a. 如果shouldOverrideUrlLoading返回true,則由應用的代碼進行處理,webView不處理;
b.如果shouldOverrideUrlLoading返回false,則由webView去處理,即webView加載url。
上面的第2點的b,其實就是shouldOverrideUrlLoading的默認實現,我們經常見到一些代碼這樣寫:
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
其實這樣跟直接return false是一樣的處理效果。
- 處理自定義的url
我在測試時發現一個問題,當我使用webView加載一個有效的http鏈接,如:
webView.loadUrl("http://www.baidu.com");
加載時,shouldOverrideUrlLoading會被回調到,而如果直接加載一個無效的自定義鏈接,如:
webView.loadUrl("yxy://abc");
則shouldOverrideUrlLoading不會被回調,webView會出現一個錯誤提示:
此時如果直接去點擊頁面中的藍色鏈接,又會發現shouldOverrideUrlLoading被回調了。通過Google,發現官方有一篇文章(Migrating to WebView in Android 4.4)專門提到這一點,從Android 4.4開始,WebView有了一些新的特性,有點方面也跟以前不同了,在處理用戶自定義的url時,校驗貌似更嚴格了:
The new WebView applies additional restrictions when requesting resources and resolving links that use a custom URL scheme. For example, if you implement callbacks such as shouldOverrideUrlLoading() or shouldInterceptRequest(), then WebView invokes them only for valid URLs.
If you are using a custom URL scheme or a base URL and notice that your app is receiving fewer calls to these callbacks or failing to load resources on Android 4.4, ensure that the requests specify valid URLs that conform to RFC 3986.
當shouldOverrideUrlLoading被回調時,可以通過下面的方式來實現應用自己的處理:
// The URL scheme should be non-hierarchical (no trailing slashes)
private static final String APP_SCHEME = "example-app:";
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.startsWith(APP_SCHEME)) {
urlData = URLDecoder.decode(url.substring(APP_SCHEME.length()), "UTF-8");
respondToData(urlData);
return true;
}
return false;
}
- 通過Linkify,在TextView中生成超鏈接
(……)
WebView中Java和JS交互
我的項目中是使用了這個開源項目,非常好用——https://github.com/lzyzsd/JsBridge
自定義UA
項目中為了方便web端統計分析,我們需要在WebView的UserAgent中加入app的特定標識:
// 獲取當前WebView的UA
String ua = webView.getSettings().getUserAgentString();
// 在當前UA字符串的末尾增加app的標識和版本號等信息
webView.getSettings().setUserAgent(ua + " APP_TAG/5.0.1");
輸入法設置
為了避免WebView中彈出鍵盤遮擋住光標,需要在對應的Activity中增加如下配置:
android:windowSoftInputMode="stateHidden|adjustResize"
集成騰訊瀏覽服務TBS
TBS的官網:http://x5.tencent.com/index
在使用Android的WebView時,遇到過很多問題。比如支持html頁面內點擊按鈕打開本地圖片上傳到服務器這個需求,Android各個版本WebView的api都不同,而且更奇葩的是有的手機竟然完全不支持這個功能,原因竟然是Android在WebView版本迭代時造成的歷史遺留bug。諸如此類大坑小坑還是挺多的,碎片化太嚴重。(可以看看知乎這個提問:http://www.zhihu.com/question/31316646?sort=created)
目前移動端系統內置瀏覽器的常見內核有 Webkit,Blink,Trident,Gecko 等,其中 iPhone 和 iPad 等蘋果 iOS 平臺主要是 WebKit,Windows Phone 8 系統瀏覽器內核是 Trident,Android 4.4 之前的 Android 系統瀏覽器內核是 WebKit,Android4.4 系統瀏覽器切換到了Chromium,內核是 Webkit 的分支 Blink。所以Android4.4之前與之后存在一些WebView的兼容問題。
提到兼容問題,我們肯定會想到一些三方的SDK可以完美地解決這些問題。所以去年時我們項目嘗試接入騰訊的X5(TBS的官網)。當時X5還是1.x的版本,集成后發現很不好用,因為它是要共享微信或者手Q的內核,但是app啟動后老是加載不到X5內核,自己去調系統內核了,10次只有1、2次能調用到X5內核,SDK提供的demo也一樣有問題,而且文檔很簡陋,很多地方寫得不清楚,論壇提問題也沒人解決,所以只好放棄了。今年聽說X5發布了2.x的版本,功能也相對穩定了,所以現在再次嘗試。
下載SDK,發現有兩種:
第一種說明了只共享微信或者手Q的內核,那就是說如果用戶手機上沒有微信和手Q,就不能使用X5內核了,雖然現在基本每臺手機都有微信或手Q,但還是自帶X5內核比較好,而且下載了兩個skd對比,發現只是jar包大小上的區別。
開始集成了!
1.把SDK解壓后,找出里面的jar包導入到工程,我使用的AS,所以直接粘貼到lib包下面:
2.根據集成文檔,把項目中所有用到的WebView及其相關的類都替換為“com.tencent.smtt”包下同名的類:
必須要替換完全了,包括java代碼和xml中使用到的地方,否則會發生錯誤;
3.如果使用的是“Android SDK(With download)”的SDK,即我們現在使用的可獨立下載X5內核的SDK,則需要使用下面代碼允許第三方app下載X5內核:
QbSdk.allowThirdPartyAppDownload(true);
因為文檔中強調這句代碼要在創建WebView之前調用,xml中的WebView會再執行Activity中的代碼之前被創建,所以我覺得最好將這句代碼放在Application中。
4.加入必要的權限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
我將文章開頭講到的測試代碼中WebView相關的類替換為SDK提供的同名類并加入第3點的代碼后,運行代碼,WebView正常加載,那么如何判斷使使用了X5內核呢,有一個小技巧,就是長按文字,喚出復制菜單:
如果復制的樣式改變了,則說明X5內核啟動了。當我多實驗了幾次后,發現并不一定每次app一打開,X5內核就會加載成功,可能是下載X5內核的工作不一定可以立馬完成吧,有時還要等一段時間多啟動幾次才加載上X5的內核。但比1.x的SDK感覺要好很多,至少沒出現一直加載不出X5內核的情況。
【首次加載即能使用X5內核的方法】
原來官方在這里有解決方案:http://x5.tencent.com/doc?id=1002_1
真心建議X5的接入文檔能夠更詳細更完善一點,否則很多地方接入時真得很費勁,要來回看各個角落里的信息才能解決一些基礎問題,論壇上也有很多遺留問題其實可以集中放在文檔中的。
如何首次啟動WebView就能加載到X5內核呢,SDK中其實直接提供了對應的API,只需要在Application中加上下面的代碼就可以了:
public class MainApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
QbSdk.initX5Environment(this, QbSdk.WebviewInitType.FIRSTUSE_AND_PRELOAD, new QbSdk.PreInitCallback() {
@Override
public void onCoreInitFinished() {
Log.d("MainApplication", "x5 core load success");
}
@Override
public void onViewInitFinished(boolean b) {
}
});
}
}
initX5Environment 內部會創建一個線程向后臺查詢當前可用內核版本號,這個函數內是異步執行所以不會阻塞 App 主線程,這個函數內是輕量級執行所以對 App 啟動性能沒有影響,當 App 后續創建 webview 時就可以首次加載 x5 內核了。首界面就使用tbs webview的,因為 initX5Environment 需要做初始化操作,不適用首次加載 x5的方案。
雖然是預加載,如果在沒有加載完成前就啟動了含有WebView的界面,還是會有一點卡頓。
5.API使用的調整
- 在使用WebView時,我們獲取它的寬度是使用:
webView.getWidth();
但因為SDK所提供的WebView類,是對系統WebView的聚合包裝,所以獲取寬度時需要用:
webView.getView().getWidth();
- 調整cookie的使用
com.tencent.smtt.sdk.CookieManager和com.tencent.smtt.sdk.CookieSyncManager的相關接口的調用,在接入SDK后,需要放到創建X5的WebView之后(也就是X5內核加載完成)進行;否則,cookie的相關操作只能影響系統內核。
6.獲取異常上報信息
可將以下API返回的信息攜帶進異常上報的附加信息里
WebView.getTbsCoreVersion(); // 返回內核版本信息
WebView.getTbsSDKVersion(); // 返回瀏覽器SDK版本信息
WebView.getCrashExtraMessage; // 返回crash線索信息
7.兼容視頻播放
- 享受頁面視頻的完整播放體驗需要在WebView所在的Activity中增加下面的聲明:
android:configChanges="orientation|screenSize|keyboardHidden"
加上之后,就支持了視屏播放的橫豎屏切換和全屏非全屏的切換。
- 視頻為了避免閃屏和透明問題,需要如下設置:
- 網頁中的視頻,上屏幕的時候,可能出現閃爍的情況,需要如下設置:Activity在onCreate時需要設置:
getWindow().setFormat(PixelFormat.TRANSLUCENT); //這個對宿主沒什么影響,建議聲明
- 在非硬繪手機和聲明需要controller的網頁上,視頻切換全屏和全屏切換回頁面內會出現視頻窗口透明問題,需要如下設置:
聲明當前Activiy的<item name="android:windowIsTranslucent">false為不透明。
特別說明:這個視各app情況所需,不強制需求,如果聲明了,對體驗更有利
- 以下接口禁止(直接或反射)調用,避免視頻畫面無法顯示:
webview.setLayerType()
webview.setDrawingCacheEnabled(true);
X5對視頻的兼容很不錯,在使用原生WebView時,播放視頻的效果是下面這樣:
樣式丑,全屏和橫屏的效果也很不好。使用X5內核的WebView時,播放視頻效果如下:
播放進度、功能按鈕等的樣式美觀了,而且橫屏和全屏的支持也很贊,還可以鎖定屏幕和調整畫面比例,唯一美中不足的是有廣告:
8.混淆
TBS jar 已經混淆過,所以 App 混淆時可以不再混淆。也可以添加集成文檔中給出的混淆策略。
9.接入TBS視頻播放器
TBS不僅提供了強大的網頁瀏覽功能,更提供了強大的頁面H5視頻播放支持,播放器同時支持頁面,小窗,全屏播放體驗,強大的解碼能力,包括mp4,rmvb,flv,avi等26種視頻格式支持。
TBS播放器的播放場景不僅局限于H5頁面播放,也可以接入一般的視頻流鏈接,比如本地文件,網絡的視頻流鏈接。開發者如果想播放一個視頻鏈接,在不自己開發播放器的前提下,一般做法是將視頻的播放鏈接放到一個Intent里面,拋給系統的播放器進行播放,那么當你集成了TBS后,你只需要通過簡單的方式接入視頻播放調用接口,這樣你不需要寫任何一句關于播放器的代碼,就可以享受一個本地播放器體驗,播放視頻再不需要Intent來跨App、跨進程的調用了。
- 在AndroidManifest中注冊VideoActivity
<activity
android:name="com.tencent.smtt.sdk.VideoActivity"
android:alwaysRetainTaskState="true"
android:configChanges="orientation|screenSize|keyboardHidden"
android:exported="false"
android:launchMode="singleTask">
<intent-filter>
<action android:name="com.tencent.smtt.tbs.video.PLAY" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
- 調用播放視頻的接口
//判斷當前Tbs播放器是否已經可以使用。
public static boolean canUseTbsPlayer(Context context)
//直接調用播放接口,傳入視頻流的url
public static void openVideo(Context context, String videoUrl)
//extraData對象是根據定制需要傳入約定的信息,沒有需要可以傳null
public static void openVideo(Context context, String videoUrl, Bundle extraData)
直接調用以上接口,就可以打開VideoActivity播放傳入url對應的視頻,相當于集成了一個內部播放器,非常方便:
TbsVideo.openVideo(context, "http://www.huixuedu.com/uploads/media/151201/1-151201112136.mp4");