Android网络图片加载优化

本文介绍了如何优化Android应用中的网络图片加载,通过在WiFi环境下预加载下一屏图片到本地,减少用户等待时间。文章详细阐述了利用Android-Universal-Image-Loader进行改造,实现只下载不解析,以及在MyAdapter中的优化策略,显著提升了图片翻页时的加载速度,降低了内存占用。

网络图片加载优化

比如使用淘宝浏览产品的时候(大部分应用也是如此),就会发现每次下拉产品目录进行更新的时候,都会出现对应的Item的时候,才开始从网络下载并加载图片。
taobao加载
这里写图片描述

可以看到宝贝图片下拉刷新的时候,图片加载是实时从网络下载的。即使在Wifi的网络环境下,加载图片也是有比较大的延迟。

假设我们浏览每屏宝贝需要2s的时间(人眼对于淘宝搜索的宝贝其实过滤速度非常快)。如果每一屏页面需要1s才可以完全加载完图片,则如果浏览10屏的宝贝,就会需要30s。如果加载图片几乎不需要时间,则只需要20s。这个节约比例是很大的。如果不是使用wifi而是移动网络,则加载时间可能会更长。这种情况不单单是淘宝,很多带有图片的app都会出现这种图片需要很久加载的情况。

而解决这种问题的思路其实很简单也很直观,那就是在wifi或者用户打开设置的情况下,提前加载一屏的图片,保存在本地硬盘(不是内存)。如果浏览到时则从本地加载。

加载的优化基于上一篇文章:
使用Android-Universal-Image-Loader加载网络图片
http://blog.youkuaiyun.com/chen52671/article/details/44680765

使用Android-Universal-Image-Loader是为了简化这个例子,如果你有更好地网络图片下载和内存缓存,硬盘缓存的工具,道理是一样的。

实现思路:
1,判断网络环境是否为WiFi。
2,如果是则开启预缓存,当在Page1时,下载Page2的图片到本地;下拉到paga2时,图片直接从本地读取到内存并显示,同时下载Page3到本地。以此类推

让Android-Universal-Image-Loader只下载

ImageLoader主要使用displayImage来加载某URI的图片到ImageView。还有个方法是loadImage()——用来异步加载图片:其也是通过调用displayImage来操作的,不过传递进去的imageAware(可以当ImageView理解)是一个空实现,当Bitmap加载完毕后,返回一个回调。
但是都没法满足我们的要求:图片流只下载至本地硬盘缓存,但是不解析成Bitmap。更不要提将解析好的Bitmap加载到内存缓存中。
借用一张ImageLoader的displayImage流程图:

display-image-flow-chart
图片来自Android Universal Image Loader 源码分析
这里写图片描述

为了实现只下载,不加载。需要对Android-Universal-Image-Loader做一下变动(对不住了~~)
1,ImageLoader类添加一个downloadImage方法,和displayImage类似,同样要遵循已有的配置,也要判断是不是在本地硬盘缓存已经有该图片。
2,如果DisplayImageOptions根本就没配置cacheOnDisk,那就对不住了,不下载。

修改如下:

增加一个DownloadTask

package com.nostra13.universalimageloader.core;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;


import android.os.Handler;
import android.util.Log;

import com.nostra13.universalimageloader.core.decode.ImageDecoder;
import com.nostra13.universalimageloader.core.download.ImageDownloader;

import com.nostra13.universalimageloader.core.listener.DownloadListener;

import com.nostra13.universalimageloader.utils.IoUtils;

public class DownloadTask implements Runnable, IoUtils.CopyListener {
   
   
    private String Tag = "DownloadTask";
    private final ImageLoaderEngine engine;
    private final ImageDownloadInfo imageDownloadInfo;
    private final Handler handler;
    // Helper references
    private final ImageLoaderConfiguration configuration;
    private final ImageDownloader downloader;
    private final ImageDownloader networkDeniedDownloader;
    private final ImageDownloader slowNetworkDownloader;
    private final ImageDecoder decoder;
    final DownloadListener listener;
    final String uri;

    final DisplayImageOptions options;

    private final boolean syncLoading;

    public DownloadTask(ImageLoaderEngine engine,
            ImageDownloadInfo imageDownloadInfo, Handler handler) {
        this.engine = engine;
        this.imageDownloadInfo = imageDownloadInfo;
        this.handler = handler;

        configuration = engine.configuration;
        downloader = configuration.downloader;
        networkDeniedDownloader = configuration.networkDeniedDownloader;
        slowNetworkDownloader = configuration.slowNetworkDownloader;
        decoder = configuration.decoder;
        uri = imageDownloadInfo.uri;
        listener = imageDownloadInfo.listener;
        options = imageDownloadInfo.options;
        syncLoading = options.isSyncLoading();
    }

