Android_ListView图片下载三级缓存处理

Posted 9编程小王子9

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android_ListView图片下载三级缓存处理相关的知识,希望对你有一定的参考价值。

转载表明出处:http://blog.csdn.net/zcr317121966/article/details/52146297


这次练习的是图片的三级缓存处理。主要是避免下载图片过大过多导致的内存泄露的问题,我们要将下载的图片进行缓存。缓存主要分三级,是首先存

储再强引用LruCache中,存不下之后存在软引用SoftRerences中,同时也会存在本地的SD卡中。由于LruCache和SoftRerences是存在运行时内存

中,虽然容量下,但是读取快,而且程序一旦退出,数据就会丢失。同时本地内存卡中可以存入大量数据,但是读取慢,但是退出程序后数据任然存在

本地,不会丢失。所以结合双方不同的特性我们将其结合起来使用,所以进行图片的三级缓存处理。

步骤:首先会从内存和本地中取出图片,有的话可以直接显示,如果没有则就要通过网络下载图片,并存入到内存和本地中。

取出的顺序,先从LruCache中读取,如果有直接显示,没有的话从SoftRerences中找,有的话显示,没有就去本地SD卡中寻找,有的话显示,如果也没有

那么就只能通过网络下载图片。下载图片后存入顺序,先存如到LruCache和本地中,如果LruCache存满,则将系统自动删除的图片存入到SoftReferences

中。

LruCache:

1.再创建对象时可以指定存储容量一般为当前运行时内存的1/8或者1/16。

2.当存满后会自动删除之前数据并存入新数据。

3.自身是一个相当于map集合可以存储n组键值对。

SoftRerences:

1.没有最大存储大小,存储大小可以根据当前设备调整。

2.如果内存也存储满了,也会自动删除数据,保证能获取新的数据。

3.本身只能存储一个对象。两者配合使用可以存储更多数据。

接下来介绍以下将网络上的数据和图片下载后显示到listview中的缓存操作,详细见代码。

主要分为:

1.MyMainActivity主函数类主要用于初始化控件,网络下载数据初始化数据源并设置listview适配器。

2.AsyncTaskOfJson异步任务解析JSON数据类 主要通过url下载数据后json解析得到数据源。

3.AsyncTaskOfBitmap异步任务下载Bitmap图片类主要是通过网址下载bitmap图片并通过接口回调返回。

4.MyInterface接口定义接口方法,通过调用接口方法回传数据。

5.DataBean对象类用于存储对应json解析的数据存储类。

6.HttpURLUtils类网络请求下载工具类主要结合异步任务类进行下载操作。

7.MyBaseAdapter自定义适配器类主要是listview适配器对网络下载的数据图片进行缓存处理,读取操作。


1.MyMainActivity主函数类

package com.bane.mymain;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.ListView;
import com.bane.adapter.MyBaseAdapter;
import com.bane.asynctaskjson.AsyncTaskOfJson;
import com.bane.bean.DataBean;
import com.bane.myinterface.InterfaceData;
import com.example.administrator.android_picturesave.R;

import java.util.ArrayList;
import java.util.List;

//主函数类初始化控件,设置适配器和开启网络下载数据的任务
public class MyMainActivity extends AppCompatActivity 


    //tag标签
    private static final String TAG = "===";

    //创建一个list集合存储下载的数据
    private List<DataBean> listData = new ArrayList<>();

    //网址数据为JSON
    private String path = "http://lib.wap.zol.com.cn/ipj/docList/?v=6.0&class_id=0&page=4&vs=and412&retina=1";

    //声明适配器引用
    private MyBaseAdapter adapter ;

    //声明listview引用
    private ListView listview;

    //初始化控件方法
    private void assignViews() 
        listview = (ListView) findViewById(R.id.listview);
    


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

        //初始化控件
        assignViews();

        //开启网络下载异步任务
        startAsyncTask();

    

    private void startAsyncTask() 

        //创建异步任务下载数据
        new AsyncTaskOfJson(new InterfaceData()

            @Override
            public void getDataOfJson(List<DataBean> data) 
                if(data!=null)
                    //通过接口回调的到数据之后加入都list集合中存储
                    listData.addAll(data);

                    Log.i(TAG, "getDataOfJson: "+listData.size());
                    //数据回传回来之后进行适配器的初始化
                    initAdapter();

                
            
        ).execute(path);

    

    private void initAdapter() 

        //创建适配器对象
        adapter = new MyBaseAdapter(this,listData);

        //设置适配器
        listview.setAdapter(adapter);

        //刷新适配器
        adapter.notifyDataSetChanged();
    


