Android项目框架之图片加载框架的选择

本文来自刘兆贤的博客_CSDN博客-Java高级,Android旅行,Android基础领域博主 ,引用必须注明出处!

从Android爆发以后,自定义的控件如EditTextWithDelete、ActionBar、PullToRresh逐步进入开发者的视野,想起11年的时候,基本项目中使用的UI还全都是Android提供的基础控件,一点的动画+布局,下载和网络请求都是用HttpClient,图片加载当然也是下载后再使用,当时的程序资源可没有现在这么丰富,看看Github上开源的项目,现在的程序员应该感到幸福。

项目开发从程序上来讲,万古不变两大事情,一是网络通信框架,二是图片加载框架,有关网络框架上一篇已经介绍了async-http和okhttp,而其他如volly同时拥有网络请求和图片加载两个框架,很多人图省事就一次性使用了,当然facebook自己的开源框架也是写的非常不错,接下来再一一介绍;先贴一张11年我们自己写的imageloader

补充一下,上面框架还支持给图片设置像素点占位大小;看到这么多功能,对于现在的项目基本满足要求,缺点是:网络请求直接使用未封装的HttpUrlConnection,以及图片选项设置麻烦一些(如可以设置磁盘和内存最大宽高),不支持webp和gif。再看看其他几种图片加载框架的异同

Fresco,facebook出品,最大的优势在于可展示加载过程,即加载进度、加载前图片、加载中图片、加载后图片、加载失败图片等,还可以设置图片的形状;并且提供加载Gif、Webp图片的方法。

Picasso,默认Config为8888,如果加载图片不成功,需要改565,原因内存不足。

Glide是升级版本的piccaso:1、支持跟fragment和activity生命周期绑定;2、同时可以直接剪切图片;3、网络请求直接使用okhttp;4、相对imageloader,API相对简单。默认加载RGB565格式图片,缺点是没有直接从fileCaceh取文件的接口、不支持webp、防止图片错乱只能设置setTag(int,String)。

volly,基于老的imageloader又做了次封装,差别不是太大,功能弱化一些,同glide支持图片动画,但它同时支持gif和webp。不适用于直播和较大图片下载,因为它解析后的缓存一般直接放在内存中。

https://developer.android.com/training/volley/index.html

而后面这几种框架,都是在imageloader兴起之后出现的,都有加载过程,在我看来也仅有fresco和glide是真正写出了跟原框架不同的东西。

public class ImageDownloader {

    private static ImageDownloader instance = null;
    private static File cacheDir;
    public static Map<String, SoftReference<Bitmap>> bitMapCache = new HashMap<String, SoftReference<Bitmap>>();

    public static ImageDownloader getInstance() {
        if (instance == null) {
            instance = new ImageDownloader();
        }
        return instance;
    }

    private ImageDownloader() {
        // Find the dir to save cached images
        if (android.os.Environment.getExternalStorageState().equals(
                android.os.Environment.MEDIA_MOUNTED))
            cacheDir = new File(
                    android.os.Environment.getExternalStorageDirectory(),
                    "WholeMag");
        else
            cacheDir = WholeMagApplication.getInstance().getCacheDir();
        if (!cacheDir.exists())
            cacheDir.mkdirs();
    }

    public void download(String actName, String url, ImageView imageView) {
        BitmapDownloaderTask task = new BitmapDownloaderTask(imageView, actName);
        task.execute(url);
        // return task.doInBackground(url);
    }

    class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> {
        // private String url;
        // private boolean flag;
        private final WeakReference<ImageView> imageViewReference; // 使用WeakReference解决内存问题
        private String actName;

        public BitmapDownloaderTask(ImageView imageView, String actName) {
            imageViewReference = new WeakReference<ImageView>(imageView);
            this.actName = actName;
        }

