想了半天都没想出来写什么作为第一篇博客比较好,突然发现这次项目的版本控制是github,三级缓存和屏幕适配又是一个跑不掉的话题,就以这些话题开始我的博客之旅吧,同时总结一下在AndroidStudio中如何导出jar包。

([参考郭霖的blog](http://blog.csdn.net/guolin_blog/article/details/28863651))
([参考李晨玮的blog](http://www.cnblogs.com/lichenwei)/)

不管是提升用户体验还是提升APP的流畅度,三级缓存都是必不可少的,在这里就先总结一下三级缓存的使用。

  

​ 加载网络图片时,使用三级缓存可以有效的提高用户体验以及程序的流畅度。
  三级缓存为以下内容:网络层/内存层/磁盘层
  缓存的流程如下:
  1. 网络层下载图片 Volley->imageLoader
  2. 存入内存层 LruCache
  3. 存入磁盘层 DiskLruCache
  而当以上操作均完成以后,此次下载的图片就写入到了手机中,下次运行程序时,就会优先到目录中查找是否存在该图片,如果存在就直接使用,免除了多次加载耗费流量。

首先,在使用之前,包含volley的库以及DiskLrucache, 链接如下
[volley](http://download.csdn.net/detail/jonstank2013/9293561)
[DiskLrucache](http://download.csdn.net/detail/jonstank2013/9293555)

 1. 定义MyApplication, 在其中获取到context,避免每次都需要单独获取一次上下文对象。
 在MyApplication中设置有一个静态的TAG,这个TAG是为了获取基础类的名称,以此作为每一个加入RequestQueue的request的标志

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
package com.mylibs.volley_disklrucache_lrucache.customview;

import android.app.Application;

/**
* Created by lizhongquan on 15-11-23.
*/
public class MyApplication extends Application {

/**
* Application
*/
public static MyApplication myApplication;

/**
* Get Tag to setTag for everyAty
*/
public static String TAG;

public static MyApplication getMyApplication() {
return myApplication;
}

@Override
public void onCreate() {
super.onCreate();

myApplication = this;
TAG = this.getClass().getSimpleName();
}
}

然后在AndroidManifest文件中声明: 

1
2
3
<application
android:name=".customview.MyApplication" >
</application>

同样的,为了避免重复编写,将各种工具类进行单独的封装

在Volley中,存在一个RequestQueue就可以了,其他所有的请求都直接放在这个队列中

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
package com.mylibs.volley_disklrucache_lrucache.utils;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.Volley;
import com.mylibs.volley_disklrucache_lrucache.customview.MyApplication;

/**
* Created by lizhongquan on 15-11-23.
*/
public class VolleyRequestQueue {

/**
* get RequestQueue
*/
public static RequestQueue mRequestQueue = Volley.newRequestQueue(MyApplication.getMyApplication());

/**
* add request
*/
public static void addRequest(Request<?> request, Object tag){
if (null != tag){
request.setTag(tag);
}
mRequestQueue.add(request);
}

/**
* cancel all request
*/
public static void cancelRequest(Object tag){
mRequestQueue.cancelAll(tag);
}
}

       journal内容
对于DiskLruCache, 他是在手机的保存目录中创建一个journal文件,在此文件中有如下内容:
第一行是个固定的字符串“libcore.io.DiskLruCache”,标志着我们使用的是DiskLruCache技术。
第二行是DiskLruCache的版本号,这个值是恒为1。
第三行是应用程序的版本号,我们在open()方法里传入的版本号是什么这里就会显示什么。
第四行是valueCount,这个值也是在open()方法中传入的,通常情况下都为1。
第五行是一个空行。
前五行也被称为journal文件的头。
第六行是以一个DIRTY前缀开始的,后面紧跟着缓存图片的key。通常我们看到DIRTY这个字样都不代表着什么好事情,意味着这是一条脏数据。每当我们调用一次DiskLruCache的edit()方法时,都会向journal文件中写入一条DIRTY记录,表示我们正准备写入一条缓存数据,但不知结果如何。然后调用commit()方法表示写入缓存成功,这时会向journal中写入一条CLEAN记录,意味着这条“脏”数据被“洗干净了”,调用abort()方法表示写入缓存失败,这时会向journal中写入一条REMOVE记录。也就是说,每一行DIRTY的key,后面都应该有一行对应的CLEAN或者REMOVE的记录,否则这条数据就是“脏”的,会被自动删除掉。
第七行的那条记录,除了CLEAN前缀和key之外,后面还有一个152313,这是什么意思呢?其实,DiskLruCache会在每一行CLEAN记录的最后加上该条缓存数据的大小,以字节为单位。152313也就是缓存的图片的字节数了,换算出来大概是148.74K,和缓存图片刚刚好一样大。

以上的一段话包括图片均来自郭霖的博客。

同时,由上可以看出,我们必须对每一个文件名创建一个独立的文件名,比较推荐的一种方式是利用MD5转换。
这个工具类没有好介绍的,直接拿来用就可以了

MD5的工具类:

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
package com.mylibs.volley_disklrucache_lrucache.utils;

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
* Created by lizhongquan on 15-11-23.
*/
public class MD5Util {

/**
* Encrypt the information by MD5
*/

public static String md5(String plainText) {
byte[] secretBytes = null;
try {
secretBytes = MessageDigest.getInstance("md5").digest(
plainText.getBytes());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("no md5!");
}

/**
* 16 Band
*/
String md5code = new BigInteger(1, secretBytes).toString(16);

/**
* If number.lenth < 32, add 0 in its front
*/
for (int i = 0; i < 32 - md5code.length(); i++) {
md5code = "0" + md5code;
}
return md5code;
}
}

之后,开始准备Volley的ImageLoader的图片缓存类ImageCacheUtil

图片缓存类包含的内容如下:
1.在它的构造方法中设置LruCache的最大缓存,首先获取到可用内存的最大值,使用内存如果超过这个值会引起OutOfMemory异常
2.使用最大可用内存值的1/8作为缓存的大小
3.从LruCache或者DiskLruCache中取出Bitmap, 对于DiskCache来说,首先会利用经MD5转换后的唯一名来进行查找,如果找到,就取出相关的资源
4.保存资源到LruCache或DiskLruCache中。对于DiskLruCache而言,同样是先检测后操作
5.获取磁盘的缓存地址。一般而言,缓存的地址会存放在 /sdcard/Android/data//cache 这个路径下,但是有的手机是没有SD卡的,因此需要进行一个判断。如果存在,使用getExternalCacheDir()方法来获取缓存路径,否则就调用getCacheDir()方法来获取缓存路径。前者获取到的是/sdcard/Android/data//cache ,后者获取到的是 /data/data//cache
6.获取应用程序的版本号。由于DiskLruCache的设置,所以只要我们APP的版本号改变了,缓存路径下存储的数据都会被清楚,因为DiskLruCache认为当应用程序有版本更新的时候,所有数据都应从网上重新获取。

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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package com.mylibs.volley_disklrucache_lrucache.utils;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Environment;
import android.support.v4.util.LruCache;
import android.util.Log;

import com.android.volley.toolbox.ImageLoader;
import com.mylibs.volley_disklrucache_lrucache.DiskLruCache;
import com.mylibs.volley_disklrucache_lrucache.customview.MyApplication;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;

/**
* Created by lizhongquan on 15-11-23.
*/
public class ImageCacheUtil implements ImageLoader.ImageCache {

// get Tag
private String TAG = ImageCacheUtil.this.getClass().getSimpleName();

// LruCache / DiskLruCache
private static LruCache<String, Bitmap> mLruCache;
private static DiskLruCache mDiskLruCache;

// DiskMaxSize
private static final int DISKMAXSIZE = 10 * 1024 * 1024;

public ImageCacheUtil() {
// get 1/8 of memory to Cache
int maxSize = (int) (Runtime.getRuntime().maxMemory() / 8);

// Instance LruCaceh对象
mLruCache = new LruCache<String, Bitmap>(maxSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight();
}
};

// get DiskLruCahce
try {
mDiskLruCache = DiskLruCache.open(getDiskCacheDir(
MyApplication.getMyApplication(), "DiskLruCache"),
getAppVersion(MyApplication.getMyApplication()), 1, DISKMAXSIZE);
} catch (IOException e) {
e.printStackTrace();
}
}

/**
* getBitmap from LruCache or diskLruCache
*/
@Override
public Bitmap getBitmap(String url) {
if (mLruCache.get(url) != null) {
// get from LruCache
Log.i(TAG, "get from LruCache");
return mLruCache.get(url);
} else {
String key = MD5Util.md5(url);
try {
if (mDiskLruCache.get(key) != null) {
// get from DiskLruCache
DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
Bitmap bitmap = null;
if (snapshot != null) {
bitmap = BitmapFactory.decodeStream(snapshot.getInputStream(0));
// save into LruCache
mLruCache.put(url, bitmap);
Log.i(TAG, "get from diskLruCache");
}
return bitmap;
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}

/**
* save into LruCache or DiskLruCache
*/
@Override
public void putBitmap(String url, Bitmap bitmap) {
// save into LruCache
mLruCache.put(url, bitmap);

// get the uniqueName
String key = MD5Util.md5(url);
try {
// Judge if the DiskLruCache exists
// If not, save into DiskLruCache
if (mDiskLruCache.get(key) == null) {
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (editor != null) {
OutputStream outputStream = editor.newOutputStream(0);
if (bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)) {
editor.commit();
} else {
editor.abort();
}
}
mDiskLruCache.flush();
}
} catch (IOException e) {
e.printStackTrace();
}

}

/**
* Judge the SDCard is exits or not and then choose the DiskLruCacheDir
*
* @param context context
* @param uniqueName the name of cache
* @return The cachePath
*/
public static File getDiskCacheDir(Context context, String uniqueName) {
String cachePath;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
|| !Environment.isExternalStorageRemovable()) {
cachePath = context.getExternalCacheDir().getPath();
} else {
cachePath = context.getCacheDir().getPath();
}
return new File(cachePath + File.separator + uniqueName);
}

/**
* getAppVersion
*
* @param context
* @return appVersion
*/
public int getAppVersion(Context context) {
try {
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
return info.versionCode;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return 1;
}
}

图片缓存管理类ImageCacheManager
向外部提供一个loadImage的重载方法,一个传入加载图片的宽高,一个默认加载原图,使外部不再需要关注任何关于缓存的操作

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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package com.mylibs.volley_disklrucache_lrucache.customview;

import android.graphics.Bitmap;
import android.widget.ImageView;

import com.android.volley.VolleyError;
import com.android.volley.toolbox.ImageLoader;
import com.mylibs.volley_disklrucache_lrucache.utils.ImageCacheUtil;
import com.mylibs.volley_disklrucache_lrucache.utils.VolleyRequestQueue;

/**
* Created by lizhongquan on 15-11-23.
* ImageCacheManager
*/
public class ImageCacheManager {

private static String TAG = ImageCacheManager.class.getSimpleName();

// get ImageCache
private static ImageLoader.ImageCache mImageCache = new ImageCacheUtil();

// get ImageLoader
public static ImageLoader mImageLoader = new ImageLoader(
VolleyRequestQueue.mRequestQueue, mImageCache);

/**
* ImageListener
*
* @param imageView ImageView
* @param defaultImage defaultImage
* @param errorImage errorImage
* @return new ImageLoader.ImageListener
*/
public static ImageLoader.ImageListener getImageListener(final ImageView imageView,
final Bitmap defaultImage,
final Bitmap errorImage) {

return new ImageLoader.ImageListener() {

@Override
public void onErrorResponse(VolleyError error) {
// Error
if (errorImage != null) {
imageView.setImageBitmap(errorImage);
}
}

@Override
public void onResponse(ImageLoader.ImageContainer response, boolean isImmediate) {
// Success
if (response.getBitmap() != null) {
imageView.setImageBitmap(response.getBitmap());
} else if (defaultImage != null) {
imageView.setImageBitmap(defaultImage);
}
}
};
}

/**
* Method to LoadImage
*
* @param url URL
* @param imageView ImageView
* @param defaultImage defaultImage
* @param errorImage errorImage
*/
public static void loadImage(String url, ImageView imageView, Bitmap defaultImage,
Bitmap errorImage) {
mImageLoader.get(url, ImageCacheManager.getImageListener(imageView,
defaultImage, errorImage), 0, 0);
}

/**
* Method to LoadImage
*
* @param url URL
* @param imageView ImageView
* @param defaultImage defaultImage
* @param errorImage errorImage
* @param maxWidth maxWidth
* @param maxHeight maxHeight
*/
public static void loadImage(String url, ImageView imageView, Bitmap defaultImage,
Bitmap errorImage, int maxWidth, int maxHeight) {
mImageLoader.get(url, ImageCacheManager.getImageListener(imageView, defaultImage,
errorImage), maxWidth, maxHeight);
}

/**
* Method to getBitmapFromRes
* It's connected to the Method loadImage
*
* @param context
* @param resId
* @return
*/
public static Bitmap getBitmapFromRes(Context context, int resId) {
Resources res = context.getResources();
return BitmapFactory.decodeResource(res, resId);
}
}

MainActivity.java
activty_main.xml中只包含一个TextView 和一个ImageView

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
package com.mylibs.volley_disklrucache_lrucache.testaty;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

import com.mylibs.volley_disklrucache_lrucache.R;
import com.mylibs.volley_disklrucache_lrucache.customview.ImageCacheManager;
import com.mylibs.volley_disklrucache_lrucache.customview.MyApplication;

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

TextView click = (TextView) findViewById(R.id.click);

final ImageView imageView = (ImageView) findViewById(R.id.imageView);

click.setText("click");
click.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String address = "https://www.baidu.com/img/baidu_jgylogo3.gif";
ImageCacheManager.loadImage(address,
imageView,
ImageCacheManager.getBitmapFromRes(MyApplication.getMyApplication(), R.mipmap.ic_launcher),
ImageCacheManager.getBitmapFromRes(MyApplication.getMyApplication(), R.mipmap.ic_launcher));
}
});
}
}

AndroidStudio导出jar包

相关的代码及测试已经完毕,现在来生成一个jar包,方便以后使用

AndroidStudio下打包生成jar包有两种方式,一种是在build.gradle中编写代码导出,一种是直接在终端中导出。
网上很多推荐在build.gradle中编写导出的,但是就我而言还是比较喜欢直接在终端导出,毕竟终端自己用得多一点

首先, ./gradlew clean build 这一步之前是需要确定已经安装了gradlew的,如果没有安装的话,最好翻墙安装,不然会装很久

之后打开终端,输入:jar cvf libs_volley_lrucache_disklrucache.jar -C /home/lizhongquan/AndroidStudioWorkplace/Volley_DiskLrucache_Lrucache/app/build/intermediates/classes/release .

最后一排的路径可以直接右键/app/build/intermediates/classes/release获得

最后回车,就可以在目录下看到生成的jar包了.

上传到github

由于此次项目的版本控制工具选择了github(git.oschina.net), 所以在这里也记录一下github的相关操作
此次代码上传的位置:https://github.com/LiZHongquan2013/Libs_VolleyDiskLruCacheLruCache.git

首先,创建一个仓库

git init .
git add .
git commit -m “libs_volley_lrucache_disklrucache”
git remote add origin https://github.com/LiZHongquan2013/Libs_VolleyDiskLruCacheLruCache.git
git push origin master

以前收藏的一个有关git相关操作的博客,挺详细
http://blog.csdn.net/small_rice_/article/details/45095323

第一篇博客到这里就结束了,下一篇为三种屏幕适配