KJFrameForAndroid 又叫KJLibrary,免费的、开源的、简易的、遵循Apache Licence 2.0开源协议发布的android应用开发框架,总共分为五大模块:UILibrary,UtilsLibrary,HttpLibrary,BitmapLibrary,DBLibrary。
UILibrary模块分为两部分,widget、topology 更多介绍...
widget部分包含了目前应用开发中常见的自定义控件,例如上下拉ListView、瀑布流、可缩放的ImageView。
Topology部分包含一个使用IOC设计思想的控件初始化方式:可通过注解的方式进行UI绑定,与设置监听,在Activity和Fragment中均可以通过一行代码绑定控件并实现点击监听;还包含了在目前应用开发中常见的布局界面,如侧滑效果,高效的底部TAB导航,3D效果的切换。
同时UILibrary为开发者定义了完善的BaseActivity和BaseFragment,开发者只需手动继承就可以获得Topology部分的全部功能。
UtilsLibrary模块 包含了应用开发中的常用工具类,例如系统级别的Log管理、网络状态监测、Bitmap压缩工具类、获取屏幕宽高以及单位转换的工具类、错误信息处理与文件处理工具类、preference工具类、字符串操作与常用正则判断等。
HttpLibrary模块 使用HttpClient与HttpUrlConnection两种实现方式实现网络通信、数据上传、多线程断点下载。根据Google建议:在2.3系统之前由于HttpUrlConnection不稳定且有一定的BUG,应该尽量使用HttpClient;在2.3以后的系统,若只是简单的数据交互,应该使用更加轻量级、易扩展的HttpUrlConnection。对于实现的方式,KJLibrary将交由开发者来选择。
BitmapLibrary模块 的使用:可以让开发者在使用imageview加载图片的时候无需考虑图片加载过程中出现的OOM问题以及在ListView滑动过程中出现的图片错位问题。
DBLibrary模块 目前使用的是开源框架afinal的FinalDB,是Android中的ORM框架,一行代码就可以进行增删改查操作。支持一对多,多对一等查询。项目主页:http://www.open-open.com/lib/view/home/1404358619608
KJFrameForAndroid框架学习----高效设置网络图片
KJFrameForAndroid框架项目地址:https://github.com/kymjs/KJFrameForAndroid
或备用地址http://git.oschina.net/kymjs/KJFrameForAndroid
我们都知道,计算机读取数据时:内存的读取速度是最快的,然后是文件的读取速度,最后是网络资源的读取。
如果每次加载同一张图片都要从网络获取,那代价实在太大了。所以同一张图片只要从网络获取一次就够了,然后在本地缓存起来,之后加载同一张图片时就 从缓存中加载就可以了。
从内存缓存读取图片是最快的,但是因为Android对每个应用所能使用的内存容量都有限制,所以最好再加上文件缓存。文件缓存空间也不是无限大的,容量越大读取效率越低,这个很好理解,从沙漠中找出丢失的一根针和从盘子中找到一根针,哪个容易一想即知。因此我们常设置一个限定大小比如10M。
所以,加载图片的流程应该是:
1、先从内存缓存中获取,取到则返回,取不到则进行下一步;
2、从文件缓存中获取,取到则返回并更新到内存缓存,取不到则进行下一步;
3、从网络下载图片,并更新到内存缓存和文件缓存。
如果您只想了解文件缓存与内存缓存公用,请查看下一篇博文。
在过去,我们经常会使用一种非常流行的内存缓存技术的实现,即软引用或弱引用 (SoftReference or WeakReference)。但是根据Google的描述:现在已经不再推荐使用这种方式了,因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。另外,Android 3.0 (API Level 11)中,图片的数据会存储在本地的内存当中,因而无法用一种可预见的方式将其释放,这就有潜在的风险造成应用程序的内存溢出并崩溃。
因此,我们更多的是去使用lru算法(Least Recently Used 近期最少使用算法)最初这种算法是用在操作系统调度上的。他的原理是通过个线性表存储数据,并记录数据每次调用次数,越常用到的排名就越靠前,越少用到的排名就越靠后,如果是一个新加入的数据,就会把它放在第一位,然后移除掉排名最后一位的数据。这里是KJFrameForAndroid框架中关于内存lru算法的实现方式 LruMemoryCache
既然是加载网络图片,那么当然需要加载的控件和网络图片地址作为参数,示例如下所示
1
2
3
4
5
6
7
8
9
10
11
|
private
void
loadImage(ImageView imageView, String imageUrl) {
// 首先访问内存缓存,判断图片是否已经存在
Bitmap bitmap = mMemoryCache.get(StringUtils.md5(imageUrl));
if
(bitmap !=
null
) {
imageView.setImageBitmap(bitmap);
}
else
{
//否则就去网络下载
BitmapWorkerTask task =
new
BitmapWorkerTask(imageView);
task.execute(imageUrl);
}
}
|
至于实际下载的方法,我就不详细讲解了,相信大家都能想到,就是一个网络请求,然后下载图片,再转成bitmap,最后设置为控件图片。然而这里有一个需要注意的重要地方,就是当我们把图片下载成功后要记得在mMemoryCache中缓存起来。
这里是KJFraemForAndroid应用开发框架中的一段网络图片加载的代码:
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
|
/**
* 加载图片(核心方法)
*
* @param imageView
* 要显示图片的控件(ImageView设置src,普通View设置bg)
* @param imageUrl
* 图片的URL
*/
private
void
loadImage(View imageView, String imageUrl) {
if
(config.callBack !=
null
)
config.callBack.imgLoading(imageView);
Bitmap bitmap = mMemoryCache.get(StringUtils.md5(imageUrl));
if
(bitmap !=
null
) {
if
(imageView
instanceof
ImageView) {
((ImageView) imageView).setImageBitmap(bitmap);
}
else
{
imageView.setBackgroundDrawable(
new
BitmapDrawable(bitmap));
}
if
(config.callBack !=
null
)
config.callBack.imgLoadSuccess(imageView);
if
(config.isDEBUG)
KJLoger.debugLog(getClass().getName(),
"download success, from memory cache\n"
+ imageUrl);
}
else
{
if
(imageView
instanceof
ImageView) {
((ImageView) imageView).setImageBitmap(config.loadingBitmap);
}
else
{
imageView.setBackgroundDrawable(
new
BitmapDrawable(
config.loadingBitmap));
}
BitmapWorkerTask task =
new
BitmapWorkerTask(imageView);
taskCollection.add(task);
task.execute(imageUrl);
}
}
/********************* 异步获取Bitmap并设置image的任务类 *********************/
private
class
BitmapWorkerTask
extends
AsyncTask<String, Void, Bitmap> {
private
View imageView;
public
BitmapWorkerTask(View imageview) {
this
.imageView = imageview;
}
@Override
protected
Bitmap doInBackground(String... params) {
Bitmap bitmap =
null
;
byte
[] res = downloader.loadImage(params[
0
]);
if
(res !=
null
) {
bitmap = BitmapCreate.bitmapFromByteArray(res,
0
, res.length,
config.width, config.height);
}
if
(bitmap !=
null
&& config.openMemoryCache) {
// 图片载入完成后缓存到LrcCache中
putBitmapToMemory(params[
0
], bitmap);
if
(config.isDEBUG)
KJLoger.debugLog(getClass().getName(),
"put to memory cache\n"
+ params[
0
]);
}
return
bitmap;
}
@Override
protected
void
onPostExecute(Bitmap bitmap) {
super
.onPostExecute(bitmap);
if
(imageView
instanceof
ImageView) {
if
(bitmap !=
null
) {
((ImageView) imageView).setImageBitmap(bitmap);
}
}
else
{
imageView.setBackgroundDrawable(
new
BitmapDrawable(bitmap));
}
if
(config.callBack !=
null
)
config.callBack.imgLoadSuccess(imageView);
taskCollection.remove(
this
);
}
}
|
深入理解图片加载在实际项目中的应用:
以上只是网络图片加载并缓存的基本操作,那么我们如果在实际项目中使用必须考虑到代码的完备性与可扩展性。
①比如我们想指定图片的大小,虽然我们可以通过设置view的固定宽高来强制图片的显示大小,但如果是一张几兆的图片,而我们只需要15*15分辨率大小的显示区域,这显然是浪费的;
②又比如,我们希望控件在网络正在下载图片时先显示一个默认的图片(比如一个灰色的头像)又或者是图片下载的时候显示一个环形的进度条,那么上面的代码是没有办法的;
③再比如,我们希望图片的下载方式有多种,对于不同网站来源有不同的下载方式。。。。
这些种种特殊的需求告诉我们,上面的代码完全没有办法做到。那么为了控件的完备性与可扩展性,我们就需要一个配置器、一个显示器、一个下载器。。等等根据特殊需要而添加的插件式开发。
因此,我们可以看到在KJFrameForAndroid框架的org.kymjs.aframe.bitmap包下有着KJBitmapConfig、I_ImageLoder、I_Display等等用final修饰的类或者协议接口。
比如KJBitmapConfig类,是一个用final修饰的配置器类,通过这个配置器,我们就可以动态的对每一张下载的图片设置宽高、以及内存大小等。而I_ImageLoder、I_Display则是两个协议接口,分别定义了下载器和显示器的方法,这里实际上是GoF设计模式中工厂方法模式的应用,只是这里的工厂实际上并不是用来创建对象,而是用来定义显示方法或下载方法的,不论是哪个实现了I_ImageLoder抽象工厂的实际工厂,都必须有一个加载图片的方法。那么在项目的实际应用中,就可以不管这个下载器的实际工厂是什么,只需要调用工厂的加载图片的方法就行了。
1
2
3
4
5
6
7
8
9
10
11
12
|
/**
* 图片载入接口协议,可自定义实现此协议的下载器
*
* @explain 采用工厂方法模式设计的下载器,本类也是一个抽象工厂类,用于生产byte[]产品
* @author kymjs(kymjs123@gmail.com)
* @version 1.0
* @created 2014-7-11
*/
public
interface
I_ImageLoder {
public
byte
[] loadImage(String imageUrl);
}
|
这里我们应该就可以知道上面的代码段中有这么一段代码原因了
byte[] res = downloader.loadImage(params[0]);
downloader实际上就是下载器的抽象工厂。
至于显示器的逻辑和下载器是一样的,这里我就不详细介绍了,大家可以自己查看KJFrameForAndroid的源代码或示例项目。
这里是I_ImageLoader下载器协议的一个实现类 Downloader.java,大家当然也可以根据自己的需要去实现自己的下载器,这完全没有任何作为扩展试开发,这对于图片设置代码本身没有任何影响。
浅析KJFrameForAndroid框架如何高效加载Bitmap
我们在写Android程序的时候,肯定会用到很多图片。那么对于图片的压缩处理自然是必不可少。为什么要压缩?我想这个问题不必在强调了,每个人在最初学习Android的时候肯定都会知道这么一个原因:我们编写的应用程序都是有一个最大内存限制,其中JAVA程序和C程序(NDK调用时)共享这一块内存大小,程序占用了过高的内存就容易出现OOM(OutOfMemory)异常。至于这个最大内存是多少,我们可以通过调用Runtime.getRuntime().maxMemory()方法验证一下。
正因为受到内存大小限制这一关键原因(其实不止这个原因,我想一张1M的图片和一张10k的图片,载入的速度必然也是不同的吧)。 如果你的控件大小只有40*40像素的大小,只是为了显示一张缩略图,这时候把一张1024*768像素的图片完全加载到内存中显然是不值得的,因此我们都会对图片做压缩处理。
BitmapFactory这个类提供了多个方法(decodeByteArray, decodeFile, decodeResource等)用于创建Bitmap对象,我们可以根据图片的来源选择合适的方法。然而这些方法会为已经读取的bitmap分配内存,这时如果是一张非常大的图片就会导致OOM出现。为此,每一种解析方法都提供了一个BitmapFactory.Options参数,可以通过将这个参数的inJustDecodeBounds属性设置为true就可以让解析方法禁止为bitmap分配内存,但是如此设置后BitmapFactory的返回值也不再是一个Bitmap对象,而是null。虽然Bitmap是null了,但是BitmapFactory.Options的outWidth、outHeight和outMimeType属性都会被赋值。使用这个技巧让我们可以在加载图片之前就获取到图片的长宽值和类型,从而根据情况对图片进行压缩。
1
2
3
4
5
6
|
BitmapFactory.Options options =
new
BitmapFactory.Options();
options.inJustDecodeBounds =
true
;
BitmapFactory.decodeFile(pathName, options);
int
h = options.outHeight;
int
w = options.outWidth;
String type = options.outMimeType;
|
那么知道了图片的宽高,要如何压缩呢?BitmapFactory.Options有一个inSampleSize属性,这个int值表示图片的原宽高变为1/inSampleSize倍,如果原图是1024*768,inSampleSize=2,那么压缩后图片就变成了512*384。 最后将BitmapFactory.Options设置合适的inSampleSize值,并且记得将inJustDecodeBounds设置回false,再调用一次BitmapFactory相应的创建Bitmap的方法,并把Options传入,就可以得到压缩后的图片了。
这里有一个节选自开源Android应用开发框架KJFrameForAndroid中的一段代码
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
|
/**
* 图片压缩处理(使用Options的方法)
*
* @使用方法 首先你要将Options的inJustDecodeBounds属性设置为true,BitmapFactory.decode一次图片。
* 然后将Options连同期望的宽度和高度一起传递到到本方法中。
* 之后再使用本方法的返回值做参数调用BitmapFactory.decode创建图片。
*
* @explain BitmapFactory创建bitmap会尝试为已经构建的bitmap分配内存
* ,这时就会很容易导致OOM出现。为此每一种创建方法都提供了一个可选的Options参数
* ,将这个参数的inJustDecodeBounds属性设置为true就可以让解析方法禁止为bitmap分配内存
* ,返回值也不再是一个Bitmap对象, 而是null。虽然Bitmap是null了,但是Options的outWidth、
* outHeight和outMimeType属性都会被赋值。
* @param reqWidth
* 目标宽度
* @param reqHeight
* 目标高度
*/
public
static
BitmapFactory.Options calculateInSampleSize(
final
BitmapFactory.Options options,
int
reqWidth,
int
reqHeight) {
// 源图片的高度和宽度
final
int
height = options.outHeight;
final
int
width = options.outWidth;
int
inSampleSize =
1
;
if
(height > reqHeight || width > reqWidth) {
// 计算出实际宽高和目标宽高的比率
final
int
heightRatio = Math.round((
float
) height
/ (
float
) reqHeight);
final
int
widthRatio = Math.round((
float
) width / (
float
) reqWidth);
// 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高
// 一定都会大于等于目标的宽和高。
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
// 设置压缩比例
options.inSampleSize = inSampleSize;
options.inJustDecodeBounds =
false
;
return
options;
}
|
以上的方法适合使用在读取一个未知来源的图片时使用,因为你不知道这个未知来源图片的大小,那么还有一种方法是用在已经载入内存的图片,对已经载入内存的图片做压缩以后重新保存到本地,从而可以把一张原本1M大小的图片变成一张10K的图片。
这种方法的核心思想是首先将图片转成一个输出流,并记录输出流的byte数组大小,通过调用bitmap对象的compress方法,对图片做一次压缩以及格式化,并将byte数组大小与期望压缩的目标大小比对,得出压缩比率,并调用Bitmap的缩放方法,缩放计算出的压缩比率,从而得到压缩后的方法。
下面我们继续来看KJFrameForAndroid框架中的另一段代码:
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
|
/**
* 图片压缩方法:(使用compress的方法)
*
* @explain 如果bitmap本身的大小小于maxSize,则不作处理
* @param bitmap
* 要压缩的图片
* @param maxSize
* 压缩后的大小,单位kb
*/
public
static
void
imageZoom(Bitmap bitmap,
double
maxSize) {
// 将bitmap放至数组中,意在获得bitmap的大小(与实际读取的原文件要大)
ByteArrayOutputStream baos =
new
ByteArrayOutputStream();
// 格式、质量、输出流
bitmap.compress(Bitmap.CompressFormat.JPEG,
100
, baos);
byte
[] b = baos.toByteArray();
// 将字节换成KB
double
mid = b.length /
1024
;
// 获取bitmap大小 是允许最大大小的多少倍
double
i = mid / maxSize;
// 判断bitmap占用空间是否大于允许最大空间 如果大于则压缩 小于则不压缩
if
(i >
1
) {
// 缩放图片 此处用到平方根 将宽带和高度压缩掉对应的平方根倍
// (保持宽高不变,缩放后也达到了最大占用空间的大小)
bitmap = scale(bitmap, bitmap.getWidth() / Math.sqrt(i),
bitmap.getHeight() / Math.sqrt(i));
}
}
/***
* 图片的缩放方法
*
* @param src
* :源图片资源
* @param newWidth
* :缩放后宽度
* @param newHeight
* :缩放后高度
*/
public
static
Bitmap scale(Bitmap src,
double
newWidth,
double
newHeight) {
// 记录src的宽高
float
width = src.getWidth();
float
height = src.getHeight();
// 创建一个matrix容器
Matrix matrix =
new
Matrix();
// 计算缩放比例
float
scaleWidth = ((
float
) newWidth) / width;
float
scaleHeight = ((
float
) newHeight) / height;
// 开始缩放
matrix.postScale(scaleWidth, scaleHeight);
// 创建缩放后的图片
return
Bitmap.createBitmap(src,
0
,
0
, (
int
) width, (
int
) height,
matrix,
true
);
}
|
另外附上KJFrameForAndroid框架项目地址: https://github.com/kymjs/KJFrameForAndroid