        @Override
        protected Bitmap doInBackground(String... params) { // 实际的下载线程,内部其实是concurrent线程,所以不会阻塞
            Bitmap rebmp = getLocalBitmap(params[0], actName);
            if (rebmp == null)
                rebmp = downloadBitmap(params[0], actName);
            if (rebmp == null) {
                doInBackground(params[0]);
            }
            return rebmp;
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) { // 下载完后执行的
            if (isCancelled()) {
                bitmap = null;
            }
            if (imageViewReference != null) {
                ImageView imageView = imageViewReference.get();
                if (imageView != null) {
                    imageView.setDrawingCacheEnabled(true);
                    Bitmap temp = imageView.getDrawingCache();
                    imageView.setDrawingCacheEnabled(false);
                    if (temp != null) {
                        temp.recycle();
                    }
                    double widthX = (float) WholeMagDatas.getDeviceWidth()
                            / bitmap.getWidth(); // 图片宽度拉伸比例
                    int bitmapHight = bitmap.getHeight();// 图片高度
                    imageView.setImageBitmap(bitmap); // 下载完设置imageview为刚才下载的bitmap对象
                    if (actName.equals(AppData.NEWS_DETAIL_ACT)) {
                        FrameLayout.LayoutParams ll = new FrameLayout.LayoutParams(
                                android.view.ViewGroup.LayoutParams.FILL_PARENT,
                                (int) (bitmapHight * widthX), Gravity.CENTER);
                        imageView.setLayoutParams(ll);
                    }
                    AlphaAnimation alphaAnimation = new AlphaAnimation(0, 1);// 创建一个AlphaAnimation对象
                    alphaAnimation.setDuration(500);// 设置动画执行的时间(单位:毫秒)
                    imageView.startAnimation(alphaAnimation);
                }
            }
        }
    }

    static Bitmap getLocalBitmap(String url, String actName) {
        if (bitMapCache.containsKey(url)) {
            return bitMapCache.get(url).get();
        }
        File f = Tools.getFile(actName, url);
        InputStream inputStream = null;
        try {
            // decode image size
            BitmapFactory.Options o = new BitmapFactory.Options();
            o.inPreferredConfig = Bitmap.Config.RGB_565;
            o.inDither = false;
            o.inPurgeable = true;
            // o.inTempStorage = new byte[12 * 1024];
            inputStream = new FileInputStream(f);
            // Bitmap bitmap = BitmapFactory.decodeFile(f.getAbsolutePath());
            Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, o);
            bitMapCache.put(url, new SoftReference<Bitmap>(bitmap));
            return bitmap;
        } catch (Exception e) {
        } finally {
            if (null != inputStream) {
                try {
                    inputStream.close();
                } catch (Exception ex) {
                }
            }
        }
        return null;
    }

    static Bitmap downloadBitmap(String url, String actName) {
        final AndroidHttpClient client = AndroidHttpClient.newInstance("linux");
        final HttpGet getRequest = new HttpGet(url);

        try {
            HttpResponse response = client.execute(getRequest);
            final int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode != HttpStatus.SC_OK) {
                return null;
            }

            final HttpEntity entity = response.getEntity();
            if (entity != null) {
                File f = Tools.getFile(actName, url);
                OutputStream os = new FileOutputStream(f);
                InputStream inputStream = null;
                try {
                    inputStream = entity.getContent();
                    BitmapFactory.Options o = new BitmapFactory.Options();
                    o.inPreferredConfig = Bitmap.Config.RGB_565;
                    o.inDither = false;
                    o.inPurgeable = true;
                    final Bitmap bitmap = BitmapFactory.decodeStream(
                            inputStream, null, o);
                    bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os);
                    bitMapCache.put(url, new SoftReference<Bitmap>(bitmap));
                    return bitmap;
                } finally {
                    if (inputStream != null) {
                        inputStream.close();
                    }
                    if (null != os) {
                        os.close();
                    }
                    entity.consumeContent();
                }
            }
        } catch (Exception e) {
            getRequest.abort();
        } finally {
            if (client != null) {
                client.close();
            }
        }
        return null;
    }
}


