Android爬坑之旅之WebView.docx
- 文档编号:24692969
- 上传时间:2023-05-31
- 格式:DOCX
- 页数:16
- 大小:36.76KB
Android爬坑之旅之WebView.docx
《Android爬坑之旅之WebView.docx》由会员分享,可在线阅读,更多相关《Android爬坑之旅之WebView.docx(16页珍藏版)》请在冰豆网上搜索。
Android爬坑之旅之WebView
Android爬坑之旅之WebView
对于用户体验要求较高或者与硬件交互较多的功能我们一般都会采用Native原生的方式来实现。
而用户交互少,偏展示类,活动类的功能我们则通常采用H5的方式来实现,
例如新闻类的app,详情展示页一般就是H5的页面
一方面图文排版上web有着先天的优势,同时纯展示类的页面在目前的移动设备上,性能体验已经很难让用户分辨是网页还是原生了;
另一方面,H5的页面跨平台,方便在原生客户端上实现分享功能,拥有较强的传播性,我们平时常见的活动页面也拥有这样的优势,所以你看到的活动页面也基本都是H5,只需轻轻一点就能分享到各个平台;
同时,H5的页面开发降低了开发成本,一套代码,web,android,ios都能访问。
(然而实际开发过程中,H5的适配也都是各种泪)
既然HybirdApp有这么多优势,那在Android中我们通过什么样的方式在原生项目中嵌入H5页面呢?
那就不得不提到我们的WebVew了,作为官方唯一用来显示web的组件,
展示网页这样的任务也只能交给它了。
AViewthatdisplayswebpages.ThisclassisthebasisuponwhichyoucanrollyourownwebbrowserorsimplydisplaysomeonlinecontentwithinyourActivity.ItusestheWebKitrenderingenginetodisplaywebpagesandincludesmethodstonavigateforwardandbackwardthroughahistory,zoominandout,performtextsearchesandmore.
引用官方文档的一句话:
WebView是一个用来在Activity中显示我们网页的视图组件,它通过webkit渲染引擎渲染和显示我们的web页面,并且包含了web的历史导航操法,页面放大缩小,文本搜索等方法。
我们首先来看一下WebView的基本用法:
WebView的基本用法
关于WebView的基本用法,大部分人也是轻车熟路,
了解完WebView的基本用法,那就来总结下最近项目中遇到的关于WebView的坑
项目中使用WebView遇到的问题
如图所示,
一般情况下,我们WebView所在界面由顶部带标题的原生导航栏跟WebView的内容部分组成,
而WebView中的界面可能在点击后还会再跳其他Web页面(如图点击请假会在当前WebView跳转请假的Web页面)。
由于点击内容的不确定性,所以通常情况下,最简单的做法就是捕获h5页面的标签来进行标题设置。
对于捕获标签内容的方式,WebView也很好地提供了支持,我们可以通过继承WebChromeClient的onReceivedTitle来进行获取:
privateclassWebViewChromeClientextendsWebChromeClient{
@Override
publicvoidonReceivedTitle(WebViewview,Stringtitle){
super.onReceivedTitle(view,title);
mTitleText.setTitle(String.valueOf(view.getTitle()));
}
}
然而这样的方式在实际使用中有一个问题:
当通过webView.goBack()方式返回上一级Web页面的时候不会触发这个方法,因此会导致标题无法跟随历史记录返回上一级页面。
所以在项目中,
我们可以通过重写WebViewClient的onPageFinished方法,在onPageFinished中对界面标题进行设置。
因为不管是历史记录的返回还是点击跳转都会触发页面加载,
当页面加载完成时(不包括js动态创建以及img图片加载完毕)都会触发onPageFinished这个方法,
此时我们去获取的标题内容不会有任何问题,可以确保在页面返回时能够获取到正确的标题。
mWebView.setWebViewClient(newWebViewClient(){
//Web页面每次加载并完成时会触发该方法
@Override
publicvoidonPageFinished(WebViewview,Stringurl){
super.onPageFinished(view,url);
mToolbar.setTitle(String.valueOf(view.getTitle()));
Log.i(LOG_TAG,"onPageFinished");
}
});
注:
这种做法有一个缺陷,就是返回上一个界面的时候,等页面加载完成的时候标题才会显示出来,为了更好地优化,我们可以创建一个集合用来保存我们的标题,加载url的时候把标题添加进集合,当返回上一级页面的时候,从集合中取出标题进行显示,同时从集合中移除标题。
WebView中的Web页面存在标签时无法打开文件选择器
在我们的手机浏览器中,当web页面中有按钮标签的时候点击会自动打开系统的文件选择器,
然而这个功能在主流系统的WebView中没有被默认实现,
因此,为了让点击时能够打开系统的文件选择器,
我们必须通过重写WebChromeClient来实现点击打开系统文件选择器。
代码如下:
publicclassMainActivityextendsAppCompatActivity{
/**Android5.0以下版本的文件选择回调*/
protectedValueCallback
/**Android5.0及以上版本的文件选择回调*/
protectedValueCallback
protectedstaticfinalintREQUEST_CODE_FILE_PICKER=51426;
protectedStringmUploadableFileTypes="image/*";
privateWebViewmWebView;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initWebView();
}
privatevoidinitWebView(){
mWebView=(WebView)findViewById(R.id.my_webview);
mWebView.loadUrl("file:
///android_asset/index.html");
mWebView.setWebChromeClient(newOpenFileChromeClient());
}
privateclassOpenFileChromeClientextendsWebChromeClient{
//Android2.2(APIlevel8)到Android2.3(APIlevel10)版本选择文件时会触发该隐藏方法
@SuppressWarnings("unused")
publicvoidopenFileChooser(ValueCallback
openFileChooser(uploadMsg,null);
}
//Android3.0(APIlevel11)到Android4.0(APIlevel15))版本选择文件时会触发,该方法为隐藏方法
publicvoidopenFileChooser(ValueCallback
openFileChooser(uploadMsg,acceptType,null);
}
//Android4.1(APIlevel16)--Android4.3(APIlevel18)版本选择文件时会触发,该方法为隐藏方法
@SuppressWarnings("unused")
publicvoidopenFileChooser(ValueCallback
openFileInput(uploadMsg,null,false);
}
//Android5.0(APIlevel21)以上版本会触发该方法,该方法为公开方法
@SuppressWarnings("all")
publicbooleanonShowFileChooser(WebViewwebView,ValueCallback
if(Build.VERSION.SDK_INT>=21){
finalbooleanallowMultiple=fileChooserParams.getMode()==FileChooserParams.MODE_OPEN_MULTIPLE;//是否支持多选
openFileInput(null,filePathCallback,allowMultiple);
returntrue;
}
else{
returnfalse;
}
}
}
@SuppressLint("NewApi")
protectedvoidopenFileInput(finalValueCallback
//Android5.0以下版本
if(mFileUploadCallbackFirst!
=null){
mFileUploadCallbackFirst.onReceiveValue(null);
}
mFileUploadCallbackFirst=fileUploadCallbackFirst;
//Android5.0及以上版本
if(mFileUploadCallbackSecond!
=null){
mFileUploadCallbackSecond.onReceiveValue(null);
}
mFileUploadCallbackSecond=fileUploadCallbackSecond;
Intenti=newIntent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
if(allowMultiple){
if(Build.VERSION.SDK_INT>=18){
i.putExtra(Intent.EXTRA_ALLOW_MULTIPLE,true);
}
}
i.setType(mUploadableFileTypes);
startActivityForResult(Intent.createChooser(i,"选择文件"),REQUEST_CODE_FILE_PICKER);
}
publicvoidonActivityResult(finalintrequestCode,finalintresultCode,finalIntentintent){
if(requestCode==REQUEST_CODE_FILE_PICKER){
if(resultCode==Activity.RESULT_OK){
if(intent!
=null){
//Android5.0以下版本
if(mFileUploadCallbackFirst!
=null){
mFileUploadCallbackFirst.onReceiveValue(intent.getData());
mFileUploadCallbackFirst=null;
}
elseif(mFileUploadCallbackSecond!
=null){//Android5.0及以上版本
Uri[]dataUris=null;
try{
if(intent.getDataString()!
=null){
dataUris=newUri[]{Uri.parse(intent.getDataString())};
}
else{
if(Build.VERSION.SDK_INT>=16){
if(intent.getClipData()!
=null){
finalintnumSelectedFiles=intent.getClipData().getItemCount();
dataUris=newUri[numSelectedFiles];
for(inti=0;i dataUris[i]=intent.getClipData().getItemAt(i).getUri(); } } } } } catch(Exceptionignored){} mFileUploadCallbackSecond.onReceiveValue(dataUris); mFileUploadCallbackSecond=null; } } } else{ //这里mFileUploadCallbackFirst跟mFileUploadCallbackSecond在不同系统版本下分别持有了 //WebView对象,在用户取消文件选择器的情况下,需给onReceiveValue传null返回值 //否则WebView在未收到返回值的情况下,无法进行任何操作,文件选择器会失效 if(mFileUploadCallbackFirst! =null){ mFileUploadCallbackFirst.onReceiveValue(null); mFileUploadCallbackFirst=null; } elseif(mFileUploadCallbackSecond! =null){ mFileUploadCallbackSecond.onReceiveValue(null); mFileUploadCallbackSecond=null; } } } } } 注: 当用户点击inputfile弹出文件选择器后,点击取消或者返回按钮没有执行选择时,必须在onActivityResult里给valueCallback的onReceiveValue传null,因为valueCallback持有的是WebView,在onReceiveValue没有回传值的情况下,WebView无法进行下一步操作,会导致取消选择文件后,点击inputfile不会再响应: if(mFileUploadCallbackFirst! =null){ mFileUploadCallbackFirst.onReceiveValue(null); mFileUploadCallbackFirst=null; } elseif(mFileUploadCallbackSecond! =null){ mFileUploadCallbackSecond.onReceiveValue(null); mFileUploadCallbackSecond=null; } WebView中的web页面调用系统选择器或者相机导致app进入后台被系统释放 众所周知,WebView基于webkit内核来渲染web页面,因此使用起来相当于一个小型浏览器,即使页面内容不复杂,只要使用WebView也会占用大量的内存。 而Android的内存回收机制,在系统内存不足的情况下会优先释放内存占用较大的app从而回收内存资源,此时正在使用WebView的运行在后台的app肯定是首当其冲被回收的。 因此,当WebView通过inputfile调用系统文件选择器,或者通过文件选择器调用了相机时,我们的app就进入了后台,在部分低端Android设备(尤其红米这类手机,默认的神隐模式会在app进入后台的时候较大概率的释放app)或者系统内存资源不足的情况下,我们的app就会优先被释放掉,导致文件选择完毕后,回到上一界面时,app的界面重新走了onCreate,web页面也因此重建了。 对于部分需要填写大量表单的web页面来说,用户填写的数据会随着界面的销毁重建而丢失,而选择的文件也因为页面的重建而无法回传给inputfile,这对于用户的体验来说肯定是不友好的。 也许你会说,重写onSaveInstance保存数据就是啦。 这也是我一开始考虑的, 我们的WebView也提供了saveState以及restoreState来保存状态。 然而悲催的是,这两个方法并不会保存web页面内的数据,它只保存了WebView加载的页面,前进后退的历史状态等数据。 引用官方文档的描述: SavesthestateofthisWebViewusedinonSaveInstanceState(Bundle) .PleasenotethatthismethodnolongerstoresthedisplaydataforthisWebView.ThepreviousbehaviorcouldpotentiallyleakfilesifrestoreState(Bundle) wasneverlled. PleasenotethatthismethodnolongerstoresthedisplaydataforthisWebView WebView的saveState并不会保存界面的数据。 所以,对于表单数据的恢复,我们只能自己想办法了,我们这里采用了两套方案: 1.通过WebView与JS交互,在onSaveInstance的时候触发界面保存数据,保存数据的方式也大体分为两种, 一种使用H5自带的localStorage来进行数据存储,页面销毁重建的时候H5页面判断本地localStorage数据是否有值,有就将值重新填充到页面表单,提交数据后清除本地localStorage的数据。 这种方式需要给WebView开启对localStorage的支持。 WebSettingssettings=mWebView.getSettings(); settings.setDomStorageEnabled(true); 另一种则提供JS接口将数据传递给原生,通过原生代码将数据保存到本地,在页面重建渲染完成时,web页面通过JS接口调用原生方法拉取数据判断是否有值,有则填充表单,无则不做操作,提交数据后调用JS接口调用原生方法清空本地数据。 由web端自己处理,在表单页面文本输入失去焦点时自动保存数据,页面销毁重建时,自己拉取数据进行判断。 这种方式对原生的依赖较低,个人更倾向这种方式,当然最终由于项目的特殊情况,我们还是采用了第一种方式。 这是表单数据的恢复, 而对于从系统文件选择器选择的文件web页面是无法直接接收并处理了,这里我们提供了一个JS接口在web页面加载完成时,进行触发,并将数据传递给web页面。 说到这里,不得不提另外一个问题 WebView调用服务端页面如何访问本地文件 上面我们提到了通过JS接口将选择的文件数据传递给web页面, 然而由于安全原因,WebView限制了远程url页面访问本地文件, 如果我们加载的url是服务端的页面,那我们没有任何办法直接通过文件地址来访问客户端本地的文件 我们知道,WebView用来加载网页的方式主要有三种: loadUrl(Stringurl) loadUrl(Stringurl,Map loadData(Stringdata,StringmimeType,Stringencoding) loadDataWithBaseURL(StringbaseUrl,Stringdata,StringmimeType,Stringencoding,StringhistoryUrl) loadData()和loadDataWithBaseURL()都是直接将数据加载进WebView中,相当于显示的一个本地Web loadUrl也可以通过访问本地的文件地址(例如本地asset目录下的存放了index.html页面,可以通过loadUrl("file: ///android_asset/index.html")来显示web页面) 对于这样的三种加载本地内容的方式,我们可以使用多种方式来传递路径供web页面传递,这里以图片为例(相册目录下test/IMG_20170105_093405.jpg): 1.直接通过文件的绝对地址来提供给页面显示: ///storage/emulated/0/dcim/test/IMG_20170105_093405.jpg'/> 2.通过媒体库查询出来的contenturi地址展示 //media/external/images/media/102610'/> 3.通过FileProvider转换的contenturi地址展示 //com.test.myfileprovider/dcim/test/IMG_20170105_093405.jpg'/> 可当你使用loadUrl(Stringurl)加载服务端的http地址时,以上三种方法将均无法使用,经过各种尝试,目前找到两种方案来提供给web端进行
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Android WebView