最近收拾了公司有关图片加载代码,这部分代码也不知道当时怎么想的,自己写了一套图片懒加载控件,我是觉得这应该用一些稳定的图片加载开源库,比方 Glide 之类的,毕竟这些开源库有那么多人的多年保护,用起来不会有许多暗病,最近收拾这些图片加载的代码真是弄的心力交瘁。
一向改不是办法,想着应该也不难,就自己动手写了一个,下面看看吧!
完结思路
这儿收拾了一下图片懒加载的一个过程,实践便是下载到显现,当然我这写的思路仅供参考:
- 初始化
- 设置 (默许图、取内存缓存)
- 加载 (取本地缓存、下载、存本地缓存)
- 处理 (创建Bitmap、紧缩)
- 缓存 (内存缓存)
- 更新 (主线程更新)
简略说下,初始化便是设置一些数据,设置便是设置图片链接,我把默许图和内存缓存图写一起了,放在里边,加载便是取文件,处理是从文件到 bitmap 并加上其他处理,缓存是到内存缓存,更新是主线程更新,这儿还有一层本地缓存,但是我不想悉数写在这儿,耦合性太高,后面运用一个专门的文件处理类来完结。
详细完结
经过上面思路,实践只要将各个部分解耦开来,一步步完结就好,这儿代码也不多,我就直接悉数贴出来了,看下面:
import android.content.Context;
import android.graphics.Bitmap;
import android.support.v4.util.Consumer;
import android.support.v7.widget.AppCompatImageView;
import android.util.AttributeSet;
import android.util.Log;
import java.io.File;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author: silence
* @date: 2021-05-27
* @description: 简略图片懒加载
*/
public class LazyImageView extends AppCompatImageView {
//图片链接
private String mUrl = "";
//默许图片
private static Bitmap sDefaultBitmap;
//失利图片
private static Bitmap sErrorBitmap;
//文件处理东西
private static IFileHelper sFileHelper;
//图片处理东西
private static IBitmapHelper sBitmapHelper;
//内存缓存 - 10条,主动删去最老数据,Bitmap会主动回收
private final static Map<String, Bitmap> sBitmapCache = new LinkedHashMap<String, Bitmap>() {
protected boolean removeEldestEntry(Map.Entry<String, Bitmap> eldest) {
return size() >= 10;
}
};
public LazyImageView(Context context) {
super(context);
}
public LazyImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public LazyImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
//初始化
public static void init(Bitmap defaultBitmap, Bitmap errorBitmap, IFileHelper fileHelper, IBitmapHelper bitmapHelper) {
//BitmapFactory.decodeResource(getResources(), R.mipmap.img_def);
LazyImageView.sDefaultBitmap = defaultBitmap;
LazyImageView.sErrorBitmap = errorBitmap;
LazyImageView.sFileHelper = fileHelper;
LazyImageView.sBitmapHelper = bitmapHelper;
}
//设置
public void show(String url) {
Log.d("TAG", "show: " + url);
if (!mUrl.equals(url)) {
mUrl = url;
//取内存缓存,无内存缓存设为默许图
display(null != sBitmapCache.get(url) ? sBitmapCache.get(url) : sDefaultBitmap);
//加载链接
load(file -> {
if (null != file) {
Bitmap bitmap = handle(file);
cache(bitmap);
display(bitmap);
} else {
display(sErrorBitmap);
}
});
}
}
//加载
private void load(Consumer<File> resultHandler) {
Log.d("TAG", "load: ");
sFileHelper.download(mUrl, resultHandler);
}
//处理
private Bitmap handle(File file) {
Log.d("TAG", "handle: ");
return sBitmapHelper.handle(file);
}
//缓存
private void cache(Bitmap bitmap) {
Log.d("TAG", "cache: ");
sBitmapCache.put(mUrl, bitmap);
}
//显现
private void display(Bitmap bitmap) {
Log.d("TAG", "display: ");
this.post(()-> setImageBitmap(bitmap));
}
//文件处理,解耦,如有需求重写 download 即可
public interface IFileHelper {
void download(String url, Consumer<File> resultHandle);
}
//图片处理,解耦,如有需求重写handle函数
public interface IBitmapHelper {
Bitmap handle(File file);
}
}
简略说明
简略说明一下,实践这儿的代码和上面思路完全一致,处理构造函数,剩下的便是六个过程对应的函数,这儿写了两个接口,用来处理获取文件和处理 bitmap,详细完结能够依据需求另做处理。
//默许图片
private static Bitmap sDefaultBitmap;
//失利图片
private static Bitmap sErrorBitmap;
上面是默许显现图片和加载失利图片的 bitmap,写成了类变量,节省内存占用。
//内存缓存 - 10条,主动删去最老数据,Bitmap会主动回收
private final static Map<String, Bitmap> sBitmapCache = new LinkedHashMap<String, Bitmap>() {
protected boolean removeEldestEntry(Map.Entry<String, Bitmap> eldest) {
return size() >= 10;
}
};
内存缓存运用了 LinkedHashMap,首要运用它的移除老元素功用,内存缓存我不希望过多,但是页面常常刷新的时分,快速复用已有 bitmap 还是很有必要的。
//初始化
public static void init(Bitmap defaultBitmap, Bitmap errorBitmap, IFileHelper fileHelper, IBitmapHelper bitmapHelper) {
//BitmapFactory.decodeResource(getResources(), R.mipmap.img_def);
LazyImageView.sDefaultBitmap = defaultBitmap;
LazyImageView.sErrorBitmap = errorBitmap;
LazyImageView.sFileHelper = fileHelper;
LazyImageView.sBitmapHelper = bitmapHelper;
}
在 init 函数总对类变量就行赋值,写成了静态函数,全局只需求设置一次即可。
BitmapFactory.decodeResource(getResources(), R.mipmap.img_def);
关于从图片资源 id 到 bitmap 能够经过上面方法完结,需求在 context 环境中执行,我不想耦合进来,所以在 init 函数前自行转化吧。
//设置
public void show(String url) {
Log.d("TAG", "show: " + url);
if (!mUrl.equals(url)) {
mUrl = url;
//取内存缓存,无内存缓存设为默许图
display(null != sBitmapCache.get(url) ? sBitmapCache.get(url) : sDefaultBitmap);
//加载链接
load(file -> {
if (null != file) {
Bitmap bitmap = handle(file);
cache(bitmap);
display(bitmap);
} else {
display(sErrorBitmap);
}
});
}
}
能够看到,实践上首要逻辑都是在 show 函数中完结的,这儿将其他函数组合起来,由于 load 函数中需求在异步线程中处理,所以传递了一个 Consumer 进去,这儿 Consumer 要用旧版本的 Consumer 不然需求安卓 v24 以上才干用。经过 Lambda 表达式,咱们对拿到 load 函数完结后的结果,成功则缓存、显现,失利则显现失利的 bitmap。至于缓存和显现里边的代码,应该不必解释了,很简略。
完结获取文件
上面代码中咱们只设置了一个接口来获取文件,下面咱们来完结该接口:
/**
* @author: silence
* @date: 2021-05-27
* @description: 简略文件处理东西
*/
public class FileHelper implements LazyImageView.IFileHelper {
//缓存途径,应用默许贮存途径
private static final String CACHE_DIR =
"/data" + Environment.getDataDirectory().getAbsolutePath() + "/" +
getApplication().getPackageName() + "/cache/";
//缓存巨细
private static final int BUFFER_SIZE = 1024;
//线程池
final ExecutorService threadPool = Executors.newFixedThreadPool(8);
//相同链接的锁, 这儿用LinkedHashMap约束一下贮存的数量
private final Map<String, Semaphore> mUrlLockMap =
new LinkedHashMap<String, Semaphore>() {
protected boolean removeEldestEntry(Map.Entry<String, Semaphore> eldest) {
return size() >= 64 * 0.75;
}
};
//下载文件
public void download(String url, Consumer<File> resultHandler) {
threadPool.execute(()-> {
File downloadFile = null;
//空途径
if (TextUtils.isEmpty(url)) {
//注意运用旧版本Consumer
resultHandler.accept(null);
return;
}
//查看本地缓存
downloadFile = new File(getLocalCacheFileName(url));
if (downloadFile.exists()) {
resultHandler.accept(downloadFile);
return;
}
//一起下载文件会对同一个文件做修正,需求运用锁机制,运用信号量简略点
Semaphore semaphore;
synchronized (mUrlLockMap) {
semaphore = mUrlLockMap.get(url);
if (null == semaphore) {
semaphore = new Semaphore(1);
mUrlLockMap.put(url, semaphore);
}
}
//确保锁一定解锁
try {
semaphore.acquire();
//再次查看是否有本地缓存,解开锁之后可能下载完结
downloadFile = new File(getLocalCacheFileName(url));
if (downloadFile.exists()) {
resultHandler.accept(downloadFile);
return;
}
//网络下载部分
HttpURLConnection conn = null;
BufferedInputStream inputStream = null;
FileOutputStream outputStream = null;
RandomAccessFile randomAccessFile;
File cacheFile = new File(getLocalCacheFileName(url));
//要下载文件巨细
long remoteFileSize = 0, sum = 0;
byte[] buffer = new byte[BUFFER_SIZE];
try {
URL conUrl = new URL(url);
conn = (HttpURLConnection) conUrl.openConnection();
remoteFileSize = Long.parseLong(conn.getHeaderField("Content-Length"));
existsCase:
if (cacheFile.exists()) {
long cacheFileSize = cacheFile.length();
//反常情况
if (cacheFileSize == remoteFileSize) {
break existsCase;
} else if (cacheFileSize > remoteFileSize) {
//假如呈现文件过错,要删去
//noinspection ResultOfMethodCallIgnored
cacheFile.delete();
cacheFile = new File(getLocalCacheFileName(url));
cacheFileSize = 0;
}
conn.disconnect(); // must reconnect
conn = (HttpURLConnection) conUrl.openConnection();
conn.setConnectTimeout(30000);
conn.setReadTimeout(30000);
conn.setInstanceFollowRedirects(true);
conn.setRequestProperty("User-Agent", "VcareCity");
conn.setRequestProperty("RANGE", "buffer=" + cacheFileSize + "-");
conn.setRequestProperty("Accept",
"image/gif,image/x-xbitmap,application/msword,*/*");
//随机拜访
randomAccessFile = new RandomAccessFile(cacheFile, "rw");
randomAccessFile.seek(cacheFileSize);
inputStream = new BufferedInputStream(conn.getInputStream());
//继续写入文件
int size;
sum = cacheFileSize;
while ((size = inputStream.read(buffer)) > 0) {
randomAccessFile.write(buffer, 0, size);
sum += size;
}
randomAccessFile.close();
} else {
conn.setConnectTimeout(30000);
conn.setReadTimeout(30000);
conn.setInstanceFollowRedirects(true);
if (!cacheFile.exists()) {
//noinspection ResultOfMethodCallIgnored
cacheFile.createNewFile();
}
inputStream = new BufferedInputStream(conn.getInputStream());
outputStream = new FileOutputStream(cacheFile);
int size;
while ((size = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, size);
sum += size;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (null != conn) conn.disconnect();
if (null != inputStream) inputStream.close();
if (null != outputStream) outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
//下载结束
long dwonloadFileSize = cacheFile.length();
if (dwonloadFileSize == remoteFileSize && dwonloadFileSize > 0) {
//成功
resultHandler.accept(new File(getLocalCacheFileName(url)));
}else {
resultHandler.accept(null);
}
} catch (Exception e) {
e.printStackTrace();
//反常的话传递空值
resultHandler.accept(null);
} finally {
//开释信号量
semaphore.release();
}
});
}
//获取缓存文件名
private String getLocalCacheFileName(String url) {
return CACHE_DIR + url.substring(url.lastIndexOf("/"));
}
}
这儿运用了线程池来下载文件,缓存途径中需求运用到 context,解耦不太充分,读者能够直接设置途径。
//相同链接的锁, 这儿用LinkedHashMap约束一下贮存的数量
private final Map<String, Semaphore> mUrlLockMap =
new LinkedHashMap<String, Semaphore>() {
protected boolean removeEldestEntry(Map.Entry<String, Semaphore> eldest) {
return size() >= 64 * 0.75;
}
};
为了防止相同的链接触发对同一个文件的修正,这儿还是用到了锁机制,详细做法是对每个链接设置一个答应数量为1的信号量,相同的链接被答应下载的时分才干下载,不过要记得开释信号量。
完结图片处理
下面完结图片处理逻辑,实践上这儿能够对 bitmap 进行紧缩,读者能够自行设计。
/**
* @author: silence
* @date: 2021-05-27
* @description: 简略图片处理东西
*/
public class BitmapHelper implements LazyImageView.IBitmapHelper {
@Override
public Bitmap handle(File file) {
return decodePhotoFile(file);
}
//依据文件生成bitmap
private Bitmap decodePhotoFile(File file) {
Bitmap bitmap = null;
BitmapFactory.Options options = new BitmapFactory.Options();
try(FileInputStream instream = new FileInputStream(file);) {
bitmap = BitmapFactory.decodeStream(instream, null, options);
}catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
}
我这儿只是最简略的将文件转化成了 bitmap。
实践运用
这儿演示一下代码运用,而在 XML 中运用相似且更简略。
LazyImageView.init(
BitmapFactory.decodeResource(getResources(), R.mipmap.img_def),
BitmapFactory.decodeResource(getResources(), R.mipmap.img_load_fail),
new FileHelper(), new BitmapHelper());
LinearLayout linearLayout = findViewById(R.id.title);
LazyImageView lazyImageView = new LazyImageView(this);
linearLayout.addView(lazyImageView);
lazyImageView.show("https://api.dujin.org/bing/1920.php");
这儿我用必应每日一图做了实验,能够下载并显现,第一次下载完结后取缓存速度会快许多。
其他设置宽高巨细什么的和 ImageView 一样,毕竟是从 ImageView 承继过来的,圆角什么的就自己搞了吧!
结语
这儿虽然自己写代码完结图片的懒加载,可是我还是觉得应该用一些稳定的开源库去加载图片,一是文档,而是交接给他人也好理解,当然这样一个功用写在来,还是挺有意思的,特别是解耦、下载和锁机制,希望能帮到有需求的读者!
end