当年为了防止图片爆掉出现OOM,使用了软引用,觉得还不错;图片加载的基本原理就是,把url+imageview抛过来,然后开启异步线程加载,根据获取的byte流decode成bitmap,最后在UI线程将图片加载到imageview上;也没有说做本地缓存,仅做了应用缓存;没有对图片进行压缩或者设置格式,使占用内存更小,展示也更合理;LruCache基本原理跟上面使用软引用的过程差不多,只不过多限制了图片占用内存的大小,计算图片使用的频率,对应用层、SD卡层均做了封装。
上面介绍完,相信你也对图片加载有个大概的轮廓,我们拿开源的imageloader为例,来讲讲图片加载框架的一些细节


public final class ImageLoaderConfiguration {

    final Resources resources;//主要给图片设计宽高时,获得屏幕宽高使用
    final int maxImageWidthForMemoryCache;//内存中最大的图片宽度
    final int maxImageHeightForMemoryCache;//内存中最大的图片高度

    final int maxImageWidthForDiskCache;//SD卡中最大的图片宽度
    final int maxImageHeightForDiskCache;//SD卡中最大的图片高度
    final BitmapProcessor processorForDiskCache;//从SD卡获得Bitmap的加载器

    final Executor taskExecutor;//加载图片时的执行器
    final Executor taskExecutorForCachedImages;//加载缓存时的执行器
    final boolean customExecutor;//是否使用默认执行器
    final boolean customExecutorForCachedImages;//是否使用默认缓存执行器

    final int threadPoolSize;//线程数,可以用来控制展示当前界面的item图片
    final int threadPriority;//线程的执行优先级
    final QueueProcessingType tasksProcessingType;//是LILO还是LIFO,默认是前者,但一般喜欢后者

    final MemoryCache memoryCache;//内存缓存对象,如不写可用默认
    final DiskCache diskCache;//SD卡缓存对象,如不写可用默认
    final ImageDownloader downloader;//图片加载器,根据网络(http/s)、file、content、drawable、asset来加载
    final ImageDecoder decoder;//图片解析器,根据获取的图片参数拿到Bitmap
    final DisplayImageOptions defaultDisplayImageOptions;//设置图片加载状态和结果,见下面源码

//不用网络下载图片的下载器,可理解为加载SD卡图片的加载器
    final ImageDownloader networkDeniedDownloader;
    final ImageDownloader slowNetworkDownloader;//仅网络下载图片的下载器,支持断点续传

拿这些变量来讲,基本就可以说明事情 


public final class DisplayImageOptions {

    private final int imageResOnLoading;//图片是否加载中
    private final int imageResForEmptyUri;//图片是否来自于空url
    private final int imageResOnFail;//图片是否加载失败
    private final Drawable imageOnLoading;//加载中的图片
    private final Drawable imageForEmptyUri;//空数据的图片
    private final Drawable imageOnFail;//加载失败的图片
    private final boolean resetViewBeforeLoading;//加载完是否重置(意味着放弃之前的加载)
    private final boolean cacheInMemory;//是否缓存在内存中
    private final boolean cacheOnDisk;//是否缓存在SD卡中
    private final ImageScaleType imageScaleType;//要多大的图片,统一设置
    private final Options decodingOptions;//Bitmap的options对象
    private final int delayBeforeLoading;//是否延迟加载,可用于非当前页面图片
    private final boolean considerExifParams;//是否支持jpeg图片的rotate和flip等方法
    private final Object extraForDownloader;//额外数据
    private final BitmapProcessor preProcessor;//加载不在内存中的图片
    private final BitmapProcessor postProcessor;//加载在图片中的图片
    private final BitmapDisplayer displayer;//展示图片
    private final Handler handler;//这个就不用讲了吧,跟主线程交互必不可少的工具
    private final boolean isSyncLoading;//是否同步加载 

判断是否是主线程:

  public static boolean isOnMainThread() {
        return Looper.myLooper() == Looper.getMainLooper();
    }

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刘兆贤

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值