从资源更新原理来看Last-Modified和Etag基本是类似的,那为什么http协议中要搞2个标识呢?
Last-Modified存在的问题:
1、在集群服务器上各个服务器上的文件时间可能不同。
2、如果用旧文件覆盖新文件,因为时间更前,浏览器不会请求这个更旧的文件。
3、时间精度为s级,对文件修改精度有严格要求的场景不能满足
为什么使用Etag请求头?
Etag 主要为了解决 Last-Modified 无法解决的一些问题。
1、一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新GET;
2、某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说1s内修改了N次),If-Modified-Since能检查到的粒度是s级的,这种修改无法判断(或者说UNIX记录MTIME只能精确到秒)
3、某些服务器不能精确的得到文件的最后修改时间;
为此,HTTP/1.1引入了 Etag(Entity Tags).Etag仅仅是一个和文件相关的标记,可以是一个版本标记,比如说v1.0.0或者说"2e681a-6-5d044840"这么一串看起来很神秘的编码。但是HTTP/1.1标准并没有规定Etag的内容是什么或者说要怎么实现,唯一规定的是Etag需要放在""内。尤其是在做断点下载/续传时,表现得比较明显
页面加载速度优化
影响页面加载速度的因素有非常多,我们在对 WebView 加载一个网页的过程进行调试发现,每次加载的过程中都会有较多的网络请求,除了 web 页面自身的 URL 请求,还会有 web 页面外部引用的JS、CSS、字体、图片等等都是个独立的 http 请求。这些请求都是串行的,这些请求加上浏览器的解析、渲染时间就会导致 WebView 整体加载时间变长,消耗的流量也对应的真多。接下来我们就来说说几种优化方案来是怎么解决这个问题的。
选择合适的 WebView 缓存
WebView 缓存看似就是开启几个开关的问题,但是要弄懂这几种缓存机制还是很有深度。下图是腾讯某工程师总结六种 H5 常用的缓存机制的优势及适用场景。
浏览器缓存机制:
主要前端负责,Android 端不需要进行特别的配置。
Dom Storage(Web Storage)存储机制:
配合前端使用,使用时需要打开 DomStorage 开关。
WebView myWebView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = myWebView.getSettings();
webSettings.setDomStorageEnabled(true);
Web SQL Database 存储机制:
虽然已经不推荐使用了,但是为了兼容性,还是提供下 Android 端使用的方法
WebView myWebView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = myWebView.getSettings();
webSettings.setDatabaseEnabled(true);
final String dbPath = getApplicationContext().getDir(“db”,Context.MODE_PRIVATE).getPath();
webSettings.setDatabasePath(dbPath)
Application Cache 存储机制
Application Cache(简称 AppCache)似乎是为支持 Web App 离线使用而开发的缓存机制。它的缓存机制类似于浏览器的缓存(Cache-Control 和 Last-Modified)机制,都是以文件为单位进行缓存,且文件有一定更新机制。但 AppCache 是对浏览器缓存机制的补充,不是替代。
不过根据官方文档,AppCache 已经不推荐使用了,标准也不会再支持。现在主流的浏览器都是还支持 AppCache的,以后就不太确定了。同样给出 Android 端启用 AppCache 的代码。
WebView myWebView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = myWebView.getSettings();
webSettings.setAppCacheEnabled(true);
final String cachePath = getApplicationContext().getDir(“cache”,Context.MODE_PRIVATE).getPath();
webSettings.setAppCachePath(cachePath);
webSettings.setAppCacheMaxSize(5_1024_1024);
Indexed Database 存储机制
IndexedDB 也是一种数据库的存储机制,但不同于已经不再支持的 Web SQL Database。IndexedDB 不是传统的关系数据库,可归为 NoSQL 数据库。IndexedDB 又类似于 Dom Storage 的 key-value 的存储方式,但功能更强大,且存储空间更大。
Android 在4.4开始加入对 IndexedDB 的支持,只需打开允许 JS 执行的开关就好了。
WebView myWebView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = myWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
File System API
File System API 是 H5 新加入的存储机制。它为 Web App 提供了一个虚拟的文件系统,就像 Native App 访问本地文件系统一样。由于安全性的考虑,这个虚拟文件系统有一定的限制。Web App 在虚拟的文件系统中,可以进行文件(夹)的创建、读、写、删除、遍历等操作。很可惜到目前,Android 系统的 WebView 还不支持 File System API。
简单的介绍完了上面六种 H5 常用的缓存模式,想必大家能对 Android WebView 所支持的缓存模式有个粗略的了解。如果想和前端更好的配合使用 Android WebView 所支持的缓存,建议看下这篇文章《H5 缓存机制浅析 移动端 Web 加载性能优化》
*常用资源预加载:*
上面介绍的缓存技术,能优化二次启动 WebView 的加载速度,那首次加载 H5 页面的速度该怎么优化呢?上面分析了一次加载过程会有许多外部依赖的 JS、CSS、图片等资源需要下载,那我们能不能提前将这些资源下载好,等H5 加载时直接替换呢?
好在从 API 11(Android 3.0)开始,WebView 引入了 shouldInterceptRequest 函数,这个函数有两种重载。
public WebResourceResponse shouldInterceptRequest(WebView webView, String url) 从 API 11 引入,API 21 废弃
public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request) 从 API 21 开始引入
考虑到目前大多数 App 还要支持 API 14,所以还是使用 shouldInterceptRequest (WebView view, String url) 为例。
WebView mWebView = (WebView) findViewById(R.id.webview);
mWebView.setWebViewClient(new WebViewClient() {
@Override
public WebResourceResponse shouldInterceptRequest(WebView webView, final String url) {
WebResourceResponse response = null;
// 检查该资源是否已经提前下载完成。我采用的策略是在应用启动时,用户在 wifi 的网络环境下 // 提前下载 H5 页面需要的资源。
boolean resDown = JSHelper.isURLDownValid(url);
if (resDown) {
jsStr = JsjjJSHelper.getResInputStream(url);
if (url.endsWith(“.png”)) {
response = getWebResourceResponse(url, “image/png”, “.png”);
} else if (url.endsWith(“.gif”)) {
response = getWebResourceResponse(url, “image/gif”, “.gif”);
} else if (url.endsWith(“.jpg”)) {
response = getWebResourceResponse(url, “image/jepg”, “.jpg”);
} else if (url.endsWith(“.jepg”)) {
response = getWebResourceResponse(url, “image/jepg”, “.jepg”);
} else if (url.endsWith(“.js”) && jsStr != null) {
response = getWebResourceResponse(“text/javascript”, “UTF-8”, “.js”);
} else if (url.endsWith(“.css”) && jsStr != null) {
response = getWebResourceResponse(“text/css”, “UTF-8”, “.css”);
} else if (url.endsWith(“.html”) && jsStr != null) {
response = getWebResourceResponse(“text/html”, “UTF-8”, “.html”);
}
}
// 若 response 返回为 null , WebView 会自行请求网络加载资源。
return response;
}
});
private WebResourceResponse getWebResourceResponse(String url, String mime, String style) {
WebResourceResponse response = null;
try {
response = new WebResourceResponse(mime, “UTF-8”, new FileInputStream(new File(getJSPath() + TPMD5.md5String(url) + style)));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return response;
}
public String getJsjjJSPath() {
String splashTargetPath = JarEnv.sApplicationContext.getFilesDir().getPath() + “/JS”;
if (!TPFileSysUtil.isDirFileExist(splashTargetPath)) {
TPFileSysUtil.createDir(splashTargetPath);
}
return splashTargetPath + “/”;
}
**
*1:常用 JS 本地化及延迟加载***
**
**资源等文件(不需要更新)本地存储,在需要的时候直接从本地获取。哪些资源需要我们去存储在本地呢,当然是一些不会被更新的资源,例如图片文件,js文件,css文件,比预加载更粗暴的优化方法是直接将常用的 JS 脚本本地化,直接打包放入 apk 中。比如 H5 页面获取用户信息,设置标题等通用方法,就可以直接写入一个 JS 文件,放入 asserts 文件夹,在 WebView 调用了onPageFinished() 方法后进行加载。需要注意的是,在该 JS 文件中需要写入一个 JS 文件载入完毕的事件,这样前端才能接受都爱 JS 文件已经种植完毕,可以调用 JS 中的方法了。 附上一段本地化的 JS 代码。
javascript: ;
(function() {
try{
window.JSBridge = {
‘invoke’: function(name) {
var args = [].slice.call(arguments, 1),
callback = args.pop(),
params, obj = this[name];
if (typeof callback !== ‘function’) {
params = callback;
callback = function() {}
} else {
params = args[0]
} if (typeof obj !== ‘object’ || typeof obj.func !== ‘function’) {
callback({
‘err_msg’: ‘system:function_not_exist’
});
return
}
obj.callback = callback;
obj.params = params;
obj.func(params)
},
‘on’: function(event, callback) {
var obj = this[‘on’ + event];
if (typeof obj !== ‘object’) {
callback({
‘err_msg’: ‘system:function_not_exist’
});
retrun
}
if (typeof callback !== ‘undefined’) obj.callback = callback
},
‘login’: {
‘func’: function(params) {
prompt(“login”, JSON.stringify(params))
},
‘params’: {},
‘callback’: function(res) {}
},
‘settitle’: {
‘func’: function(params) {
prompt(“settitle”,JSON.stringify(params))
},
‘params’: {},
‘callback’: function(res) {}
},
}catch(e){
alert(‘demo.js error:’+e);
}
var readyEvent = document.createEvent(‘Events’);
readyEvent.initEvent(‘JSBridgeReady’, true, true);
document.dispatchEvent(readyEvent)
})();
关于 JS 延迟加载
Android 的 OnPageFinished 事件会在 Javascript 脚本执行完成之后才会触发。如果在页面中使 用JQuery,会在处理完 DOM 对象,执行完 $(document).ready(function() {}); 事件自会后才会渲染并显示页面。而同样的页面在 iPhone 上却是载入相当的快,因为 iPhone 是显示完页面才会触发脚本的执行。所以我们这边的解决方案延迟 JS 脚本的载入,这个方面的问题是需要Web前端工程师帮忙优化的。
**
*2:使用第三方 WebView 内核***
WebView 的兼容性一直也是困扰我们 Android 开发者的一个大问题,不说 Android 4.4 版本 Google 使用了Chromium 替代 Webkit 作为 WebView 内核,就看看国内众多的第三方 ROM 都有可能会对原生的 WebView 做出修改,这时候如果出现兼容问题,是非常难定位到问题和解决的。
在一次使用微信浏览订阅公众号文章的过程中,发现微信的 H5 页面有一行 『QQ 浏览器 X5 内核提供技术支持』。顺着这个线索我就找到了腾讯浏览服务。发现腾讯已经把这个功能开放了,而且集成的 SDK 很小只有212 KB。这是很惊人的,通过介绍才发现这个 SDK 是可以共享微信和手机 QQ 的 X5 内核。这就很方便了,作为国内市场最不可或缺的两个 App,我们能只需要集成一个很小的 SDK 就可以共享使用 X5 内核了,不得不说腾讯还是很有想法的。
简单摘录些功能亮点,想必能让大家高潮一番。详细内容大家可以直接到腾讯浏览服务看看,我相信不会让你们失望的。
网页浏览能力
Web页面crash率降低75%
页面打开速度提升35%
流量节省60%
阅读模式
去除网页中广告等杂质
优化文章的阅读体验
文件打开能力
包括会话页的互传文件及邮件中附件
支持doc、ppt、xls、pdf等办公格式
支持jpg、gif、png、bmp等图片格式
支持zip、rar等压缩文件
支持mp3、mp4、RMVB等音视频格式
视频菜单能力
支持屏幕调节等常规视频菜单功能
灵活切换全屏&小窗功能
*3:加快HTML网页装载完成的速度*
默认情况html代码下载到WebView后,webkit开始解析网页各个节点,发现有外部样式文件或者外部脚本文件时,会异步发起网络请求下载文件,但如果在这之前也有解析到image节点,那势必也会发起网络请求下载相应的图片。在网络情况较差的情况下,过多的网络请求就会造成带宽紧张,影响到css或js文件加载完成的时间,造成页面空白loading过久。解决的方法就是告诉WebView先不要自动加载图片,等页面finish后再发起图片加载。
故在WebView初始化时设置如下代码:
public void int () {
if(Build.VERSION.SDK_INT >= 19) {
webView.getSettings().setLoadsImagesAutomatically(true);
} else {
webView.getSettings().setLoadsImagesAutomatically(false);
}
}
同时在WebView的WebViewClient实例中的onPageFinished()方法添加如下代码:
@Override
public void onPageFinished(WebView view, String url) {
if(!webView.getSettings().getLoadsImagesAutomatically()) {
webView.getSettings().setLoadsImagesAutomatically(true);
}
}
从上面的代码,可以看出我们对系统API在19以上的版本作了兼容。因为4.4以上系统在onPageFinished时再恢复图片加载时,如果存在多张图片引用的是相同的src时,会只有一个image标签得到加载,因而对于这样的系统我们就先直接加载。
**
*4:自定义出错界面***
当WebView加载页面出错时(一般为404 NOT FOUND),安卓WebView会默认显示一个卖萌的出错界面。但我们怎么能不让用户发现原来我使用的是网页应用呢,我们期望的是用户在网页上得到是如原生般应用的体验,那就先要从干掉这个默认出错页面开始。当WebView加载出错时,我们会在WebViewClient实例中的onReceivedError()方法接收到错误,我们就在这里做些手脚:
@Override
public void onReceivedError (WebView view, int errorCode, String description, String failingUrl) {
super.onReceivedError(view, errorCode, description, failingUrl);
loadDataWithBaseURL(null, “”, “text/html”, “utf-8”, null);
mErrorFrame.setVisibility(View.VISIBLE);
}
从上面可以看出,我们先使用loadDataWithBaseURL清除掉默认错误页内容,再让我们自定义的View得到显示(mErrorFrame为蒙在WebView之上的一个LinearLayout布局,默认为View.GONE)。
远程网页需访问本地资源
当我们在WebView中加载出从web服务器上拿取的内容时,是无法访问本地资源的,如assets目录下的图片资源,因为这样的行为属于跨域行为(Cross-Domain),而WebView是禁止的。解决这个问题的方案是把html内容先下载到本地,然后使用loadDataWithBaseURL加载html。这样就可以在html中使用 file:///android_asset/xxx.png 的链接来引用包里面assets下的资源了。示例如下:
private void loadWithAccessLocal(final String htmlUrl) {
new Thread(new Runnable() {
public void run() {
try {
final String htmlStr = NetService.fetchHtml(htmlUrl);
if (htmlStr != null) {
TaskExecutor.runTaskOnUiThread(new Runnable() {
@Override
public void run() {
loadDataWithBaseURL(htmlUrl, htmlStr, “text/html”, “UTF-8”, “”);
}
});
return;
}
} catch (Exception e) {
Log.e(“Exception:” + e.getMessage());
}
TaskExecutor.runTaskOnUiThread(new Runnable() {
@Override
public void run() {
onPageLoadedError(-1, “fetch html failed”);
}
});
}
}).start();
}
上面有几点需要注意:
•从网络上下载html的过程应放在工作线程中
•html下载成功后渲染出html的步骤应放在UI主线程,不然WebView会报错
•html下载失败则可以使用我们前面讲述的方法来显示自定义错误界面
5:WebView 导致的内存泄露
Android 中的 WebView 存在很大的兼容性问题,不仅仅是 Android 系统版本的不同对 WebView 产生很大的差异,另外不同的厂商出货的 ROM 里面 WebView 也存在着很大的差异。更严重的是标准的 WebView 存在内存泄露的问题,看这里WebView causes memory leak - leaks the parent Activity。所以通常根治这个问题的办法是为 WebView 开启另外一个进程,通过 AIDL 与主进程进行通信,WebView 所在的进程可以根据业务的需要选择合适的时机进行销毁,从而达到内存的完整释放。
这段话来自胡凯翻译的 Google Android 内存优化之 OOM 。这里提到的让 WebView 独立运行在一个进程里,用完 WebView 后直接销毁这个进程,即使内存泄露了,也不会影响到主进程。微信,手 Q 等 App 也采用了这个方案。但是这就涉及到了跨进程通讯,处理起来就比较麻烦。
另外个解决方案,就是使用自己封装的 WebView,比如上面提到的 X5 内核,且使用 WebView 的时候,不在 XML 里面声明,而是在代码中直接 new 出来,传入 application context 来防止 activity 引用被滥用。
WebView webView = new WebView(getContext().getApplicationContext();
webFrameLayout.addView(webView, 0);
在使用了这个方式后,基本上 90% 的 WebView 内存泄漏的问题便得以解决。
6:客户端UI优化
怎么让用户看不到WebView加载前的白色页面呢?首次加载后页面的跳转可以用上面的步骤进行优化,可以提供给用户一个很好的体验,那加载的第一页呢?我们需要WebView预加载页面,这个该怎么做到的呢?下面提供两种方法:
ViewPager,将欢迎页面与WebView页面一起放进ViewPager中,设置预加载页面个数,使WebView所在页面可以预加载,在加载完毕的时候切换到WebView所在页面。
FrameLayout,将欢迎页面与WebView页面的布局合在一起,显示在一个页面内,起始隐藏WebView布局,待WebView加载完毕,隐藏欢迎布局,显示WebView布局。
使用FrameLayout简单一些,两种方法都是需要对WebChromeClient的onProgressChanged进行监听,加载完毕进行页面切换,如下:
webView.setWebChromeClient(new WebChromeClient() {
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
if (newProgress >= 100) {
// 切换页面
}
}
});
*7:WebView独立进程*
有效增大App的运存,减少由webview引起的内存泄露对主进程内存的占用。
避免WebView的Crash影响App主进程的运行。
拥有对WebView独立进程操控权。
WebView进程与其他进程通讯的方式
把webview独立进程之后会发现,埋点功能和接收主进程数据都不正常了,这里就涉及到进程间通讯的问题了;
进程通讯无非就是那几种,aidl,messager,content provider,广播;
在这里就不再复述了,我是采用广播的方式来做的。
*8:WebView硬件加速导致页面渲染闪烁*
4.0以上的系统我们开启硬件加速后,WebView渲染页面更加快速,拖动也更加顺滑。但有个副作用就是,当WebView视图被整体遮住一块,
然后突然恢复时(比如使用SlideMenu将WebView从侧边滑出来时),这个过渡期会出现白块同时界面闪烁。
解决这个问题的方法是在过渡期前将WebView的硬件加速临时关闭,过渡期后再开启,代码如下:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { webview.setLayerType(View.LAYER_TYPE_SOFTWARE, null);}
9:webview的配置
WebSettings用于管理WebView状态配置,当WebView第一次被创建时,WebView包含着一个默认的配置,这些默认的配置将通过get方法返回,通过WebView中的getSettings方法获得一个WebSettings对象,如果一个WebView被销毁,在WebSettings中所有回调方法将抛出IllegalStateException异常。
1、setSupportZoom(boolean support)
设置WebView是否支持使用屏幕控件或手势进行缩放,默认是true,支持缩放。
getSettings.setSupportZoom(false);
2、setMediaPlaybackRequiresUserGesture(boolean require)
设置WebView是否通过手势触发播放媒体,默认是true,需要手势触发。
getSettings.setMediaPlaybackRequiresUserGesture(false);
3、setBuiltInZoomControls(boolean enabled)
设置WebView是否使用其内置的变焦机制,该机制集合屏幕缩放控件使用,默认是false,不使用内置变焦机制。
getSettings.setBuiltInZoomControls(true);
4、setDisplayZoomControls(boolean enabled)
设置WebView使用内置缩放机制时,是否展现在屏幕缩放控件上,默认true,展现在控件上。
getSettings.setDisplayZoomControls(false);
5、setAllowFileAccess(boolean allow)
设置在WebView内部是否允许访问文件,默认允许访问。
getSettings.setAllowFileAccess(false);
6、setAllowContentAccess(boolean allow)
设置WebView是否使用其内置的变焦机制,该机制结合屏幕缩放控件使用,默认是false,不使用内置变焦机制。
getSettings.setAllowContentAccess(false);
文末
那么对于想坚持程序员这行的真的就一点希望都没有吗?
其实不然,在互联网的大浪淘沙之下,留下的永远是最优秀的,我们考虑的不是哪个行业差哪个行业难,就逃避掉这些,无论哪个行业,都会有他的问题,但是无论哪个行业都会有站在最顶端的那群人。我们要做的就是努力提升自己,让自己站在最顶端,学历不够那就去读,知识不够那就去学。人之所以为人,不就是有解决问题的能力吗?挡住自己的由于只有自己。点击我的GitHub下述资料免费领取
Android希望=技能+面试
- 技能
- 面试技巧+面试题
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
免费领取](https://bbs.csdn.net/topics/618156601)**
Android希望=技能+面试
- 技能
[外链图片转存中…(img-mH5YZ5EN-1710985852467)] - 面试技巧+面试题
[外链图片转存中…(img-Cr1kOmM3-1710985852468)]
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-7PACi1aZ-1710985852468)]
[外链图片转存中…(img-P1Nj5zj1-1710985852469)]
[外链图片转存中…(img-4WVm7Pj7-1710985852469)]
[外链图片转存中…(img-i29yWw4c-1710985852470)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
[外链图片转存中…(img-BxjyTCaE-1710985852470)]