android-smart-image-view源码分析,万分膜拜

  • 【ContactImage(系统联系人头像的获取,通过在构造函数传入联系人的ID,然后在getBitmap方法中根据ID查找对应的联系人头像)】---->

package com.loopj.android.image;

import java.io.InputStream;

import android.content.ContentUris;

import android.content.ContentResolver;

import android.content.Context;

import android.provider.ContactsContract;

import android.graphics.Bitmap;

import android.graphics.BitmapFactory;

import android.net.Uri;

/*

  • 获取联系人头像资源

*/

public class ContactImage implements SmartImage {

/****************************************************************************************************

  • 注:Android系统中访问其他app的数据时,一般都是通过ContentProvider实现,

  • 一个ContentProvider类实现了一组标准的方法接口,能够让其他app保存或者读取它提供的各种数据类型。

  • 其他app通过ContentResolver接口就可以访问ContentProvider提供的数据。

  • 备注:(获取的是手机的联系人头像,而不是Sim卡中的联系人头像的。Sim卡由于容量限制等原因,无联系人头像数据)

  • –使用时记得添加获取联系人头像的权限

*****************************************************************************************************/

//联系人头像ID

private long contactId;

public ContactImage(long contactId) {

this.contactId = contactId;

}

public Bitmap getBitmap(Context context) {

Bitmap bitmap = null;

//获取ContentResolver实例

ContentResolver contentResolver = context.getContentResolver();

try {

//根据ID生成查找联系人的Uri

/*

关于withAppendedId方法:

Open Declaration Uri android.content.ContentUris.withAppendedId(Uri contentUri, long id)

Appends the given ID to the end of the path.

Parameters:

contentUri – to start with

id – to append

Returns:

a new URI with the given ID appended to the end of the path */

Uri uri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, contactId);

//调用Contact类中的openContactPhotoInputStream获得图像InputStream对象

InputStream input = ContactsContract.Contacts.openContactPhotoInputStream(contentResolver, uri);

if(input != null) {

//将数据流decode为bitmap对象并返回

bitmap = BitmapFactory.decodeStream(input);

}

} catch(Exception e) {

e.printStackTrace();

}

return bitmap;

}

}

那ContactImage中联系人ID是怎么来的呢,其实也是通过ContentResolver查出来的,下面示例代码:

private static final int DISPLAY_NAME_INDEX = 0;

private static final int PHONE_NUMBER_INDEX = 1;

private static final int PHOTO_ID_INDEX = 2;

private static final int CONTACT_ID_INDEX = 3;

private static final String[] PHONES_PROJECTION = new String[] {

Phone.DISPLAY_NAME, Phone.NUMBER, Phone.PHOTO_ID,Phone.CONTACT_ID };

private void getPhoneContact(Context context) {

ContentResolver contentResolver = context.getContentResolver();

Cursor cursor = contentResolver.query(Phone.CONTENT_URI, PHONES_PROJECTION, null, null, null);

if (cursor != null) {

while(cursor.moveToNext()) {

String displayName = cursor.getString(DISPLAY_NAME_INDEX); // 联系人名字

String phoneNum = cursor.getString(PHONE_NUMBER_INDEX); // 联系人号码

Long contactId = cursor.getLong(CONTACT_ID_INDEX); // 联系人id

Long photoId = cursor.getLong(PHOTO_ID_INDEX); // 联系人头像id(photoId大于0时表示联系人有头像)

}

cursor.close();

}

}

好了,讲到这里,相信大家通过注释可以对这三个类有一定的了解了。可能有同学注意到了,那个WebImage中的  WebImageCache  是什么东西,里面是如何实现的呢?好,我们下面来说说关于这个类的源码实现,相信大家看了会对“缓存”这个高大上的词有一定认识。在WebImageCache中其实是实现了二级缓存,接着我们就来说说这个类。

SmartImageView中的二级缓存


相信大家都知道,在开发中,为了加快图片的访问速度,避免系统资源的浪费,用户体验上的流畅,都会引入缓存的机制,由于App内存有限,若超过了这个限制,系统便会报错OutOfMemory,这是个很头疼的问题。引入缓存的机制目的就是为了让App在使用中更加流畅,体验更好,减少不必要的资源开销。SmartImageView库中也引入了简单的二级缓存,数据获取速度取决于物理介质,一般是 内存>磁盘>网络,故在加载图片时,会优先判断是否命中内存缓存,没有则查找磁盘缓存,最终才会考虑从网络上加载,同时更新内存缓存和磁盘缓存记录。

