Android OkHttp与物理存储介质缓存:DiskLruCache(2)
Android OkHttp与物理存储介质缓存:DiskLruCache(2)
本文在附录文章8,9的基础之上,把Android OkHttp与DiskLruCache相结合,综合此两项技术,实现基于OkHttp的物理存储介质缓存DiskLruCache。
用一个完整的例子加以说明。该例子的代码要实现这样的过程:代码启动后,要往一个ImageView里面加载一张网络图片,首先检查DiskLruCache是否已经存在该图片的缓存,如果存在,则直接复用缓存,如果不存在则使用OkHttp把图片异步从网络加载,当OkHttp异步加载网络图片成功后,要做两件事情:
一, 毫无疑问,要把该图片设置到目标ImageView里面。代码启动后首先要检查本地的DiskLruCache物理存储介质上是否已经有特定图片的缓存,如果有,则直接复用,不再浪费网络资源重复加载。
二,把该图片的数据写入DiskLruCache缓存中,为以后的缓存使用。此情况是当DiskLruCache不存在特定资源(本例是图片)缓存时候,要从网络加载。我使用OkHttp网络驱动加载,当OkHttp加载图片成功后,一方面要把图片设置到ImageView,另外一方面要把图片缓存到DiskLruCache以备后续使用。
完整代码:
package zhangphil.demo; 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.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.widget.ImageView; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import okhttp3.Call; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import okhttp3.Callback; import com.jakewharton.disklrucache.DiskLruCache; public class MainActivity extends AppCompatActivity { private String TAG = "zhangphil_tag"; private String UNIQUENAME = "zhangphil_cache"; private DiskLruCache mDiskLruCache = null; //缓存大小 private int DISK_CACHE_MAX_SIZE = 10 * 1024 * 1024; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //初始化DiskLruCache makeDiskLruCache(); //在布局里面放一个ImageView,放网络请求后的图片 final ImageView image = (ImageView) findViewById(R.id.imageView); //我的博客头像 String image_url = "http://avatar.csdn.net/9/7/A/1_zhangphil.jpg"; Bitmap bmp = readBitmapFromDiskLruCache(image_url); //首先检查DiskLruCache是否已经缓存了特定资源,如果有则直接复用。 //如果没有则从网路加载。 if (bmp != null) { image.setImageBitmap(bmp); } else { downloadBitmapFromNetwork(image, image_url); } } //从DiskLruCache中读取缓存 private Bitmap readBitmapFromDiskLruCache(String url) { DiskLruCache.Snapshot snapShot = null; try { //把url转换成一个md5字符串,然后以这个md5字符串作为key String key = urlToKey(url); snapShot = mDiskLruCache.get(key); } catch (Exception e) { e.printStackTrace(); } if (snapShot != null) { Log.d(TAG, "发现缓存:" + url); InputStream is = snapShot.getInputStream(0); Bitmap bitmap = BitmapFactory.decodeStream(is); Log.d(TAG, "从缓存中读取Bitmap."); return bitmap; } else return null; } //把byte字节写入缓存DiskLruCache private void writeToDiskLruCache(String url, byte[] buf) throws Exception { Log.d(TAG, url + " : 开始写入缓存..."); //DiskLruCache缓存需要一个key,我先把url转换成md5字符串, //然后以md5字符串作为key键 String key = urlToKey(url); DiskLruCache.Editor editor = mDiskLruCache.edit(key); OutputStream os = editor.newOutputStream(0); os.write(buf); os.flush(); editor.commit(); mDiskLruCache.flush(); Log.d(TAG, url + " : 写入缓存完成."); } private void makeDiskLruCache() { try { File cacheDir = getDiskCacheDir(this, UNIQUENAME); if (!cacheDir.exists()) { Log.d(TAG, "缓存目录不存在,创建之..."); cacheDir.mkdirs(); } else Log.d(TAG, "缓存目录已存在,不需创建."); //第二个参数我选取APP的版本code。DiskLruCache如果发现第二个参数version不同则销毁缓存 //第三个参数为1,在写缓存的流时候,newOutputStream(0),0为索引,类似数组的下标 mDiskLruCache = DiskLruCache.open(cacheDir, getVersionCode(this), 1, DISK_CACHE_MAX_SIZE); } catch (Exception e) { e.printStackTrace(); } } private void downloadBitmapFromNetwork(final ImageView image, final String image_url) { Log.d(TAG, "从网络中加载图片资源 ... @ " + image_url); //初始化OkHttpClient final OkHttpClient client = new OkHttpClient(); //创建OkHttpClient针对某个url的数据请求 Request request = new Request.Builder().url(image_url).build(); Call call = client.newCall(request); //请求加入队列 call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { //此处处理请求失败的业务逻辑 } @Override public void onResponse(Call call, Response response) throws IOException { //如果response响应成功则继续,否则返回 if (!response.isSuccessful()) return; //我写的这个例子是请求一个图片 //response的body是图片的byte字节 byte[] bytes = response.body().bytes(); //已经获得图片数据,记到要写入硬盘缓存 //出于性能考虑,此处可以放到后台或者放到一个线程里面处理 //简单期间,我就在这儿直接写缓存了。 try { writeToDiskLruCache(image_url, bytes); } catch (Exception e) { e.printStackTrace(); } //把byte字节组装成图片 final Bitmap bmp = BitmapFactory.decodeByteArray(bytes, 0, bytes.length); //回调是运行在非ui主线程, //数据请求成功后,在主线程中更新 runOnUiThread(new Runnable() { @Override public void run() { //网络图片请求成功,更新到主线程的ImageView image.setImageBitmap(bmp); } }); } }); } /* * * 当SD卡存在或者SD卡不可被移除的时候,就调用getExternalCacheDir()方法来获取缓存路径, * 否则就调用getCacheDir()方法来获取缓存路径。 * 前者获取到的就是 /sdcard/Android/data/<application package>/cache * 而后者获取到的是 /data/data/<application package>/cache 。 * * */ public File getDiskCacheDir(Context context, String uniqueName) { String cachePath = null; if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()) { cachePath = context.getExternalCacheDir().getPath(); } else { cachePath = context.getCacheDir().getPath(); } File dir = new File(cachePath + File.separator + uniqueName); Log.d(TAG, "缓存目录:" + dir.getAbsolutePath()); return dir; } //版本名 public static String getVersionName(Context context) { return getPackageInfo(context).versionName; } //版本号 public static int getVersionCode(Context context) { return getPackageInfo(context).versionCode; } private static PackageInfo getPackageInfo(Context context) { PackageInfo pi = null; try { PackageManager pm = context.getPackageManager(); pi = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_CONFIGURATIONS); return pi; } catch (Exception e) { e.printStackTrace(); } return pi; } public static String urlToKey(String url) { return getMD5(url); } /* * 传入一个字符串String msg,返回Java MD5加密后的16进制的字符串结果。 * 结果形如:c0e84e870874dd37ed0d164c7986f03a */ public static String getMD5(String msg) { MessageDigest md = null; try { md = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } md.reset(); md.update(msg.getBytes()); byte[] bytes = md.digest(); String result = ""; for (byte b : bytes) { // byte转换成16进制 result += String.format("%02x", b); } return result; } }
涉及到网络和读写存储,不要忘记加权限:
<!-- SDCard中创建与删除文件权限 --> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> <!-- 向SDCard写入数据权限 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.INTERNET"></uses-permission>
附录文章:
1,《Android第三方异步网路加载库AsyncHttpClient内部实现缓存策略了吗?》链接:http://blog.csdn.net/zhangphil/article/details/48595817
2,《Android图片加载与缓存开源框架:Android Glide》链接:http://blog.csdn.net/zhangphil/article/details/45535693
3,《Android获取App版本号和版本名》链接:http://blog.csdn.net/zhangphil/article/details/43795099
4,《基于Java LinkedList,实现Android大数据缓存策略》链接:http://blog.csdn.net/zhangphil/article/details/44116885
5,《使用新式LruCache取代SoftReference缓存图片,Android异步加载图片》链接:http://blog.csdn.net/zhangphil/article/details/43667415
6,《使用Android新式LruCache缓存图片,基于线程池异步加载图片》链接:http://blog.csdn.net/zhangphil/article/details/44082287
7,《Java MD5(字符串)》链接:http://blog.csdn.net/zhangphil/article/details/44152077
8,《Android OkHttp(1)》链接:http://blog.csdn.net/zhangphil/article/details/51861503
9,《Android二级缓存之物理存储介质上的缓存DiskLruCache》链接:http://blog.csdn.net/zhangphil/article/details/51888974