ListView 异步加载并使用LruCache进行缓存

来源:互联网 发布:mac照片和文稿里的图片 编辑:程序博客网 时间:2024/06/09 20:12

      这篇文章是根据慕课网上的ListView的视频讲解做的练习:

1.视频链接:http://www.imooc.com/video/7871

2.json数据获取地址:http://www.imooc.com/api/teacher?type=4&num=30

数据格式截图:

                        

                                  


app效果展示:

  


一共有四个类,两个布局文件:

1.activity的布局文件:activity_main.xml

<?xml version="1.0" encoding="utf-8"?><RelativeLayout 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:paddingBottom="@dimen/activity_vertical_margin"    android:paddingLeft="@dimen/activity_horizontal_margin"    android:paddingRight="@dimen/activity_horizontal_margin"    android:paddingTop="@dimen/activity_vertical_margin"    tools:context="com.example.admin.mylistviewdemo.MainActivity">    <ListView        android:id="@+id/lv_main"        android:layout_width="match_parent"        android:layout_height="wrap_content" /></RelativeLayout>

2.具体列表项的布局:item_layout.xml

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:orientation="horizontal">    <ImageView        android:id="@+id/iv_icon"        android:layout_width="64dp"        android:layout_height="64dp"        android:src="@mipmap/ic_launcher" />    <LinearLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_gravity="center"        android:orientation="vertical"        android:paddingLeft="4dp">        <TextView            android:id="@+id/tv_title"            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:maxLines="1"            android:text="title"            android:textSize="18sp" />        <TextView            android:id="@+id/tv_content"            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:maxLines="3"            android:text="content"            android:textSize="13sp" />    </LinearLayout></LinearLayout>

3.MainActivity.java文件

package com.example.admin.mylistviewdemo;import android.os.AsyncTask;import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.util.Log;import android.widget.ListView;import org.json.JSONArray;import org.json.JSONException;import org.json.JSONObject;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.io.UnsupportedEncodingException;import java.net.URL;import java.util.ArrayList;import java.util.List;public class MainActivity extends AppCompatActivity {    public ListView mListView;    public List<NewsBean> mNewsBeanList;    public static String JSONPATH = "http://www.imooc.com/api/teacher?type=4&num=30";    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mListView = (ListView) findViewById(R.id.lv_main);        mNewsBeanList = new ArrayList<>();        new NewsAsyncTask().execute(JSONPATH);    }    //用于根据指定的Url异步地获取json数据    class NewsAsyncTask extends AsyncTask<String, Void, List<NewsBean>> {        @Override        protected void onPostExecute(List<NewsBean> newsBeen) {            super.onPostExecute(newsBeen);            NewsAdapter mNewsAdapter = new NewsAdapter(MainActivity.this,newsBeen,mListView);            mListView.setAdapter(mNewsAdapter);        }        @Override        protected List<NewsBean> doInBackground(String... params) {            //这里传进来的参数就是我们需要解析的json数据的地址,因为就只有一个参数,            // 因此取参数数组的第一个元素params[0]            return getJsonData(params[0]);        }    }    //从指定的流中读取json数据,将得到的json数据转化为String类型的字符串    public String readStream(InputStream is) {        String result = "", line;        BufferedReader bf = null;        try {            //将输入流读入字符缓冲数组中            bf = new BufferedReader(new InputStreamReader(is, "utf-8"));            while ((line = bf.readLine()) != null) {                result += line;            }            return result;        } catch (UnsupportedEncodingException e) {            e.printStackTrace();        } catch (IOException e) {            e.printStackTrace();        }        return null;    }    //通过指定的Url来解析json数据,封装进NewsBean,并且装入mList    public List<NewsBean> getJsonData(String url) {        //用于将数据封装到该NewsBean中        NewsBean mNewsBean;        try {            String mJsonString = readStream(new URL(JSONPATH).openStream());            JSONObject mJSONObject  = new JSONObject(mJsonString);            JSONArray mJSONArray = mJSONObject.getJSONArray("data");            for (int i = 0; i < mJSONArray.length(); i++) {                //获得json数组中的子json对象                mJSONObject = mJSONArray.getJSONObject(i);                //将数据封装到该NewsBean中                mNewsBean = new NewsBean();                mNewsBean.newsTitle = mJSONObject.getString("name");                mNewsBean.newsContent = mJSONObject.getString("description");                mNewsBean.newsIconUrl = mJSONObject.getString("picSmall");                //添加该NewsBean到List中                mNewsBeanList.add(mNewsBean);            }            return mNewsBeanList;        } catch (IOException e) {            Log.d("qc", "网络连接故障");            System.out.println("网络连接故障");            e.printStackTrace();        } catch (JSONException e) {            Log.d("qc", "json数据异常");            System.out.println("json数据异常");            e.printStackTrace();        }        return null;    }}

