以前看了些关于图片优化处理缓存比较全的视频(感谢慕风网),现在回顾觉得还是挺好的也就总结出来下,感觉针对图片做处理这块还真的用的比较多,本文章只要使用异步线程AsyncTask、自定义的ImageLoader和LRU算法来实现,还专门对AsyncTask针对线程管理和自定义核心线程和总运行线程并针对某些可能比较耗时没处理完阻塞线程进行的管理,当然这块有很多第三方框架可以实现,但还是自己写些自己见解的东西比较好,希望对你们有所帮助!
文章总体实现几点:
1、通过异步加载避免阻塞UI线程
2、通过自定义ImageLoader+LRU算法机制实现三级缓存图片
3、通过监听ListView的滑动状态尽可能的优化获取图片资源方式
其实顾名思义就是使用异步去访问加载网络请求数据(╯﹏╰);这不是跟没说一样吗?其实我也没有合理的解释,除非你懂啥叫异步?不就是不同步嘛(妖,那还不是跟没说一样(╯﹏╰))。好吧,所谓异步线程就是开启多条不是可以同步处理的线程处理,当然这线程数还是需要加以控制管理的,不然会造成程序特别耗性能,也就是我们常用到的AsyncTask。
首先,为了提高用户的体验,加载页面数据不会感觉到明显的卡顿,当然还有一点Android机制是main线程不可以进行些耗时操作,不然会阻塞UI线程报ANR异常,所以不得不开启子线程来处理。
(1)、多线程/线程池实现。
(2)、AsyncTask(当然这种方式也就是线程池和handler进行了封装)
1、看一个类当然是从它的构造方法,首先分析下其都做了哪些操作
public AsyncTask() { mWorker = new WorkerRunnable<Params, Result>() { public Result call() throws Exception { mTaskInvoked.set(true); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); return postResult(doInBackground(mParams)); } }; mFuture = new FutureTask<Result>(mWorker) { @Override protected void done() { try { final Result result = get(); postResultIfNotInvoked(result); } catch (InterruptedException e) { android.util.Log.w(LOG_TAG, e); } catch (ExecutionException e) { throw new RuntimeException("An error occured while executing doInBackground()", e.getCause()); } catch (CancellationException e) { postResultIfNotInvoked(null); } catch (Throwable t) { throw new RuntimeException("An error occured while executing " + "doInBackground()", t); } } }; }
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) { if (mStatus != Status.PENDING) { switch (mStatus) { case RUNNING: throw new IllegalStateException("Cannot execute task:" + " the task is already running."); case FINISHED: throw new IllegalStateException("Cannot execute task:" + " the task has already been executed " + "(a task can be executed only once)"); } } mStatus = Status.RUNNING; onPreExecute(); mWorker.mParams = params; exec.execute(mFuture); return this; }这里看到最后使用了exec.execute(mFruture)调用了,这里是线程池启动器,此时启动了线程池。并执行了FutureTask,而WorkerRunnable是作为了callback的回调。最终执行了mWorker里面的call方法。对于线程池,其代码比较复杂也就大概了解至此。对于开启线程就需要先申请内存空间,再执行run方法,线程池就是需要开启多条线程,其实预先申请5条(默认),要是之后的呢?那么就先将其转为task任务暂存,等条件达到也就是有空余的线程可以出来处理时再去取task执行。线程处理完成任务之后当然需要更新UI上的数据,那么这就需要使用到了handler机制了,再继续往下看源代码。
private void finish(Result result) { if (isCancelled()) { onCancelled(result); } else { onPostExecute(result); } mStatus = Status.FINISHED; } 方法调用了他,我们继续看谁调用了finish private static class InternalHandler extends Handler { @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"}) @Override public void handleMessage(Message msg) { AsyncTaskResult result = (AsyncTaskResult) msg.obj; switch (msg.what) { case MESSAGEPOSTRESULT: // There is only one result result.mTask.finish(result.mData[0]); break; case MESSAGEPOSTPROGRESS: result.mTask.onProgressUpdate(result.mData); break; } } }
最后说下Handler还有AsyncTask的描述为什么是The task instance must be created on the UI thread ? 了解Handler机制的人就知道,其实Handler在处理消息队列(MessageQueue)的时候就需要使用到轮询器(Looper)来进行循环获取消息,虽然我们可以new 很多个Handler但是他们本身就只存在一个Looper,当然UI线程中系统就默认帮我们开启了一个Looper,这样主线程就不需要去创建Looper了,要是不在UI线程初始化使用AsyncTask,那么在子线程中就需要自身去创建 个Looper提供给Handler使用,不然就会报异常。当然这样做还是会出现问题,因为AsyncTask使用到prepare需要在更新UI数据,其必须是在UI主线程的Looper中才可以实现(因为AsyncTask本身使用的就是UI线程下的Handler处理),它还是会因为创建出来的Looper不是UI Looper导致异常。所以要想使用AsyncTask就必须要在UI线程中初始化使用,还有一个AsyncTask对象只能调用一次excuste且需要在UI线程中调用。
AsyncTask总体运行机制:FutureTask获取任务执行run方法,run方法中调用了WorkerRunnable的call方法回调然后调用了doInBackground方法,doInbackground方法再将处理结果通过handler处理调用onProgressUpdapte和onPostExecute或者onCancelled方法进行数据更新操作(当然这只是一小部分)。
/** *Params:doInBackground方法的参数类型;请求类型,如:url就是String类型的网址链接 *Progress:AsyncTask所执行的后台任务的进度类型;如果不需要更新进度就直接返回void *Result:后台任务的返回结果类型。 **/ public abstract class AsyncTask<Params, Progress, Result>
AsyncTask的调用无非就以下几个方法:
onPreExecute() //此方法会在后台任务执行前被调用,用于进行一些准备工作 doInBackground(Params... params) //此方法中定义要执行的后台任务,在这个方法中可以调用publishProgress来更新任务进度(publishProgress内部会调用onProgressUpdate方法) onProgressUpdate(Progress... values) //由publishProgress内部调用,表示任务进度更新 onPostExecute(Result result) //后台任务执行完毕后,此方法会被调用,参数即为后台任务的返回结果 onCancelled() //此方法会在后台任务被取消时被调用
取:
1、先从运行内存中获取图片对象
2、内存中没有在到本地SD卡上获取
3、本地SD卡上也没有再去网络上获取
package com.example.lainanzhou.imagecachedemo.utils; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.AsyncTask; import android.os.Environment; import android.os.Message; import android.os.StatFs; import android.util.Log; import android.util.LruCache; import android.widget.ImageView; import android.widget.ListView; import com.example.lainanzhou.imagecachedemo.R; import com.example.lainanzhou.imagecachedemo.adapter.NewsAdapter; import java.io.BufferedInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * TODO: * 通过AsyncTask和LRU算法实现图片三级缓存的图片操作类 * 存: * 1.先网络获取 * 2.存到运行内存中和写到sd卡上 * 取: * 1.先取运行内存中的图片缓存 * 2.再取sd卡图片缓存资源 * 3.最后取网络上的图片资源 * 还有避免图片出现显示错乱情况(ListView的缓存机制复用convertView导致) * 设置View的Tag即可避免 * * @author Joker * @createDate 2016/7/15. */ public class ImageLoader { private static ImageLoader mImageLoader; private ImageView iv; private String mUrl; public static LruCache<String, Bitmap> mLruCache; private static final int FREE_SD_SPACE_NEEDED_TO_CACHE = 10; private android.os.Handler mHandler = new android.os.Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); if (iv.getTag() == mUrl) {//获取ImageView的tag区别设置图片避免图片显示错乱 Bitmap bitmap = (Bitmap) msg.obj; iv.setImageBitmap(bitmap); } } }; private int mCorePoolSize = 10; private int mMaximumPoolSize = 20; private int mKeepAliveTime ; private TimeUnit unit = TimeUnit.MILLISECONDS; // BlockingQueue<Runnable> workQueue = new // ArrayBlockingQueue<Runnable>(10);// 阻塞队列 private BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>();// 阻塞队列 private ThreadFactory threadFactory = Executors.defaultThreadFactory(); // RejectedExecutionHandler handler = new // ThreadPoolExecutor.AbortPolicy();//如果出现错误,则直接抛出异常 // RejectedExecutionHandler handler = new // ThreadPoolExecutor.CallerRunsPolicy();// 如果出现错误,直接执行加入的任务 // RejectedExecutionHandler handler = new // ThreadPoolExecutor.DiscardOldestPolicy();// // 如果出现错误,移除第一个任务,执行加入的任务 private RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy();// 如果出现错误,不做处理 //第一种设置AsyncTask线程池方式 private ThreadPoolExecutor mExecutor = new ThreadPoolExecutor(mCorePoolSize,// 核心线程数 : 10 mMaximumPoolSize,// 最大线程数 : 20 mKeepAliveTime,// 保持的时间长度 unit,// keepAliveTime单位 workQueue,// 任务队列 threadFactory,// 线程工厂 handler);// 错误捕获器 //第二种修改AsyncTask线程池方式 private ExecutorService mExecutorService = Executors.newFixedThreadPool(10); public synchronized static ImageLoader instance() { if (mImageLoader == null) { mImageLoader = new ImageLoader(); //获取程序运行时最大内存空间 long maxMemory = Runtime.getRuntime().maxMemory(); int cacheSize = (int) (maxMemory / 4); mLruCache = new LruCache<String, Bitmap>(cacheSize) { //获取每次加载对象缓存的内存大小;默认返回的是元素的个数而非内存大小 @Override protected int sizeOf(String key, Bitmap value) { //每次存对象都会走的方法 return value.getByteCount(); } }; } return mImageLoader; } private ImageLoader() { } /** * 直接开启线程处理方式显示图片 * 加锁避免疯狂滑动开启多余线程处理同个url * * @param imageView * @param url */ public void showImageFromThread(final ImageView imageView, final String url) { //取缓存 Bitmap bitmap = getBitmapFromCache(url); //缓存存在就直接显示 if (bitmap != null) { imageView.setImageBitmap(bitmap); Log.d(getClass().getSimpleName(), "取内存缓存" + url); } else { synchronized (url) { new Thread() { @Override public void run() { super.run(); iv = imageView; mUrl = url; Bitmap bitmap = getBitmapFromUrl(url); //缓存图片 addBitmap2Cache(url, bitmap); Message mes = Message.obtain(); mes.obj = bitmap; mHandler.sendMessage(mes); } }.start(); } } } /** * 从Url获取bitmap对象 * * @param urlString * @return */ private Bitmap getBitmapFromUrl(String urlString) { Bitmap bitmap = null; InputStream is = null; try { URL url = new URL(urlString); HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); is = new BufferedInputStream(urlConnection.getInputStream()); bitmap = BitmapFactory.decodeStream(is);//将流对象转成bitmap对象 urlConnection.disconnect();//释放资源 } catch (IOException e) { e.printStackTrace(); } finally { try { if (is != null) is.close(); } catch (IOException e) { e.printStackTrace(); } } return bitmap; } public void showImageFromAsyncTask(ImageView imageView, String urlString, boolean isLoader) { //取缓存 Bitmap bitmap = getBitmapFromCache(urlString); if (bitmap == null) { if (isLoader) { bitmap = getBitmapFromSdCard(urlString); if (bitmap == null && isLoader) // new ImageAsyncTask(imageView).execute(urlString);//缓存没有就网络加载 new ImageAsyncTask(imageView,urlString);//缓存没有就网络加载 else if (imageView.getTag().equals(urlString)) { addBitmap2Cache(urlString, bitmap);//缓存到内存 imageView.setImageBitmap(bitmap);//有缓存就直接显示 Log.d(getClass().getSimpleName(), "取Sd卡缓存" + urlString); } } else if (imageView.getTag().equals(urlString)) { imageView.setImageResource(R.mipmap.ic_launcher);//设置默认图片 } } else if (imageView.getTag().equals(urlString)) { imageView.setImageBitmap(bitmap);//有缓存就直接显示 Log.d(getClass().getSimpleName(), "取内存缓存" + urlString); } } class ImageAsyncTask extends AsyncTask<String, Void, Bitmap> { private ImageView mImageView; private String url; public ImageAsyncTask(ImageView imageView,String... parmas) { this.executeOnExecutor(mExecutor,parmas); // this.executeOnExecutor(mExecutorService,parmas); mImageView = imageView; } @Override protected Bitmap doInBackground(String... params) { url = params[0]; Bitmap bitmap = getBitmapFromUrl(url); //缓存图片 if (bitmap != null) { addBitmap2Cache(url, bitmap); writeBitmap2SdCard(bitmap, url); } return bitmap; } @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); if (bitmap == null && mImageView.getTag().equals(url)) mImageView.setImageResource(R.mipmap.ic_launcher);//设置默认图片 else if (mImageView.getTag().equals(url)) mImageView.setImageBitmap(bitmap); } } /** * 加载停止可见的图片资源 * * @param start * @param visibleCount */ public void loadVisibleImages(ListView listView, int start, int visibleCount, boolean isLoader) { for (int i = start; i < visibleCount; i++) { // new ImageAsyncTask(imageView).execute(urlList.get(i).getImageUrl()); ImageView imageView = (ImageView) listView.findViewWithTag(NewsAdapter.mUrls[i]); showImageFromAsyncTask(imageView, NewsAdapter.mUrls[i], isLoader); } } /** * 添加图片到缓存中 * * @param url * @param bitmap */ private void addBitmap2Cache(String url, Bitmap bitmap) { if (getBitmapFromCache(url) == null) { mLruCache.put(url, bitmap); Log.d(getClass().getSimpleName(), "内存缓存" + url); } } /** * 从缓存中获取图片资源 * * @param url * @return */ private Bitmap getBitmapFromCache(String url) { return mLruCache.get(url); } private void writeBitmap2SdCard(Bitmap bitmap, String path) { if (bitmap == null) { return; } //判断sdcard上的空间 if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) { //SD空间不足 return; } //创建缓存目录,系统一运行就得创建缓存目录的 String filename = convertUrlToFileName(path); String dir = getDirectory(); File dirFile = new File(dir); if (!dirFile.exists()) dirFile.mkdirs(); File file = new File(dirFile + "/" + filename); try { file.createNewFile(); OutputStream outStream = new FileOutputStream(file); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outStream); outStream.flush(); outStream.close(); } catch (IOException e) { e.printStackTrace(); } } /** * 计算sdcard上的剩余空间 **/ private int freeSpaceOnSd() { StatFs stat = new StatFs(Environment.getExternalStorageDirectory().getPath()); double sdFreeMB = ((double) stat.getAvailableBlocks() * (double) stat.getBlockSize()) / 1024 * 1024; return (int) sdFreeMB; } private Bitmap getBitmapFromSdCard(String url) { String path = getDirectory() + "/" + convertUrlToFileName(url); File file = new File(path); if (file.exists()) { Bitmap bmp = BitmapFactory.decodeFile(path); if (bmp == null) { file.delete(); } else { return bmp; } } return null; } /** * 取SD卡路径 **/ private String getSDPath() { boolean sdCardExist = Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED); //判断sd卡是否存在 if (sdCardExist) { //获取根目录 return Environment.getExternalStorageDirectory().getPath(); } return ""; } /** * 将url转成文件名 **/ private String convertUrlToFileName(String url) { String[] strs = url.split("/"); return strs[strs.length - 1]; } /** * 获得缓存目录 **/ private String getDirectory() { String dir = getSDPath() + "/" + "imgCache"; return dir; } }
只有在滑动状态是停止的时候才去访问网络加载数据。
package com.example.lainanzhou.imagecachedemo.adapter; import android.content.Context; import android.util.Log; 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 com.example.lainanzhou.imagecachedemo.R; import com.example.lainanzhou.imagecachedemo.bean.NewsBean; import com.example.lainanzhou.imagecachedemo.utils.ImageLoader; import java.util.List; /** * TODO: * 处理ListView显示数据和监听当前ListView滑动状态来加载数据 * * @author Joker * @createDate 2016/7/15. */ public class NewsAdapter extends BaseAdapter implements AbsListView.OnScrollListener { private LayoutInflater mLayoutInflater; private List<NewsBean> mBeanList; private boolean isFirst = false; private int start; private int end; private ListView mListView; public static String[] mUrls; public NewsAdapter(Context context, ListView listView, List<NewsBean> data) { mLayoutInflater = LayoutInflater.from(context); mBeanList = data; mListView = listView; mListView.setOnScrollListener(this); } @Override public int getCount() { return mBeanList == null ? 0 : mBeanList.size(); } @Override public Object getItem(int position) { return mBeanList == null ? null : mBeanList.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) { viewHolder = new ViewHolder(); convertView = mLayoutInflater.inflate(R.layout.item_layout, null); viewHolder.iv = (ImageView) convertView.findViewById(R.id.imageView); viewHolder.tv_titile = (TextView) convertView.findViewById(R.id.title); viewHolder.tv_content = (TextView) convertView.findViewById(R.id.content); convertView.setTag(viewHolder); } else { viewHolder = (ViewHolder) convertView.getTag(); } String ivUrl = mBeanList.get(position).getImageUrl(); viewHolder.iv.setTag(ivUrl); //设置网路图片 // mImageLoader.showImageFromAsyncTask(viewHolder.iv, ivUrl); // ImageLoader.instance().showImageFromAsyncTask(viewHolder.iv, ivUrl); viewHolder.tv_titile.setText(mBeanList.get(position).getTitle()); viewHolder.tv_content.setText(mBeanList.get(position).getContent()); return convertView; } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { //只有滑动状态发生改变才会走的方法,首次进来不会走 if (scrollState == SCROLL_STATE_IDLE) {//停止状态 //网络加载停止时可见项 // ImageLoader.instance().loadVisibleImages(mListView, start, end); showImageLoader(start, end, true); } else {//其他状态 //停止网络加载任务 showImageLoader(start, end, false); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { //滑动就会走的方法,首次进来也会走 start = firstVisibleItem; end = start + visibleItemCount; if (!isFirst && visibleItemCount > 0) { if (mUrls == null) mUrls = new String[mBeanList.size()]; // ImageLoader.instance().loadVisibleImages(mListView, start, end); for (int i = start; i < start + mBeanList.size(); i++) { mUrls[i] = mBeanList.get(i - start).getImageUrl(); } showImageLoader(start, end, true); isFirst = true; return; } showImageLoader(start, end, false); } class ViewHolder { private ImageView iv; private TextView tv_titile; private TextView tv_content; } private void showImageLoader(int firstVisibleItem, int visibleCounts, boolean isLoader) { ImageLoader.instance().loadVisibleImages(mListView, firstVisibleItem, visibleCounts, isLoader); Log.d(getClass().getSimpleName(), "滑动状态:" + isLoader); } }
package com.example.lainanzhou.imagecachedemo; import android.os.AsyncTask; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.widget.ListView; import com.example.lainanzhou.imagecachedemo.adapter.NewsAdapter; import com.example.lainanzhou.imagecachedemo.bean.NewsBean; 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 { private ListView mListView; private String url = "http://www.imooc.com/api/teacher?type=4&num=30"; private List<NewsBean> mBeanList = new ArrayList<>(); private int start, visibleCounts;//可见item条目起始标志 private boolean isFirst; private NewsAdapter mNewsAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mListView = (ListView) findViewById(R.id.listView); new NewsAsyncTask().execute(url);//启动异步请求 } class NewsAsyncTask extends AsyncTask<String, Void, List<NewsBean>> { @Override protected List<NewsBean> doInBackground(String... params) { return getJsonDataFromUrl(params[0]);//传进来的参数只有一个url } @Override protected void onPostExecute(List<NewsBean> newsBeen) { super.onPostExecute(newsBeen); //更新UI数据界面 mNewsAdapter = new NewsAdapter(MainActivity.this, mListView, mBeanList); mListView.setAdapter(mNewsAdapter); } } /** * 从url中获取结果 * * @param param * @return */ private List<NewsBean> getJsonDataFromUrl(String param) { try { InputStream is = new URL(url).openStream(); String jsonString = radStream(is); try { JSONObject jsonObject = new JSONObject(jsonString); JSONArray jsonArray = jsonObject.getJSONArray("data"); for (int i = 0; i < jsonArray.length(); i++) { NewsBean newsBean = new NewsBean(); jsonObject = jsonArray.getJSONObject(i); newsBean.setImageUrl(jsonObject.getString("picSmall")); newsBean.setTitle(jsonObject.getString("name")); newsBean.setContent(jsonObject.getString("description")); mBeanList.add(newsBean); } } catch (JSONException e) { e.printStackTrace(); } } catch (IOException e) { e.printStackTrace(); } return mBeanList; } /** * 获取输入流结果 * * @param is * @return */ private String radStream(InputStream is) { String result = ""; try { String line = ""; //将输入字节流转化为字符流来处理 InputStreamReader isr = new InputStreamReader(is, "utf-8"); //读取字符流 BufferedReader br = new BufferedReader(isr); try { while ((line = br.readLine()) != null) { result += line; } //关闭流 br.close(); isr.close(); } catch (IOException e) { e.printStackTrace(); } } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return result; } }
最后附带项目链接地址:点击打开链接