package com.wangdong.bitmapcache; import android.graphics.Bitmap; import android.os.AsyncTask; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.Adapter; import android.widget.BaseAdapter; import android.widget.GridView; import android.widget.ImageView; import android.widget.RelativeLayout; import java.util.HashSet; import java.util.Set; /** * 下面我通过一个例子来演示一下 LruCache 内存缓存 和 DisLruCache 本地缓存 的使用 * * 主要功能就是一个用 GridView 来展示大量的网络图片, 因为GridView 有服用机制, * 1.滑出屏幕的 ImageView 持有的图片进行回收,再将 ImagView 指定新的图片,从而避免了OOM * 2.同时在 ImageView 加载图片是我们使用了内存 和 硬盘进行双缓存 * 3.每一个图片的加载和显示任务都运行在独立的线程(异步任务)中,除非这个图片缓存在 LruCache 中,这种情况下图片会立即显示。 * 4.如果需要的图片缓存在 DiskLruCache,也会开启一个独立的线程队列。如果在缓存中都没有正确的图片,任务线程会从网络下载。 * * 注意:如果是自定义 View 来实现照片墙,没有复用机制的话,那么需要在 ImageView 滑出屏幕后手动回收图片, * 即将ImageView加载一张默认空图片,从而切断与之前图片的引用,让系统可以回收之前图片,避免OOM。 * */ public class MainActivity extends AppCompatActivity implements AbsListView.OnScrollListener { private GridView gridview; private BitmapCaChe bitmapCache;//LruCache 内存缓存 private BitmapDiskCache bitmapDiskCache; //本地存储卡缓存 private SettaskCollection; //图片下载任务 private int mFirstVisibleItem; private int mVisibleItemCount; private boolean isFirstEnter = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); bitmapCache = new BitmapCaChe(); //创建 LruCache 内存缓存 bitmapDiskCache = new BitmapDiskCache(this); //创建 DisLruCache 本地缓存 taskCollection = new HashSet (); //创建下载任务集合 gridview = (GridView) findViewById(R.id.gridview); //初始GridView控件 gridview.setAdapter(new Adapter()); //GridView设置Adapter gridview.setOnScrollListener(this); //设置GridView 滑动监听 } /**OnScrollListener 滚动接口回调之调用结束时 * * @param view 正在报告其滚动状态的视图 * @param firstVisibleItem 第一个可见单元格的索引(忽略如果visibleItemCount=0) * @param visibleItemCount 可见item数 * @param totalItemCount 列表适配器中的总项数。 */ @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { mFirstVisibleItem = firstVisibleItem; mVisibleItemCount = visibleItemCount; // 下载的任务应该由onScrollStateChanged里调用,但首次进入程序时onScrollStateChanged并不会调用, // 因此在这里为首次进入程序开启下载任务。 if (isFirstEnter && visibleItemCount > 0) { loadBitmaps(firstVisibleItem, visibleItemCount); isFirstEnter = false; } } /**OnScrollListener 滚动接口回调之触发时候 * * @param view 正在报告其滚动状态的视图。 * @param scrollState 当前滚动状态。 * SCROLL_STATE_FLING用户之前一直在使用触摸滚动,并进行了一次狂欢。 * SCROLL_STATE_IDLE视图不是滚动的。 * SCROLL_STATE_TOUCH_SCROLL用户正在使用触摸滚动,他们的手指仍然在屏幕上。 */ @Override public void onScrollStateChanged(AbsListView view, int scrollState) { // 仅当GridView静止时才去下载图片,GridView滑动时取消所有正在下载的任务 if (scrollState == SCROLL_STATE_IDLE) { //停止滚动 loadBitmaps(mFirstVisibleItem, mVisibleItemCount); //下载图片 } else { cancelAllTasks(); //开始滚动停止下载图片 } } private void loadBitmaps(int firstVisibleItem, int visibleItemCount) { try { for (int i = firstVisibleItem; i < firstVisibleItem + visibleItemCount; i++) { String imageUrl = Images.imageUrls[i]; BitmapDownloadTask task = new BitmapDownloadTask(); taskCollection.add(task); task.execute(imageUrl); } } catch (Exception e) { e.printStackTrace(); } } public int dip2px(float dipValue) { final float scale = getResources().getDisplayMetrics().density; return (int) (dipValue * scale + 0.5f); } public void showlog(String info) { System.out.print("LRU " + info + "\n"); } public void cancelAllTasks() { if (taskCollection != null) { for (BitmapDownloadTask task : taskCollection) { task.cancel(false); } } } @Override protected void onPause() { super.onPause(); bitmapDiskCache.flush(); } @Override protected void onDestroy() { super.onDestroy(); bitmapDiskCache.close(); } /**首先继承AsyncTask<开始参数类型,后天执行参数类型,结果返回参数类型*/ class BitmapDownloadTask extends AsyncTask { private String imageUrl; /*执行异步任务代码处理*/ @Override protected Bitmap doInBackground(String... params) { imageUrl = params[0];//获取图片网址 Bitmap bitmap = bitmapDiskCache.getBitmapFromDiskCache(imageUrl);//获取 if (bitmap != null) { bitmapCache.addBitmapToMemoryCache(imageUrl, bitmap); return bitmap; } else { bitmapDiskCache.downloadBitmapToDiskCache(imageUrl, new BitmapDiskCache.DownloadListener() { @Override public void downloadSuccess(Bitmap bitmap) { bitmapCache.addBitmapToMemoryCache(imageUrl, bitmap); } @Override public void downloadFail() { } }); return bitmapDiskCache.getBitmapFromDiskCache(imageUrl); } } //ui线程执行 @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); ImageView imageView = (ImageView) gridview.findViewWithTag(imageUrl); //通过Tag找到对应的ImageView,如果ImageView滑出屏幕,则返回null if (imageView != null && bitmap != null) { imageView.setImageBitmap(bitmap); } taskCollection.remove(this); } } /** * 适配器 Adapter */ public class Adapter extends BaseAdapter { @Override public int getCount() { return Images.imageUrls.length; } @Override public String getItem(int position) { return Images.imageUrls[position]; } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder = null; if(convertView == null) { viewHolder = new ViewHolder(); convertView = LayoutInflater.from(MainActivity.this).inflate(R.layout.image_item, null); viewHolder.image = (ImageView) convertView.findViewById(R.id.img); convertView.setTag(viewHolder); } else { viewHolder = (ViewHolder)convertView.getTag(); } // RelativeLayout.LayoutParams pra = (RelativeLayout.LayoutParams) viewHolder.image.getLayoutParams();//获取布局参数 //item中imageview控件布局 高 等于 :(Gridview的宽 减去 2dp*2 )除以3 pra.height = (gridview.getWidth() - 2*dip2px(2)) / 3; //然后再把布局参数设置给 item中ImagView控件 viewHolder.image.setLayoutParams(pra);//这样就可以让GridView中每个Item适配所有手机屏幕 显示正方形的图片 //获取数据 String imageUrl = getItem(position); //给每个ImageView设置唯一的tag,防止图片加载错乱 /* ListView、GridView都有两个方法setTag/findViewWithTag,简单的说就是给Item中的View设置一个唯一的Tag值, 之后可以通过这个Tag值找到与其对应的View,因为ListView和GridView都有复用机制,所以当Item被滑出屏幕后,有可能会被复用, Item中的View也就被设置了另外一个Tag值,所以这时通过之前的Tag寻找View返回的是null。 这里给ImageView设置了一个唯一Tag值,就是待加载图片的url,之后图片下载完成,再通过这个Tag找到ImageView, 如果ImageView为null,说明图片已经滑出了屏幕,此时不再加载,否则ImageView加载刚刚下载完成的图片, 这样防止了下载好的图片加载到别的ImageView而形成错乱。 */ viewHolder.image.setTag(imageUrl); //通过图片HTTP网址作为key值获取内存中缓存bitmap Bitmap bitmap = bitmapCache.getBitmapFromMemCache(imageUrl); //如果内存缓存中没有为null值 if (bitmap != null) { //如果内存缓存中获取bitmap不为null 就设置显示 viewHolder.image.setImageBitmap(bitmap); } else { //否则显示默认系统本地图片 viewHolder.image.setImageResource(R.drawable.error); } return convertView; } public class ViewHolder { public ImageView image; } } }
public class Images { public final static String[] imageUrls = new String[] { "https://b260.photo.store.qq.com/psb?/V13dV9iu45C4on/vijrZCYDbRX0Al2mp0nkoUUg081aqGxkPJ0g5YttlT8!/b/dFNm.pqUZAAA&bo=9wEgAwAAAAABB*U!&rf=viewer_4", "http://b263.photo.store.qq.com/psb?/V13dV9iu45C4on/ld7gzaKuYPY4FVV3YLPGOlKNcd6tJOXVe*NoND8iDA0!/b/dHy0xZyiFQAA&bo=WAIgAwAAAAABJ3k!&rf=viewer_4", "https://b260.photo.store.qq.com/psb?/V13dV9iu45C4on/AVmjKV.U9eNwC3w9BFmMGC7O1CUlBJLkFopsUO0pWGw!/b/dJYE*5qLYwAA&bo=WAIgAwAAAAABB1k!&rf=viewer_4", "http://b226.photo.store.qq.com/psb?/V13dV9iu45C4on/t7i4ojNEchzmj8oVvS3Ww8gShvnCNxJVU6eWvwptngk!/b/dLSUv4ajKgAA&bo=gALgAQAAAAABF1M!&rf=viewer_4", "http://b262.photo.store.qq.com/psb?/V13dV9iu45C4on/ZSVvZcmEmCOXOGZqG*zsoPQXK1xGPYwFoup9CboY0Co!/b/dDOvLpw5WwAA&bo=ngK8AQAAAAABFxE!&rf=viewer_4", "http://b262.photo.store.qq.com/psb?/V13dV9iu45C4on/3VYju5d3RQ4xDvVVWEBbZAEDni.A1Q0QIcq0aAldUmo!/b/dGqvMZyCXAAA&bo=WAIgAwAAAAABJ3k!&rf=viewer_4", "http://b226.photo.store.qq.com/psb?/V13dV9iu45C4on/lffHq5pqnbqzcT4CM1pBzYNrgU1MZjwz9U66wwBHn48!/b/dAAHu4bHKgAA&bo=WAIgAwAAAAABJ3k!&rf=viewer_4", "http://b226.photo.store.qq.com/psb?/V13dV9iu45C4on/fge4NUg88cPsdEexJYo5IO6sZmK0*JjfeW.Cj02AVvg!/b/dHELvobgKgAA&bo=WAKOAQAAAAABF.U!&rf=viewer_4", "http://b265.photo.store.qq.com/psb?/V13dV9iu45C4on/x9VFJ.TXojclV6u4KCiccjh38JUW9tzOqMqUUktjL5Q!/b/dA4XA56dFQAA&bo=ngK.AQAAAAABFxM!&rf=viewer_4", "http://b260.photo.store.qq.com/psb?/V13dV9iu45C4on/YAALwLbXuCfmZs0yrwQlvEA0B1Yr*YCwFEOk5pwJErY!/b/dJCG*ZoFZAAA&bo=FQIgAwAAAAABFwQ!&rf=viewer_4", "http://b260.photo.store.qq.com/psb?/V13dV9iu45C4on/r*0TtYwXUvyiz6flf0M*TQ86.amb*iB4W6JDR9wK6Y0!/b/dPm5Bpv6YQAA&bo=wgFYAgAAAAABF6k!&rf=viewer_4", "http://b262.photo.store.qq.com/psb?/V13dV9iu45C4on/NVE9RTyqIbiQm6AzysyEXhRbRtxqjvpcaQ2efZ5RtPY!/b/dHSlK5yTXAAA&bo=dwH0AQAAAAABF7M!&rf=viewer_4", "http://b261.photo.store.qq.com/psb?/V13dV9iu45C4on/qKVr2zu99W5es*oSaypR5FxQjf9yldPPYIEhPWSi4gc!/b/dH6pnZsxXwAA&bo=PwKuAQAAAAABF6I!&rf=viewer_4", "http://b263.photo.store.qq.com/psb?/V13dV9iu45C4on/iBl*p3eJ31sW5PoxH3Tbwsb0GxBOtsUIiDEMTuTz4nA!/b/dCDDxZyhFQAA&bo=WALCAQAAAAABJ5k!&rf=viewer_4", "http://b263.photo.store.qq.com/psb?/V13dV9iu45C4on/g2b1yRkprzhkbNfak5BVEGtWDC2GDFilTYl5ku11A88!/b/dIdHx5ytFQAA&bo=WAKRAQAAAAABF*o!&rf=viewer_4", "http://b264.photo.store.qq.com/psb?/V13dV9iu45C4on/AEDj41AXX.uP2GGSniwFK11sjDwKtvXegwHgQV49*WI!/b/dD9qZ52tFQAA&bo=FQIgAwAAAAABFwQ!&rf=viewer_4", "http://b260.photo.store.qq.com/psb?/V13dV9iu45C4on/m3q7UGDFIXWzllGJ8BvWyeKH0BV91YBYQi*XCb0R0b8!/b/dGoD*JqsYwAA&bo=FAIgAwAAAAABFwU!&rf=viewer_4", "http://b261.photo.store.qq.com/psb?/V13dV9iu45C4on/C1gh8OtWuASKj04vzt7unB3oGf0o1HrlyW*IAMuaVGU!/b/dOTQoJskXwAA&bo=ngL5AQAAAAABF1Q!&rf=viewer_4", "http://b264.photo.store.qq.com/psb?/V13dV9iu45C4on/RvFGR5QUZhkPylO9E9aWNRKewIOqKSwX.vuM2u3USWU!/b/dF*XX52xFQAA&bo=9AGaAgAAAAABF10!&rf=viewer_4", "http://b226.photo.store.qq.com/psb?/V13dV9iu45C4on/1BXVEGEjHmTq0avTc3FCRZDNp*Vgch2zxRBRaVtsMWY!/b/dPYXwYYFKwAA&bo=WAIgAwAAAAABF0k!&rf=viewer_4", "http://b260.photo.store.qq.com/psb?/V13dV9iu45C4on/WNZFEWVctZoBjl7GrgVs*rbuNNusAa2c1IDx1yuYeWw!/b/dFkxCJvBYQAA&bo=WAKPAQAAAAABF.Q!&rf=viewer_4", "http://b265.photo.store.qq.com/psb?/V13dV9iu45C4on/wjlbOuA99fS8pW8Kx1z4rg4lZ374LSmiRHrTf*hKmeU!/b/dOATA56eFQAA&bo=xgFaAgAAAAABF68!&rf=viewer_4", "http://b263.photo.store.qq.com/psb?/V13dV9iu45C4on/.c0KQfB9wS6jTuBGG7BDlKphn2KQFCJUGFpZxmoIFgw!/b/dCk.xJzoFAAA&bo=WALCAQAAAAABF6k!&rf=viewer_4", "http://b226.photo.store.qq.com/psb?/V13dV9iu45C4on/r7Pk2oHSm68IktNoZZDjY.5Iy7JzbpzuKhNAY027zpk!/b/dCuEvIbgKgAA&bo=WAIgAwAAAAABF0k!&rf=viewer_4", "http://b226.photo.store.qq.com/psb?/V13dV9iu45C4on/4Hz*dWlQ473Y6LBi4oznj0dcAxag5VeP6WpMXfTiGmo!/b/dKVrtoaHKgAA&bo=WALCAQAAAAABF6k!&rf=viewer_4", "http://b260.photo.store.qq.com/psb?/V13dV9iu45C4on/2mX2zpFFk4ylYQYQcVRwyA4z8yHsK.nEzBpn77UrD.Q!/b/dDinBps*YQAA&bo=WAIgAwAAAAABF0k!&rf=viewer_4", "http://b262.photo.store.qq.com/psb?/V13dV9iu45C4on/cr5ZgXxKaxIpmhpF1G6gMfMARG**eTV5vDhy8zf5EcI!/b/dJwbLZxJXAAA&bo=WAIgAwAAAAABF0k!&rf=viewer_4", "http://b265.photo.store.qq.com/psb?/V13dV9iu45C4on/1UCeX0OgVRzBgft*UUFky*k48DNkfa5XcphJ9K7u**0!/b/dHGN.53LFQAA&bo=WAKOAQAAAAABF.U!&rf=viewer_4", "http://b260.photo.store.qq.com/psb?/V13dV9iu45C4on/ZTkouoP.d*75aymkf9GgU321K.4yrWfvefP.vDaImKI!/b/dDuSA5sxYwAA&bo=XgGFAQAAAAABF.s!&rf=viewer_4", "http://b260.photo.store.qq.com/psb?/V13dV9iu45C4on/3Y0L4jmW6cSj1hmwQxP6Mvj1bGlRDYUlbR9LFCpnkOw!/b/dH75*prqYwAA&bo=WAIgAwAAAAABJ3k!&rf=viewer_4", "http://b261.photo.store.qq.com/psb?/V13dV9iu45C4on/EI0clShfZRE*tll8m5aqsZ5f1kkK33wZhSK0XTS5CmU!/b/dFR*lJu6XgAA&bo=WALVAQAAAAABF74!&rf=viewer_4", "http://b265.photo.store.qq.com/psb?/V13dV9iu45C4on/KNEGjdAxSWPiFuCgALNAPomwDSEfrmE9z5YXY.z5LhE!/b/dOaJAZ6fFQAA&bo=EwIgAwAAAAABFwI!&rf=viewer_4", "http://b264.photo.store.qq.com/psb?/V13dV9iu45C4on/qyg6ObRxptXvYOZMCp4kZoTQcWbJfq3fFqi8D.piYEU!/b/dH7uYp2kFQAA&bo=ngK.AQAAAAABFxM!&rf=viewer_4", "http://b262.photo.store.qq.com/psb?/V13dV9iu45C4on/36muu2dDA*40NI4gXDFgP28vImFlZMEblYeJPJS80oI!/b/dEVAMJwBWwAA&bo=EwIgAwAAAAABFwI!&rf=viewer_4", "http://b262.photo.store.qq.com/psb?/V13dV9iu45C4on/mOY9P0g3bKYuZ1ypPzLaRPE6hh9.ZtwGqUUTULueMFQ!/b/dPK6LpzeWgAA&bo=WAIgAwAAAAABF0k!&rf=viewer_4", "http://b264.photo.store.qq.com/psb?/V13dV9iu45C4on/.Rv6WHl*3VJBs.5oWXAY9l*gN282sMnougFaU2zRuGc!/b/dF*pYp2dFQAA&bo=wgFYAgAAAAABF6k!&rf=viewer_4", "http://b264.photo.store.qq.com/psb?/V13dV9iu45C4on/TZPJVMKPjwaQPyZrd1dC*lgRmQ9wzpy5c2ZP0fbtSo8!/b/dBxoYZ2rFQAA&bo=WAKOAQAAAAABF.U!&rf=viewer_4", "http://b265.photo.store.qq.com/psb?/V13dV9iu45C4on/SqFHv6iQsyKbLQK2*sP34ocsVTJ.O81.W9kJ64fkCfE!/b/dL0cA56hFQAA&bo=WAIgAwAAAAABF0k!&rf=viewer_4", "http://b262.photo.store.qq.com/psb?/V13dV9iu45C4on/YY.sZiI51CsxDcbEGYCZSjpGmaTtcUiwfv6MA20kWSw!/b/dP09M5zZWwAA&bo=EwIgAwAAAAABFwI!&rf=viewer_4", "http://b220.photo.store.qq.com/psb?/V13dV9iu45C4on/kWy12QYZrdda0xxhW2atig6FLyV.51gNj8UyC9zn2m0!/b/YQwWL4MtdgAAYlt2J4NEdQAA&bo=IAMVAgAAAAABFAc!&rf=viewer_4", "https://b398.photo.store.qq.com/psb?/V13dV9iu2Ug5tr/LslLujKdmB*NTrinArOUID3xxSKor2Q1z8YpSF3xZyk!/b/dKNHQ.1LCQAA&bo=gAJxBAAAAAABB9c!&rf=viewer_4", "http://b397.photo.store.qq.com/psb?/V13dV9iu2Ug5tr/R3p2cy0wQY1O21r3.Yn53Cd7blPiv7MOykfQANMc9Y0!/b/dLH5pew8CQAA&bo=gAJxBAAAAAABF8c!&rf=viewer_4", "http://b397.photo.store.qq.com/psb?/V13dV9iu2Ug5tr/ElbfGIY8Iv.RU5xamGbN8LVyfNUqb2DvvHzFqGXxGnU!/b/dGPCrew7CQAA&bo=gAJxBAAAAAABF8c!&rf=viewer_4", "http://b399.photo.store.qq.com/psb?/V13dV9iu2Ug5tr/6lMnYjNvzsCIloelIrZHeyJIqwASPF.dGq7RUhy3Nu0!/b/dFhD1.31EwAA&bo=gAJxBAAAAAABF8c!&rf=viewer_4", "http://b165.photo.store.qq.com/psb?/V13dV9iu2Ug5tr/poEAD7B4Qwe8bI124U1rAz4Rmc4wZXoXiv1*KnlkJSg!/b/dLkrXGJLIQAA&bo=cQSAAgAAAAABF8c!&rf=viewer_4", "http://b165.photo.store.qq.com/psb?/V13dV9iu2Ug5tr/aGUisRhv*y6tB63G4vrYWQ9rloH1CyId57yLaGQA7D0!/b/dACXXWIsIQAA&bo=cQSAAgAAAAABF8c!&rf=viewer_4", "http://b168.photo.store.qq.com/psb?/V13dV9iu2Ug5tr/aIXT.CTixGedfOi8PPr0nCCvA29u.2qul*amB88ZxMs!/b/dHRzJ2QFCgAA&bo=gAKAAgAAAAABFzA!&rf=viewer_4", "http://b167.photo.store.qq.com/psb?/V13dV9iu2Ug5tr/VY8NcdtZePxle1mn1FXPbadOH.XSqHGU8uScoWezUEo!/b/dCtXkGOkAgAA&bo=IAMWAgAAAAABJzc!&rf=viewer_4", "http://b232.photo.store.qq.com/psb?/V13dV9iu2Ug5tr/*RIQpwoj97vmihjQCyoJwX2xHvhimVrMqjePgm5rfP4!/b/dDj4SYqMGwAA&bo=IAMVAgAAAAABFwQ!&rf=viewer_4", "http://b228.photo.store.qq.com/psb?/V13dV9iu2Ug5tr/gahu96xz4UXJPkHGv.NVP2DbR9F8urUtvEtrZHtMv.c!/b/dNvR84dHCwAA&bo=wAOAAgAAAAAKF3o!&rf=viewer_4", "http://b162.photo.store.qq.com/psb?/V13dV9iu2Ug5tr/ynza6fgyZqqXjcPh9px*ZnhZUhQplwPXE5dEaTsom4Q!/b/dHrfk2BuIwAA&bo=3QJXAgAAAAABF7o!&rf=viewer_4", "http://b162.photo.store.qq.com/psb?/V13dV9iu2Ug5tr/H2dN0YjxgWQtyXlWs06ueEDbanylPVNf*h1UVeqrdzk!/b/dKudnmAYHQAA&bo=4QLzAgAAAAABJxI!&rf=viewer_4" }; }
/** * 本地 缓存 */ public class BitmapDiskCache { private DiskLruCache mDiskLruCache = null; // public BitmapDiskCache(Context context) { try { File cacheDir = getDiskCacheDir(context, "bitmap"); if (!cacheDir.exists()) { cacheDir.mkdirs(); } mDiskLruCache = DiskLruCache.open(cacheDir, getAppVersion(context), 1, 10 * 1024 * 1024); } catch (Exception e) { e.printStackTrace(); } } /** * 本地缓存路径 * @param context 环境变量 * @param uniqueName 文件名称 * @return {File} */ public File getDiskCacheDir(Context context, String uniqueName) { String cachePath; /** * 判断SD卡是否挂载,和存在 */ if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()) { //获取sd卡的缓存文件地址 cachePath = context.getExternalCacheDir().getPath(); } else { //获取应用下缓存地址 cachePath = context.getCacheDir().getPath(); } /** * 在缓存文件下创建自定义命名的文件夹并返回File */ return new File(cachePath + File.separator + uniqueName); } /** * 通过应用管理获取应用 版本code号 * @param context * @return */ 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; } //因为涉及下载,需要在线程中执行此方法 public void downloadBitmapToDiskCache(String imageUrl, DownloadListener mDownloadListener) { try { String key = getMD5String(imageUrl); DiskLruCache.Editor editor = mDiskLruCache.edit(key); if (editor != null) { OutputStream outputStream = editor.newOutputStream(0); if (downloadUrlToStream(imageUrl, outputStream)) { editor.commit(); if (mDownloadListener != null) { Bitmap bmp = getBitmapFromDiskCache(imageUrl); mDownloadListener.downloadSuccess(bmp); } } else { editor.abort(); if (mDownloadListener != null) { mDownloadListener.downloadFail(); } } } mDiskLruCache.flush(); } catch (IOException e) { e.printStackTrace(); } } /** * MD5加密 * @param key * @return */ public String getMD5String(String key) { String cacheKey; try { final MessageDigest mDigest = MessageDigest.getInstance("MD5"); mDigest.update(key.getBytes()); cacheKey = bytesToHexString(mDigest.digest()); } catch (NoSuchAlgorithmException e) { cacheKey = String.valueOf(key.hashCode()); } return cacheKey; } /** * 字节 转 16进制 转 String * @param bytes * @return */ private String bytesToHexString(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < bytes.length; i++) { String hex = Integer.toHexString(0xFF & bytes[i]); if (hex.length() == 1) { sb.append('0'); } sb.append(hex); } return sb.toString(); } private boolean downloadUrlToStream(String urlString, OutputStream outputStream) { //系统版本高 HttpURLConnection 使用回报错 需要在清单文件中添加 android:usesCleartextTraffic="true" HttpURLConnection urlConnection = null; BufferedOutputStream out = null; BufferedInputStream in = null; try { final URL 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 (final IOException e) { e.printStackTrace(); } finally { if (urlConnection != null) { urlConnection.disconnect(); } try { if (out != null) { out.close(); } if (in != null) { in.close(); } } catch (final IOException e) { e.printStackTrace(); } } return false; } /** * 获取本地缓存中的图片 * @param imageUrl 通过图片的url * @return */ public Bitmap getBitmapFromDiskCache(String imageUrl) { try { String key = getMD5String(imageUrl); DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key); if (snapShot != null) { InputStream is = snapShot.getInputStream(0); Bitmap bitmap = BitmapFactory.decodeStream(is); return bitmap; } } catch (IOException e) { e.printStackTrace(); } return null; } /** * 移除 指定的imageUrl的图片 * @param imageUrl * @return */ public boolean removeBitmapFromDiskCache(String imageUrl) { try { String key = getMD5String(imageUrl); return mDiskLruCache.remove(key); } catch (IOException e) { e.printStackTrace(); } return false; } /** * 清空本地图片缓存 */ public void removeAll() { try { mDiskLruCache.delete(); } catch (IOException e) { e.printStackTrace(); } } /** * 获取本地缓存 图片个数 */ public void getAllSize() { mDiskLruCache.size(); } public void flush() { try { mDiskLruCache.flush(); } catch (IOException e) { e.printStackTrace(); } } public void close() { try { mDiskLruCache.close(); } catch (IOException e) { e.printStackTrace(); } } public interface DownloadListener { public void downloadSuccess(Bitmap bitmap); public void downloadFail(); } }
public class BitmapCaChe { private LruCachemMemoryCache; public BitmapCaChe() { //获取到可用内存的最大值,使用内存超出这个值会引起OutofMemory异常 int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); // 使用最大可用内存值的1/8作为缓存的大小。 int cacheSize = maxMemory / 8; mMemoryCache = new LruCache (cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { // 重写此方法来衡量每张图片的大小,默认返回图片数量。 return bitmap.getByteCount() / 1024; } }; } /** * 添加图片 * 如果内存缓存中没有就添加到内存缓存中如果有就不执行 key判断 */ public void addBitmapToMemoryCache(String key, Bitmap bitmap) { if (getBitmapFromMemCache(key) == null) { mMemoryCache.put(key, bitmap); } } /** * 获取内存缓存中的图片并返回, * 如果就没就通过 LruCache 内部方法 创建返回null值 * V createdValue = create(key); * if (createdValue == null) { * return null; * } * @param key * @return */ public Bitmap getBitmapFromMemCache(String key) { return mMemoryCache.get(key); } }
/** ****************************************************************************** * Taken from the JB source code, can be found in: * libcore/luni/src/main/java/libcore/io/DiskLruCache.java * or direct link: * https://android.googlesource.com/platform/libcore/+/android-4.1.1_r1/luni/src/main/java/libcore/io/DiskLruCache.java ****************************************************************************** * * A cache that uses a bounded amount of space on a filesystem. Each cache * entry has a string key and a fixed number of values. Values are byte * sequences, accessible as streams or files. Each value must be between {@code * 0} and {@code Integer.MAX_VALUE} bytes in length. * *The cache stores its data in a directory on the filesystem. This * directory must be exclusive to the cache; the cache may delete or overwrite * files from its directory. It is an error for multiple processes to use the * same cache directory at the same time. * *
This cache limits the number of bytes that it will store on the * filesystem. When the number of stored bytes exceeds the limit, the cache will * remove entries in the background until the limit is satisfied. The limit is * not strict: the cache may temporarily exceed it while waiting for files to be * deleted. The limit does not include filesystem overhead or the cache * journal so space-sensitive applications should set a conservative limit. * *
Clients call {@link #edit} to create or update the values of an entry. An * entry may have only one editor at one time; if a value is not available to be * edited then {@link #edit} will return null. *
Clients call {@link #get} to read a snapshot of an entry. The read will * observe the value at the time that {@link #get} was called. Updates and * removals after the call do not impact ongoing reads. * *
This class is tolerant of some I/O errors. If files are missing from the
* filesystem, the corresponding entries will be dropped from the cache. If
* an error occurs while writing a cache value, the edit will fail silently.
* Callers should handle other problems by catching {@code IOException} and
* responding appropriately.
*/
public final class DiskLruCache implements Closeable {
static final String JOURNAL_FILE = "journal";
static final String JOURNAL_FILE_TMP = "journal.tmp";
static final String MAGIC = "libcore.io.DiskLruCache";
static final String VERSION_1 = "1";
static final long ANY_SEQUENCE_NUMBER = -1;
private static final String CLEAN = "CLEAN";
private static final String DIRTY = "DIRTY";
private static final String REMOVE = "REMOVE";
private static final String READ = "READ";
private static final Charset UTF_8 = Charset.forName("UTF-8");
private static final int IO_BUFFER_SIZE = 8 * 1024;
/*
* This cache uses a journal file named "journal". A typical journal file
* looks like this:
* libcore.io.DiskLruCache
* 1
* 100
* 2
*
* CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
* DIRTY 335c4c6028171cfddfbaae1a9c313c52
* CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
* REMOVE 335c4c6028171cfddfbaae1a9c313c52
* DIRTY 1ab96a171faeeee38496d8b330771a7a
* CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
* READ 335c4c6028171cfddfbaae1a9c313c52
* READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
*
* The first five lines of the journal form its header. They are the
* constant string "libcore.io.DiskLruCache", the disk cache's version,
* the application's version, the value count, and a blank line.
*
* Each of the subsequent lines in the file is a record of the state of a
* cache entry. Each line contains space-separated values: a state, a key,
* and optional state-specific values.
* o DIRTY lines track that an entry is actively being created or updated.
* Every successful DIRTY action should be followed by a CLEAN or REMOVE
* action. DIRTY lines without a matching CLEAN or REMOVE indicate that
* temporary files may need to be deleted.
* o CLEAN lines track a cache entry that has been successfully published
* and may be read. A publish line is followed by the lengths of each of
* its values.
* o READ lines track accesses for LRU.
* o REMOVE lines track entries that have been deleted.
*
* The journal file is appended to as cache operations occur. The journal may
* occasionally be compacted by dropping redundant lines. A temporary file named
* "journal.tmp" will be used during compaction; that file should be deleted if
* it exists when the cache is opened.
*/
private final File directory;
private final File journalFile;
private final File journalFileTmp;
private final int appVersion;
private final long maxSize;
private final int valueCount;
private long size = 0;
private Writer journalWriter;
private final LinkedHashMap