AsyncTask异步任务与LruCache缓存策略实现图片加载
AsyncTask异步任务
以下内容节选自官方文档:
AsyncTask enables proper and easy use of the UI thread. This class allows to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers.
异步任务使我们能简单正确的使用UI线程,这个类允许我们做后台操作并将结果展示到UI线程中去,而不需要进行复杂的线程和handler操作
AsyncTask is designed to be a helper class around Thread and Handler and does not constitute a generic threading framework. AsyncTasks should ideally be used for short operations (a few seconds at the most.)
异步任务这个类是作为线程和Handler的辅助类,而不该被看做通用的线程框架,异步任务用在短时操作(几秒钟)上十分理想。
If you need to keep threads running for long periods of time, it is highly recommended you use the various APIs provided by the java.util.concurrent package such as Executor, ThreadPoolExecutor and FutureTask.
如果需要保持线程持续长时间运行,那么建议你使用Java并发库提供的APIs,比如Executor,ThreadPoolExcutor和FutureTask。
An asynchronous task is defined by a computation that runs on a background thread and whose result is published on the UI thread.
异步的任务是指运行在后台的线程执行的种种计算,这个计算结果将返回到UI线程。
An asynchronous task is defined by 3 generic types, called Params, Progress and Result, and 4 steps, called onPreExecute, doInBackground, onProgressUpdate and onPostExecute
异步任务有3个泛型参数,分别是Params,Progress和Result,同事有四个步骤,onPreExecute,doInBackground,onProgressUpdate,和onPostExecute。
注意以下文字:
Order of execution:
When first introduced, AsyncTasks were executed serially on a single background thread. Starting with DONUT, this was changed to a pool of threads allowing multiple tasks to operate in parallel. Starting with HONEYCOMB, tasks are executed on a single thread to avoid common application errors caused by parallel execution.
最开始引入的时候,异步任务是严格在一个后台单线程中被执行的,从DONUT开始(通过Build.VERSION_CODES.DONUT查看到改版本是 September 2009发布属于Android 1.6,API 4)改成允许多任务并行执行线程池的方式。从HONEYCOMB(February 2011: Android 3.0. API 11)开始,任务有变回单线程以避免很多由于并行执行造成的应用程序出错。所以目前的异步任务是单线程执行的。
简介,AsyncTask是一个轻量级的异步任务框架,一般由于Android单线程的模型,导致网络访问请求需要异步完成.相比于handler,AsyncTask更加方便灵活,只需要重写几个方法即可:
- Result doInBackground(Params… params) 这里的返回值Result就是上面官方文档中说的第三个泛型参数。params参数列表,这是AsyncTask.execute(Params… params)方法中传过去的参数列表
- void onPreExecute(),这个方法在doInBackground之前执行,可以做些初始化操作。
- void onProgressUpdate(Void… value),在 publishProgress(Progress…)之后调用,这些参数是publishProgress传过来的。
- void publishProgress (Progress… values) 这个方法可以在doInBackground方法中调用,用来在后台进行耗时操作的同时通知UI线程做一些更新,该方法触发onProgressUpdate,当后台任务被取消时,则不会回调onProgressUpdate方法。
使用
AsyncTask must be subclassed to be used. The subclass will override at least one method (doInBackground(Params…)), and most often will override a second one (onPostExecute(Result).)
异步任务需要作为子类被使用,子类必须覆写doInBackground方法,通常还要覆写onPostExecute方法,官方例子:
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
totalSize += Downloader.downloadFile(urls[i]);
publishProgress((int) ((i / (float) count) * 100));
// Escape early if cancel() is called
if (isCancelled()) break;
}
return totalSize;
}
protected void onProgressUpdate(Integer... progress) {
setProgressPercent(progress[0]);//这里可以显示进度条等
}
protected void onPostExecute(Long result) {
showDialog("Downloaded " + result + " bytes");
}
}
然后执行下面代码即可执行异步任务
new DownloadFilesTask().execute(url1, url2, url3);
自定义AsyncTask例子:
/**
* 实现网络访问的异步操作
*/
class NewsAsyncTask extends AsyncTask<String, Void, List<NewsBean>> {
//程序先运行到这里
@Override
protected List<NewsBean> doInBackground(String... params) {
Build.VERSION_CODES.HONEYCOMB
//prams[0]是闯入的URL
Log.e("test", "Task中" + Thread.currentThread().getName().toString());
publishProgress();
return getJsonData(params[0]);
}
@Override
protected void onPreExecute() {
super.onPreExecute();
}
/**
* 我们已经有了数据源这时候要通过是配置器来刷新数据
*
* @param newsBeans
*/
@Override
protected void onPostExecute(List<NewsBean> newsBeans) {
super.onPostExecute(newsBeans);
NewsAdapter adapter = new NewsAdapter(MainActivity.this, newsBeans);
mListView.setAdapter(adapter);
adapter.notifyDataSetChanged();
}
@Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
}
}
在适当的地方执行异步任务
MainActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mListView = (ListView) findViewById(R.id.lv_main);
//创建异步任务
mNewsAsyncTask = new NewsAsyncTask();
mNewsAsyncTask.execute(URL);
Log.e("test", "onCreate中---->" + Thread.currentThread().getName().toString());
}
完整的代码如下:
public class MainActivity extends Activity {
private ListView mListView;
private String URL = "http://www.imooc.com/api/teacher?type=4&num=30";
private NewsAsyncTask mNewsAsyncTask;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mListView = (ListView) findViewById(R.id.lv_main);
//创建异步任务
mNewsAsyncTask = new NewsAsyncTask();
mNewsAsyncTask.execute(URL);
Log.e("test", "onCreate中---->" + Thread.currentThread().getName().toString());
}
@Override
protected void onResume() {
super.onResume();
}
/**
* 实现网络访问的异步操作
*/
class NewsAsyncTask extends AsyncTask<String, Void, List<NewsBean>> {
//程序先运行到这里
@Override
protected List<NewsBean> doInBackground(String... params) {
Build.VERSION_CODES.HONEYCOMB
//prams[0]是闯入的URL
Log.e("test", "Task中" + Thread.currentThread().getName().toString());
publishProgress();
return getJsonData(params[0]);
}
@Override
protected void onPreExecute() {
super.onPreExecute();
}
/**
* 我们已经有了数据源这时候要通过是配置器来刷新数据
*
* @param newsBeans
*/
@Override
protected void onPostExecute(List<NewsBean> newsBeans) {
super.onPostExecute(newsBeans);
NewsAdapter adapter = new NewsAdapter(MainActivity.this, newsBeans);
mListView.setAdapter(adapter);
adapter.notifyDataSetChanged();
}
@Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
}
}
//Stream是字节流,java.io.Reader和java.io.InputStream组成了Java输入类,是IO库提供的平行独立的等级结构
//InputStream和Output处理8位元,byte是8bit,inputstreamreader将inputstream适配到reader
//Reader和Writer处理16位元,可以处理中文,char是16bit,FIleReader重文件输入,CharArrayReader重程序字符数组输入,PipedReader从另一个线程的PipedWriter写的字符
private String readStream(InputStream in) {
InputStreamReader isr = null;
String result = "";
try {
String line = "";//每一行数据
//使用BufferedReader对InputStreamReader进行包装,Reader是字符流
isr = new InputStreamReader(in, "utf-8");
//
BufferedReader br = new BufferedReader(isr);
//br有许多方法,如read,readLine
while ((line = br.readLine()) != null) {
result = line + result;
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
private List<NewsBean> getJsonData(String weburl) {
List<NewsBean> newsBeanList = new ArrayList<>();
try {
URL url = new URL(weburl);
HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection();
InputStream is = httpUrlConnection.getInputStream();//从网络中获取输入流
String jsonStr = readStream(is);//将输入流装换成string字符串
//使用内置的Json解析工具
JSONObject jsonObject;
NewsBean newsBean;
jsonObject = new JSONObject(jsonStr);
JSONArray jsonArray = (JSONArray) jsonObject.getJSONArray("data");
for (int i = 0; i < jsonArray.length(); i++) {
jsonObject = jsonArray.getJSONObject(i);
newsBean = new NewsBean();
newsBean.newsIconUrl = jsonObject.getString("picSmall");
newsBean.newsTitle = jsonObject.getString("name");
newsBean.newsContent = jsonObject.getString("description");
newsBeanList.add(newsBean);
}
Log.e("test", jsonStr);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
Log.e("test", newsBeanList.toString());
return newsBeanList;
}
}
NewsAdapter.java
public class NewsAdapter extends BaseAdapter {
List<NewsBean> mList;
LayoutInflater mInflater;
ImageLoader mImageLoader;
public NewsAdapter(Context context, List<NewsBean> mList) {
this.mList = mList;
mInflater = LayoutInflater.from(context);
mImageLoader = new ImageLoader();
}
@Override
public int getCount() {
return mList.size();
}
@Override
public Object getItem(int position) {
return mList.get(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) {
convertView = mInflater.inflate(R.layout.item_layout, null);//无父控件
viewHolder = new ViewHolder();
viewHolder.ivIcon = (ImageView) convertView.findViewById(R.id.iv_Icon);
viewHolder.tvContent = (TextView) convertView.findViewById(R.id.tv_content);
viewHolder.tvTitle = (TextView) convertView.findViewById(R.id.tv_title);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();//从缓存中得到convertView的控件
}
String imgUrl = mList.get(position).newsIconUrl;
viewHolder.ivIcon.setTag(imgUrl);
// new ImageLoader().showImageByThread(viewHolder.ivIcon, mList.get(position).newsIconUrl);
}
class ViewHolder {
public TextView tvTitle, tvContent;
public ImageView ivIcon;
}
}
实体类NewsBean.java
public class NewsBean {
public String newsIconUrl;
public String newsTitle;
public String newsContent;
}
涉及到LruCache缓存的类ImageLoader.java
public class ImageLoader {
// Lru算法
private LruCache<String, Bitmap> mCache;
public ImageLoader() {
int maxCache = (int) Runtime.getRuntime().maxMemory();
int cacheSize = maxCache / 4;
//设置最大缓存
mCache = new LruCache<String, Bitmap>(cacheSize) {
//在每次存入的时候调用
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount();
}
};
}
public void addBitmapToCache(String url, Bitmap bitmap) {
if (getBitMapFromUrl(url) == null) {
mCache.put(url, bitmap);
}
}
public Bitmap getBitmapFromCache(String url) {
return mCache.get(url);
}
private ImageView mImageView;
private String imgUrl = "";
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (mImageView.getTag().equals(imgUrl))
mImageView.setImageBitmap((Bitmap) msg.obj);
//mImageView.setImageResource();//在UI线程中执行
}
};
// public void showImageByThread(ImageView imageView, final String url) {
// mImageView = imageView;
// imgUrl = url;
// new Thread() {
// @Override
// public void run() {
// Bitmap bitmap = getBitMapFromUrl(url);
// Message message = Message.obtain();
// message.obj = bitmap;
// handler.sendMessage(message);
// }
// }.start();
// }
public Bitmap getBitMapFromUrl(String imageurl) {
Bitmap bitmap;
InputStream is = null;
try {
URL url = new URL(imageurl);
HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
is = httpURLConnection.getInputStream();
bitmap = BitmapFactory.decodeStream(is);
httpURLConnection.disconnect();
//Thread.sleep(1000);异步加载不用这个
return bitmap;
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
public void showImageByAnsyncTask(ImageView imageView, String url) {
//先从缓存中读取
Bitmap bitmap = getBitmapFromCache(url);
if (bitmap == null) {
new NewsAsyncTask(imageView, url).execute(url);//execute要将参数传过去
} else {
mImageView.setImageBitmap(bitmap);
}
}
/**
* 使用异步任务加载图片
*/
private class NewsAsyncTask extends AsyncTask<String, Void, Bitmap> {
private ImageView mImageView;
private String mUrl;
public NewsAsyncTask(ImageView imageView, String url) {
mImageView = imageView;
mUrl = url;
}
/**
* 获取图片在这里操作
*
* @param params
* @return
*/
@Override
protected Bitmap doInBackground(String... params) {
Bitmap bitmap = getBitMapFromUrl(params[0]);
if (bitmap != null) {
addBitmapToCache(params[0], bitmap);
}
return bitmap;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
if (mUrl.equals(mImageView.getTag()))
mImageView.setImageBitmap(bitmap);
}
@Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
}
}
}
LruCache缓存
下面解释下ImageLoader中使用到的LruCache,以下内容截取自官方文档,可能翻译的不好,有错误请大家指正:
A cache that holds strong references to a limited number of values. Each time a value is accessed, it is moved to the head of a queue
LruCache持有有限个数的“值”的强引用,每一次一个“值”获取到,它就被移到队列的头部。
When a value is added to a full cache, the value at the end of that queue is evicted and may become eligible for garbage collection.
当某个值加到缓存已满的lrucache中时,在队尾的值会被赶走,并且可能会合法地被GC垃圾回收掉。
If your cached values hold resources that need to be explicitly released,override entryRemoved(boolean, K, V, V).
如果你的缓存值持有需要显式被释放的资源时,覆写entryRemoved方法。
If a cache miss should be computed on demand for the corresponding keys, override create(K)
如果缓存无法计算一些“键”(keys)对应的值(value),覆写create方法去实现计算key对应的value。默认实现是返回null。
This simplifies the calling code, allowing it to assume a value will always be returned, even when there’s a cache miss.
这简化了我们调用代码,允许 假定某个“值”始终会得到返回,即使某个缓存已经丢失。
By default, the cache size is measured in the number of entries. Override sizeOf(K, V) to size the cache in different units
默认情况下,缓存的尺寸由entries(缓存bitmap的话尺寸就是bitmap的个数)的数量所衡量,如果需要用不同的单位衡量缓存,覆写sizeOf方法。sizeof默认的返回是1(缓存bitmap的话就是指一个bitmap的大小)。
官方API提供的例子如下:
int cacheSize = 4 * 1024 * 1024; // 4MiB
LruCache bitmapCache = new LruCache (cacheSize) {
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount();
}}
注意这里cacheSize和sizeOf中返回值的单位需要一致,不然有可能会出错。
下面再提供官方Training-Building Apps with Graphics & Animation-Displaying Bitmaps Efficiently-Caching Bitmaps文章中提供的缓存Bitmap图片的例子:
private LruCache<String, Bitmap> mMemoryCache;
@Override
protected void onCreate(Bundle savedInstanceState) {
...
// Get max available VM memory, exceeding this amount will throw an
// OutOfMemory exception. Stored in kilobytes as LruCache takes an
// int in its constructor.
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// Use 1/8th of the available memory for this memory cache.
final int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
// The cache size will be measured in kilobytes rather than
// number of items.
return bitmap.getByteCount() / 1024;
}
};
...
}
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
public Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}
上面缓存的大小的计量单位统一为KB。
Note: In this example, one eighth of the application memory is allocated for our cache. On a normal/hdpi device this is a minimum of around 4MB (32/8). A full screen GridView filled with images on a device with 800x480 resolution would use around 1.5MB (800*480*4 bytes), so this would cache a minimum of around 2.5 pages of images in memory.
官方文档说了,在这个例子中,开辟了1/8的应用内存空间作为缓存最大值,一个hdpi分辨率的设备最少大概有32M内存的样子,这时候一个应弄能得到的大概4M,全屏的GridView在hdpi(800*480)的手机上将会使用800*400*4=1.5M的内存空间,所以达到4M最大值需要2.5页满屏的图片。