2.AsyncTaskOfJson异步任务解析JSON数据类

package com.bane.asynctaskjson;

import android.os.AsyncTask;
import android.util.Log;

import com.bane.bean.DataBean;
import com.bane.httputils.HttpURLUtils;
import com.bane.myinterface.InterfaceData;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by Bane on 2016/8/7 0007.
 * 异步任务类
 * 用于下载path对应网址的数据得到相应的文字和图片对应的网址
 * (详细解析见上篇博文代码)
 */
public class AsyncTaskOfJson extends AsyncTask<String,Void,List<DataBean>>

    private static final String TAG = "===";

    private InterfaceData interfaceData;

    private List<DataBean> list = new ArrayList<>();

    public AsyncTaskOfJson(InterfaceData interfaceData)

        this.interfaceData = interfaceData;

    

    @Override
    protected List<DataBean> doInBackground(String... params) 

        Log.i(TAG, "doInBackground:params "+params[0]);

        String json = HttpURLUtils.getDataOfString(params[0]);

        Log.i(TAG, "doInBackground:json "+json);

        try 
            JSONObject ob = new JSONObject(json);

            JSONArray array = ob.getJSONArray("list");

            for (int i = 0; i < array.length(); i++) 

                JSONObject obj = array.getJSONObject(i);
                String stitle = obj.getString("stitle");
                String imgsrc2 = obj.getString("imgsrc2");

                Log.i(TAG, "doInBackground:stitle, imgsrc2"+stitle+","+imgsrc2);

                DataBean dataBean = new DataBean(stitle,imgsrc2);
                list.add(dataBean);

            

            Log.i(TAG, "doInBackground: list.size()"+list.size());

            return list;

         catch (JSONException e) 
            Log.i(TAG, "doInBackground: JSONException=================");
            e.printStackTrace();
        
        return null;
    

    @Override
    protected void onPostExecute(List<DataBean> result) 
        super.onPostExecute(result);

        if(result!=null)

            interfaceData.getDataOfJson(result);
        
    



3.AsyncTaskOfBitmap异步任务下载Bitmap图片类

package com.bane.asynctaskbitmap;


import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;

import com.bane.httputils.HttpURLUtils;
import com.bane.myinterface.InterfaceBitmap;

/**
 * Created by Bane on 2016/8/7 0007.
 * 异步任务类
 * 用于下载网址对应的图片 并用接口回调返回bitmap对象
 * (详细解析见上篇博文代码)
 */
public class AsyncTaskOfBitmap extends AsyncTask<String,Void,Bitmap>

    private InterfaceBitmap interfaceBitmap;
    private String url;

    public AsyncTaskOfBitmap(InterfaceBitmap interfaceBitmap)

        this.interfaceBitmap = interfaceBitmap;

    

    @Override
    protected Bitmap doInBackground(String... params) 

        url = params[0];

        byte[] buf = HttpURLUtils.getDataOfByte(url);

        Bitmap bitmap = BitmapFactory.decodeByteArray(buf,0,buf.length);

        return bitmap;
    

    @Override
    protected void onPostExecute(Bitmap bitmap) 

        if(bitmap != null)

            interfaceBitmap.getDataOfBitmap(url,bitmap);
        
    



4.MyInterface接口

package com.bane.myinterface;

import android.graphics.Bitmap;

/**
 * Created by Bane on 2016/8/7 0007.
 * 将得到的bitmap对象和对应网址回传
 */
public interface InterfaceBitmap 


    public void getDataOfBitmap(String url,Bitmap bitmap);

package com.bane.myinterface;

import com.bane.bean.DataBean;

import java.util.List;

/**
 * Created by Bane on 2016/8/7 0007.
 * JSON解析数据回传接口
 */
public interface InterfaceData 

    public void getDataOfJson(List<DataBean> list);




5.DataBean对象类

package com.bane.bean;

