Android照片墙应用

code小生,一个专注 Android 领域的技术分享平台

作者:某昆
地址:https://www.jianshu.com/p/b592d3f2e005
声明:本文已获 某昆 授权,转发等请联系原作者授权

本文主要内容

  • 照片墙应用介绍

  • 缓存使用思路

  • 多线程使用

  • 杂谈

记得本人工作之后,手头上第一个活就是做一个本地壁纸展示程序,当时正流行1080P的屏幕,为了展示屏幕效果,壁纸图片巨大无比,轻松突破10M,而且内置壁纸还有15张左右,当时面临的两个问题,一个就是oom问题,另一个就是流畅度问题,虽然后来都解决了,但之后就对这种巨大的图片有点虚。。

惭愧的是,5年以后的今天才写了这篇文章,不忘初心方得始终,这个道理虽然简单但今天才真正明白

照片墙应用介绍

应用中需要显示大量的图片,图片从网络获取,效果如下图:

640

应用有两个问题:

  • 流畅地显示

  • 省流量

流畅性是所有应用都必须要有的,图片要从网上下载,下载过程必须放到工作线程当中来解决。另外图片也有可以有一部分保存在内存当中,直接从内存当中获取并显示,这才是最快的方式。

图片有很多,用户理论上只用下载一遍图片就行了,不能重复去下载,缓存可以帮助用户节省流量。

缓存使用思路

Android 缓存原理一文中说明了缓存的原理以及使用方式,如有不明白可参考此文。

其实缓存思路特别简单,在需要使用图片的时候,先查内存缓存,如果没有再查硬盘缓存,还没有则去网络下载,下载完毕后加入硬盘缓存以及内存缓存,以备下次再用。缓存是不是已满,该如何删除交给缓存工具类决定,但大体思路一定是这样。

本文中也是这么做的,而且硬盘缓存读取相当于IO过程,较缓慢,也可以放到工作线程当中,于是就可以把查内存缓存以外的所有操作都放到工作线程当中完成。

照片墙的核心代码如下:

private void loadBitmaps(ImageView imageView, String url) {
    try {
        Bitmap bitmap = getBitmapFromMemoryCache(url);
        if (bitmap == null) {
            Task task = new Task(imageView, url);
            mPools.submit(task);
        } else {
            if (imageView != null && bitmap != null) {
                imageView.setImageBitmap(bitmap);
            }
        }
    } catch (Exception e) {
        // TODO: handle exception
    }
}

如果从内存缓存中无法获取到图片,则向线程池中提交一个任务,在线程池中完成图片的获取。

class Task implements Runnable {
    ImageView iv;
    String imageUrl;

    Task(ImageView view, String url) {
        iv = view;
        imageUrl = url;
    }

    @Override
    public void run() {
        FileDescriptor fileDescriptor = null;
        FileInputStream fileInputStream = null;
        Snapshot snapshot = null;
        try {
            final String key = hashKeyForDisk(imageUrl);
            snapshot = mDiskLruCache.get(key);
            if (snapshot == null) {
                Editor editor = mDiskLruCache.edit(key);
                if (editor != null) {
                    OutputStream outputStream = editor.newOutputStream(0);
                    if (downloadUrlToStream(imageUrl, outputStream)) {
                        editor.commit();
                    } else {
                        editor.abort();
                    }
                }
                snapshot = mDiskLruCache.get(key);
            }
            if (snapshot != null) {
                fileInputStream = (FileInputStream) snapshot
                        .getInputStream(0);
                fileDescriptor = fileInputStream.getFD();
            }
            Bitmap bitmap = null;
            if (fileDescriptor != null) {
                bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);
            }
            if (bitmap != null) {
                addBitmapToMemoryCache(key, bitmap);
                Result result = new Result(iv, bitmap, imageUrl);
                Message msg = Message.obtain(mHandler, MSG_SHOW_BITMAP, result);
                msg.sendToTarget();
            }
        } catch (Exception e) {
            Log.e("okunu""run", e);
        }
    }
}

和前文所说思路一样,先从硬盘缓存中读取,如果没有再从网络中下载图片。一定不能忘记将图片添加进入硬盘缓存和内存缓存中来,这一步非常重要。

由于硬盘缓存的使用方法,在从网上下载图片的时候,是直接下载到缓存文件当中的,而不是先下载再复制一份到硬盘缓存当中,因为这样可以节省一次IO过程。

