转自:http://my.oschina.net/DragonWang/blog/64489
最近在对编写完后的FileManager做优化,发觉其中异步加载图片的那块代码还需要在重构一下。
首先我先说明一下,该FileManager中显示文件图标的控件为GridView,并且最大可视区域为20个图标,就是因为要同时显示20个才给我惹了大麻烦。
简单地说是由于测试部在对FileManager的稳定性进行非常暴力的测试发生的问题,他们极其迅速地多次上下来回滑动GridView,创建过多AsyncTask导致了CPU无法负荷而发生ANR。这个问题也是由于之前我对android线程的了解还不够深入所引发的。AsyncTask本质是属于线程池,多次new所消耗的资源远远超过了Thread,这就是为什么AsyncTask比较适合简短、少次的异步操作。下面是官方解释: 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.) 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
pacakge such as Executor
, ThreadPoolExecutor
and FutureTask
.
并且那是每次触发getView时如果cache中没有图片的bitmap话,就会new AsyncTask来执行获取缩略图的操作,这样的后果就是那段代码变成了垃圾。因此我就从网上寻找可行的优化方案,找到了一个比较靠谱的http://cindy-lee.iteye.com/blog/1300818 ,这回监听了Scroll状态,new的子线程是少了,可是我仔细想了想,感觉他的代码还是可以进一步优化(七楼就是我提的两个改进方案)。我的终极优化方案就是应用启动时就专门创建一个子线程来根据ScrollListener的状态执行解析缩略图的操作。
终于黄天不服有心人,还真让我在MIUI的FileExplore源码和android的Contacts源码中找到了。
SyncThumbnailExtractor 是异步提取缩略图的主类,最主要的就是继承于HandlerThread的ExtractorThread,通过mExtractorHandler = new Handler(getLooper(), this) 创建唯一的线程,产成消息队列,这样它会无限循环地处理send进来的Message,执行提取缩略图的操作。好了,不废话,直接代码。
001
public
class
SyncThumbnailExtractor
implements
Callback{
003
private
static
final
String LOADER_THREAD_NAME =
"FileIconLoader"
;
005
* Type of message sent by the UI thread to itself to indicate that some
006
* thumbnails need to be extracted.
008
private
static
final
int
MESSAGE_REQUEST_EXTRACTING =
1
;
011
* Type of message sent by the loader thread to indicate that some thumbnails
012
* have been extracted.
014
private
static
final
int
MESSAGE_THUMBNAIL_EXTRACTED =
2
;
016
private
boolean
mPaused;
017
private
boolean
mDecodingRequested =
false
;
019
final
Handler mMainHandler =
new
Handler(
this
);
020
ExtractorThread mExtractorThread;
021
private
Context mContext ;
023
private
final
ConcurrentHashMap<ImageView, FileInfo> mPendingRequests =
new
ConcurrentHashMap<ImageView, FileInfo>();
024
private
final
static
ConcurrentHashMap<String, ImageHolder> mImageCache =
new
ConcurrentHashMap<String, ImageHolder>();
026
private
static
abstract
class
ImageHolder {
027
public
static
final
int
NEEDED =
0
;
029
public
static
final
int
EXTRACTING =
1
;
031
public
static
final
int
EXTRACTED =
2
;
035
public
static
ImageHolder create(String mime) {
038
if
(mime.contains(ThumbnailUtils.APK)){
039
return
new
DrawableHolder();
041
else
if
(MediaFile.isImageByMimeType(mime) ||
042
MediaFile.isVideoByMimeType(mime)){
043
return
new
BitmapHolder();
049
public
abstract
boolean
setImageView(ImageView v);
051
public
abstract
boolean
isNull();
053
public
abstract
void
setImage(Object image);
056
private
static
class
BitmapHolder
extends
ImageHolder {
057
SoftReference<Bitmap> bitmapRef;
060
public
boolean
setImageView(ImageView v) {
061
if
(bitmapRef.get() ==
null
)
063
v.setImageBitmap(bitmapRef.get());
068
public
boolean
isNull() {
069
return
bitmapRef ==
null
;
073
public
void
setImage(Object image) {
074
bitmapRef = image ==
null
?
null
:
new
SoftReference<Bitmap>((Bitmap) image);
078
private
static
class
DrawableHolder
extends
ImageHolder {
079
SoftReference<Drawable> drawableRef;
082
public
boolean
setImageView(ImageView v) {
083
if
(drawableRef.get() ==
null
)
086
v.setImageDrawable(drawableRef.get());
091
public
boolean
isNull() {
092
return
drawableRef ==
null
;
096
public
void
setImage(Object image) {
097
drawableRef = image ==
null
?
null
:
new
SoftReference<Drawable>((Drawable) image);
101
private
static
class
FileInfo{
102
public
FileInfo(String path,String mime){
111
public
SyncThumbnailExtractor(Context context) {
118
mPendingRequests.clear();
125
if
(mExtractorThread !=
null
) {
126
mExtractorThread.quit();
127
mExtractorThread =
null
;
133
public
void
resume(){
135
if
(!mPendingRequests.isEmpty()) {
136
<span style=
"background-color:#ffffff;"
>requestExtracting</span>();
145
* Load thumbnail into the supplied image view. If the thumbnail is already cached,
146
* it is displayed immediately. Otherwise a request is sent to load the
147
* thumbnail from the database.
149
* @param id, database id
151
public
boolean
decodeThumbnail(ImageView view, String path,String mime) {
152
boolean
extracted = loadCache(view, path, mime);
154
mPendingRequests.remove(view);
156
mPendingRequests.put(view,
new
FileInfo(path,mime));
159
<span style=
"background-color:#ffffff;"
>requestExtracting</span>();
166
private
void
setImageByMimeType(ImageView image,String mime){
167
if
( mime.contains(ThumbnailUtils.APK)){
168
image.setImageResource(R.drawable.apk);
170
else
if
(mime.contains(ThumbnailUtils.VIDEO)) {
171
image.setImageResource(R.drawable.video);
173
else
if
(mime.contains(ThumbnailUtils.IMAGE)) {
174
image.setImageResource(R.drawable.image);
179
* Checks if the thumbnail is present in cache. If so, sets the thumbnail on the
180
* view, otherwise sets the state of the thumbnail to
181
* {<a href="http://my.oschina.net/link1212" target="_blank" rel="nofollow">@link</a> BitmapHolder#NEEDED}
183
private
boolean
loadCache(ImageView view, String path, String mime) {
184
ImageHolder holder = mImageCache.get(path);
186
if
(holder ==
null
) {
187
holder = ImageHolder.create(mime);
190
mImageCache.put(path, holder);
191
}
else
if
(holder.state == ImageHolder.EXTRACTED) {
192
if
(holder.isNull()) {
193
setImageByMimeType(view, mime);
198
if
(holder.setImageView(view)) {
202
holder.setImage(
null
);
205
setImageByMimeType(view, mime);
206
holder.state = ImageHolder.NEEDED;
211
* Sends a message to this thread itself to start loading images. If the
212
* current view contains multiple image views, all of those image views will
213
* get a chance to request their respective thumbnails before any of those
214
* requests are executed. This allows us to load images in bulk.
216
private
void
requestExtracting() {
217
if
(!mDecodingRequested) {
218
mDecodingRequested =
true
;
219
mMainHandler.sendEmptyMessage(MESSAGE_REQUEST_EXTRACTING);
224
* @Description: handle <span style="background-color:#ffffff;">MESSAGE_REQUEST_EXTRACTING message to create </span><span style="background-color:#ffffff;">ExtractorThread and start</span> * to extract thumbnail in mPendingRequests's file
226
* <a href="http://my.oschina.net/u/556800" target="_blank" rel="nofollow">@return</a>
229
public
boolean
handleMessage(Message msg) {
231
case
MESSAGE_REQUEST_EXTRACTING:
232
mDecodingRequested =
false
;
233
if
(mExtractorThread ==
null
) {
234
mExtractorThread =
new
ExtractorThread();
235
mExtractorThread.start();
237
mExtractorThread.requestLoading();
239
case
MESSAGE_THUMBNAIL_EXTRACTED:
241
processExtractThumbnails();
250
* Goes over pending loading requests and displays extracted thumbnails. If some of
251
* the thumbnails still haven't been extracted, sends another request for image
254
private
void
processExtractThumbnails() {
255
Iterator<ImageView> iterator = mPendingRequests.keySet().iterator();
256
while
(iterator.hasNext()) {
257
ImageView view = iterator.next();
258
FileInfo info = mPendingRequests.get(view);
259
boolean
extracted = loadCache(view, info.path, info.mime);
265
if
(!mPendingRequests.isEmpty()) {
272
private
class
ExtractorThread
extends
HandlerThread
implements
Callback{
274
private
Handler mExtractorHandler;
279
public
ExtractorThread() {
280
super
(LOADER_THREAD_NAME);
284
* Sends a message to this thread to extract requested thumbnails.
286
public
void
requestLoading() {
287
if
(mExtractorHandler ==
null
) {
288
mExtractorHandler =
new
Handler(getLooper(),
this
);
290
mExtractorHandler.sendEmptyMessage(
0
);
294
* @Description: extract thumbnail
296
* <a href="http://my.oschina.net/u/556800" target="_blank" rel="nofollow">@return</a>
299
public
boolean
handleMessage(Message msg) {
300
Iterator<FileInfo> iterator = mPendingRequests.values().iterator();
301
while
(iterator.hasNext()) {
302
FileInfo info = iterator.next();
304
ImageHolder holder = mImageCache.get(info.path);
305
if
(holder !=
null
&& holder.state == ImageHolder.NEEDED) {
307
holder.state = ImageHolder.EXTRACTING;
309
if
(info.mime ==
null
){
310
holder.setImage(FileUtil.sInvalidBmp);
312
if
(info.mime.contains(ThumbnailUtils.APK)){
313
Drawable icon = ThumbnailUtils.getApkIcon(mContext, info.path);
314
holder.setImage(icon);
316
else
if
(MediaFile.isVideoByMimeType(info.mime)){
317
holder.setImage(ThumbnailUtils.getVideoThumb(info.path));
319
else
if
(MediaFile.isImageByMimeType(info.mime)){
320
holder.setImage(ThumbnailUtils.getScaleImageThumb(mContext, info.path));
324
holder.state = BitmapHolder.EXTRACTED;
325
mImageCache.put(info.path, holder);
329
mMainHandler.sendEmptyMessage(MESSAGE_THUMBNAIL_EXTRACTED);
然后在Adapter的构造函数中创建该类:
1
public
FileGridQueneAdapter(Context context, GridView gridView) {
2
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
4
syncThumbExtractor =
new
SyncThumbnailExtractor(context);
6
mGridView.setOnScrollListener(
this
);
getView中为需要缩略图的文件调用decodeThumbnail方法:
1
String mime = FileUtil.getMime(path);
2
if
(ThumbnailUtils.isNeedSyncDecodeByMime(mime)){
3
syncThumbExtractor.decodeThumbnail(icon, path, mime);
5
icon.setImageBitmap(ThumbnailUtils.getThumbnail(mContext, path));
在Adapter中添加SyncThumbnailExtractor四个操作,供Activity以及ScrollListener使用:
02
syncThumbExtractor.clear();
06
syncThumbExtractor.pause();
10
syncThumbExtractor.stop();
14
syncThumbExtractor.resume();
最后给GridView或者ListView添加ScrollListener:
02
public
void
onScrollStateChanged(AbsListView view,
int
scrollState) {
03
if
(scrollState == OnScrollListener.SCROLL_STATE_FLING){
11
public
void
onScroll(AbsListView view,
int
firstVisibleItem,
int
visibleItemCount,