(一)LruCache緩存處理
1、加載圖片的正確流程是:“內存-文件-網絡 三層cache策略”
a、先從內存緩存中獲取,取到則返回,取不到則進行下一步;
b、從文件緩存中獲取,取到則返回並更新到內存緩存,取不到則進行下一步;
c、從網絡下載圖片,並更新到內存緩存和文件緩存。
2、內存緩存分類:四種級別由高到低依次爲:強引用、軟引用、弱引用和虛引用
a、強引用:(在Android中LruCache就是強引用緩存)
如果一個對象具有強引用,那就類似於必不可少的生活用品,垃圾回收器絕不會回收它。當內存空間不足,Java虛擬機寧願拋出OOM異常,使程序異常終止,也不會回收具有強引用的對象來解決內存不足問題。
b、軟引用(SoftReference):
軟引用類似於可有可無的生活用品。如果內存空間足夠,垃圾回收器就不會回收它,如果內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。使用軟引用能防止內存泄露,增強程序的健壯性。
c、弱引用(WeakReference):
弱引用與軟引用的區別在於:只具有弱引用的對象擁有更短暫的生命週期。在垃圾回收器線程掃描它所管轄的內存區域的過程中,一旦發現了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存。不過,由於垃圾回收器是一個優先級很低的線程, 因此不一定會很快發現那些只具有弱引用的對象。
d、虛引用(PhantomReference)
“虛引用”顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用並不會決定對象的生命週期。如果一個對象僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收。 虛引用主要用來跟蹤對象被垃圾回收的活動。
Demo1:
點擊download更新圖片,
若可以從強引用中獲取,則從強引用中獲取,
如不能從強引用中獲取,則從軟引用中尋找,如果找到且不爲null,則將bitmap添加到強引用中,並從軟引用中移除
如果在軟引用中找不到,則開啓線程(注意:下載圖片等費時操作要在子線程中完成)從網絡下載圖片,並將bitmap添加到強引用中
代碼如下:
MainActivity:
public class MainActivity extends AppCompatActivity {
private ImageView ivPic = null;
private String url = "http://ww2.sinaimg.cn/mw690/4b08ac5ejw1f8ffqk2hicj21ag1gxaz9.jpg";
private Bitmap bitmap = null;
private ProgressDialog pdshow = null;
private LruCache<String,Bitmap> lruCache = null;
private HashMap<String,SoftReference<Bitmap>> softMap = new HashMap<String,SoftReference<Bitmap>>();
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case 0:
pdshow = new ProgressDialog(MainActivity.this);
pdshow.setTitle("title");
pdshow.setMessage("洪荒之力已開啓......");
pdshow.setIcon(R.mipmap.ic_launcher);
pdshow.show();
break;
case 1:
Bitmap bitmap = (Bitmap) msg.obj;
if(bitmap != null){
ivPic.setImageBitmap(bitmap);
}
pdshow.dismiss();
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
// Runtime.getRuntime().maxMemory()返回的是java虛擬機(這個進程)能構從操作系統那裏挖到的最大的內存
int maxSize = (int) (Runtime.getRuntime().maxMemory()/8);
lruCache = new LrucacheUtils(maxSize,softMap);
}
public Bitmap getBitmapFromMemoryByUrl(String url){
bitmap = lruCache.get(url);
// 從強引用中拿數據
if(bitmap != null){
Log.d("test","---------強引用獲取圖片--------");
return bitmap;
}else{
// 從軟引用中拿數據
SoftReference<Bitmap> soft = softMap.get(url);
if(soft != null){
// 一旦SoftReference保存了對一個Java對象的軟引用後,
// 在垃圾線程對 這個Java對象回收前,SoftReference類所提供的get()方法返回Java對象的強引用。
// 另外,一旦垃圾線程回收該Java對象之 後,get()方法將返回null
bitmap = soft.get();
Log.d("test","===========軟引用中獲取圖片============");
if(bitmap != null){
lruCache.put(url,bitmap);
Log.d("test","*******從軟引用中添加到強引用*******");
softMap.remove(url);
Log.d("test","++++++++添加到強引用中後從軟引用中移除+++++");
return bitmap;
}else{
// 若對應bitmap爲null,將此軟引用移除
softMap.remove(url);
}
}
}
return null;
}
private void initView() {
ivPic = (ImageView) findViewById(R.id.ivPic);
}
public void download(View view) {
bitmap = getBitmapFromMemoryByUrl(url);
if(bitmap != null){
ivPic.setImageBitmap(bitmap);
Log.d("test","--------從緩存中獲取到數據-------");
}else {
new Thread(){
@Override
public void run() {
super.run();
handler.sendEmptyMessage(0);
Message message = Message.obtain();
message.what = 1;
byte[] data = OkHttpUtils.getBytesByUrl(url);
bitmap = BitmapFactory.decodeByteArray(data,0,data.length);
Log.d("test","----------從網絡下載圖片-------------");
// 將數據放到強引用中(不然每次點download都會重新下載圖片)
lruCache.put(url,bitmap);
message.obj = bitmap;
handler.sendMessage(message);
}
}.start();
}
}
}
LruCacheUtils:
public class LrucacheUtils extends LruCache<String,Bitmap> {
private HashMap<String,SoftReference<Bitmap>> softMap = null;
// maxSize 規定的最大存儲空間
public LrucacheUtils(int maxSize,HashMap<String,SoftReference<Bitmap>> softMap) {
super(maxSize);
this.softMap = softMap;
}
}
Demo2:
點擊download下載圖片,點擊save根據url將下載好的圖片保存到緩存中,點擊get從緩存中根據指定url獲取對應的緩存圖片,點擊delete從緩存中根據指定url刪除對應的緩存圖片。
MainActivity:
public class MainActivity extends AppCompatActivity {
private ImageView ivPic = null;
private String url = "http://ww3.sinaimg.cn/mw690/4b08ac5ejw1f8ab0ibt6jj22yo4g0npj.jpg";
private LruCacheUtils lruCacheUtiles = null;
// 從網絡上下載的圖片
private Bitmap bitmap = null;
// 從緩存中獲取的圖片
private Bitmap lruCacheBitmap = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
lruCacheUtiles = new LruCacheUtils();
lruCacheUtiles.initLruche();
}
public void clickMe(View view){
switch (view.getId()){
// 從網絡上下載指定圖片
case R.id.btnDown:
new Thread(){
@Override
public void run() {
super.run();
byte[] data = OkHttpUtils.getBytesByUrl(url);
bitmap = BitmapFactory.decodeByteArray(data,0,data.length);
Log.d("test","圖片下載完畢"+bitmap);
}
}.start();
break;
// 將網絡上下載的圖片保存到lruCache對象
case R.id.btnSave:
if(bitmap != null){
lruCacheUtiles.setBitmap2URL(url,bitmap);
Log.d("test","圖片已保存");
}
break;
// 從lruCache對象中根據指定url對應的緩存圖片,並將緩存圖片設置給ImageView控件
case R.id.btnGet:
lruCacheBitmap = lruCacheUtiles.getBitmapByURL(url);
Log.d("test","取出來的圖片");
ivPic.setImageBitmap(lruCacheBitmap);
break;
// 從lruCache對象中刪除指定url對應的圖片資源
case R.id.btnDel:
lruCacheUtiles.deleteBitmapByURL(url);
break;
}
}
private void initView() {
ivPic = (ImageView) findViewById(R.id.ivPic);
}
}
LruCacheUtils:
public class LruCacheUtils {
private LruCache<String,Bitmap> lruCache = null;
// 初始化緩存
public LruCache initLruche(){
int maxSize = 4 * 1024 * 1024;
lruCache = new LruCache<>(maxSize);
return lruCache;
}
// 獲取指定url的Bitmap對象
public Bitmap getBitmapByURL(String url){
if(lruCache != null){
return lruCache.get(url);
}
return null;
}
// 把Bitmap對象放入key值爲url的緩存中
public void setBitmap2URL(String url,Bitmap bitmap){
if(getBitmapByURL(url) == null){
lruCache.put(url,bitmap);
}
}
// 從緩存中刪除指定url的Bitmap對象
public void deleteBitmapByURL(String url){
if(getBitmapByURL(url) != null){
lruCache.remove(url);
}
}
}
(二)Bitmap二次採樣:
參考自:http://www.th7.cn/Program/Android/201503/405000.shtml
意義:BitmapFactory解碼一張圖片時,有時會遇到OOM錯誤。這往往是由於圖片過大造成的。要想正常使用,則需要分配更少的內存空間來存儲。
Android使用BitmapFactory.Options解決加載大圖片內存溢出問題;
1、BitmapFactory.Options.inJustDecodeBounds:
注意:並不是在此屬性的true和false之間才能對圖片進行操作,此屬性僅代表是否返回實際的Bitmap
如果該值設爲true那麼將不返回實際的bitmap不給其分配內存空間而裏面只包括一些解碼邊界信息即圖片大小信息。[通過設置inJustDecodeBounds爲true,獲取到outHeight(圖片原始高度)和 outWidth(圖片的原始寬度),然後計算一個inSampleSize(縮放值),然後就可以取圖片了,這裏要注意的是,inSampleSize 可能小於0,必須做判斷]。
2、BitmapFactory.Options.inSampleSize:設置縮放值
設置恰當的inSampleSize可以使BitmapFactory分配更少的空間以消除該錯誤
3、BitmapFactory.Option.inPreferredConfig:設置單位像素佔用的字節數
inPreferredConfig的值爲Bitmap.Config類型,Bitmap.Config類是個枚舉類型
可以爲以下值:
(1)、Bitmap.Config ALPHA_8 :
(2)、Bitmap.Config ARGB_4444 :不推薦使用
(3)、Bitmap.Config ARGB_8888:
一個像素佔用四個字節,alpha(A)值,Red(R)值,Green(G)值,Blue(B)值各佔8個bites,共32bites,即4個字節。這是一種高質量的圖片格式,電腦上普通採用的格式。它也是Android手機上一個Bitmap的默認格式。
(4)、Bitmap.Config RGB_565:一個像素佔用2個字節,沒有alpha(A)值,能夠達到比較好的呈現效果,相對於ARGB_8888來說也能減少一半的內存開銷。
Bitmap佔用內存的計算:
Android中一張圖片(Bitmap)佔用的內存主要和以下幾個因素有關:圖片長度,圖片寬度,單位像素佔用的字節數。
一張圖片(Bitmap)佔用的內存=圖片長度圖片寬度單位像素佔用的字節數(圖片長度和圖片寬度的單位是像素)。圖片(Bitmap)佔用的內存和屏幕密度(Density)無關。
創建一個Bitmap時,其單位像素佔用的字節數由其參數BitmapFactory.Options的inPreferredConfig變量決定。
**備註:**ARGB指的是一種色彩模式,裏面A代表Alpha,R表示red,G表示green,B表示blue,其實所有的可見色都是紅綠藍組成的,所以紅綠藍又稱爲三原色。 ARGB就是:透明度 紅色 綠色 藍色。
步驟:
先設置BitmapFactory.Options的inJustDecodeBounds爲true
通過BitmapFactory.decodeByteArray()方法返回圖片大小
設置縮放值inSampleSize
設置單位像素佔用的字節數inPreferredConfig
設置BitmapFactory.Options的inJustDecodeBounds爲false
再調用BitmapFactory.decodeByteArray()方法返回的即爲二次採樣後的Bitmap對象
代碼:
public class BitmapUtils {
public static Bitmap getOptionBitmapByByteArray(byte[] data){
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
// 若options.inJustDecodeBounds爲true,
// 那麼將不返回實際的bitmap不給其分配內存空間而裏面只包括一些解碼邊界信息即圖片大小信息,也就不會那麼頻繁的發生OOM了
BitmapFactory.decodeByteArray(data,0,data.length,options);
// 設置縮放值
options.inSampleSize = 3;
// 設置單位像素佔用的字節數
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
// 設置options.inJustDecodeBounds爲false
options.inJustDecodeBounds = false;
return BitmapFactory.decodeByteArray(data,0,data.length,options);
}
}
Demo:
MainActiviy:
public class MainActivity extends AppCompatActivity {
private ListView lvShow = null;
private List<DataInfo> list = null;
private MyAdapter adapter = null;
String[] urls={
"http://img2.duitang.com/uploads/item/201210/16/20121016190338_z3jFA.jpeg",
"http://m.chanyouji.cn/tips/hanju.jpg",
"http://m.chanyouji.cn/articles/534/51b6a7a15a4c35948f09cfb4ec6bfe69.jpg",
"http://m.chanyouji.cn/articles/578/50e0f1407caccc6e7379d5699df8582e.jpg",
"http://m.chanyouji.cn/articles/587/e14f0f98ab6c0551a077a0acc63e1a55.jpg",
"http://m.chanyouji.cn/articles/596/46eab7447b19db0759c3f29c40661595.jpg",
"http://m.chanyouji.cn/articles/610/56fa2310ba3935bc9bb3d97c668e5052.jpg",
"http://m.chanyouji.cn/articles/573/eb25779a20b80a891397325aeeebc715.jpg",
"http://p.chanyouji.cn/58129/1375088377136p180kqdqfb1pfet5815hr1qf4107s8.jpg",
"http://m.chanyouji.cn/articles/609/b67b9b38ef6fdd70788f12d1fcd226bd.jpg"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
setData();
adapter = new MyAdapter(list,this);
lvShow.setAdapter(adapter);
}
private void setData() {
list = new ArrayList<DataInfo>();
DataInfo dataInfo = null;
for (int i = 0;i<urls.length;i++){
String title = "title"+i;
String url = urls[i];
dataInfo = new DataInfo(title,url);
list.add(dataInfo);
}
}
private void initView() {
lvShow = (ListView) findViewById(R.id.lvShow);
}
}
MyAdapter:
public class MyAdapter extends BaseAdapter {
private List<DataInfo> list = null;
private Context context = null;
private LruCacheUtils lruCacheUtils = null;
private Handler handler = null;
public MyAdapter(List<DataInfo> list, Context context) {
this.list = list;
this.context = context;
lruCacheUtils = new LruCacheUtils();
lruCacheUtils.initLruCache();
handler = new Handler();
}
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int i) {
return list.get(i);
}
@Override
public long getItemId(int i) {
return i;
}
@Override
public View getView(final int i, View view, ViewGroup viewGroup) {
View v = null;
final ViewHolder holder ;
if(view == null){
holder = new ViewHolder();
v = LayoutInflater.from(context).inflate(R.layout.list_item,viewGroup,false);
holder.txtTitle = (TextView) v.findViewById(R.id.txtTitle);
holder.ivPic = (ImageView) v.findViewById(R.id.ivPic);
v.setTag(holder);
}else{
v = view;
holder = (ViewHolder) v.getTag();
}
DataInfo dataInfo = list.get(i);
if(dataInfo != null){
String title = dataInfo.getTitle();
final String url = dataInfo.getUrl();
holder.txtTitle.setText(title);
// 先從緩存中獲取圖片,獲取不到指定圖片再從網絡下載
if(lruCacheUtils.getBitmapByUrl(url) != null){
Log.d("test","從緩存中獲取圖片");
holder.ivPic.setImageBitmap(lruCacheUtils.getBitmapByUrl(url));
}else{
new Thread(){
@Override
public void run() {
super.run();
byte[] data = OkHttpUtils.getBytesByUrl(url);
// ???通過二次採樣獲得的Bitmap
final Bitmap bitmap = BitmapUtils.getOptionBitmapByByteArray(data);
Log.d("test","網絡下載的圖片"+i);
if (bitmap != null){
Log.d("test","將圖片保存到緩存中"+i);
lruCacheUtils.setBitmap2Url(url,bitmap);
}
// **重點:**子線程不能更新UI,通過handler的post方法將更新操作添加到主線程
handler.post(new Runnable() {
@Override
public void run() {
holder.ivPic.setImageBitmap(bitmap);
}
});
}
}.start();
}
}
return v;
}
class ViewHolder{
TextView txtTitle;
ImageView ivPic;
}
}