考虑到缓存查找的速度问题,在实现内存缓存时一般都会使用类似哈希表这样查找时间复杂度低的数据结构。由于存在多个线程同时在哈希表中查找的情况,需要考虑多线程并发访问的问题。故使用 ConcurrentHashMap。内存缓存中我们不会直接持有Bitmap实例的引用,而是通过SoftReference来持有Bitmap对象的软引用,如果一个对象具有软引用,内存空间足够时,垃圾回收器不会回收它,只有在内存空间不足时,才会回收这些对象占用的内存。因此,软引用通常用来实现内存敏感的高速缓存。关于引用问题,可具体参见博文java中对象的引用(强引用、软引用、弱引用、虚引用)

Android系统上磁盘缓存可以放在内部存储空间,也可以放在外部存储空间(即SD卡)。对于小图片的缓存可以放在内部存储空间中,但当图片比较大,数量比较多时,那么就应该将图片缓存放到SD卡上,毕竟内部存储空间一般比SD卡空间要小很多。SmartImageView库的磁盘缓存是放在内部存储空间中的,也就是app的缓存目录,该目录使用 Context.getCacheDir() 函数来获取,格式类似于:/data/data/app的包名/cache。cache目录主要用于存放缓存文件,当系统的内部存储空间不足时,该目录下面的文件会被删除;当然,不能依赖系统来清理这些缓存文件,而是应该对这些缓存文件设置最大存储空间,当实际占用空间超过这个最大值时,就需要对使用一定的算法对缓存文件进行清理。这一点在SmartImage库中并没有做考虑。

我们来看看WebImageCache类中对内存缓存和磁盘缓存的实现,先看其构造方法:

//构造方法,构建两级缓存空间

public WebImageCache(Context context) {

// Set up in-memory cache store

memoryCache = new ConcurrentHashMap<String, SoftReference>();

// Set up disk cache store

Context appContext = context.getApplicationContext();

diskCachePath = appContext.getCacheDir().getAbsolutePath() + DISK_CACHE_PATH;

//先根据URL在cache目录中生成对应的文件

File outFile = new File(diskCachePath);

outFile.mkdirs();

diskCacheEnabled = outFile.exists();

// Set up threadpool for image fetching tasks

writeThread = Executors.newSingleThreadExecutor();

}

具体从缓存获取Bitmap实例的实现方法:

/**

  • 从Memory获取bitmap实例

  • @param url

  • @return

*/

private Bitmap getBitmapFromMemory(String url) {

Bitmap bitmap = null;

//通过memoryCache取出软引用

SoftReference softRef = memoryCache.get(getCacheKey(url));

//判断系统有无回收该引用

if(softRef != null){

//get

bitmap = softRef.get();

}

return bitmap;

}

/**

  • 从Disk获取Bitmap实例

  • @param url

  • @return

*/

private Bitmap getBitmapFromDisk(String url) {

Bitmap bitmap = null;

if(diskCacheEnabled){

//根据URL在磁盘上查找对应的文件

String filePath = getFilePath(url);

File file = new File(filePath);

//若存在则decode为Bitmap实例

if(file.exists()) {

bitmap = BitmapFactory.decodeFile(filePath);

}

}

return bitmap;

}

/**

  • 获取磁盘缓存路径

  • @param url

  • @return

*/

private String getFilePath(String url) {

return diskCachePath + getCacheKey(url);

}

/**

  • 获取缓存Key

  • @param url

  • @return

*/

private String getCacheKey(String url) {

if(url == null){

throw new RuntimeException(“Null url passed in”);

} else {

//URL中可能包含一些特殊字符,在将URL转换成文件名时需要做预处理,过滤掉这些字符。

return url.replaceAll(“[.😕,%?&=]”, “+”).replaceAll(“[+]+”, “+”);

}

}

那是如何缓存的呢?我们再来看两个方法:

/**

  • 将Bitmap存到内存缓存

  • @param url

  • @param bitmap

*/

private void cacheBitmapToMemory(final String url, final Bitmap bitmap) {

//将数据put到HashMap中,存入的是Bitmap的软引用

memoryCache.put(getCacheKey(url), new SoftReference(bitmap));

}

/**

  • 将Bitmap存入到磁盘

  • @param url

  • @param bitmap

*/

