上节课我们学习了缓存模块的实现, 缓存分做两份:
Memory Cache和
File Cache。方法也很简单,分别是:
Memory Cache和File Cache在上一课中有具体的实现,这里有一个异步的任务处理器——
AsyncImageDownloader,它用来在后台下载数据,完成下载后存储数据到缓存中,并更新UI的显示
。让我们来看看它是如何实现的:
到这里,一个简单的缓存框架就搭建成功了。它简洁有效,但是非常单薄,似乎不够强大,需要你们根据自己的需求进行修改。另外它本来的目的就是用于演示,理解这个以后,我们再来看Google的BitmapFun。
- 存储文件
- 按唯一key值索引文件
- 清空缓存
区别在于内存缓存读取优先,应为它读写的速度更快。但是考虑到内存限制,退而选用文件存储,分担内存缓存的压力。
原理非常简单,在第一课中已经详细分析了。那么要怎么才能将这个缓存模块与UI模块的显示关联起来呢?在这里我们需要一个控制器,掌管数据流向和读写,同时控制UI的显示。
那么这个控制器需要以下的元素:
- 内存缓存
- 硬盘缓存
- 异步任务处理
- 控制UI显示
1 | //caches |
2 | private MemoryCache memoryCache; |
3 | private FileCache fileCache; |
4 | //Asynchronous task |
5 | private static AsyncImageLoader imageLoader; |
01 | class AsyncImageDownloader extends AsyncTask<Void, Void, Bitmap>{ |
02 | private ImageView imageView; |
03 | private String fileName; |
04 | |
05 | public AsyncImageDownloader(ImageView imageView, String fileName){ |
06 | this .imageView = imageView; |
07 | this .fileName = fileName; |
08 | } |
09 | |
10 | @Override |
11 | protected void onPreExecute() { |
12 | super .onPreExecute(); |
13 | imageView.setImageResource(R.drawable.placeholder); |
14 | } |
15 | |
16 | @Override |
17 | protected Bitmap doInBackground(Void... arg0) { |
18 | String url = Utils.getRealUrlOfPicture(fileName); |
19 | HttpResponse response = new HttpRetriever().requestGet(url, null ); |
20 | Log.i(TAG, "url: " + url); |
21 | Log.i(TAG, "respone: " + response); |
22 | InputStream in = null ; |
23 | try { |
24 | if (response != null && response.getEntity() != null ) |
25 | in = response.getEntity().getContent(); |
26 | } catch (IllegalStateException e) { |
27 | e.printStackTrace(); |
28 | return null ; |
29 | } catch (IOException e) { |
30 | e.printStackTrace(); |
31 | return null ; |
32 | } |
33 | |
34 | //TODO to be optimized: adjust the size of bitmap |
35 | return BitmapFactory.decodeStream(in); |
36 | } |
37 | |
38 | @Override |
39 | protected void onPostExecute(Bitmap result) { |
40 | super .onPostExecute(result); |
41 | if (result != null && imageView != null ) |
42 | imageView.setImageBitmap(result); |
43 | |
44 | //TODO cache the bitmap both in sdcard & memory |
45 | memoryCache.put(fileName, result); // key is a unique token, value is the bitmap |
46 | |
47 | fileCache.put(fileName, result); |
48 | } |
49 | } |
可以看到这个类的构造函数需要两个参数,分别是文件名和对应要显示的ImageView,那么在任务开始的时候,可以为该ImageView设置未下载状态的图片,然后下载完成后更新UI。
注:需要提醒的是,这里的唯一key值,我使用的是文件名,因为我接收到的文件名是唯一的。猿媛们也可以根据自己的需求,设计自己的唯一key值算法。
接下来,我们需要读用key值索引相应的Bitmap:
01 | public Bitmap getBitmap(String key){ |
02 | Bitmap bitmap = null ; |
03 | //1. search memory |
04 | bitmap = memoryCache.get(key); |
05 | |
06 | //2. search sdcard |
07 | if (bitmap == null ){ |
08 | File file = fileCache.getFile(key); |
09 | if (file != null ) |
10 | bitmap = BitmapHelper.decodeFile(file, null ); |
11 | } |
12 | |
13 | return bitmap; |
14 | } |
读取到Bitmap后进行显示:
01 | public void displayBitmap(ImageView imageView, String fileName){ |
02 | //no pic for this item |
03 | if (fileName == null || "" .equals(fileName)) |
04 | return ; |
05 | |
06 | Bitmap bitmap = getBitmap(fileName); |
07 | //search in cache, if there is no such bitmap, launch downloads |
08 | if (bitmap != null ){ |
09 | imageView.setImageBitmap(bitmap); |
10 | } |
11 | else { |
12 | Log.w(TAG, "Can't find the file you required." ); |
13 | new AsyncImageDownloader(imageView, fileName).execute(); |
14 | } |
15 | } |
不过,我将它应用在一个小项目中,性能还不错。对于小项目的需求,应该是够的。
最后,附上使用方法,以及整个类的源码。
使用方法:
1 | AsyncImageLoader imageLoader = AsyncImageLoader.getInstance( this );、 |
2 | imageLoader.displayBitmap(imageView, fileName); |
源码:
001 | public class MainActivity extends Activity { |
002 |
003 | private static final String TAG = "MainActivity" ; |
004 | |
005 | private static final boolean DEBUG = true ; |
006 | |
007 | private static String url = Config.SERVER_URL + Config.REQUEST_UPDATE; |
008 | |
009 | //views |
010 | private ListView listViewNews; |
011 | |
012 | //controller |
013 | private AsyncImageLoader imageLoader; |
014 | private NewsAdapter newsAdapter; |
015 | |
016 | /** |
017 | * Adapter for the pictures |
018 | * */ |
019 | private class ViewHolder{ |
020 | public ImageView imageViewPic; |
021 | public TextView textViewNewsEN; |
022 | public TextView textViewNewsZN; |
023 | public TextView textViewTags; |
024 | public TextView textViewLikeIt; |
025 | public TextView textViewCreationDate; |
026 | } |
027 | |
028 | class NewsAdapter extends ArrayAdapter<SoftNews>{ |
029 |
030 | private ArrayList<SoftNews> newsList; |
031 | private LayoutInflater inflater; |
032 | |
033 | private boolean notEmpty(String str){ |
034 | if (str != null && ! "" .equals(str)) |
035 | return true ; |
036 | |
037 | return false ; |
038 | } |
039 | |
040 | public NewsAdapter(Context context, int resource, ArrayList<SoftNews> newsList) { |
041 | super (context, resource, newsList); |
042 | this .newsList = newsList; |
043 | this .inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); |
044 | } |
045 |
046 | @Override |
047 | public View getView( int position, View convertView, ViewGroup parent) { |
048 | ViewHolder holder = null ; |
049 | if (convertView == null ) { //Prepare the view if doesn t exits |
050 | convertView = inflater.inflate(R.layout.news_item, null ); |
051 | holder = new ViewHolder(); |
052 | |
053 | holder.imageViewPic = (ImageView)convertView.findViewById(R.id.imageViewPic); |
054 | holder.textViewNewsEN = (TextView)convertView.findViewById(R.id.textViewNewsEN); |
055 | holder.textViewNewsZN = (TextView)convertView.findViewById(R.id.textViewNewsZN); |
056 | holder.textViewTags = (TextView)convertView.findViewById(R.id.textViewTags); |
057 | holder.textViewLikeIt = (TextView)convertView.findViewById(R.id.textViewLikeIt); |
058 | holder.textViewCreationDate = (TextView)convertView.findViewById(R.id.textViewCreationDate); |
059 | |
060 | convertView.setTag(holder); |
061 | } else |
062 | holder = (ViewHolder)convertView.getTag(); |
063 | |
064 | //set data |
065 | SoftNews news = newsList.get(position); |
066 | if (news != null ){ |
067 | if (notEmpty(news.getPicture())){ |
068 | holder.imageViewPic.setVisibility(View.VISIBLE); |
069 | imageLoader.displayBitmap(holder.imageViewPic, news.getPicture()); |
070 | } |
071 | else |
072 | holder.imageViewPic.setVisibility(View.GONE); |
073 | if (notEmpty(news.getNews_en())) |
074 | holder.textViewNewsEN.setText(news.getNews_en()); |
075 | if (notEmpty(news.getNews_zn())) |
076 | holder.textViewNewsZN.setText(news.getNews_zn()); |
077 | if (news.getCreationDate() != null ) |
078 | try { |
079 | holder.textViewCreationDate.setText(Utils.dateToString(news.getCreationDate(), Config.NEWS_ITEM_DATE_FORMAT)); |
080 | } catch (Exception e) { e.printStackTrace(); } |
081 | if (notEmpty(news.getTags())) |
082 | holder.textViewTags.setText(news.getTags()); |
083 | } |
084 | return convertView; |
085 | } |
086 | } |
087 | |
088 | private void testUpdates() throws IllegalStateException, IOException { |
089 | InputStream in = null ; |
090 | if (DEBUG){ |
091 | //for test |
092 | in = Utils.readSampleJson(getResources()); |
093 | } else { |
094 | // for real function |
095 | HttpRetriever httpRetriever = new HttpRetriever(); |
096 | HttpResponse httpResponse = httpRetriever.requestGet(url, null ); |
097 | in = httpResponse.getEntity().getContent(); |
098 | } |
099 | ArrayList<SoftNews> newsList = Utils.readNewsFromJsonStream(in); |
100 | for ( int i= 0 ; newsList != null && i<newsList.size(); i++){ |
101 | //Log.i(TAG, "news " + i + ": " + newsList.get(i).toString()); |
102 | newsAdapter.add(newsList.get(i)); |
103 | } |
104 | |
105 | //refresh ui |
106 | newsAdapter.notifyDataSetChanged(); |
107 | } |
108 | |
109 | @Override |
110 | public boolean onCreateOptionsMenu(Menu menu) { |
111 | getMenuInflater().inflate(R.menu.activity_main, menu); |
112 | return true ; |
113 | } |
114 | |
115 | @Override |
116 | public boolean onOptionsItemSelected(MenuItem item) { |
117 | switch (item.getItemId()) { |
118 | case R.id.menu_clear_cache: |
119 | imageLoader.clearCache(); |
120 | break ; |
121 |
122 | default : |
123 | break ; |
124 | } |
125 | return super .onOptionsItemSelected(item); |
126 | } |
127 | |
128 | /** Activity life cycle */ |
129 | @Override |
130 | public void onCreate(Bundle savedInstanceState) { |
131 | super .onCreate(savedInstanceState); |
132 | setContentView(R.layout.news_list); |
133 | |
134 | imageLoader = AsyncImageLoader.getInstance( this ); |
135 | |
136 | //views |
137 | listViewNews = (ListView) findViewById(R.id.listViewNews); |
138 | |
139 | newsAdapter = new NewsAdapter( this , R.layout.news_item, new ArrayList<SoftNews>()); |
140 | listViewNews.setAdapter(newsAdapter); |
141 | |
142 | try { |
143 | testUpdates(); |
144 | } catch (IllegalStateException e) { |
145 | e.printStackTrace(); |
146 | } catch (IOException e) { |
147 | e.printStackTrace(); |
148 | } |
149 | } |
150 |
151 | } |
来源:http://blog.youkuaiyun.com/floodingfire/article/details/8249122