Android 入门:图片加载

要求:了解drawable加载规则以及图片缓存

Android 中 Bitmap 内存优化

在进行内存优化之前应考虑这些问题:

  • 预估加载整张图片的所需要的内存
  • 为加载这张图片愿意提供多少内存
  • 用于展示这张图片的控件的实际大小
  • 当前设备的屏幕尺寸和分辨率

主要是避免资源浪费

Bitmap 占用内存的大小 = Bitmap 宽度大小 * Bitmap 高度大小 = Bitmap 高度 * Bitmap 宽度 * (手机的 dpi / drawable 文件夹的最大 dpi)^ 2 * 每个像素的字节大小

每个像素的字节大小:

Config占用字节大小(byte)说明
ALPHA_8 (1)1单透明通道
RGB_565 (3)2简易RGB色调
ARGB_4444 (4)4已废弃
ARGB_8888 (5)424位真彩色
RGBA_F16 (6)8Android 8.0 新增(更丰富的色彩表现HDR)
HARDWARE (7)SpecialAndroid 8.0 新增 (Bitmap直接存储在graphic memory)注1

由此我们可以知道 Bitmap 内存大小的优化方式为:

  1. 资源文件合理放置,高分辨率图片可以放到高分辨率目录下。
  2. 使用低色彩的解析模式,如 RGB565,减少单个像素的字节大小。
  3. 图片缩小,减少尺寸。

Android drawable 微技巧

图片资源放在 drawable 文件夹中,image 最后的大小会根据 dpi 进行缩放,缩放规则是 (image 显示的高宽 = image 图片实际高宽 * 手机的 dpi / drawable 文件夹的最大 dpi),并且最后的值会进行四舍五入。

drawable 文件夹对应的 dpi 范围表格如下:

dpi 范围密度
0dpi ~ 120dpildpi
120dpi ~ 160dpimdpi
160dpi ~ 240dpihdpi
240dpi ~ 320dpixhdpi
320dpi ~ 480dpixxhdpi
480dpi ~ 640dpixxxhdpi

根据上面的公式,我们很容易得知,将图片放在低密度的文件夹中时,在高密度的手机显示,图片会被放大;而如果将图片放在高密度文件夹中,在低密度手机上显示,图片会被缩小。而由于图片的放大会增加像素点,并且图片会显得很模糊,但是缩小图片不会有什么副作用。

由于 UI 不可能给每种密度都设置一套图像,但是我们可以要求尽量根据高密度设备设置来设置图片资源,这样将图片放在高密度文件夹中能够节省内存开销。最后根据测试最后将图片放在 drawable-xxhdpi 文件夹是最优的选择,因为 drawable-xxxhdpi 的屏幕密度的设备并不常见,并且密度太高的图片占用的内存本身就已经比较大。综上设置图片时根据 320~480 dpi 进行设置图片,然后将图片放在 drawable-xxhdpi 文件夹中。

图片缓存

内存缓存(LruCache)

在使用内存缓存之前应该考虑如下问题:

  • 你的设备可以为每个应用程序分配多大的内存?
  • 设备的屏幕上一次最多能显示多少张图片?有多少图片需要预加载,因为有可能很快就要显示在屏幕上。
  • 设备的屏幕大小和屏幕分辨率是多少?一个超高分辨率的设备(例如 Galaxy Nexus) 比起一个较低分辨率的设备(例如 Nexus S),在持有相同数量图片的时候,需要更大的缓存空间。
  • 图片的尺寸和大小,还有每张图片会占多大的内存空间。
  • 图片的被访问频率有多高?应该让访问频率高的图片常驻在内存中,或者使用多个 LruCache 对象来区分不同组的图片。
  • 你能维持好数量和质量之间的平衡吗?有时候,存储多个低像素的图片,而后在后台去开启线程加载高像素的图片会更加有效。

用到的就是 LruCache 类,这个类是 Google 官方已经帮我写在源码中的。

具体作用就是利用 LinkedHashMap(散列表)实现的一个 LRU(最近最少使用) 缓存淘汰算法。

使用方法:

使用方法就是用 LruCache.put(K key, V value) 方法,使用「键-值对」的方式存放,然后使用 LruCache.put(K key) 获取资源就行了。

硬盘缓存(DiskLruCache)

主要是用到 DiskLruCache 类,这个类是第三方的,可以去 Google Source 下载,或者去 GitHub 或者 CSDN 上找找。下载好之后将其复制到项目中即可。

打开缓存

用到 DiskLruCache.open() 方法

public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)

第一个参数就是缓存文件存放的位置,一般都是放在 /sdcard/Android/data//cache 这个路径,这个路径被认定为应用程序的缓存路径,当程序被卸载后会被一起清除。

第二个参数是当前应用程序的版本号,当版本号改变时,缓存的数据会被清除。

第三个参数每个缓存条目的数量,一般填 1 就行。