另外还有一些小细节,比如最后如何刷新界面,首先在工作线程中是无法刷新UI的,所以在此处用handler,将结果返回主线程中处理。

if (msg.what == MSG_SHOW_BITMAP) {
            Object obj = msg.obj;
            if (obj != null) {
                Result result = (Result) obj;
                if (result.iv.getTag().equals(result.url)) {
                    result.iv.setImageBitmap(result.bitmap);
                }
            }
        }

结果中包含图片、imageview以及url,为了防止图片显示错乱,还加以判定,只有imageview的tag等于此url的时候,才更新图片,如此则不会更新错乱。

多线程使用

在这种有大量耗时操作的时候,开启工作线程是非常必要的,但如何优雅地使用线程,其实仍然有门道。

android中有很多种开启工作线程的方式。

  • AsyncTask,封装地非常好,不同的回调函数还处于不同的线程当中,方便用户拿结果更新UI,但如果有多个AsyncTask实例在执行,它们是顺序执行,并不是想象中的多线程在多核CPU上同时执行。

  • Thread,原始的线程使用方式,如果构造太多,不优雅,线程不能得到复用,浪费资源,开启一个线程也是有开销的

  • HandlerThread,能够与handler结合,一种非常优雅的工作线程开启方式,不过不太适合大量任务的情况,这种只相当于线程池中只有一个线程在跑
    线程池,重量级武器,适合大量任务的情况

  • android中开启工作线程包括但不限于以上4种,它们的优劣大致如上所述,需要我们根据不同的情形选用不同的方式,写出优雅的代码。

在照片墙应用中,明显是有大量任务需要工作线程来执行的,以上四种情况中,最适合的就是线程池了,它的速度效率是最高的,有兴趣的同学可以去做做实验,以四种不同方式来完成任务,看看哪个效率最高

杂谈

在文章一开始的时候,就聊到一个话题,OOM的问题,如果图片太大,如果防止OOM呢?

这个问题相信很多人都知道答案:

/**
     * If set to true, the decoder will return null (no bitmap), but
     * the <code>out...</code> fields will still be set, allowing the caller to
     * query the bitmap without having to allocate the memory for its pixels.
     */

    public boolean inJustDecodeBounds;

    /**
     * If set to a value > 1, requests the decoder to subsample the original
     * image, returning a smaller image to save memory. The sample size is
     * the number of pixels in either dimension that correspond to a single
     * pixel in the decoded bitmap. For example, inSampleSize == 4 returns
     * an image that is 1/4 the width/height of the original, and 1/16 the
     * number of pixels. Any value <= 1 is treated the same as 1. Note: the
     * decoder uses a final value based on powers of 2, any other value will
     * be rounded down to the nearest power of 2.
     */

    public int inSampleSize;

利用inJustDecodeBounds,计算出inSampleSize,相信这样的逻辑网上一找一大堆,本人在此不再复述。如果你的图片应用,图片都是大于5M以上的高清大图,那么一定要考虑这个方法了。

另外由url转化成hash key的时候,怎么这么费劲呢?

public String hashKeyForDisk(String key) {
    String cacheKey;
    try {
        final MessageDigest mDigest = MessageDigest.getInstance("MD5");
        mDigest.update(key.getBytes());
        cacheKey = bytesToHexString(mDigest.digest());
        Log.i("okunu""cacheKey = " + cacheKey);
    } catch (NoSuchAlgorithmException e) {
        cacheKey = String.valueOf(key.hashCode());
    }
    return cacheKey;
}

private String bytesToHexString(byte[] bytes) {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < bytes.length; i++) {
        String hex = Integer.toHexString(0xFF & bytes[i]);
        if (hex.length() == 1) {
            sb.append('0');
        }
        sb.append(hex);
    }
    return sb.toString();
}

第一步,我们通常能理解,获取url的md5值,因为url中可能含有各种奇异字符,不适合作为key来使用,但bytesToHexString方法的作用是什么?

大家可以仔细地看看硬盘缓存中文件的名字的长度,就是为了防止名字长度不一致,当长度短一位的时候,补0。

代码已经上传到github当中,有需要的可以取用
https://github.com/okunu

照片处理

Android 选择图片上传功能【支持多选拍照预览等】

640


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值