说正文之前,先来个故事提提神,如有雷同,纯属巧合:
开始,公司主项目只有一个m站
领导:我们要开发原生的APP,一切向m站看齐,时间紧,任务中,努力吧,骚年!
码农:领导,放心,保证完成任务。霹雳啪哒。。。。开始了
码农:领导完成了。但是,很多没有接口,都是本地数据
领导看了看,说:还行,可以交差了;现在,另外一个项目需要重做,上阵吧
码农:领导那个完成了。
领导:好的,情况有变,那个你们就放市场上不用管了,回来接着弄这个主项目吧。然后,玩了玩主项目的APP说,不对啊,怎么数据都跟m站的不一样啊。
此时,m站已经更新了好几版了。。。
码农:看了看项目,说有些数据是本地的,有些数据是因为接口没有了。。。。
领导:那你们赶紧按照m站改吧。
码农:好的。
领导:你们改完没,我能不能每天看到你们做的新东西啊
码农:m站都没有改完,我们更没有了。。想每天都能看到我们做的新东西的话,比较困难。。
领导:你们看m站更新挺快的,有没有办法把m站嵌入APP啊
码农:可以啊。然后,本篇文章诞生了。。。。
正式开始
简介: Webview是Android系统控件。在Android4.4以下WebView采用WebKit内核,在Android4.4以上采用chromium内核。Webkit 默认采用JavaScriptCore引擎,效率十分的低效;在Android4.4以上换成了V8引擎,直接提升了JavaScript性能。详细介绍点击这里
技术点:
1,原生跟Html交互
2,调用原生相机相册
3,页面重定向
4,缓存问题
5,加载失败处理
6,优化问题
一、原生跟Html交互
一般项目,涉及到会经常变动的页面,一般都会采用html的方式来做。用到html,一般就会涉及到原生跟html的交互了。
- Html调用原生
用Html 调用原生一般有两种做法:1,WebView.addJavascriptInterface。2,拦截url(一般双方会定一个协议,来拦截)
-
- 第一种
JS
- 第一种
function getCreditCard(num,appSessionId){
var appSessionArry = [num,appSessionId];
try{
//Android
window.androidCreditManage.demo(appSessionArry);
}catch(err){
//IOS
window.webkit.messageHandlers.creditManage.postMessage(appSessionArry);
};
return num;
};
java代码
//允许JS交互
settings.setJavaScriptEnabled(true);
//JS交互
mWebView.addJavascriptInterface(new JSInterface(), "androidCreditManage");
···
final class JSInterface {
//4.2系统以上只有添加这个注解才可以
@JavascriptInterface
public void demo(final String[] num) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (num == null || num.length <= 0) {
return;
}
if (num.length >= 2) {
SharePreferHelper.setTest(num[1]);
}
getCorpId(num[0]);
}
});
}
}
-
- 第二种,url拦截
通过复写 WebViewClient 的shouldOverrideUrlLoading(WebView view, String url)方法
@Override public boolean shouldOverrideUrlLoading(WebView view, String url) { if (interceptUrl(url)) { return true; } return super.shouldOverrideUrlLoading(view, url); } ··· //判断是否符合协议,进行拦截 public boolean interceptUrl(String url) { //栗子:url=js:"js://token?arg=1111" //Uri uri = Uri.parse(url); //uri.getScheme();// =js //uri.getAuthority(); //= token //Set<String> params = uri.getQueryParameterNames();// 参数 return false; }
- 第二种,url拦截
原生调用HTML
1, JS代码
function showCreditIcon(){ //something };
2,java代码
通常情况下我们是用下面来调用
mWebView.loadUrl("javascript:showCreditIcon()");
上面的调用效率并不高,在Android4.4及以上提供了下面的API。所以,我们可以在Android4.4以上采用下面的方式,在Android4.4一下采用上面的方式。
mWebView.evaluateJavascript("javascript:showCreditIcon()", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
//TODO: 处理返回的结果
}
});
二、调用原生相机相册
因为项目用的是嵌套m站的方式,m站在浏览器是调用浏览器的访问文件方式。但是,在嵌套到原生APP里面后。。。不行啦!
然后,我们需要复写WebChromeClient的openFileChooser,onShowFileChooser(5.0及以上系统)方法来用原生实现调取拍照,相册功能的过程。
代码如下:
//1,告诉客户端显示一个文件选择器
//2,这是处理HTML form表单的 type是'file'的button.
//3,取消请求调用filePathCallback.onReceiveValue(null)
//4,如果实现了这个方法返回true,必须调用一个 返回结果或者取消请求
/**
* Tell the client to show a file chooser.
*
* This is called to handle HTML forms with 'file' input type, in response to the
* user pressing the "Select File" button.
* To cancel the request, call <code>filePathCallback.onReceiveValue(null)</code> and
* return true.
*
* @param webView The WebView instance that is initiating the request.
* @param filePathCallback Invoke this callback to supply the list of paths to files to upload,
* or NULL to cancel. Must only be called if the
* <code>showFileChooser</code> implementations returns true.
* @param fileChooserParams Describes the mode of file chooser to be opened, and options to be
* used with it.
* @return true if filePathCallback will be invoked, false to use default handling.
*
* @see FileChooserParams
*/
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
super.onShowFileChooser(webView, filePathCallback, fileChooserParams);
//打开选择相册相机dialog
mFilePathCallback = filePathCallback;
//如果filePathCallback被调用返回true,默认处理返回false
return true;
}
public void openFileChooser(ValueCallback<Uri> filePathCallback) {
//打开选择相册相机dialog
mFilePathCallbackOld = filePathCallback;
}
public void openFileChooser(ValueCallback filePathCallback, String acceptType) {
//打开选择相册相机dialog
mFilePathCallbackOld = filePathCallback;
}
public void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType, String capture) {
//打开选择相册相机dialog
mFilePathCallbackOld = filePathCallback;
}
处理调取后的结果
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
/**
* 处理页面返回或取消选择结果
*/
switch (requestCode) {
case REQUEST_CODE_PICK_PHOTO:
pickPhotoResult(resultCode, data);
break;
case REQUEST_CODE_TAKE_PHOTO:
takePhotoResult(resultCode);
break;
default:
break;
}
}
···
private void pickPhotoResult(int resultCode, Intent data) {
//m站调用文件管理器
if (mFilePathCallback != null) {
Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
if (result != null) {
//获取路径
String path =getPath(this, result);
Uri uri = Uri.fromFile(new File(path));
//返回给mFilePathCallback路径的uri
mFilePathCallback.onReceiveValue(new Uri[]{uri});
} else {
/**
* 实现了方法,必须返回结果或返回null.不然,无法重复回调
*/
mFilePathCallback.onReceiveValue(null);
mFilePathCallback = null;
}
}
}
三、页面重定向
在我们使用WebView的过程中,尤其,加载第三方Url的时候,不可避免的会遇到重定向的问题
一般的处理方式有两个:
1,自己维护一个webview的历史栈,自己实现返回上一个页面
2,重写WebViewClient shouldOverrideUrlLoading()方法
在项目中,我用的是第一种方式,具体参考:点我点我
第二种方式其实非常简单,我使用的测试机器(有限)也没有问题,如下
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
//就是直接返回false
return false;
}
最后,查资料还发现了一种解决方式,也是复写shouldOverrideUrlLoading()方法;
但是,在4.2的模拟器上,发下无法解决问题,代码如下
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
WebView.HitTestResult hitTestResult = view.getHitTestResult();
if (!TextUtils.isEmpty(url) && hitTestResult == null) {
view.loadUrl(url);
return true;
}
return super.shouldOverrideUrlLoading(view, url);
}
四、缓存问题
webview的缓存模式有五种:
1,LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据
2,LOAD_DEFAULT: 根据cache-control决定是否从网络上取数据。
3,LOAD_CACHE_NORMAL: API level 17中已经废弃, 从API level 11开始作用同LOAD_DEFAULT模式
4,LOAD_NO_CACHE: 不使用缓存,只从网络获取数据.
5,LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。
使用时候:一般推荐有网的时候使用DFAULT,没网使用LOAD_CACHE_ELSE_NETWORK
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);
···
/**
* 清除WebView缓存
*/
public void clearWebViewCache(){
//清理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);
}
}
五、加载失败处理
加载失败也是加载完成了,所以 ,WebChromeClient的onProgressChanged(WebView view, int newProgress)方法中newProgress也会走到100,用这个方法并不能解决问题。
加载失败处理,一般是复写 WebViewClient的onReceivedError(方法)。 需要注意的是: 这个方法在api 23里面 已经 deprecation。如果需要继续使用 则在该方法上面加入 @SuppressWarnings(“deprecation”),并且这个方法并不能捕获404。
@SuppressWarnings("deprecation")
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
super.onReceivedError(view, errorCode, description, failingUrl);
showErrorView();
}
在api 23以上通过onReceivedHttpError()可以捕获
@Override
public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
super.onReceivedHttpError(view, request, errorResponse);
}
六、优化问题
- 1,WebView的释放问题
在页面结束时,资源要自己释放,不然,退出了内存也会很高。
应用内存使用命令:
adb shell dumpsys meminfo -a <’process id>/<’process name>
详细介绍可以看这里:点我点我
/**
* 释放WebView
*/
private void recycleWebView(){
if(mWebView != null){
mRootView.removeView(mWebView);
mWebView.removeAllViews();
mWebView.destroy();
}
}
@Override
protected void onDestroy() {
recycleWebView();
super.onDestroy();
}
- 其他的暂时还没有尝试。如,常用资源预加载,常用 JS 本地化及延迟加载等。
优化可以参考这里
一些注意事项
1,在android 5.0以上WebView要同时加载http和https,需要如下设置
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
webView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
2,当然,你也可以选择鹅厂的TBS。相关文章介绍
最后
现在,项目只有一个模块是用原生做,其他的都是嵌套m站,嵌套m站部分基本快完成了,这里总结了项目大部分用到的东西。有不对的地方,欢迎指出;有好的建议,也欢迎留言。