private void cacheBitmapToDisk(final String url, final Bitmap bitmap) {

/*******************************************************

  • 将Bitmap存入磁盘缓存是通过线程池ExecutorService实现

  • 1.限制同时存在的线程个数

  • 2.是解决同步问题。smart-image库使用的是只有一个线程的线程池,

*******************************************************/

writeThread.execute(new Runnable() {

@Override

public void run() {

if(diskCacheEnabled) {

BufferedOutputStream ostream = null;

try {

//调用Bitmap.compress函数按指定压缩格式和压缩质量将Bitmap写到磁盘文件输出流中(在构造方法中根据URL创建对应文件)

ostream = new BufferedOutputStream(new FileOutputStream(new File(diskCachePath, getCacheKey(url))), 2*1024);

bitmap.compress(CompressFormat.PNG, 100, ostream);

} catch (FileNotFoundException e) {

e.printStackTrace();

} finally {

try {

if(ostream != null) {

ostream.flush();

ostream.close();

}

} catch (IOException e) {}

}

}

}

});

}

到此,关于其缓存原理大概就是这么个样子了,如果大家还有不明白的,我后面给出源码,可根据注释进行学习。

我们接着往下看关于SmartImageView和SmartImageTask两个类的类图结构:

从类图可以看到,有两个类:SmartImageTask、SmartImageView,另外还有两个静态类,一个是继承Handler的静态类:

public static class OnCompleteHandler extends Handler{}

另一个是抽象的静态类:

public abstract static class OnCompleteListener{}

那么如何理解这两个类呢?可能有同学已经从关系图和字面上理解了,咦~ ,难道是一个类专注于后台图片加载处理、另一个则专注于UI处理。哟西~,这位同学,你说对了!确实就是如此。来来来,脸挪过来 ……

SmartImageTask实现了Runnable接口,我们来看看这个类的源码:

package com.loopj.android.image;

import android.content.Context;

import android.graphics.Bitmap;

import android.os.Handler;

import android.os.Message;

/**

  • 专注于后台图片加载处理的Task类

  • 实现了Runnable接口

*/

public class SmartImageTask implements Runnable {

private static final int BITMAP_READY = 0;

private boolean cancelled = false;

private SmartImage image;

private Context context;

private OnCompleteHandler onCompleteHandler;

public void setOnCompleteHandler(OnCompleteHandler handler){

this.onCompleteHandler = handler;

}

//图片加载完成的回调接口OnCompleteListener

public static class OnCompleteHandler extends Handler {

//将handler定义成static,是为了避免内存泄露,可参考博客:http://blog.csdn.net/gao_chun/article/details/46046637

@Override

public void handleMessage(Message msg) {

Bitmap bitmap = (Bitmap)msg.obj;

onComplete(bitmap);

}

public void onComplete(Bitmap bitmap){};

}

//用于SmartImageView类中加载完成后的回调接口

public abstract static class OnCompleteListener {

public abstract void onComplete();

//这里也说明了此方法的作用:加载图片回调的方法,重写此方法以获取位图的句柄,增加了重载的实现使其与以前版本兼容

/***

  • Convient method to get Bitmap after image is loaded.

  • Override this method to get handle of bitmap

  • Added overloaded implementation to make it backward compatible with previous versions

*/

public void onComplete(Bitmap bitmap){

onComplete();

}

}

//构造方法,将SmartImage作为参数

public SmartImageTask(Context context, SmartImage image) {

this.image = image;

this.context = context;

}

@Override

public void run() {

//使用SmartImage的getBitmap函数来获取URL的Bitmap实例

if(image != null) {

complete(image.getBitmap(context));

context = null;

}

}

public void cancel() {

cancelled = true;

}

/**

  • 获取Bitmap实例

  • @param bitmap

*/

public void complete(Bitmap bitmap){

if(onCompleteHandler != null && !cancelled) {

//获取实例完成后发送消息个Handler

onCompleteHandler.sendMessage(onCompleteHandler.obtainMessage(BITMAP_READY, bitmap));

}

}

}

可以看到,在SmartImageTask中实现了回调机制供SmartImageView使用,大家可以结合注释进行阅读。回调主要是以下实现:

public static class OnCompleteHandler extends Handler {

//将handler定义成static,是为了避免内存泄露,可参考博客:http://blog.csdn.net/gao_chun/article/details/46046637

@Override

public void handleMessage(Message msg) {

Bitmap bitmap = (Bitmap)msg.obj;

onComplete(bitmap);

}

public void onComplete(Bitmap bitmap){};

}