/**
 * Created by Bane on 2016/8/7 0007.
 * bean类  根据JSON数据类型定义的类 存储对应数据
 */
public class DataBean 


    private String stitle;
    private String imgsrc2;

    public DataBean(String stitle, String imgsrc2) 
        this.stitle = stitle;
        this.imgsrc2 = imgsrc2;
    

    public String getStitle() 
        return stitle;
    

    public String getImgsrc2() 
        return imgsrc2;
    

    public void setStitle(String stitle) 
        this.stitle = stitle;
    

    public void setImgsrc2(String imgsrc2) 
        this.imgsrc2 = imgsrc2;
    

    @Override
    public String toString() 
        return "DataBean" +
                "stitle='" + stitle + '\\'' +
                ", imgsrc2='" + imgsrc2 + '\\'' +
                '';
    



6.HttpURLUtils类网络请求下载工具类

package com.bane.httputils;

import android.util.Log;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

/**
 * Created by Bane on 2016/8/7 0007.
 * 封装网络请求工具类用于可以调用类中静态方法
 * 分别返回网址对应的String数据和byte[]类型数据
 * (详细解析见上篇博文代码)
 */
public class HttpURLUtils 

    private static final String TAG = "===";

    public static String getDataOfString(String path)
        StringBuffer sb =null;

        try 
            URL url = new URL(path);

            HttpURLConnection conn = (HttpURLConnection) url.openConnection();

            conn.setRequestMethod("GET");
            conn.setConnectTimeout(100000);
            conn.connect();

            if(conn.getResponseCode() == HttpURLConnection.HTTP_OK)

                InputStream is = conn.getInputStream();

                byte[] buf = new byte[1024];

                int len = 0;

                sb = new StringBuffer();

                while ((len = is.read(buf))!=-1)

                    String info = new String(buf,0,len);
                    sb.append(info);

                

//                Log.i(TAG, "getDataOfString: "+sb.toString());

                return sb.toString();
            

         catch (MalformedURLException e) 
            e.printStackTrace();
         catch (IOException e) 
            e.printStackTrace();
        

        return null;
    


    public static byte[] getDataOfByte(String path)

        ByteArrayOutputStream baos = null;
        try 
            URL url = new URL(path);

            HttpURLConnection conn = (HttpURLConnection) url.openConnection();

            conn.setRequestMethod("GET");
            conn.setConnectTimeout(100000);
            conn.connect();

            if(conn.getResponseCode() == HttpURLConnection.HTTP_OK)

                InputStream is = conn.getInputStream();

                byte[] buf = new byte[1024];

                int len = 0;

                baos = new ByteArrayOutputStream();


                while ((len = is.read(buf))!=-1)

                    baos.write(buf,0,len);
                    baos.flush();

                

//                Log.i(TAG, "getDataOfString: "+baos.toByteArray());

                return baos.toByteArray();

            

         catch (MalformedURLException e) 
            e.printStackTrace();
         catch (IOException e) 
            e.printStackTrace();
        

        return null;
    


7.MyBaseAdapter自定义适配器类

package com.bane.adapter;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Environment;
import android.util.Log;
import android.util.LruCache;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import com.bane.asynctaskbitmap.AsyncTaskOfBitmap;
import com.bane.bean.DataBean;
import com.bane.myinterface.InterfaceBitmap;
import com.example.administrator.android_picturesave.R;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

/**
 * Created by Administrator on 2016/8/7 0007.
 *
 * 适配器类
 * 用于处理网络上下载的图片的缓存的操作
 * 主要是分三级缓存对网络上的数据进行处理
 * 以防止图片过大导致的内存泄露
 */