    @Override
    public void run() {
        if (waitIfPaused())
            return;

        ReentrantLock downloadLock = engine.getLockForUri(uri);

        if (downloadLock.isLocked()) {
            Log.d(Tag, "locked");
        }

        downloadLock.lock();
        try {
            File imageFile = configuration.diskCache.get(uri);
            if (imageFile != null && imageFile.exists()
                    && imageFile.length() > 0) {
                return;// 文件存在,不用忙活了
            }
            if (options.isCacheOnDisk()) {
                if(tryCacheImageOnDisk()){
                    listener.onDownloadComplete(uri);
                }
            }
            checkTaskInterrupted();
        } catch (TaskCancelledException e) {
            fireCancelEvent();
            return;
        } finally {
            downloadLock.unlock();
        }

    }

    private boolean waitIfPaused() {
        AtomicBoolean pause = engine.getPause();
        if (pause.get()) {
            synchronized (engine.getPauseLock()) {
                if (pause.get()) {
                    try {
                        engine.getPauseLock().wait();
                    } catch (InterruptedException e) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    String getLoadingUri() {
        return uri;
    }

    private void checkTaskInterrupted() throws TaskCancelledException {
        if (isTaskInterrupted()) {
            throw new TaskCancelledException();
        }
    }

    /**
     * @return <b>true</b> - if current task was interrupted; <b>false</b> -
     *         otherwise
     */
    private boolean isTaskInterrupted() {
        if (Thread.interrupted()) {
            Log.d(Tag, "Thread interrupted");
            return true;
        }
        return false;
    }

    static void runTask(Runnable r, boolean sync, Handler handler,
            ImageLoaderEngine engine) {
        if (sync) {
            r.run();
        } else if (handler == null) {
            engine.fireCallback(r);
        } else {
            handler.post(r);
        }
    }

    private void fireCancelEvent() {
        if (syncLoading || isTaskInterrupted())
            return;
        Runnable r = new Runnable() {
            @Override
            public void run() {
                listener.onDownloadCancelled(uri);
            }
        };
        runTask(r, false, handler, engine);
    }

    private boolean tryCacheImageOnDisk() throws TaskCancelledException {

        boolean loaded;
        try {
            loaded = downloadImage();
            // 暂时不resize
        } catch (IOException e) {
            loaded = false;
        }
        return loaded;
    }

    private boolean downloadImage() throws IOException {
        InputStream is = getDownloader().getStream(uri,
                options.getExtraForDownloader());
        if (is == null) {

            return false;
        } else {
            try {
                return configuration.diskCache.save(uri, is, this);
            } finally {
                IoUtils.closeSilently(is);
            }
        }
    }

    private ImageDownloader getDownloader() {
        ImageDownloader d;
        if (engine.isNetworkDenied()) {
            d = networkDeniedDownloader;
        } else if (engine.isSlowNetwork()) {
            d = slowNetworkDownloader;
        } else {
            d = downloader;
        }
        return d;
    }

    class TaskCancelledException extends Exception {
    }

    @Override
    public boolean onBytesCopied(int current, int total) {
        // TODO Auto-generated method stub
        return true;
    }
}

增加一个ImageDownloadInfo

package com.nostra13.universalimageloader.core;

import java.util.concurrent.locks.ReentrantLock;

import com.nostra13.universalimageloader.core.listener.DownloadListener;

public class ImageDownloadInfo {
   
   

    final String uri;
    final DownloadListener listener;
    final DisplayImageOptions options;

    final ReentrantLock downloadLock;

    public ImageDownloadInfo(String uri ,DisplayImageOptions options,DownloadListener listener,
            ReentrantLock downloadLock) {
        this.uri = uri;
        this.listener=listener;
        this.options = options;
        this.downloadLock = downloadLock;

    }
}

增加一个DownloadListener监听器

package com.nostra13.universalimageloader.core.listener;

public interface  DownloadListener{
   
   
    void OnDownloadStart(String imageUri);

    void onDownloadFailed(String imageUri);

    void onDownloadComplete(String imageUri);

    void onDownloadCancelled(String imageUri);
}

ImageLoader修改

/*
     * 
     * @param uri Image URI (i.e. "http://site.com/image.png",
     * "file:///mnt/sdcard/image.png")
     * 
     * @param listener 图片下载的监听器
     */
    public void downloadImage(String uri, DisplayImageOptions options,
            DownloadListener listener) {
        checkConfiguration();
        if (listener == null) {
            listener = new DownloadListener() {
                @Override
                public void onDownloadFailed(String imageUri) {
                }

                @Override
                public void onDownloadComplete(String imageUri) {
                }

                @Override
                public void onDownloadCancelled(String imageUri) {
                }

                @Override
                public void OnDownloadStart(String imageUri) {

                }
            };
        }
        if (options == null) {
            options = configuration.defaultDisplayImageOptions;
        }
        if(!options.isCacheOnDisk()) return;
        listener.OnDownloadStart(uri);
        if (TextUtils.isEmpty(uri)) {
            listener.onDownloadFailed(uri);
            return;
        }

        ImageDownloadInfo imageDownloadInfo = new ImageDownloadInfo(uri,options,listener,engine.getLockForUri(uri));
        DownloadTask downloadTask = new DownloadTask(
                engine, imageDownloadInfo,defineHandler(options));
        if (options.isSyncLoading()) {
            downloadTask.run();
        } else {
            engine.submit(downloadTask);
        }

    }

ImageLoaderEngine中添加了一个submit方法

    //for downLoad tast
    void submit(final DownloadTask task) {
        taskDistributor.execute(new Runnable() {
            @Override
            public void run() {
                File image = configuration.diskCache.get(task.getLoadingUri());
                boolean isImageCachedOnDisk = image != null && image.exists();
                initExecutorsIfNeed();
                if (isImageCachedOnDisk) {
                    //do nothing.casue we have the image files already!
                } else {
                    taskExecutor.execute(task);
                }
            }
        });
    }

由于实现的比较仓促,还有很多细节问题需要实现:
1,没有实现比如文件保存时没有resize图片大小。
2,还有如果刚刚添加了图片下载任务,这时候下拉刷新,就会图片就会增加一个加载并显示的任务,这时如果下载任务没完成,应该是取消下载任务(cancelDownloadTask),进行图片显示任务。更完善一点的是等待图片下载任务完成,再进行加载任务。这些细节就没有考虑了,所以进行时可能会有些问题。

优化

这个工程是基于上一篇文章的基础上修改优化的。http://blog.youkuaiyun.com/chen52671/article/details/44680765
原效果图如下:

ImageLoader加载

优化前

这里写图片描述
可以看出一个Page大概加载15张图片。如果是每个Item的getView时,触发item position+15的那个Item的图片的下载任务即可。

该效果图是对taobao加载宝贝图片的一个还原,可以看到起滑动刷新的时候,图片也是需要联网下载并加载,同样存在一定的延迟效果。

优化后:

优化后的ImageLoader加载
这里写图片描述

效果图看出,图片翻页时的加载速度明显提升,并且由于是直接缓存在本地硬盘,不会造成明显的内存占用,从一定程度上解决了翻页时的网络图片加载缓慢问题

优化修改主要在MyAdapter类。使用的ImageLoader是前面修改的版本。

MyAdapter

package com.example.gridpic;

import java.util.List;

import com.nostra13.universalimageloader.cache.disc.naming.Md5FileNameGenerator;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
import com.nostra13.universalimageloader.core.assist.QueueProcessingType;
import com.nostra13.universalimageloader.core.listener.DownloadListener;

import android.content.Context;
import android.graphics.Bitmap;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import android.widget.BaseAdapter;
import android.widget.ImageView;

public class MyAdapter extends BaseAdapter {
   
   
    List<String> imageList;
    protected ImageLoader imageLoader = ImageLoader.getInstance();
    protected DisplayImageOptions options;
    private LayoutInflater mInflater;
    public Context context;

    public MyAdapter(Context context, List<String> imageList) {
        super();
        this.imageList = imageList;
        this.context = context;
        options = new DisplayImageOptions.Builder()
                .showImageOnLoading(R.drawable.ic_launcher)
                .showImageForEmptyUri(R.drawable.ic_launcher)
                .showImageOnFail(R.drawable.ic_launcher).cacheInMemory(true)
                .cacheOnDisk(true).bitmapConfig(Bitmap.Config.RGB_565).build();

        this.mInflater = LayoutInflater.from(context);
        // 初始化ImageLoader
        ImageLoaderConfiguration.Builder config = new ImageLoaderConfiguration.Builder(
                context);
        config.threadPriority(Thread.NORM_PRIORITY - 2);
        config.denyCacheImageMultipleSizesInMemory();
        config.diskCacheFileNameGenerator(new Md5FileNameGenerator());
        config.diskCacheSize(50 * 1024 * 1024); // 50 MiB
        config.tasksProcessingOrder(QueueProcessingType.LIFO);
        config.writeDebugLogs(); // Remove for release app
        ImageLoader.getInstance().init(config.build());
    }

    @Override
    public int getCount() {
        return imageList.size();
    }

    @Override
    public Object getItem(int arg0) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public long getItemId(int arg0) {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public View getView(int position, View view, ViewGroup viewGroup) {
        ViewHolder holder = null;
        if (view == null) {
            view = mInflater.inflate(R.layout.picitem, null);
            holder = new ViewHolder();
       
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值