//用于SmartImageView类中加载完成后的回调接口

public abstract static class OnCompleteListener {

public abstract void onComplete();

//这里也说明了此方法的作用:加载图片回调的方法,重写此方法以获取位图的句柄,增加了重载的实现使其与以前版本兼容

/***

  • Convient method to get Bitmap after image is loaded.

  • Override this method to get handle of bitmap

  • Added overloaded implementation to make it backward compatible with previous versions

*/

public void onComplete(Bitmap bitmap){

onComplete();

}

}

下面我们看看SmartImageView中的核心方法:

/**

  • 核心方法,加载图片

  • @param image SmartImage实例

  • @param fallbackResource 加载失败的图片

  • @param loadingResource 加载中的图片

  • @param completeListener 加载完成后的回调接口

*/

public void setImage(final SmartImage image, final Integer fallbackResource, final Integer loadingResource, final SmartImageTask.OnCompleteListener completeListener) {

// Set a loading resource

if(loadingResource != null){

//不为空则设置loading中的图片

setImageResource(loadingResource);

}

// Cancel any existing tasks for this image view

if(currentTask != null) {

//取消当前执行任务

currentTask.cancel();

currentTask = null;

}

// Set up the new task

currentTask = new SmartImageTask(getContext(), image);

currentTask.setOnCompleteHandler(new SmartImageTask.OnCompleteHandler() {

@Override

public void onComplete(Bitmap bitmap) {

if(bitmap != null) {

setImageBitmap(bitmap);

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
img

总结

学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯。所以:贵在坚持!

最后如何才能让我们在面试中对答如流呢?

答案当然是平时在工作或者学习中多提升自身实力的啦,那如何才能正确的学习,有方向的学习呢?有没有免费资料可以借鉴?为此我整理了一份Android学习资料路线:

这里是一部分我工作以来以及参与过的大大小小的面试收集总结出来的一套BAT大厂面试资料专题包,在这里免费分享给大家,主要还是希望大家在如今大环境不好的情况下面试能够顺利一点,希望可以帮助到大家。需要的小伙伴们可以点击我的GitHub获取免费领取方式

好了,今天的分享就到这里,如果你对在面试中遇到的问题,或者刚毕业及工作几年迷茫不知道该如何准备面试并突破现状提升自己,对于自己的未来还不够了解不知道给如何规划,可以去我的主页加一下技术群。来看看同行们都是如何突破现状,怎么学习的,来吸收他们的面试以及工作经验完善自己的之后的面试计划及职业规划。

最后,祝愿即将跳槽和已经开始求职的大家都能找到一份好的工作!

s9-1711045162719)]
[外链图片转存中…(img-4WbsBOdH-1711045162719)]
[外链图片转存中…(img-7bDFWev7-1711045162720)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
[外链图片转存中…(img-eDZGjpLg-1711045162720)]

总结

学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯。所以:贵在坚持!

最后如何才能让我们在面试中对答如流呢?

答案当然是平时在工作或者学习中多提升自身实力的啦,那如何才能正确的学习,有方向的学习呢?有没有免费资料可以借鉴?为此我整理了一份Android学习资料路线:

[外链图片转存中…(img-qFtsba8F-1711045162721)]

这里是一部分我工作以来以及参与过的大大小小的面试收集总结出来的一套BAT大厂面试资料专题包,在这里免费分享给大家,主要还是希望大家在如今大环境不好的情况下面试能够顺利一点,希望可以帮助到大家。需要的小伙伴们可以点击我的GitHub获取免费领取方式

[外链图片转存中…(img-FeC96PJM-1711045162721)]

好了,今天的分享就到这里,如果你对在面试中遇到的问题,或者刚毕业及工作几年迷茫不知道该如何准备面试并突破现状提升自己,对于自己的未来还不够了解不知道给如何规划,可以去我的主页加一下技术群。来看看同行们都是如何突破现状,怎么学习的,来吸收他们的面试以及工作经验完善自己的之后的面试计划及职业规划。

最后,祝愿即将跳槽和已经开始求职的大家都能找到一份好的工作!

这些只是整理出来的部分面试题,后续会持续更新,希望通过这些高级面试题能够降低面试Android岗位的门槛,让更多的Android工程师理解Android系统,掌握Android系统。喜欢的话麻烦点击一个喜欢再关注一下~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值