public class MyBaseAdapter  extends BaseAdapter

    //log标签
    private static final String TAG = "===";
    //创建一个list集合用于接受构造函数传来的list数据的集合
    private List<DataBean> list = new ArrayList<>();

    //用于接受传来的context上下文对象
    private Context context;

    //首先定义好要存储图片的SD卡的路径方便引用
    private String path = Environment.getExternalStorageDirectory().getAbsolutePath()+"/zcr_xwz";

    //声明强引用LruCache引用 范型为String此键用来存储图片对应网址作为键,bitmap对象为值
    private LruCache<String,Bitmap> lrucache;

    //创建一个HashMap对象 同理范型为String此键用来存储图片对应网址作为键,值为软引用并存储bitmap对象
    private HashMap<String,SoftReference<Bitmap>> map = new HashMap<>();

    //声明一个线程池用来同时下载多张图片
    private Executor executor;

    //构造方法传参 并初始化声明的引用
    public MyBaseAdapter(Context context, List<DataBean> list) 

        this.context = context;
        this.list = list;

        //创建一个容量为3的线程池
        executor = Executors.newFixedThreadPool(3);

        //定义出强引用的最大大小为当前运行时内存的八分之一
        int maxSize = (int) (Runtime.getRuntime().maxMemory()/8);

        //创建强引用lrucache对象并指明大小和重写方法
        lrucache = new LruCache<String,Bitmap>(maxSize)


            /**
             * 当LruCache删除图片时就会运行此方法
             * @param evicted 是否为系统自动删除
             * @param key      被删除的键
             * @param oldValue 被删除的值
             * @param newValue 新加入数据
             * @return
             */
            @Override
            protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) 
                super.entryRemoved(evicted, key, oldValue, newValue);

                //判断是否被系统自动删除
                if(evicted) 

                    //将删除的图片存入软引用中
                    SoftReference<Bitmap> bitmapSoftReference = new SoftReference<Bitmap>(oldValue);
                    map.put(key,bitmapSoftReference);
                    Log.i(TAG, "saveBitmap: 往SoftReference中存数据!!!!");
                
            

            /**
             *  参数代表要新加入LruCache的键值对
             * @param key 加入数据的键
             * @param value 加入数据的值
             * @return  一般返回新加入的数据(图片)的大小
             */
            @Override
            protected int sizeOf(String key, Bitmap value) 

                return value.getByteCount();
            
        ;
    
    //重写适配器中的方法返回对应list集合中的数据
    @Override
    public int getCount() 
        return list.size();
    

    @Override
    public Object getItem(int position) 
        return list.get(position);
    

    @Override
    public long getItemId(int position) 
        return position;
    

    @Override
    public View getView(int position, View convertView, ViewGroup parent) 
        //声明ViewHolder的应用为空
        ViewHolder mHolder =null;


        if(convertView==null)
            //判断如果该position下的view不存在则自主创建一个
            mHolder = new ViewHolder();

            //加载写好的适配器的布局文件
            convertView = LayoutInflater.from(context).inflate(R.layout.listview_layout,parent,false);

            //通过convertView初始化控件对象
            mHolder.textView = (TextView) convertView.findViewById(R.id.textview);
            mHolder.imageView = (ImageView) convertView.findViewById(R.id.imageview);

            //将创建的mHolder对象作为tag唯一标识便于复用
            convertView.setTag(mHolder);

        else
            //一旦判断view对象存在 进行复用通过tag找到对应控件对象不再创建新的对象
            mHolder = (ViewHolder) convertView.getTag();

        

        //此时将对应position下的控件设置属性值

        //文字为得到的list中的数据
        mHolder.textView.setText(list.get(position).getStitle());

        //将每个imageview对应所放的图片的网址作为tag标识
        mHolder.imageView.setTag(list.get(position).getImgsrc2());

        //在数据下载回来之前要先默认将图片设置为小机器人图片
        mHolder.imageView.setImageResource(R.mipmap.ic_launcher);

        //通过getBitmap方法获取图片 传入参数是相应的网址tag唯一标示取得bitmap对象
        Bitmap bitmap = getBitmap(list.get(position).getImgsrc2());

        if(bitmap ==null)
            //若取得的bitmap对象为空则图片从未下载过进行loadBitmap下载图片
            //传入的参数是当前的position和复用的mHolder对象
            loadBitmap(position,mHolder);

        else
            //若能从强或者软引用或者sd卡中取得图片则已经下载过直接将取得的图片设置
            mHolder.imageView.setImageBitmap(bitmap);
        

        //返回加载了布局文件的view对象
        return convertView;
    

    //创建一个ViewHolder类用来listview中每个position下的view的复用
    class ViewHolder

        private ImageView imageView;
        private TextView textView;

    


    //下载图片的方法,将下载的图片设置再imageview上显示
    public void loadBitmap(int position , final ViewHolder holder)

        //开启异步任务下载图片
        new AsyncTaskOfBitmap(new InterfaceBitmap() 
            @Override
            public void getDataOfBitmap(String url, Bitmap bitmap) 

                //通过接口对调回传bitmap对象和对应网址

                //为防止复用导致的切换图片现象
                //将每次复用的holder做出判断是否是网址对应的imageview如是则下载之后设置
                if(holder.imageView.getTag().toString().equals(url)&&holder.imageView.getTag()!=null)

                    Log.i(TAG, "getDataOfBitmap: 下载图片");
                    //将下载好的bitmap设置再imageview上
                    holder.imageView.setImageBitmap(bitmap);

                    //将下载好的图片通过对应网址作为键保存
                    saveBitmap(url,bitmap);
                
            

            //在线程池中进行3张图片同时下载并通过list数据集合和position得到相对应的网址
        ).executeOnExecutor(executor,list.get(position).getImgsrc2());


    


    //存储图片的方法将图片网址作为键bitmap对象为值
    public void saveBitmap(String url,Bitmap bitmap)
        //首先是存入到强引用lrucache中,lrucache本身可以存储多组键值对
        lrucache.put(url,bitmap);

        Log.i(TAG, "saveBitmap: 往lruCache中存储数据!!!!");

        //由于网址对应的 / 会造成多级文件夹所以替换为 _
        String urlName = url.replace("/","_");

        //创建文件夹
        File file = new File(path);

        if(!file.exists())
            file.mkdirs();
        


        try 
            //之后也要将下载好的图片通过io流存入到本地的内存卡中
            //以便于当程序关闭之后再次开启时能从本地读取图片
            FileOutputStream fos = new FileOutputStream(file+"/"+urlName);

            //将bitmap对象压缩存入对应路径的内存卡中
            bitmap.compress(Bitmap.CompressFormat.PNG,100,fos);
            Log.i(TAG, "saveBitmap: 往内存卡中存储数据!!!!");


         catch (FileNotFoundException e) 
            e.printStackTrace();
        
    

    //从缓存中获取bitmap对象的方法
    public Bitmap getBitmap(String url)

        //首先是通过对应网址为键从运行时内存中找
        Bitmap result =  lrucache.get(url);
        //若有会返回

        if(result ==null)
            //若为空再从软引用中找
            SoftReference<Bitmap> soft = map.get(url);
            if(soft !=null)
                //若存在则result获取对应bitmap对象
                result = soft.get();
                Log.i(TAG, "getBitmap:SoftReference中取出数据: ");
            else 
                //若没有 再从本地内存卡中读取
                result = BitmapFactory.decodeFile(path+"/"+url.replace("/","_"));
                if(result!=null) 
                    Log.i(TAG, "getBitmap:从内存卡中取出数据");
                

                //若都没有证明图片从未下载过

            
        else
            Log.i(TAG, "getBitmap:lrucache中取出数据");
        

        //将得到的bitmap对象返回
        return result;
    



8.xml布局文件MyMainActivity和MyBaseAdapter布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.administrator.android_picturesave.MainActivity">

    <ListView
        android:id="@+id/listview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />
</LinearLayout>

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
   >

    <ImageView
        android:id="@+id/imageview"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:background="@mipmap/ic_launcher"
        android:scaleType="fitXY"
        />
    <TextView
        android:id="@+id/textview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@+id/imageview"
        android:textSize="30sp"
        android:gravity="center_vertical"
        android:layout_alignBottom="@+id/imageview"
        android:layout_alignTop="@id/imageview"
        />

</RelativeLayout>


9.xml清单配置文件,添加网络请求访问权限和本地SD卡读写权限。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.administrator.android_picturesave">

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">

        </activity>
        <activity android:name="com.bane.mymain.MyMainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>


10.实现效果如图面和log打印显示,读取和存储的顺序可以见缓存效果。





以上是关于Android_ListView图片下载三级缓存处理的主要内容,如果未能解决你的问题,请参考以下文章

图片三级缓存

浅谈图片载入的三级缓存

图片三级缓存的流程

Android图片三级缓存策略

Android进阶图片处理之三级缓存方案

Android中图片的三级缓存详解