上一篇文章中我們介紹了hybrid開發相關的知識。重點介紹了hybrid開發的概念,hybrid開發的作用,Android中如何實現hybrid開發,Android中實現hybrid開發的例子,以及產品開發中hybrid開發實踐等,通過對以上這些概念的介紹我們對hybrid開發應該已經有了大概的了解,更多具體的內容可參考我的:[Android產品研發(十七)–>Hybrid開發](http://blog.csdn.net/qq_23547831/article/details/51812985)
本文中我們將介紹一下Android中webview在使用過程中會遇到的一些問題。這些問題主要是webview在使用過程中我已經趟過的坑,希望通過這篇文章的介紹能夠幫助大家更好的使用webview。
下面是本文主要介紹的一些知識點,后續使用過程中可能會有更新。
* webview的性能優化
* webview注入cookie信息
* webview退出activity異常
* webview中native與js交互
* webview下載文件
* 騰訊X5瀏覽服務
最近App中相當一部分的頁面內容使用的是webview。而使用webview加載頁面一個需要注意的地方就是性能,所以最近也研究了一下webview的性能優化問題。
**webview的性能問題**
在講解webview的性能問題之前,我們先來了解一下Android webview的緩存機制。
* Android WebView緩存機制
WebView中存在著兩種緩存:網頁數據緩存(網頁數據,url等)、H5緩存(H5代碼緩存數據)
不同的緩存數據會保存在不同的文件目錄下,這里引用一下其他blog的說法:
> 當我們加載Html時候,會在我們data/應用package下生成database與cache兩個文件夾:
> 我們請求的Url記錄是保存在webviewCache.db里,而url的內容是保存在webviewCache文件夾下。

webview中也是可以設置緩存是否可用的,一般是通過WebSettings對象設置,下面我們就來看一下WebSettings對象的使用。
* Android中webview組件有幾個重要的方法
~~~
WebSettings webSettings = mWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setLoadWithOverviewMode(true);
webSettings.setAllowFileAccess(false);
webSettings.setUseWideViewPort(false);
webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
webSettings.setDatabaseEnabled(false);
webSettings.setAppCacheEnabled(false);
webSettings.setBlockNetworkImage(true);
// 設置WebView的Client
mWebView.setWebViewClient(new MWebViewClient(this));
// 設置可現實js的alert彈窗
mWebView.setWebChromeClient(new WebChromeClient());
~~~
可以看到我們可以使用WebSettings對象設置緩存是否可用,緩存DB是否可用等。我們需要首先確保這里設置了緩存可用,才可以繼續設置使用何種緩存策略。
下面我們來看一下webview的五種緩存模式:
LOAD_CACHE_ONLY: 不使用網絡,只讀取本地緩存數據
LOAD_DEFAULT: 根據cache-control決定是否從網絡上取數據。
LOAD_CACHE_NORMAL: API level 17中已經廢棄, 從API level 11開始作用同LOAD_DEFAULT模式
LOAD_NO_CACHE: 不使用緩存,只從網絡獲取數據.
LOAD_CACHE_ELSE_NETWORK,只要本地有,無論是否過期,或者no-cache,都使用緩存中的數據。
* 幾種緩存方式的實現
(1)使用LOAD_CACHE_ELSE_NETWORK緩存模式,這樣需要在APP退出的時候清除webview緩存,但是這樣做有一個弊端就是如果當前App已經是打開狀態,網頁內容有更新的話不會看到;
(2)使用LOAD_DEFAULT這種緩存方式,數據從緩存中獲取還是從網絡中獲取根據H5頁面的參數判斷,這樣做的好處是可以動態的處理更新內容;
* 設置緩存
~~~
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.getSettings().setRenderPriority(RenderPriority.HIGH); mWebView.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT); //設置 緩存模式
// 開啟 DOM storage API 功能
mWebView.getSettings().setDomStorageEnabled(true);
//開啟 database storage API 功能
mWebView.getSettings().setDatabaseEnabled(true);
String cacheDirPath = getFilesDir().getAbsolutePath()+APP_CACAHE_DIRNAME;
// String cacheDirPath = getCacheDir().getAbsolutePath()+Constant.APP_DB_DIRNAME;
Log.i(TAG, "cacheDirPath="+cacheDirPath);
//設置數據庫緩存路徑
mWebView.getSettings().setDatabasePath(cacheDirPath);
//設置 Application Caches 緩存目錄
mWebView.getSettings().setAppCachePath(cacheDirPath);
//開啟 Application Caches 功能
mWebView.getSettings().setAppCacheEnabled(true);
~~~
* 退出App時清除緩存
~~~
//清理Webview緩存數據庫
try {
deleteDatabase("webview.db");
deleteDatabase("webviewCache.db");
} catch (Exception e) {
e.printStackTrace();
}
//WebView 緩存文件
File appCacheDir = new File(getFilesDir().getAbsolutePath()+APP_CACAHE_DIRNAME);
Log.e(TAG, "appCacheDir path="+appCacheDir.getAbsolutePath());
File webviewCacheDir = new File(getCacheDir().getAbsolutePath()+"/webviewCache");
Log.e(TAG, "webviewCacheDir path="+webviewCacheDir.getAbsolutePath());
//刪除webview 緩存目錄
if(webviewCacheDir.exists()){
deleteFile(webviewCacheDir);
}
//刪除webview 緩存 緩存目錄
if(appCacheDir.exists()){
deleteFile(appCacheDir);
}
~~~
* 其他的緩存策略
網頁在加載的時候暫時不加載圖片,當所有的HTML標簽加載完成時在加載圖片具體的做法如下初始化webview的時候設置不加載圖片
~~~
webSettings.setBlockNetworkImage(true);
~~~
然后在html標簽加載完成之后在加載圖片內容:
~~~
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
mWebView.getSettings().setBlockNetworkImage(false);
}
~~~
O(∩_∩)O哈哈~,這樣做的好處就是可以給人的感覺網頁加載速度很快…
* 將網頁內容中需要的js,css引用文件保存在App本地
加載網頁內容時,在加載完成html后替換頁面內容引用的地址改為本地的資源文件地址,這樣可以直接加載本地的資源文件加快資源的訪問速度,目前主流的新聞客戶端訪問webview時多采用這種方式。
好吧,講解完了webview的性能優化問題之后我們在講解一下如何在H5頁面種入Cookie信息。
**H5頁面種入Cookie問題**
app中存在webview控件,既可以通過Native與js代碼交互的方式實現信息的交互也可以通過cookie的方式與實現Native與H5端的交互,查詢的好多資料各種各樣的實現方式都有,最終不斷嘗試基本實現了需求,現說明一下最終的實現方式;
~~~
/**
* 客戶端將cookie種入H5頁面中,H5頁面可以通過js代碼實現對native種入cookie信息的讀取操作
*/
public static void synCookies(Context context, String url, String cookie) {
CookieSyncManager.createInstance(context);
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setAcceptCookie(true);
Uri uri = null;
String domain = "";
try {
uri = Uri.parse(URLDecoder.decode(url, "utf-8"));
domain = uri.getHost();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
String cookieStr = cookieManager.getCookie(url);
// 判斷token是否發生變化,發生變化的話則更新cookie
L.i("cookieEquce:" + cookie.equals(H5Constant.TOKEN + "="));
if (!TextUtils.isEmpty(cookieStr) && cookieStr.contains(cookie) && !cookie.equals(H5Constant.TOKEN + "=")) {
return;
}
// 更新domain(不再從UserInfo中獲取,更改為從UrlInfo中獲取)
if (!TextUtils.isEmpty(URLConfig.getUrlInfo().getB4Domain())) {
domain = URLConfig.getUrlInfo().getB4Domain();
}
List<String> uaList = SysConfig.getSystemUa(context);
String md = ";domain=" + domain;
// 添加經緯度信息到Cookie中
cookieManager.setCookie(url, "lat=" + Config.lat + md);
cookieManager.setCookie(url, "lng=" + Config.lng + md);
cookieManager.setCookie(url, cookie + ";" + md);
if (uaList != null && uaList.size() > 0) {
for (String coo : uaList) {
cookieManager.setCookie(url, coo + md);
}
}
CookieSyncManager.getInstance().sync();
}
~~~
其中CookieManager是cookie的管理對象,主要實現對網頁cookie的注入與清除等工作。注入字符串的形式是:key=value;domain=url的形式(其中url為需要注入cookie的url鏈接地址)
那么如何移除cookie呢?
~~~
/**
* 移除cookie
*/
public static void removeCookies(Context context) {
CookieSyncManager.createInstance(context);
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.removeSessionCookie();
CookieSyncManager.getInstance().sync();
}
~~~
一般情況下都是在打開H5頁面的時候種入Cookie信息,然后在離開H5頁面的時候清除cookie信息。當然了通過cookie實現native與js的交互只是實現信息交互的一種方式,我們還可以通過js與java代碼相互調用的方式實現相互交互,文章的后面會有介紹。
而下面我們再來講解一下activity退出時webview報錯的問題。
**Activity退出時webview報錯的問題**
前段時間在調試代碼的時候,有一段關于webview的代碼,即退出Fragment的時候清除webview,這時候在其他手機上是沒有問題的,但是在三星Grand2中報錯,而其報錯信息是:
~~~
java.lang.Throwable: Error: WebView.destroy() called while still attached!
at Android.webkit.WebViewClassic.destroy(WebViewClassic.java:4173)
at Android.webkit.WebView.destroy(WebView.java:707)
at com.youyou.uuelectric.renter.UI.web.H5Fragment.onDestroyView(H5Fragment.java:202)
at Android.support.v4.app.Fragment.performDestroyView(Fragment.java:2167)
at Android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1141)
at Android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1248)
at Android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1230)
at Android.support.v4.app.FragmentManagerImpl.dispatchDestroy(FragmentManager.java:2079)
at Android.support.v4.app.FragmentController.dispatchDestroy(FragmentController.java:235)
at Android.support.v4.app.FragmentActivity.onDestroy(FragmentActivity.java:326)
at Android.support.v7.app.AppCompatActivity.onDestroy(AppCompatActivity.java:161)
at com.youyou.uuelectric.renter.UI.base.BaseActivity.onDestroy(BaseActivity.java:136)
at Android.app.Activity.performDestroy(Activity.java:5543)
at Android.app.Instrumentation.callActivityOnDestroy(Instrumentation.java:1134)
at Android.app.ActivityThread.performDestroyActivity(ActivityThread.java:3637)
at Android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:3672)
at Android.app.ActivityThread.access$1300(ActivityThread.java:168)
at Android.app.ActivityThread$H.handleMessage(ActivityThread.java:1382)
at Android.os.Handler.dispatchMessage(Handler.java:99)
at Android.os.Looper.loop(Looper.java:176)
at Android.app.ActivityThread.main(ActivityThread.java:5493)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:525)
at com.Android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1225)
at com.Android.internal.os.ZygoteInit.main(ZygoteInit.java:1041)
at dalvik.system.NativeStart.main(Native Method)
~~~
程序也沒有異常退出之類的動作,清除webview的代碼是這樣寫的:
~~~
@Override
public void onDestroyView() {
super.onDestroyView();
mWebView.removeAllViews();
mWebView.destroy();
}
~~~
這個錯誤大概的意思是:當你結束webview的時候,webview還依附在其父控件之下,應當在調用webview.destory()方法之前接觸他們之間的依附關系,那么既然錯誤提示信息已經很明顯了,我們就根據錯誤信息嘗試著首先執行webview父控件的清除工作,然后在執行webview控件的清除操作,所以代碼中應該這樣實現:
~~~
@Override
public void onDestroyView() {
super.onDestroyView();
swipeRefreshLayout.removeView(mWebView);
mWebView.removeAllViews();
mWebView.destroy();
}
~~~
這樣經過修改之后特定機型上關于webview的錯誤就不在了。
**webview中實現Native與js相互調用**
上面我們介紹的通過cookie實現Android native與H5的信息交互只是一種方式,我們也可以通過java代碼與js代碼直接相互調用的方式實現Android native與H5信息的相互,這里簡單的介紹一下使用方式
* native代碼調用H5的js代碼
(1)在H5頁面中添加一個js函數
~~~
<script type="text/javascript">
function uu_click(clicked_id) {
alert(clicked_id);
}
~~~
(2)在Native中通過java代碼調用
若這時候H5頁面已經被加載到webview中,則可以通過java代碼直接調用js函數:
~~~
h5Fragment.mWebView.loadUrl("javascript:uu_click" + "('" + clickId + "')");
~~~
是的,沒錯就是這么簡單,這樣就實現了java代碼對js代碼的調用,但是需要指出的是這里無法調用含有返回值的js函數,這里也算是小小的問題吧,但是話說回來,一般也沒有這種需要調用js代碼并獲得返回值的場景吧。
* js代碼調用java函數
(1)首先在java端編寫能夠被js代碼調用的java函數
* native方法的實現
~~~
/**
* 自定義實現的native函數,可被js代碼調用
*/
class JsInteration {
...
@JavascriptInterface
public void toastMessage(String message) {
Toast.makeText(getActivity(), message, Toast.LENGTH_LONG).show();
}
...
}
~~~
(2)在native中注入本地方法,供js調用;
~~~
mWebView.addJavascriptInterface(new JsInteration(), "control");
~~~
(3)在js代碼中調用java代碼:
~~~
function reply_click(clicked_id {
window.control.toastMessage(clicked_id)
}
~~~
以上就是webview中使用js代碼與java代碼相互調用的簡單例子。
**webview下載文件**
在使用webview開發中還遇到一個問題,app中webview中存在下載鏈接,但是在手機瀏覽器中點擊下載是沒有問題的,在webview中怎么都不好使。查詢了好久,原來是因為WebView默認沒有開啟文件下載的功能,如果要實現文件下載的功能,需要設置WebView的DownloadListener,通過實現自己的DownloadListener來實現文件的下載。
* 設置webview的setDownloadListener方法
~~~
mWebView.setDownloadListener(new DownloadListener() {
@Override
public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
L.i("tag", "this is a message!!!");
}
});
~~~
* 重寫onDownloadStart回調方法,實現下載文件的邏輯
比如在瀏覽器中實現下載:
~~~
@Override
public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype,
long contentLength) {
Uri uri = Uri.parse(url);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
}
~~~
這樣我們的webview中如果包含了下載鏈接就可以通過打開瀏覽器的方式實現下載了。
注:個人感覺webview沒有默認實現下載鏈接的功能更多的可能是考慮權限,安全方面的問題。
**騰訊X5瀏覽服務**
由于不同的手機版本問題,各個廠商的定制化操作,webview在不同的手機上表現有很大的不同,所以webview的適配工作在Android機上顯得比較重要,這里我們就簡單的介紹一下騰訊的X5瀏覽服務,其相當于Android上webview的第三方框架。
這里先暫時看一下騰訊X5服務的官方介紹,其官網是:[騰訊瀏覽服務](http://x5.tencent.com/),然后我們看一下X5瀏覽服務官網對其的描述。
> 騰訊瀏覽服務由QQ瀏覽器團隊出品,致力于優化移動端webview體驗的整套解決方案,使用QQ瀏覽器X5內核SDK和X5云端服務,解決移動端webview使用過程中出現的一切問題,優化用戶的瀏覽體驗,同時騰訊還將持續提供后續的更新和優化,為開發者提供最新最優秀的功能和服務。
> X5SDK是通過調用微信/手機QQ/空間的X5內核,解決系統webview兼容性差、加載速度慢、功能缺陷等問題,開發接入便捷,大小只有253K,僅需幾行代碼,即可解決一切令開發者們頭疼的問題,為用戶提供最優秀的瀏覽體驗。
> 同時,QQ瀏覽器團隊還將持續更新和優化X5內核,持續優化功能,并保證兼容各種web新特性。
- 騰訊X5瀏覽服務的優勢:
1) 速度快:相比系統webView的網頁加載速度有近30%的提升。
2) 省流量:云端優化技術使流量節省20%
3) 更安全:24小時安全問題解決機制
4) 更穩定:經過億級用戶的使用考驗,CRASH率0.15%
5) 集成強大的視頻播放器,支持各種視頻格式直接打開
6) 適屏排版、字體設置等瀏覽增強功能的提供
7) Html5更完整支持。
8) 無系統內核的碎片化問題,更少的兼容性問題
那么現在那些App具體接入了X5服務了呢?