在该类中,我们主要通过在AsyncTackzhong 中异步地进行json数据的获取,主要在getJsonData(String url)方法中进行json数据的解析。

4.NewsBean.java为我们定义的用来存放数据的javabean

package com.example.admin.mylistviewdemo;/** * Created by admin on 2016/9/5. */public class NewsBean {    public String newsIconUrl;    public String newsTitle;    public String newsContent;}

5.我们自定义了一个类:ImageLoader,来进行图片的加载

package com.example.admin.mylistviewdemo;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.os.AsyncTask;import android.util.Log;import android.util.LruCache;import android.widget.ImageView;import android.widget.ListView;import java.io.BufferedInputStream;import java.io.IOException;import java.net.HttpURLConnection;import java.net.URL;import java.util.HashSet;import java.util.Set;/** * Created by admin on 2016/9/5. */public class ImageLoader {    //创建cache    private LruCache<String, Bitmap> mLruCache;    //将传入的ListView赋给mListView,从而可以直接对列表项进行设置,比如只加载可见的列表项,或者设置bitmap等    private ListView mListView;    //创建set,用来存储我们的那些加载图片的线程,便于在需要的时候开启或者关闭相应的后台人任务    private Set<MyAsyncTask> mTasks;    //构造方法,传入我们需要加载进图片的listview,并在该构造函数中初始化缓存大小    //我们通过传如ListView,从而可以在需要的时候对ListView直接进行设置    public ImageLoader(ListView listView) {        mListView = listView;        mTasks = new HashSet<>();        //获得系统当前最大可用内存,这里取最大缓存的1/4来作为此应用的缓存大小        int maxMemory = (int) Runtime.getRuntime().maxMemory();        int cacheSize = maxMemory / 4;        mLruCache = new LruCache<String, Bitmap>(cacheSize) {            //覆写其sizeOf(String key, Bitmap value)方法,            // 返回值就是我们每次存入数据所需缓存空间的大小            @Override            protected int sizeOf(String key, Bitmap value) {                //return super.sizeOf(key, value);                //返回值为我们需要存入的bitmap的大小                return value.getByteCount();            }        };    }    //增加数据到缓存中    public void addBitmapToCache(String url, Bitmap bitmap) {        //如果缓存中已经没有的话,就存入缓存,否则略过        if (getBitmapFromCache(url) == null) {            mLruCache.put(url, bitmap);        }    }    //从缓存中读取数据    public Bitmap getBitmapFromCache(String url) {        return mLruCache.get(url);    }    public void cancelAllTasks() {        for (MyAsyncTask task : mTasks) {            //传入false表示只取消那些没有开始执行的线程,如果线程正在执行中则不取消,等待其执行完毕            task.cancel(false);        }    }    //根据传入的图片的Url去获取对应的图片bitmap    class MyAsyncTask extends AsyncTask<String, Void, Bitmap> {        private String mUrl;        //        public  MyAsyncTask(String url){//            mUrl = url;//        }        @Override        protected void onPostExecute(Bitmap bitmap) {            super.onPostExecute(bitmap);            ImageView imageView = (ImageView) mListView.findViewWithTag(mUrl);            if (imageView != null && bitmap != null) {                imageView.setImageBitmap(bitmap);            }            mTasks.remove(this);        }        @Override        protected Bitmap doInBackground(String... params) {            mUrl = params[0];            Bitmap bitmap = getBitmapFromUrl(mUrl);            if (bitmap != null) {                addBitmapToCache(mUrl, bitmap);            }            return  bitmap;        }    }    public Bitmap getBitmapFromUrl(String url) {        BufferedInputStream bufferedInputStream = null;        try {            HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();            bufferedInputStream = new BufferedInputStream(connection.getInputStream());            Bitmap bitmap = BitmapFactory.decodeStream(bufferedInputStream);            //及时关闭数据连接            connection.disconnect();            return bitmap;        } catch (IOException e) {            e.printStackTrace();        } finally {            try {                bufferedInputStream.close();            } catch (IOException e) {                Log.d("qc", "未能正常关闭流");                e.printStackTrace();            }        }        return null;    }    public void loadImages(int start, int end) {        String url;        Bitmap bitmap;        MyAsyncTask task;        ImageView imageView;        for (int i = start; i < end; i++) {            url = NewsAdapter.URLS[i];            bitmap = getBitmapFromCache(url);            if (bitmap == null) {                task = new MyAsyncTask();                task.execute(url);                mTasks.add(task);            } else {                imageView = (ImageView) mListView.findViewWithTag(url);                imageView.setImageBitmap(bitmap);            }        }    }}

在该类中我们使用了缓存机制:LruCache,以下是LruCache的源码

/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *      http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package com.android.camera.gallery;import java.lang.ref.ReferenceQueue;import java.lang.ref.WeakReference;import java.util.HashMap;import java.util.LinkedHashMap;import java.util.Map;public class LruCache<K, V> {    private final HashMap<K, V> mLruMap;    private final HashMap<K, Entry<K, V>> mWeakMap =            new HashMap<K, Entry<K, V>>();    private ReferenceQueue<V> mQueue = new ReferenceQueue<V>();    @SuppressWarnings("serial")    public LruCache(final int capacity) {        mLruMap = new LinkedHashMap<K, V>(16, 0.75f, true) {            @Override            protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {                return size() > capacity;            }        };    }    private static class Entry<K, V> extends WeakReference<V> {        K mKey;        public Entry(K key, V value, ReferenceQueue<V> queue) {            super(value, queue);            mKey = key;        }    }    @SuppressWarnings("unchecked")    private void cleanUpWeakMap() {        Entry<K, V> entry = (Entry<K, V>) mQueue.poll();        while (entry != null) {            mWeakMap.remove(entry.mKey);            entry = (Entry<K, V>) mQueue.poll();        }    }    public synchronized V put(K key, V value) {        cleanUpWeakMap();        mLruMap.put(key, value);        Entry<K, V> entry = mWeakMap.put(                key, new Entry<K, V>(key, value, mQueue));        return entry == null ? null : entry.get();    }    public synchronized V get(K key) {        cleanUpWeakMap();        V value = mLruMap.get(key);        if (value != null) return value;        Entry<K, V> entry = mWeakMap.get(key);        return entry == null ? null : entry.get();    }    public synchronized void clear() {        mLruMap.clear();        mWeakMap.clear();        mQueue = new ReferenceQueue<V>();    }}

从源码中我们可以清楚的看到,LruCache的本质就是一个hashmap.在这里我们根据指定的tag来设置对应的bitmmap,这里bitmap和其tag(具体为图片的Url)关联了起来。

6.自定义一个adapter:NewsAdapter,来为listview提供数据

package com.example.admin.mylistviewdemo;import android.content.Context;import android.graphics.Bitmap;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.AbsListView;import android.widget.BaseAdapter;import android.widget.ImageView;import android.widget.ListView;import android.widget.TextView;import java.util.List;/** * Created by admin on 2016/9/5. */public class NewsAdapter extends BaseAdapter implements AbsListView.OnScrollListener {    private List<NewsBean> mNewsBeanList;    private Context mContext;    private ListView mListView;    private LayoutInflater mInflater;    private ImageLoader mImageLoader;    private boolean isJsutEnter;    private int mStart, mEnd;    public static String[] URLS;    /**     * 传入context对象,从而可以生成LayoutInflater来解析布局文件     * 根据传进来的mNewsBeanList来为listview提供数据     * 根据传进来的ListView,可以在需要的时候直接对列表项的数据进行修改,比如设置bitmap等     */    public NewsAdapter(Context context, List<NewsBean> mNewsBeanList, ListView listView) {        mContext = context;        mInflater = LayoutInflater.from(mContext);        this.mNewsBeanList = mNewsBeanList;        mListView = listView;        mImageLoader = new ImageLoader(listView);        isJsutEnter = true;        URLS = new String[mNewsBeanList.size()];        for (int i = 0;i< URLS.length;i++){            URLS[i]= mNewsBeanList.get(i).newsIconUrl;        }        //为listView添加滚动监听器        mListView.setOnScrollListener(this);    }    //只有在滑动状态发生变化的时候才会回调该onScrollStateChanged方法,比如由滑动状态变为停止状态    @Override    public void onScrollStateChanged(AbsListView view, int scrollState) {        //当滑动停止的时候就加载可见项,否则不加载,停止所有执行加载任务的线程        if (scrollState == SCROLL_STATE_IDLE) {            mImageLoader.loadImages(mStart, mEnd);        } else{            mImageLoader.cancelAllTasks();        }    }    //每一次滚动都会回调该onScroll方法    @Override    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {        mStart = firstVisibleItem;        mEnd = firstVisibleItem + visibleItemCount;        //预加载第一页,进行visibleItemCount > 0判断是因为当列表项还没有加载出来的时候,        // 也就是当visibleItemCount为0的时候,onScroll方法就已经被回调了        if (isJsutEnter && visibleItemCount > 0) {            mImageLoader.loadImages(mStart, mEnd);            isJsutEnter = false;        }    }    @Override    public int getCount() {        return mNewsBeanList.size();    }    @Override    public Object getItem(int position) {        return mNewsBeanList.get(position);    }    @Override    public long getItemId(int position) {        return position;    }    @Override    public View getView(int position, View convertView, ViewGroup parent) {        ViewHolder viewHolder ;        if (convertView == null) {            convertView = mInflater.inflate(R.layout.item_layout, null);            viewHolder = new ViewHolder();            //从布局文件中取出对应控件的引用,并赋值给ViewHolder中的相应的控件            viewHolder.tvTitle = (TextView) convertView.findViewById(R.id.tv_title);            viewHolder.tvContent = (TextView) convertView.findViewById(R.id.tv_content);            viewHolder.ivIcon = (ImageView) convertView.findViewById(R.id.iv_icon);            //为convertView设置viewHolder标签            convertView.setTag(viewHolder);        } else {            //通过getTag()取出与convertView关联的viewHolder            viewHolder = (ViewHolder) convertView.getTag();        }        //设置viewHolder的tvTitle和tvContent,但是不能直接设置ivIcon,        // 因为在我们需要开启线程去获取图片,这样在滑动的时候会出现图片错位的问题        //因此我们需要为ivIcon设置标签(这里我们将bitmap的url设置为它的标签,从而将ivIcon与其对应的图片地址关联起来)        viewHolder.tvTitle.setText(mNewsBeanList.get(position).newsTitle);        viewHolder.tvContent.setText(mNewsBeanList.get(position).newsContent);        //为viewHolder的ivIcon设置tag        //该设置实际上是对每个ivIcon设置唯一标签,就是该ivIcon只能显示由newsIconUrl唯一标识的bitmap        String url = mNewsBeanList.get(position).newsIconUrl;        viewHolder.ivIcon.setTag(url);        //如果缓存中没有我们需要的bitmap,就显示默认图片,否则就将该bitmap设置为ivIcon显示的图片        //之所以可以这样做,是因为我们是从缓存中取bitmap,而不是从网络加载,不会差生错位。        //当到缓存中没有的时候,我们虽然加载了默认图片,看似不符合要求,        // 但是我们可以在其他地方改成我们需要的正确的图片就好了(在滑动监听器中获取我们需要的bitmap并修改)        Bitmap bitmap = mImageLoader.getBitmapFromCache(url);        if (bitmap == null) {            viewHolder.ivIcon.setImageResource(R.mipmap.ic_launcher);        } else {            viewHolder.ivIcon.setImageBitmap(bitmap);        }        return convertView;    }    class ViewHolder {        private ImageView ivIcon;        private TextView tvTitle;        private TextView tvContent;    }}
这里我们通过自定义一个ViewHolder,并将其设置为convertView的Tag,从而实现重用,并且之后通过为ImageView设置Tag来避免了出现图片的错位,通过实现 AbsListView.OnScrollListener接口,我们在需要覆写的方法中实现了ListView的预加载,并且子滚动过程中不进行图片的获取,只有当滚动停止时才进行可见列表项的设置。


PS:表示CSDN的编辑功能真的太难用了,用一会就什么也不想写了,图片插入大小还要手动设置,不可以直接拉伸。。。还是觉得博客园的好用点





0 0
原创粉丝点击