先贴个图:
你不要惊讶,这就是第一次从网络获取图片的速度,感觉比本地读取图片的速度还要快吧。加载100张图片真的只要2秒时间,你不要不相信,不信你就来看。
一、概述
在众多的app
当中,缓存可以作为衡量一款产品的好坏,既能节省流量,减少电量消耗,最重要的是用户体验好。你想想一款产品每个月消耗你100M
以上的流量,你愿意用吗?当然这里除了游戏以外。那么怎么才能做好缓存呢?这里要介绍两个重要的概念,一个是内存缓存LruCache
,一个是硬盘缓存DiskLruCache
,大家对这两个概念肯定不会陌生,如果你还不了解的话请链接郭神的Android DiskLruCache完全解析,硬盘缓存的最佳方案 真心写的很棒。从标题中就可以看出今天还有一个主角就是线程池这个概念我很久以前都听说过了,但没具体去研究过,我也只会使用它。
相关文章请链接一下地址:
Android DiskLruCache完全解析,硬盘缓存的最佳方案
二、Executors初探线程池
Android常用的线程池有以下几种,在Executors
里面对应的方法:
- newFixedThreadPool 每次执行限定个数个任务的线程池
- newCachedThreadPool 所有任务都一次性开始的线程池
- newSingleThreadExecutor 每次只执行一个任务的线程池
- newScheduledThreadPool 创建一个可在指定时间里执行任务的线程池,亦可重复执行
获取实例:
Executors.newSingleThreadExecutor();// 每次只执行一个线程任务的线程池
Executors.newFixedThreadPool(3);// 限制线程池大小为3的线程池
Executors.newCachedThreadPool(); // 一个没有限制最大线程数的线程池
Executors.newScheduledThreadPool(3);// 一个可以按指定时间可周期性的执行的线程池
- 1
- 2
- 3
- 4
我们来看看下面这个例子:
new Thread(new Runnable() {
@Override
public void run() {
}
}).start();
- 1
- 2
- 3
- 4
- 5
- 6
在功能上等价于:
mMyHandler.post(new Runnable() {
@Override
public void run() {
}
});
- 1
- 2
- 3
- 4
- 5
- 6
还等价于:
executors.execute(new Runnable() {
@Override
public void run() {
}
});
- 1
- 2
- 3
- 4
- 5
- 6
我们为啥要使用ExecutorService
呢,而不使用Thread
和Handler
?使用线程池我觉得可以对我们开启的线程进行跟进,可以复用这点很重要,能够减少内存消耗,当然也可以指定个数来执行任务的线程池、创建一个可在指定时间里执行任务的线程池。
三、DiskLruCache简单介绍
如果你想详情了解的话,请链接相关文章。
注意:在你的项目当中依赖了相关retrofit
包,DiskLruCache
类也包含在其中,免得你 重复导包。
先来看看DiskLruCache
的实例方法:
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
- 1
open()
方法接收四个参数,第一个参数指定的是数据的缓存地址,第二个参数指定当前应用程序的版本号,第三个参数指定同一个key可以对应多少个缓存文件,基本都是传1,第四个参数指定最多可以缓存多少字节的数据,好了我这里就不再重复讲解了。不懂请查看相关文章链接。
下面我们一起来看看,文章开头那个快速加载出图片的程序是怎么实现的。我通过自己的尝试,能使图片加载那么迅猛,还是蛮激动的。
1、xml布局
<ListView
android:id="@+id/lv"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
- 1
- 2
- 3
- 4
- 5
就一个ListView
,没什么好说的。
2、activity文件
lv= (ListView) findViewById(R.id.lv);
mAdapter=new TestAdapter(Images.imageThumbUrls,R.layout.photo_layout,this,lv);
lv.setAdapter(mAdapter);
- 1
- 2
- 3
- 4
- 5
都是比较常规的写法,这里主要说下Adapter
的参数:
public TestAdapter(String[] datas, int layoutId, Context context, ViewGroup view)
- 1
第一个参数代表 图片地址数组
第二个参数代表 子布局Id
第三个参数代表 上下文 context
第四个参数代表 当前的ListView
,请求网络是异步加载,防止图片错位
3、adapter文件
成员变量:
/**
* 图片缓存技术的核心类,用于缓存所有下载好的图片,在程序内存达到设定值时会将最少最近使用的图片移除掉。
*/
private LruCache<String, Bitmap> mMemoryCache;
/**
* 图片硬盘缓存核心类。
*/
private DiskLruCache mDiskLruCache;
/**
* 线程池下载图片
*/
private ExecutorService executors;
private String[] datas; //数据源
private int layoutId; //布局Id
private Context mContext; //上下文
private ViewGroup mViewGroup; //对应listview
private MyHandler mMyHandler; //hanler
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
对应的初始化:
public TestAdapter(String[] datas, int layoutId, Context context, ViewGroup view) {
this.datas = datas;
this.layoutId = layoutId;
mContext = context;
mViewGroup = view;
// taskCollection = new HashSet<BitmapWorkerTask>();
// 获取应用程序最大可用内存
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cacheSize = maxMemory / 8;
// 设置图片缓存大小为程序最大可用内存的1/8
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getByteCount();
}
};
try {
// 获取图片缓存路径
File cacheDir = getDiskCacheDir(context, "bitmap");
if (!cacheDir.exists()) {
cacheDir.mkdirs();
}
// 创建DiskLruCache实例,初始化缓存数据
mDiskLruCache = DiskLruCache
.open(cacheDir, getAppVersion(context), 1, 20 * 1024 * 1024);
} catch (IOException e) {
e.printStackTrace();
}
executors = Executors.newFixedThreadPool(3);
mMyHandler = new MyHandler(this);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
接下来一起来看看getView
方法:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
String url = (String) getItem(position);
View view;
if (convertView == null) {
view = LayoutInflater.from(mContext).inflate(layoutId, null);
} else {
view = convertView;
}
ImageView imageView = (ImageView) view.findViewById(R.id.photo);
imageView.setTag(url);//防止图片错位
imageView.setImageResource(R.drawable.empty_photo);
loadBitmaps(imageView, url);
return view;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
loadBitmaps
方法:
public void loadBitmaps(ImageView imageView, String imageUrl) {
try {
Bitmap bitmap = getBitmapFromMemoryCache(imageUrl);
if (bitmap == null) {
startExecutor(imageUrl);
} else {
if (imageView != null && bitmap != null) {
imageView.setImageBitmap(bitmap);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
加载Bitmap
对象。此方法会在LruCache
中检查所有屏幕中可见的ImageView
的Bitmap
对象, 如果发现任何一个ImageView
的Bitmap
对象不在LruCache
缓存中,那么就会接着去检测该Bitmap
是否在DiskLruCache
,如果不在就开启异步线程去下载图片,反之就添加到LruCache中并展示出来。DiskLruCache
文件转换成Bitmap
是个耗时操作,防止UI
线程卡顿,所以在线程池中进行。
startExecutor
方法又是怎么实现的呢:
public void startExecutor(final String imageUrl) {
executors.execute(new Runnable() {
@Override
public void run() {
FileDescriptor fileDescriptor = null;
FileInputStream fileInputStream = null;
DiskLruCache.Snapshot snapShot = null;
try {
// 生成图片URL对应的key
final String key = hashKeyForDisk(imageUrl);
// 查找key对应的缓存
snapShot = mDiskLruCache.get(key);
if (snapShot == null) {
// 如果没有找到对应的缓存,则准备从网络上请求数据,并写入缓存
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (editor != null) {
OutputStream outputStream = editor.newOutputStream(0);
if (downloadUrlToStream(imageUrl, outputStream)) {
editor.commit();
} else {
editor.abort();
}
}
// 缓存被写入后,再次查找key对应的缓存
snapShot = mDiskLruCache.get(key);
}
if (snapShot != null) {
fileInputStream = (FileInputStream) snapShot.getInputStream(0);
fileDescriptor = fileInputStream.getFD();
}
// 将缓存数据解析成Bitmap对象
Bitmap bitmap = null;
if (fileDescriptor != null) {
bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);
}
if (bitmap != null) {
// 将Bitmap对象添加到内存缓存当中
addBitmapToMemoryCache(imageUrl, bitmap);
}
mMyHandler.post(new Runnable() {
@Override
public void run() {
ImageView imageView = (ImageView) mViewGroup.findViewWithTag(imageUrl);
Bitmap bitmap = getBitmapFromMemoryCache(imageUrl);
if (imageView != null && bitmap != null) {
imageView.setImageBitmap(bitmap);
}
}
});
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileDescriptor == null && fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
}
}
}
}
});
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
代表比较长,需要耐着性子看。
获取图片流:
/**
* 建立HTTP请求,并获取Bitmap对象。
*
* @param urlString 图片的URL地址
* @return 解析后的Bitmap对象
*/
private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
HttpURLConnection urlConnection = null;
BufferedOutputStream out = null;
BufferedInputStream in = null;
InputStream inputStream = null;
try {
in = new BufferedInputStream(new URL(urlString).openStream());
out = new BufferedOutputStream(outputStream);
int b;
while ((b = in.read()) != -1) {
out.write(b);
}
return true;
} catch (final IOException e) {
e.printStackTrace();
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
if (inputStream != null) {
inputStream.close();
}
} catch (final IOException e) {
e.printStackTrace();
}
}
return false;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
经过测试new URL(urlString).openStream()
获取图片流的方法最快。这里获取流也可以使用retrofit
:
try {
ResponseBody responseBody = client.getRectService().downBitmaps(urlPath).execute().body();
if (responseBody != null) {
return responseBody.byteStream();//返回图片流
}
} catch (IOException e) {
e.printStackTrace();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
由于retrofit
内部进行了一些封装,获取流的时间较长,这里不推荐使用。
还可以这样获取流:
final URL url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.getInputStream();
- 1
- 2
- 3
四、结论
第一个时间是开始加载第一张图片的时间
第二个时间是加载完最后一张图片的时间
它们的时间戳就2秒多。
在来看一下 monitors:
相关推荐
【Retrofit2】 Retrofit是由Square公司开发的一款强大的Android网络请求库,它允许开发者通过简单的接口定义来处理网络请求。Retrofit的核心理念是将HTTP请求API转换为Java接口,这样开发者可以像调用本地方法一样...
本项目标题提到的"安卓retrofit2 + rxjava2 + okhttp3多线程下载"正是一个利用现代Android开发库来优化下载流程的方法。以下将详细介绍这三个库以及它们如何协同工作实现多线程下载。 1. **Retrofit2**: Retrofit...
本框架使用Retrofit2+Rxjava2+Rxandroid+okhttp3+Lifecycle 的MVP网络框架,精简Google官方AAC(Android Architecture Components)框架,实现APP生命周期的管理
基于MVP+RxJava+Retrofit2+Okhttp3+Rxlifecycle+Butterknife的开发框架。架构层:V层只负责视图的操作,P层只负责数据的交互,M层处理逻辑的操作。网络层,包括普通的get/post请求,单图多图上传,带对话框试请求,...
在Android开发中,Retrofit2、RxJava2和OkHttp3是三个非常重要的库,它们共同构建了一个高效、灵活的网络请求框架。本篇文章将深入探讨这三个组件的基础知识,以及如何进行封装和在实际项目中使用。 首先,Retrofit...
RxJava、Retrofit2和Okhttp3是目前非常流行的三大开源库,它们各自承担着不同的职责,共同构建了一个高效、灵活的网络请求框架。这个"RxJava+Retrofit2+Okhttp3经典封装demo"将展示如何整合这三个库,实现优雅的异步...
Retrofit2和OkHttp3是Android开发中的两个强大工具,它们可以帮助我们更方便、高效地实现SOAP请求。下面我们将详细探讨这两个库以及如何将它们结合使用来实现SOAP请求。 首先,Retrofit2是Square公司推出的一个类型...
协程+Retrofit+ViewModel+LiveData+DataBinding框架demo,相关博客参考https://blog.csdn.net/liuxingyuzaixian/article/details/125427338
本教程将探讨如何结合MVP(Model-View-Presenter)架构、Retrofit2、OkHttp3和RxJava2来打造一个实用且简约的网络请求解决方案。 **MVP架构** MVP全称为Model-View-Presenter,是一种设计模式,它将业务逻辑、用户...
mvp+rxjava+retrofit2+glide 封装 图片选择器 超级封装 代码少
在某些情况下(如开发环境无网络),要使用retrofit2+okhttp3+rxjava2,那么只能添加第三方jar包。 百度网盘链接: https://pan.baidu.com/s/1dFjNmSD 密码: nbwy
在Android应用开发中,网络请求是必不可少的一部分,Retrofit2和RxJava2是两种非常流行的库,用于构建和管理网络接口。本篇文章将详细讲解如何使用Retrofit2和RxJava2进行网络请求,并进行异常统一封装处理,以便...
retrofit2+rxjava2+rxrecive2+rxbinding2 retrofit2+rxjava2+rxrecive2+rxbinding2 retrofit2+rxjava2+rxrecive2+rxbinding2
本教程将重点讲解如何利用Retrofit2和OkHttp3这两个强大的库来实现Android应用通过SOAP协议请求Web Service。 Retrofit是由Square公司开发的一款Type-Safe HTTP客户端,它允许开发者用Java注解来描述HTTP请求,使得...
Retrofit2、OkHttp3和RxJava的组合是当前广泛采用的一种解决方案,它们各自承担着不同的角色,共同优化了网络通信的性能和用户体验。以下将详细讲解这三个组件的核心功能和它们如何协同工作。 **Retrofit2** 是...
在现代Android应用开发中,MVVM(Model-View-ViewModel)架构、RxJava、Retrofit2、OkHttp以及组件化开发已经成为主流技术栈。这些工具和技术的组合为开发者提供了高效、可测试和易于维护的解决方案。 **MVVM架构**...
Retrofit2和RxJava2可以很好地融入这种架构:Retrofit2作为Model层获取网络数据,RxJava2处理数据流并控制请求顺序,而Controller则根据需求订阅这些流并更新View。 在"retrofit2+rxjava2+mvc"框架中,每个模块的...
RxJava2、Retrofit2和OkHttp3是Android开发中常用的三大网络库,它们各司其职,共同构建了一个高效、灵活的网络请求框架。在Android应用开发中,高效的网络通信是至关重要的,而这三个库的结合使用可以极大提升网络...
综上所述,"rxjava2+retrofit2+mvp"的组合提供了强大的工具来构建现代化的Android应用,包括高效的网络请求、数据管理、本地存储和图片加载。DbFlow和Glide的使用增强了数据持久化和视觉呈现,而RxJava 2和Retrofit ...
在Android开发中,Retrofit2、OkHttp3和RxJava是三个非常重要的库,它们各自在不同的层面上为网络通信提供了强大的支持。本篇将详细解释这三个库的作用、结合使用的方式以及它们如何协同工作。 首先,Retrofit2是...