在進(jìn)行APP+H5混合開發(fā)的時(shí)候,一些功能是用native方法實(shí)現(xiàn)的,如登陸,一些功能是用H5實(shí)現(xiàn)的。所以往往需要將在native方法登陸的狀態(tài)同步到H5中避免再次登陸。這種情況在Android開發(fā)中比較常見靠抑,下面總結(jié)一下webview在項(xiàng)目中遇到的坑,順便給即將入坑的小伙伴一點(diǎn)小小的建議适掰,希望有所作用颂碧,相互學(xué)習(xí)進(jìn)步(大佬繞過……)
WebView常見需注意的API:
WebChromeClient是輔助WebView處理Javascript的對(duì)話框,網(wǎng)站圖標(biāo)攻谁,網(wǎng)站title稚伍,加載進(jìn)度等 :
onCloseWindow(關(guān)閉WebView)
onCreateWindow()
onJsAlert (WebView上alert是彈不出來東西的,需要定制你的WebChromeClient處理彈出)
onJsPrompt
onJsConfirm
onProgressChanged
onReceivedIcon
onReceivedTitle
WebView 調(diào)取本地文件上傳戚宦,部分代碼如下:
/**
*
* 繼承WebChromeClient可以做些其他的工作
*/
class ChromeClient extends WebChromeClient {
.
.
.
}
private ChromeClient mWebChromeClient = new ChromeClient() {
// android 5.0 這里需要使用android5.0 sdk
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
WebChromeClient.FileChooserParams fileChooserParams) {
Log.d("", "onShowFileChooser");
if (mUploadMessageForAndroid5 != null) {
mUploadMessageForAndroid5.onReceiveValue(null);
}
mUploadMessageForAndroid5 = filePathCallback;
/**
* 標(biāo)準(zhǔn)意圖个曙,被發(fā)送到相機(jī)應(yīng)用程序捕獲一個(gè)圖像,并返回它受楼。通過一個(gè)額外的extra_output控制這個(gè)圖像將被寫入垦搬。
* 如果extra_output是不存在的,
* 那么一個(gè)小尺寸的圖像作為位圖對(duì)象返回艳汽。如果extra_output是存在的猴贰,那么全尺寸的圖像將被寫入extra_output
* URI值。
*/
takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
File photoFile = null;
try {
// 設(shè)置MediaStore.EXTRA_OUTPUT路徑,相機(jī)拍照寫入的全路徑
photoFile = createImageFile();
takePictureIntent.putExtra("PhotoPath", mCameraPhotoPath);
} catch (Exception ex) {
Log.e("WebViewSetting", "Unable to create Image File", ex);
}
if (photoFile != null) {
// cameraUri = Uri.fromFile(photoFile);
cameraUri = getUriForFile(context, photoFile);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, cameraUri);
System.out.println(mCameraPhotoPath);
} else {
takePictureIntent = null;
}
}
showChioceDialog();
return true;
}
// 針對(duì) 3.0--
public void openFileChooser(ValueCallback<Uri> uploadMsg) {
Log.d("asdf", "openFileChooser1");
openFileChooserImpl(uploadMsg);
}
// 針對(duì) 3.0+
public void openFileChooser(ValueCallback uploadMsg, String acceptType) {
Log.d("asdf", "openFileChooser2");
openFileChooserImpl(uploadMsg);
}
// For Android 4.1
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
Log.d("asdf", "openFileChooser3");
openFileChooserImpl(uploadMsg);
}
};
兼容Android 7.0訪問權(quán)限
/**
* 適配7.0及以上
*
* @param context
* @param file
* @return
*/
private static Uri getUriForFile(Context context, File file) {
if (context == null || file == null) {
throw new NullPointerException();
}
Uri uri;
if (Build.VERSION.SDK_INT >= 24) {
uri = FileProvider.getUriForFile(context.getApplicationContext(), "com.****.fileprovider", file);
} else {
uri = Uri.fromFile(file);
}
return uri;
}
文件選擇回調(diào)上傳處理:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
UMShareAPI.get(this).onActivityResult(requestCode, resultCode, data);
if (requestCode == FILECHOOSER_RESULTCODE) {
if (null == mUploadMessage && null == mUploadMessageForAndroid5)
return;
if (mUploadMessage != null) {
Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
mUploadMessage.onReceiveValue(result);
mUploadMessage = null;
} else if (mUploadMessageForAndroid5 != null) {
Uri result = (data == null || resultCode != RESULT_OK) ? null : data.getData();
if (result != null) {
mUploadMessageForAndroid5.onReceiveValue(new Uri[] { result });
} else {
mUploadMessageForAndroid5.onReceiveValue(new Uri[] {});
}
mUploadMessageForAndroid5 = null;
}
}
if (requestCode == INPUT_FILE_REQUEST_CODE) {
if (null == mUploadMessage && null == mUploadMessageForAndroid5)
return;
afterOpenCamera();
Uri uri = cameraUri;
Uri[] uris = new Uri[1];
uris[0] = uri;
if (mUploadMessageForAndroid5 != null) {
mUploadMessageForAndroid5.onReceiveValue(uris);
mUploadMessageForAndroid5 = null;
} else {
mUploadMessage.onReceiveValue(uri);
mUploadMessage = null;
}
}
}
WebView 圖片延遲加載
有些頁(yè)面如果包含網(wǎng)絡(luò)圖片河狐,在移動(dòng)設(shè)備上我們等待加載圖片的時(shí)間可能會(huì)很長(zhǎng)米绕,所以我們需要讓圖片延時(shí)加載,這樣不影響我們加載頁(yè)面的速度馋艺,同樣代碼說話:
定義變量:
boolean blockLoadingNetworkImage=false;
在WebView初始化的時(shí)候設(shè)置栅干,就是這么簡(jiǎn)單就可以了:
blockLoadingNetworkImage = true;
webView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
//返回值是true的時(shí)候控制去WebView打開,為false調(diào)用系統(tǒng)瀏覽器或第三方瀏覽器
return true;
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
if (!blockLoadingNetworkImage){
webView.getSettings().setBlockNetworkImage(true);
}
}
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
if (blockLoadingNetworkImage){
webView.getSettings().setBlockNetworkImage(false);
}
}
});
JS調(diào)用native
js調(diào)用原生大概有兩種方法
截取url捐祠,獲取指定的url碱鳞,例如頁(yè)面上有個(gè)撥打電話的調(diào)用,我們就可以在wenview中這樣截弱庵:
webView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
//返回值是true的時(shí)候控制去WebView打開窿给,為false調(diào)用系統(tǒng)瀏覽器或第三方瀏覽器
Log.e("zhaogl", url);
if (url.startsWith("tel:")){//撥打電話
String[] ss = url.split(":");
if (ss.length > 1 && ss[1] != null){
CommonUtil.call(WebViewActivity.this,ss[1]);
}
}else if (url.equalsIgnoreCase("http://www.baidu.com/")){
WebViewActivity.this.finish();
}else {
view.loadUrl(url);
}
return true;
}
});
調(diào)用Java寫好的方法:
首先我們要定義一個(gè)方法給js調(diào)用,這里我把這個(gè)方法封裝到一個(gè)類中:
public class InJavaScript {
private static InJavaScript instance;
public InJavaScript() {
}
public static InJavaScript getInstance() {
if (instance == null){
instance = new InJavaScript();
}
return instance;
}
/**
* 分享
* @param str 內(nèi)容
* @param targetUrl url
*/
@JavascriptInterface
public void runOnAndroidShare(String str,String targetUrl) {
WebViewEvent event = new WebViewEvent();
event.isShare = true;
event.content = str;
event.url = targetUrl;
EventBus.getDefault().post(event);
}
/**
* 關(guān)閉 window.close 不起作用率拒,代替之
*/
@JavascriptInterface
public void closeWindowForAndroid(){
WebViewEvent event = new WebViewEvent();
event.isCloseWindow = true;
EventBus.getDefault().post(event);
}
}
寫好的java類及方法如果想讓js調(diào)用崩泡,還需要有如下設(shè)置:
webView.addJavascriptInterface(InJavaScript.getInstance(), "injs");
js中就可以用以下方式調(diào)用:
function sendToAndroid(){
window.injs.runOnAndroidShare(str,str);//調(diào)用android的函數(shù)
}
注意猬膨,第二中方法在4.2以下版本存在js安全漏洞允华,但是目前市場(chǎng)上的安卓大部分都已經(jīng)在4.2以上了,所以如果你的項(xiàng)目安全要求不是那么高,可以正常使用⊙ゼ牛現(xiàn)在有很多第三方的框架解決這個(gè)問題,大家可以去自己查找召耘。
解決部分手機(jī)支持webview顯示空白
因?yàn)橛捎∠蠊P記備注總結(jié)過后的百炬,參考博客沒有備注,如有雷同敬請(qǐng)留言添加
LAYER_TYPE_SOFTWARE
:
無論硬件加速是否打開污它,都會(huì)有一張Bitmap(software layer)剖踊,并在上面對(duì)WebView進(jìn)行軟渲染。
好處:
在進(jìn)行動(dòng)畫衫贬,使用software可以只畫一次View樹德澈,很省。
什么時(shí)候不要用:
View樹經(jīng)常更新時(shí)不要用固惯。尤其是在硬件加速打開時(shí)梆造,每次更新消耗的時(shí)間更多。因?yàn)殇秩就赀@張Bitmap后還需要再把這張Bitmap渲染到hardware layer上面去葬毫。
LAYER_TYPE_HARDWARE
:
硬件加速關(guān)閉時(shí)镇辉,作用同software。
硬件加速打開時(shí)會(huì)在FBO(Framebuffer Object)上面做渲染贴捡,在進(jìn)行動(dòng)畫時(shí)忽肛,View樹也只需要畫一次。
兩者區(qū)別:
1烂斋、一個(gè)是渲染到Bitmap枫夺,一個(gè)是渲染到FB上躁劣。
2、hardware可能會(huì)有一些操作不支持。
兩者相同:
都是開了一個(gè)buffer法竞,把View畫到這個(gè)buffer上面去。
LAYER_TYPE_NONE
:
這個(gè)就比較簡(jiǎn)單了坐漏,不為這個(gè)View樹建立單獨(dú)的layer
PS:GLSurfaceView和WebView默認(rèn)Layertype都是none苫幢。
GLSurfaceView
:
給GLSurfaceView設(shè)置為software或者h(yuǎn)ardware后,發(fā)現(xiàn)什么也畫不出來了图张。得出結(jié)論:GLSurfaceView的Layer type只能是none
WebView
:
以前使用WebView時(shí)碰到過一個(gè)問題锋拖,如果在WebView上面使用Animation,WebView的繪畫區(qū)域不動(dòng)祸轮。當(dāng)時(shí)的解決方案是在進(jìn)行動(dòng)畫之前對(duì)WebView進(jìn)行截屏(drawingcache)兽埃。按上面的道理試了一下,設(shè)置一個(gè)hardware或者software的layer就OK了适袜。
現(xiàn)在又碰到了另外一個(gè)問題柄错,打開硬件加速后,在一些機(jī)器上面(我的是3.2)WebView有時(shí)會(huì)出現(xiàn)某一塊區(qū)域白屏的問題。默認(rèn)的layer type是none售貌,改為hardware也不行给猾,設(shè)置為software就解決了。當(dāng)然關(guān)閉硬件加速也好了颂跨,可是那樣的話程序整體就比較慢了敢伸。所以最終方案是整體硬件加速,出問題的WebView設(shè)置software
- 可以讓3D的繪制更快一些:getHolder().setType(SurfaceHolder.SURFACE_TYPE_HARDWARE);
- 在硬件加速開啟的情況下GLSurfaceView一旦被從View樹上摘下來恒削,會(huì)使整個(gè)窗口背景變黑池颈,即使設(shè)置layer type為software也不管用。
經(jīng)過兩天的排查钓丰,發(fā)現(xiàn)了原因躯砰,我的程序是在C層由drawFrame(屬于GLThread線程)來驅(qū)動(dòng)進(jìn)行繪畫,當(dāng)GLSurfaceView被摘下來時(shí)携丁,GLSurfaceView的destroy方法被調(diào)用琢歇,我在destroy方法(屬于UI線程)中直接調(diào)用 了GLThread線程的結(jié)束方法。而GLSurfaceView.creat,sizeChanged,destroyed在UI線程则北,Render.create,sizeChanged,drawFrame在GLThread線程矿微。因此,出現(xiàn)了UI線程直接調(diào)用GLThread線程的方法的問題尚揣。最終通過GLSurfaceView.queueEvent向GLThread線程發(fā)送Runnable涌矢,問題得到解決。
看來快骗,還是軟渲染的容錯(cuò)能力比較強(qiáng)娜庇,一開硬件加速,底層就比較脆弱了方篮。
結(jié)論:一定要搞清楚哪個(gè)是UI線程名秀,哪個(gè)是GLThread線程。 - hardware acclerator是對(duì)整個(gè)窗口進(jìn)行加速藕溅,在硬件加速打開時(shí)View.isHardwareAcclerator返回true匕得。但每個(gè)View可能被渲染到的Canvas是不同的,比如View可能被通過setLayer設(shè)置了Layer巾表,這時(shí)汁掠,Canvas.isHardwareAccelerator返回false
- Android提供了三種硬件加速是否打開的控制級(jí)別,分別是Application,Activity,Window,View集币。
WebView的cookie機(jī)制相關(guān)問題(后續(xù)繼續(xù)補(bǔ)充……)
webview cookie同步
因?yàn)閍ndroid不會(huì)自動(dòng)同步cookie到WebView考阱。做iOS開發(fā)則不用擔(dān)心這個(gè)問題,因?yàn)閕os內(nèi)部已經(jīng)實(shí)現(xiàn)了cookie同步鞠苟。本文將會(huì)介紹兩種cookie同步的方式乞榨,并重點(diǎn)分析WebView的cookie機(jī)制秽之。在開始之前先講一下基于session的登錄驗(yàn)證。
Cookie的重要作用是回話識(shí)別(SeesionId)和狀態(tài)長(zhǎng)期保持(在瀏覽器保存需要長(zhǎng)期保持的數(shù)據(jù))吃既。
Cookie在安卓中的使用方式--標(biāo)示會(huì)話考榨,附加信息
- 通過Session標(biāo)示一次會(huì)話,舉個(gè)例子:注冊(cè)時(shí)鹦倚,判斷客戶端注冊(cè)錯(cuò)誤次數(shù)(注冊(cè)次數(shù)已經(jīng)超過限制董虱,顯示驗(yàn)證碼)
- 傳遞附加數(shù)據(jù),舉個(gè)例子:傳遞單點(diǎn)登陸的token申鱼。
這里需要注意的是在設(shè)置cookie之后,是不能設(shè)置以下屬性的云头,否則cookie是無效的(不只是這些屬性捐友,這里只是舉例,最好的方式是在執(zhí)行l(wèi)oadurl之前再設(shè)置cookie)
mWvSignUp.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
mWvSignUp.getSettings().setJavaScriptEnabled(true);
mWvSignUp.getSettings().setDatabaseEnabled(true);
mWvSignUp.getSettings().setDomStorageEnabled(true);
一些ajax請(qǐng)求需要帶入cookie怎么辦溃槐?
Android 5.0以下
:
mWvSignUp.setWebViewClient(new WebViewClient() {
/**
* 5.0以下
* @param view
* @param url
* @return
*/
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
syncCookie(url);
return super.shouldInterceptRequest(view, url);//將加好cookie的url傳給父類繼續(xù)執(zhí)行
}
});
Android 5.0以上
:
mWvSignUp.setWebViewClient(new WebViewClient() {
@SuppressLint("NewApi")
@Override
public WebResourceResponse shouldInterceptRequest(WebViewview, WebResourceRequest request) {
String url = request.getUrl().toString();
syncCookie(url);
return super.shouldInterceptRequest(view, url);//因?yàn)楦?.0以下的方法返回值是同一個(gè)類匣砖,所以這里偷懶直接調(diào)動(dòng)4.0方法生成請(qǐng)求
});