可以發現已經有不少的牛擦的App接入了X5服務(當然主要是騰訊系的,原因你懂的),其相對于原生的webview的最大優勢是做了各種型號手機的適配工作,而且其也添加了不少有特色的小功能,對于使用webview開發App的同學而言,可以考慮一下集成X5服務哈。
**總結**:
* webview的有一定的性能問題,可以設置不同的緩存策略提高webview的加載速率
* webview與native可以通過cookie的方式實現信息交互
* webview下載文件,需要重寫自己的native的DownloadListener類,以及其回調方法
* 實現Native與H5端信息的交互既可以使用cookie的方式也可以通過java代碼與js代碼實現
* 原生的webview在不同手機上可能存在不同的表現,建議可以使用騰訊的X5瀏覽服務屏蔽的不同手機的差別,添加了不少特色的小功能
* 其他關于webview的問題集錦,可參考:[Android WebView常見問題及解決方案匯總](http://blog.csdn.net/t12x3456/article/details/13769731/),總結的很全面哈
另外對產品研發技術,技巧,實踐方面感興趣的同學可以參考我的:
[android產品研發(一)-->實用開發規范 ](http://blog.csdn.net/qq_23547831/article/details/51534013)
[android產品研發(二)-->啟動頁優化 ](http://blog.csdn.net/qq_23547831/article/details/51541277)
[android產品研發(三)-->基類Activity ](http://blog.csdn.net/qq_23547831/article/details/51546974)
[android產品研發(四)-->減小Apk大小](http://blog.csdn.net/qq_23547831/article/details/51559066)
[android產品研發(五)-->多渠道打包](http://blog.csdn.net/qq_23547831/article/details/51569261)
[Android產品研發(六)–>Apk混淆](http://blog.csdn.net/qq_23547831/article/details/51581491)
[android產品研發(七)-->Apk熱修復](http://blog.csdn.net/qq_23547831/article/details/51587927)
[Android產品研發(八)–>App數據統計](http://blog.csdn.net/qq_23547831/article/details/51612429)
[Android產品研發(九)–>App網絡傳輸協議](http://blog.csdn.net/qq_23547831/article/details/51655330)
[Android產品研發(十)–>不使用靜態變量保存數據](http://blog.csdn.net/qq_23547831/article/details/51685310)
[Android產品研發(十一)–>應用內跳轉scheme協議](http://blog.csdn.net/qq_23547831/article/details/51685310)
[Android產品研發(十二)–>App長連接實現](http://blog.csdn.net/qq_23547831/article/details/51719389)
[Android產品研發(十三)–>App輪詢操作](http://blog.csdn.net/qq_23547831/article/details/51764773)
[Android產品研發(十四)–>App升級與更新](http://blog.csdn.net/qq_23547831/article/details/51764773)
[Android產品研發(十五)–>內存對象序列化](http://blog.csdn.net/qq_23547831/article/details/51779528)
[Android產品研發(十六)–>開發者選項](http://blog.csdn.net/qq_23547831/article/details/51809497)
[Android產品研發(十七)–>hybrid開發](http://blog.csdn.net/qq_23547831/article/details/51812985)
- 前言
- (一)–>實用開發規范
- (二)-->啟動頁優化
- (三)-->基類Activity
- (四)-->減小Apk大小
- (五)-->多渠道打包
- (六)-->Apk混淆
- (七)-->Apk熱修復
- (八)-->App數據統計
- (九)-->App網絡數據解析
- (十)-->盡量不使用靜態變量保存數據
- (十一)-->應用內跳轉Scheme協議
- (十二)-->App長連接實現
- (十三)-->App輪詢操作
- (十四)-->App升級與更新
- (十五)-->內存對象序列化
- (十六)-->開發者選項
- (十七)-->Hybrid開發
- (十八)-->webview問題集錦
- (十九)-->Android studio中的單元測試
- (二十)-->代碼Review
- (二十一)-->Android中的UI優化
- (二十二)-->Android實用調試技巧
- (二十三)-->Android中保存靜態秘鑰實踐
- (二十四)-->內存泄露場景與檢測
- (二十五)-->MVC/MVVM/MVP簡單理解