第四个参数填 10M 就行了。

private void open(Context context) {
    try {
        File cacheDir = getDiskCacheDir(context, "bitmap");
        if (!cacheDir.exists()) {
            // mkdirs() 方法不仅会创建 bitmap 文件夹,如果当前文件夹的父文件夹不存在时,也会一并创建
            cacheDir.mkdirs();
        }
        mDiskLruCache = DiskLruCache.open(cacheDir, getAppVersion(context), 1, 10 * 1024 * 1024);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

private File getDiskCacheDir(Context context, String uniqueName) {
    String cachePath;
    // 检测是否有 SD 卡,以及 SD 卡是否能被移除
    if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
            || Environment.isExternalStorageRemovable()) {
        // /sdcard/Android/data/<application package>/cache
        cachePath = context.getExternalCacheDir().getPath();
    } else {
        // /data/data/<application package>/cache
        cachePath = context.getCacheDir().getPath();
    }
    // File.separator 是 Java 封装好的解决不同平台文件分隔符不一样的方法
    // uniqueName 是不同资源文件的名称
    return new File(cachePath + File.separator + uniqueName);
}

private int getAppVersion(Context context) {
    try {
        // 当软件的版本号发生变化时,系统会删除缓存路径下所有的数据,这时候需要重新从网络上获取资源
        PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
        return info.versionCode;
    } catch (PackageManager.NameNotFoundException e) {
        e.printStackTrace();
    }
    return 1;
}

写入缓存

用到方法 DiskLruCache.editor()

public Editor edit(String key) throws IOException

editor() 方法的参数很简单就是一个 key,这个 key 将会成为缓存文件的文件名,并且必须和图片的 URL 是一一对应的。

private void download(final String imgUrl) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                String key = hashKeyForDisk(imgUrl);
                DiskLruCache.Editor editor = mDiskLruCache.edit(key);
                if (editor != null) {
                    OutputStream outputStream = editor.newOutputStream(0);
                    if (downloadUrlToStream(imgUrl, outputStream)) {
                        editor.commit();
                    } else {
                        editor.abort();
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }).start();
}

读取缓存

读取缓存就简单很多,DiskLruCache.get() 方法

public synchronized Snapshot get(String key) throws IOException

使用 key 从缓存文件夹中得到对应的文件,得到一个 Snapshot 对象,我们调用它的 getInputStream() 方法得到缓存文件的输入流。然后将输入流保存为 bitmap。

private void get(ImageView imageView, String imgUrl) {
    try {
        String key = hashKeyForDisk(imgUrl);
        DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
        if (snapshot != null) {
            InputStream is = snapshot.getInputStream(0);
            Bitmap bitmap = BitmapFactory.decodeStream(is);
            imageView.setImageBitmap(bitmap);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

移除缓存

用的就是 DiskLruCache.remove() 方法

public synchronized boolean remove(String key) throws IOException

根据 key 删除对应的图片文件

最后

如果你看到了这里,觉得文章写得不错就给个赞呗!欢迎大家评论讨论!如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足,定期免费分享技术干货。谢谢!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
大学生参加学科竞赛有着诸多好处,不仅有助于个人综合素质的提升,还能为未来职业发展奠定良好基础。以下是一些分析: 首先,学科竞赛是提高专业知识和技能水平的有效途径。通过参与竞赛,学生不仅能够深入学习相关专业知识,还能够接触到最新的科研成果和技术发展趋势。这有助于拓展学生的学科视野,使其对专业领域有更深刻的理解。在竞赛过程中,学生通常需要解决实际问题,这锻炼了他们独立思考和解决问题的能力。 其次,学科竞赛培养了学生的团队合作精神。许多竞赛项目需要团队协作来完成,这促使学生学会有效地与他人合作、协调分工。在团队合作中,学生们能够学到如何有效沟通、共同制定目标和分工合作,这对于日后进入职场具有重要意义。 此外,学科竞赛是提高学生综合能力的一种途径。竞赛项目通常会涉及到理论知识、实际操作和创新思维等多个方面,要求参赛者具备全面的素质。在竞赛过程中,学生不仅需要展现自己的专业知识,还需要具备创新意识和解决问题的能力。这种全面的综合能力培养对于未来从事各类职业都具有积极作用。 此外,学科竞赛可以为学生提供展示自我、树立信心的机会。通过比赛的舞台,学生有机会展现自己在专业领域的优势,得到他人的认可和赞誉。这对于培养学生的自信心和自我价值感非常重要,有助于他们更加积极主动地投入学习和未来的职业生涯。 最后,学科竞赛对于个人职业发展具有积极的助推作用。在竞赛中脱颖而出的学生通常能够引起企业、研究机构等用人单位的关注。获得竞赛奖项不仅可以作为个人履历的亮点,还可以为进入理想的工作岗位提供有力的支持。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值