2

我正在构建一个相对基本的新闻阅读器应用程序,该应用程序涉及在自定义列表视图中显示新闻(图像 + 标题 + 每个列表元素的简短描述)。

我的问题是如何存储从服务器下载的图像,然后将它们附加到列表视图?图像相对较小,通常为 200 X 200,采用 .jpeg 格式。

与其说是“如何有效地做到这一点”,不如说是“如何有效地做到这一点”的问题,因为我已经注意到低端手机在使用默认的“ic_launcher”图标而不是位图时会出现滞后。

当应用程序启动并同步新闻或缓存它们时,将它们作为文件存储或与其他新闻数据一起存储到新闻数据库中会更快吗?

我该怎么办?

4

2 回答 2

2

更好的是,您可以通过 ImageManager 类使用 SoftReference。

在您的 ListAdpater getView() 方法中调用 ImageManager 的 displayImage() 方法。

ImageManager 编码示例:

public class ImageManagerExemple {

private static final String LOG_TAG = "ImageManager";

private static ImageManagerExemple instance = null;

public static ImageManagerExemple getInstance(Context context) {
    if (instance == null) {
        instance = new ImageManagerExemple(context);
    } 
    return instance;        
}   

private HashMap<String, SoftReference<Bitmap>> imageMap = new HashMap<String, SoftReference<Bitmap>>();

private Context context;
private File cacheDir;

private ImageManagerExemple(Context context) {
    this.context = context;
    // Find the dir to save cached images
    String sdState = android.os.Environment.getExternalStorageState();
    if (sdState.equals(android.os.Environment.MEDIA_MOUNTED)) {
        File sdDir = android.os.Environment.getExternalStorageDirectory();      
        cacheDir = new File(sdDir,"data/yourappname");
    } else {
        cacheDir = context.getCacheDir();
    }
    if(!cacheDir.exists()) {
        cacheDir.mkdirs();
    }
}


/**
 * Display web Image loading thread
 * @param imageUrl picture web url
 * @param imageView target
 * @param imageWaitRef picture during loading
 */
public void displayImage(String imageUrl, ImageView imageView, Integer imageWaitRef) {
    String imageKey = imageUrl;     
    imageView.setTag(imageKey);
    if(imageMap.containsKey(imageKey) && imageMap.get(imageKey).get() != null) {
        Bitmap bmp = imageMap.get(imageKey).get();
        imageView.setImageBitmap(bmp);
    } else {
        queueImage(imageUrl, imageView);
        if(imageWaitRef != null)
            imageView.setImageResource(imageWaitRef);
    }
}

private void queueImage(String url, ImageView imageView) {
    ImageRef imgRef=new ImageRef(url, imageView);
    // Start thread
    Thread imageLoaderThread = new Thread(new ImageQueueManager(imgRef));
    // Make background thread low priority, to avoid affecting UI performance
    imageLoaderThread.setPriority(Thread.NORM_PRIORITY-1);
    imageLoaderThread.start();
}

private Bitmap getBitmap(String url) {
    String filename = String.valueOf(url.hashCode());
    File f = new File(cacheDir, filename);
    try {
        // Is the bitmap in our cache?
        Bitmap bitmap = BitmapFactory.decodeFile(f.getPath());
        if(bitmap != null) return bitmap;
        // Nope, have to download it
        bitmap = ImageServerUtils.pictureUrlToBitmap(url);
        // save bitmap to cache for later
        writeFile(bitmap, f);
        return bitmap;
    } catch (Exception ex) {
        ex.printStackTrace();
        Log.e(LOG_TAG, ""+ex.getLocalizedMessage());
        return null;
    }  catch (OutOfMemoryError e) {
        Log.e(LOG_TAG, "OutOfMemoryError : "+e.getLocalizedMessage());
        e.printStackTrace();
        return null;
    }
}

private void writeFile(Bitmap bmp, File f) {
    if (bmp != null && f != null) {
        FileOutputStream out = null;

        try {
            out = new FileOutputStream(f);
            //bmp.compress(Bitmap.CompressFormat.PNG, 80, out);
            bmp.compress(Bitmap.CompressFormat.JPEG, 80, out);
        } catch (Exception e) {
            e.printStackTrace();
        }
        finally { 
            try { if (out != null ) out.close(); }
            catch(Exception ex) {} 
        }
    }
}


private class ImageRef {
    public String imageUrl;
    public ImageView imageView;

    public ImageRef(String imageUrl, ImageView i) {
        this.imageUrl=imageUrl;
        this.imageView=i;
    }
}

private class ImageQueueManager implements Runnable {
    private ImageRef imageRef;
    public ImageQueueManager(ImageRef imageRef) {
        super();
        this.imageRef = imageRef;
    }
    @Override
    public void run() {
        ImageRef imageToLoad = this.imageRef;
        if (imageToLoad != null) {
            Bitmap bmp = getBitmap(imageToLoad.imageUrl);
            String imageKey = imageToLoad.imageUrl;
            imageMap.put(imageKey, new SoftReference<Bitmap>(bmp));
            Object tag = imageToLoad.imageView.getTag();

            // Make sure we have the right view - thread safety defender
            if (tag != null && ((String)tag).equals(imageKey)) {
                BitmapDisplayer bmpDisplayer = new BitmapDisplayer(bmp, imageToLoad.imageView);                         
                Activity a = (Activity)imageToLoad.imageView.getContext();                          
                a.runOnUiThread(bmpDisplayer);
            } 
        } 
    }
}

//Used to display bitmap in the UI thread
private class BitmapDisplayer implements Runnable {
    Bitmap bitmap;
    ImageView imageView;

    public BitmapDisplayer(Bitmap b, ImageView i) {
        bitmap=b;
        imageView=i;
    }
    @Override
    public void run() {
        if(bitmap != null) {
            imageView.setImageBitmap(bitmap);
        } 
    }
}
于 2012-07-02T14:50:23.893 回答
1

让 ListView 平滑滚动而不会出现卡顿的技巧是在用户滚动时不要以任何方式、形状或形式对其进行更新。Afaik,这本质上是 iOS 设法使其 ListViews 流畅的方式:它不允许用户对其进行任何更改(以及一般的 UI)。

只需注释掉任何更改您的 ListView 的代码,同时保持所有位图加载代码不变,您会发现在后台实际加载位图并不会真正影响性能。问题是 UI 线程无法同时跟上视图更新和滚动。

OnScrollListener您可以通过在用户滚动时阻止对 ListView 的所有更新来实现相同的目的。一旦用户停止,您就可以潜入所有待处理的更新。为了提高性能,尽量不要使用notifyDataSetChanged但迭代 ListView 的视图,只更新实际更改的视图。

于 2012-07-02T14:04:21.420 回答