DiskLruCache.Editor editor = mDiskLruCache.edit(hashKeyFormUrl(urlString));
if (editor != null) {
//参数index取0(因为上面的valueCount取的1)
OutputStream outputStream = editor.newOutputStream(0);
boolean downSuccess = downloadPictureToStream(urlString, outputStream);
if (downSuccess) {
//2、编辑提交,释放编辑器
editor.commit();
}else {
editor.abort();
}
//3、写到文件系统,会检查当前缓存大小,然后写到文件
mDiskLruCache.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
//三、缓存的获取
try {
String key = hashKeyFormUrl(urlString);
DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
FileInputStream inputStream = (FileInputStream)snapshot.getInputStream(0);
// Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
// mIvBitamp.setImageBitmap(bitmap);
//注意,一般需要采样加载,但文件输入流是有序的文件流,采样时两次decodeStream影响文件流的文职属性,导致第二次decode是获取是null
//为解决此问题,可用文件描述符
FileDescriptor fd = inputStream.getFD();
//采样加载(就是前面讲的bitmap的高效加载)
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds=true;
BitmapFactory.decodeFileDescriptor(fd,null,options);
ViewGroup.LayoutParams layoutParams = mIvBitamp.getLayoutParams();
options.inSampleSize = getInSampleSize(layoutParams.width, layoutParams.height, options.outWidth, options.outHeight);
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fd, null, options);
//存入内容缓存,绘制到view。(下次先从内存缓存获取,没有就从磁盘缓存获取,在没有就请求网络–“三级缓存”)
mBitmapLruCache.put(key,bitmap);
runOnUiThread(new Runnable() {
@Override
public void run() {
mIvBitamp.setImageBitmap(mBitmapLruCache.get(key));
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
/**
- 下载图片到文件输入流
*/
private boolean downloadPictureToStream(String urlString, OutputStream outputStream) {
URL url = null;
HttpURLConnection urlConnection = null;
BufferedInputStream in = null;
BufferedOutputStream out = null;
try {
url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
in = new BufferedInputStream(urlConnection.getInputStream());
out = new BufferedOutputStream(outputStream);
int b;
while ((b=in.read()) != -1) {
//写入文件输入流
out.write(b);
}
return true;
} catch (IOException e) {
e.printStackTrace();
}finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
try {
if (in != null) {in.close();}
if (out != null) {out.close();}
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
/**
- 图片的url转成key,使用MD5
*/
private String hashKeyFormUrl(String url) {
try {
MessageDigest digest = MessageDigest.getInstance(“MD5”);
return byteToHexString(digest.digest(url.getBytes()));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
private String byteToHexString(byte[] bytes) {
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(0XFF & bytes[i]);
if (hex.length()==1) {
stringBuffer.append(0);
}
stringBuffer.append(hex);
}
return stringBuffer.toString();
}
三、ImageLoader
前面说的 Bitmap的高效加载、LruCache、DiskLruCache,是一个图片加载框架必备的功能点。下面就来封装一个ImageLoader。首先罗列 实现的要点:
- 图片压缩,就是采样加载
- 内存缓存,LruCache
- 磁盘缓存,DiskLruCache
- 网络获取,请求网络url
- 同步加载,外部子线程同步执行
- 异步加载,ImageLoader内部线程异步执行
说明,
- ”三级缓存“的逻辑:加载时 先从内存缓存获取,有就返回bitmap绘制图片到view,若没有就从磁盘缓存获取;磁盘缓存有就返回bitmap并缓存到内存缓存,没有就请求网络;网络请求回来,就缓存到磁盘缓存,然后从磁盘缓存获取返回。
- 同步加载,是在外部的子线程中执行,同步加载方法内部没有开线程,所以加载过程是耗时的 会阻塞 外部的子线程,加载完成后 需要自行切到主线程绘制到view。
- 异步加载,外部可在任意线程执行,因为内部实现是在子线程(线程池)加载,并且内部会通过Handler切到主线程,只需要传入view,内部就可直接绘制Bitmap到view。
详细如下
public class ImageLoader {
private static final String TAG = “ImageLoader”;
private static final long KEEP_ALIVE_TIME = 10L;
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_THREAD_SIZE = CPU_COUNT + 1;
private static final int THREAD_SIZE = CPU_COUNT * 2 + 1;
private static final int VIEW_TAG_URL = R.id.view_tag_url;
private static final Object object = new Object();
private ThreadPoolExecutor mExecutor;
private Handler mMainHandler;
private Context mApplicationContext;
private static volatile ImageLoader mImageLoader;
private LruCache<String, Bitmap> mLruCache;
private DiskLruCache mDiskLruCache;
/**
- 磁盘缓存最大容量,50M
*/
private static final long DISK_LRU_CACHE_MAX_SIZE = 50 * 1024 * 1024;
/**
- 当前进程的最大内存,取进程内存的1/8
*/
private static final long MEMORY_CACHE_MAX_SIZE = Runtime.getRuntime().maxMemory() / 8;
public ImageLoader(Context context) {
if (context == null) {
throw new RuntimeException(“context can not be null !”);
}
mApplicationContext = context.getApplicationContext();
initLruCache();
initDiskLruCache();
initAsyncLoad();
}
public static ImageLoader with(Context context){
if (mImageLoader == null) {
synchronized (object) {
if (mImageLoader == null) {
mImageLoader = new ImageLoader(context);
}
}
}
return mImageLoader;
}
private void initAsyncLoad() {
mExecutor = new ThreadPoolExecutor(CORE_THREAD_SIZE, THREAD_SIZE,
KEEP_ALIVE_TIME, TimeUnit.SECONDS,
new LinkedBlockingQueue(), new ThreadFactory() {
private final AtomicInteger count = new AtomicInteger(1);
@Override
public Thread newThread(Runnable runnable) {
return new Thread(runnable, "load bitmap thread "+ count.getAndIncrement());
}
});
mMainHandler = new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(@NonNull Message msg) {
LoadResult result = (LoadResult) msg.obj;
ImageView imageView = result.imageView;
Bitmap bitmap = result.bitmap;
String url = result.url;
if (imageView == null || bitmap == null) {
return;
}
//此判断是 避免 ImageView在列表中复用导致图片错位的问题
if (url.equals(imageView.getTag(VIEW_TAG_URL))) {
imageView.setImageBitmap(bitmap);
}else {
Log.w(TAG, “handleMessage: set image bitmap,but url has changed,ignore!”);
}
}
};
}
private void initLruCache() {
mLruCache = new LruCache<String, Bitmap>((int) MEMORY_CACHE_MAX_SIZE){
@Override
protected int sizeOf(String key, Bitmap value) {
//缓存对象bitmap的大小,单位要和MEMORY_CACHE_MAX_SIZE一致
return value.getByteCount();
}
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
//移除旧缓存时会调用,可以在这里进行像资源回收的工作。
}
};
}
private void initDiskLruCache() {
File externalCacheDir = mApplicationContext.getExternalCacheDir();
if (externalCacheDir != null) {
long usableSpace = externalCacheDir.getUsableSpace();
if (usableSpace < DISK_LRU_CACHE_MAX_SIZE){
//剩余空间不够了
Log.e(TAG, “initDiskLruCache: “+“UsableSpace=”+usableSpace+” , not enough(target 50M),cannot creat diskLruCache!”);
return;
}
}
//一、创建DiskLruCache
//第一个参数是要存放的目录,这里选择外部缓存目录(若app卸载此目录也会删除);
//第二个是版本一般设1;第三个是缓存节点的value数量一般也是1;
//第四个是最大缓存容量这里取50M
try {
this.mDiskLruCache = DiskLruCache.open(mApplicationContext.getExternalCacheDir(), 1, 1, DISK_LRU_CACHE_MAX_SIZE);
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "initDiskLruCache: "+e.getMessage());
}
}
/**
- 缓存bitmap到内存
- @param url url
- @param bitmap bitmap
*/
private void addBitmapMemoryCache(String url, Bitmap bitmap) {
String key = UrlKeyTransformer.transform(url);
if (mLruCache.get(key) == null && bitmap != null) {
mLruCache.put(key,bitmap);
}
}
/**
- 从内存缓存加载bitmap
- @param url url
- @return
*/
private Bitmap loadFromMemoryCache(String url) {
return mLruCache.get(UrlKeyTransformer.transform(url));
}
/**
- 从磁盘缓存加载bitmap(并添加到内存缓存)
- @param url url
- @param requestWidth 要求的宽
- @param requestHeight 要求的高
- @return bitmap
*/
private Bitmap loadFromDiskCache(String url, int requestWidth, int requestHeight) throws IOException {
if (Looper.myLooper()==Looper.getMainLooper()) {
Log.w(TAG, “loadFromDiskCache from Main Thread may cause block !”);
}
if (mDiskLruCache == null) {
return null;
}
DiskLruCache.Snapshot snapshot = null;
String key = UrlKeyTransformer.transform(url);
snapshot = mDiskLruCache.get(key);
if (snapshot != null) {
FileInputStream inputStream = (FileInputStream)snapshot.getInputStream(0);
//Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
//一般需要采样加载,但文件输入流是有序的文件流,采样时两次decodeStream影响文件流的位置属性,
//导致第二次decode是获取是null,为解决此问题,可用文件描述符。
FileDescriptor fd = inputStream.getFD();
Bitmap bitmap = BitmapSampleDecodeUtil.decodeFileDescriptor(fd, requestWidth, requestHeight);
addBitmapMemoryCache(url,bitmap);
return bitmap;
}
return null;
}
/**
- 从网路加载图片 到磁盘缓存(然后再从磁盘中采样加载)
- @param urlString urlString
- @param requestWidth 要求的宽
- @param requestHeight 要求的高
- @return Bitmap
*/
private Bitmap loadFromHttp(String urlString, int requestWidth, int requestHeight) throws IOException {
//线程检查,不能是主线程
if (Looper.myLooper()==Looper.getMainLooper()) {
throw new RuntimeException(“Do not loadFromHttp from Main Thread!”);
}
if (mDiskLruCache == null) {
return null;
}
DiskLruCache.Editor editor = null;
editor = mDiskLruCache.edit(UrlKeyTransformer.transform(urlString));
if (editor != null) {
OutputStream outputStream = editor.newOutputStream(0);
if (downloadBitmapToStreamFromHttp(urlString, outputStream)) {
editor.commit();
}else {
editor.abort();
}
mDiskLruCache.flush();
}
return loadFromDiskCache(urlString, requestWidth, requestHeight);
}
/**
- 从网络下载图片到文件输入流
- @param urlString
- @param outputStream
- @return
*/
private boolean downloadBitmapToStreamFromHttp(String urlString, OutputStream outputStream) {
URL url = null;
HttpURLConnection urlConnection = null;
BufferedInputStream in = null;
BufferedOutputStream out = null;
try {
url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
in = new BufferedInputStream(urlConnection.getInputStream());
out = new BufferedOutputStream(outputStream);
int b;
while ((b=in.read()) != -1) {
//写入文件输入流
out.write(b);
}
return true;
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "downloadBitmapToStreamFromHttp,failed : "+e.getMessage());
}finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
IoUtil.close(in);
IoUtil.close(out);
}
return false;
}
/**
- 从网络直接下载bitmap(无缓存、无采样)
- @param urlString
- @return
*/
private Bitmap downloadBitmapFromUrlDirectly(String urlString) {
URL url;
HttpURLConnection urlConnection = null;
BufferedInputStream bufferedInputStream = null;
try {
url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
bufferedInputStream = new BufferedInputStream(urlConnection.getInputStream());
return BitmapFactory.decodeStream(bufferedInputStream);
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "downloadBitmapFromUrlDirectly,failed : "+e.getMessage());
}finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
IoUtil.close(bufferedInputStream);
}
return null;
}
public Bitmap loadBitmap(String url){
return loadBitmap(url,0,0);
}
/**
- 同步 加载bitmap
- 不能在主线程执行。加载时 先从内存缓存获取,有就返回bitmap,若没有就从磁盘缓存获取;
- 磁盘缓存有就返回bitmap并缓存到内存缓存,没有就请求网络;
- 网络请求回来,就缓存到磁盘缓存,然后从磁盘缓存获取返回。
- @return Bitmap
*/
public Bitmap loadBitmap(String url, int requestWidth, int requestHeight){
Bitmap bitmap = loadFromMemoryCache(url);
if (bitmap != null) {
Log.d(TAG, “loadBitmap: loadFromMemoryCache, url:”+url);
return bitmap;
}
try {
bitmap = loadFromDiskCache(url, requestWidth, requestHeight);
} catch (IOException e) {
e.printStackTrace();
}
if (bitmap != null) {
Log.d(TAG, “loadBitmap: loadFromDiskCache, url:”+url);
return bitmap;
}
try {
bitmap = loadFromHttp(url, requestWidth, requestHeight);
} catch (IOException e) {
e.printStackTrace();
}
if (bitmap != null){
Log.d(TAG, “loadBitmap: loadFromHttp, url:”+url);
return bitmap;
}
if (mDiskLruCache == null) {
Log.d(TAG, “loadBitmap: diskLruCache is null,load bitmap from url directly!”);
bitmap = downloadBitmapFromUrlDirectly(url);
}
return bitmap;
}
public void loadBitmapAsync(final String url, final ImageView imageView){
loadBitmapAsync(url,imageView,0,0);
}
/**
- 异步 加载bitmap
- 外部可在任意线程执行,因为内部实现是在子线程(线程池)加载,
- 并且内部会通过Handler切到主线程,只需要传入view,内部就可直接绘制Bitmap到view。
- @param url
- @param imageView
- @param requestWidth
- @param requestHeight
*/
public void loadBitmapAsync(final String url, final ImageView imageView, final int requestWidth, final int requestHeight){
if (url == null || url.isEmpty() || imageView == null) {
return;
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
最后
现在。**
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-bI29XOO3-1711376903518)]
[外链图片转存中…(img-vtgxqErl-1711376903519)]
[外链图片转存中…(img-neZHyF6P-1711376903519)]
[外链图片转存中…(img-uw88ImWf-1711376903520)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
[外链图片转存中…(img-WenP4Sor-1711376903520)]
最后
想要了解更多关于大厂面试的同学可以**点击这里免费获取《面试文档》**除此之外,我也分享一些免费的优质资源,包括:Android学习PDF+架构视频+源码笔记,高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 这几块的内容。分享给大家,非常适合近期有面试和想在技术道路上继续精进的朋友。快来获取学习资料吧~