为了创建具有视觉魅力的 app, 显示图像是必须的学会在你的 Android app 上高效地显示位图, 而不是放弃性能
声明原文链接:
http://www.codeceo.com/android-app-display-bitmaps.html
正文
如何在 Android App 上高效显示位图, 接下来我们先了解一下 android 图片显示的问题
在 Android 上显示图像的痛苦
当工作于开发视觉魅力的 app 时, 显示图像是必须的问题是, Android 操作系统不能很好地处理图像解码, 从而迫使开发者要小心某些任务以避免搞乱性能
Google 写了一个有关于高效显示位图的完整指南, 我们可以按照这个指南来理解和解决在显示位图时 Android 操作系统的主要缺陷
Android app 性能杀手
按照 Google 的指南, 我们可以列出一些我们在 Android apps 上显示图像时遇到的主要问题
降低图像采样率
无论视图大小, Android 总是解码并全尺寸 / 大小显示图像因为这个原因, 所以如果你试图加载一个大图像, 那就很容易使你的设备出现 outOfMemoryError
为了避免这种情况, 正如 Google 所说的那样, 我们应该使用 BitmapFactory 解码图像, 为 inSampleSize 参数设置一个值图象尺寸由 inSampleSize 划分, 减少存储器的使用量
- public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
- int reqWidth, int reqHeight) {
- // First decode with inJustDecodeBounds=true to check dimensions
- final BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeResource(res, resId, options);
- // Calculate inSampleSize
- options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
- // Decode bitmap with inSampleSize set
- options.inJustDecodeBounds = false;
- return BitmapFactory.decodeResource(res, resId, options);
- }
你可以手动设置 inSampleSize, 或使用显示器的尺寸计算
- public static int calculateInSampleSize(
- BitmapFactory.Options options, int reqWidth, int reqHeight) {
- // Raw height and width of image
- final int height = options.outHeight;
- final int width = options.outWidth;
- int inSampleSize = 1;
- if (height > reqHeight || width > reqWidth) {
- final int halfHeight = height / 2;
- final int halfWidth = width / 2;
- // Calculate the largest inSampleSize value that is a power of 2 and keeps both
- // height and width larger than the requested height and width.
- while ((halfHeight / inSampleSize) >= reqHeight
- && (halfWidth / inSampleSize) >= reqWidth) {
- inSampleSize *= 2;
- }
- }
- return inSampleSize;
- }
异步解码
即使在使用 BitmapFactory 时, 图像解码在 UI 线程上完成这可以冻结 app, 并导致 ANR(Application Not Responding 应用程序没有响应)警报
这个容易解决, 你只需要将解码过程放到工作线程上一种方法是使用异步任务, 正如 Google 指导中解释的那样:
- class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
- private final WeakReference<ImageView> imageViewReference;
- private int data = 0;
- public BitmapWorkerTask(ImageView imageView) {
- // Use a WeakReference to ensure the ImageView can be garbage collected
- imageViewReference = new WeakReference<ImageView>(imageView);
- }
- // Decode image in background.
- @Override
- protected Bitmap doInBackground(Integer... params) {
- data = params[0];
- return decodeSampledBitmapFromResource(getResources(), data, 100, 100));
- }
- // Once complete, see if ImageView is still around and set bitmap.
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- if (imageViewReference != null && bitmap != null) {
- final ImageView imageView = imageViewReference.get();
- if (imageView != null) {
- imageView.setImageBitmap(bitmap);
- }
- }
- }
- }
图像缓存
每当对图像进行解码并放置在一个视图中的时候, Android 操作系统默认重复整个渲染过程, 浪费了宝贵的设备存储器如果你打算在不同的地方展示相同的图像, 或因为 app 生命周期或行为要多次重新加载, 那么这可能会特别烦人
为了避免占用过多的内存, 推荐使用内存和磁盘缓存接下来, 我们将看到这些缓存之间的主要区别, 以及为什么同时使用两者有用的原因代码在这里显示的话太复杂了, 所以请自行参阅 Google 指南的位图缓存部分以了解如何实现内存和磁盘的缓存
内存缓存: 图像存储在设备内存中内存访问快速事实上, 比图像解码过程要快得多, 所以将图像存储在这里是让 app 更快更稳定的一个好主意内存缓存的唯一缺点是, 它只存活于 app 的生命周期, 这意味着一旦 app 被 Android 操作系统内存管理器关闭或杀死(全部或部分), 那么储存在那里的所有图像都将丢失请记住, 内存缓存必须设置一个最大可用的内存量否则可能会导致臭名昭著的 outOfMemoryError
磁盘缓存: 图像存储在设备的物理存储器上 (磁盘) 磁盘缓存可以一直存活于 app 启动期间, 安全地存储图片, 只要有足够的空间缺点是, 磁盘读取和写入操作可能会很慢, 而且总是比访问内存缓存慢由于这个原因, 因此所有的磁盘操作必须在工作线程执行, UI 线程之外否则, app 会冻结, 并导致 ANR 警报
每个缓存都有其优点和缺点, 因此最好的做法是两者皆用, 并从首先可用的地方读取, 通过内存缓存开始
最后的思考以及 EpicBitmapRenderer
不知道你有没有注意到, 正如我在本文开头所述, 在 Android app 上显示图片真的很让人头疼绝非看上去那么简单
为了避免在每个项目中重复这些任务, 我开发了一个 100%免费又开源的 Android 库, EpicBitmapRenderer 你可以在 EpicBitmapRenderer GitHub repo 选择它, 或在 EpicBitmapRenderer 网站了解更多
EpicBitmapRenderer 易于使用, 并在每个图像解码操作中自动化了所有这些恼人的任务, 这样你就可以专注于 app 开发
你只需要添加增加 EpicBitmapRenderer` 依赖在你的 Gradle 上(查看其他构建工具的替代品, 看看 EpicBitmapRenderer 文档的导入库部分)
在 EpicBitmapRenderer` 中解码图像是很容易的: 只需要调用所需的解码方法并管理结果看看下面这个例子, 我们从 URL 获取图片并显示于 ImageVIew 上
- EpicBitmapRenderer.decodeBitmapFromUrl(
- "http://isaacrf.com/wp-content/themes/Workality-Lite-child/images/IsaacRF.png",
- 200, 200,
- new OnBitmapRendered() {
- @Override
- public void onBitmapRendered(Bitmap bitmap) {
- //Display rendered Bitmap when ready
- ImageView imgView = findViewById(R.id.imgSampleDecodeUrl);
- imgView.setImageBitmap(bitmap);
- }
- },
- new OnBitmapRenderFailed() {
- @Override
- public void onBitmapRenderFailed(Exception e) {
- //Take actions if Bitmap fails to render
- Toast.makeText(MainActivity.this,
- "Failed to load Bitmap from URL",
- Toast.LENGTH_SHORT).show();
- }
- }
思考: 对图片如何进行优化
1 对图片的宽高进行压缩, 将图片加载到内存的过程中福图片的快高进行压缩, 以获取压缩版的图片 (据控件的大小 (显示到屏幕上的大小) 来计算出加
压缩版图片的 inSampleSize 值, 通过设置 options.inSampleSize 的数值, 来控制压缩图片程度)
2 对图片的质量进行压缩 Bitmap---compress()方法
补充: 如果图片数量非常多: 则会使用 LruCache 等缓存机制, 将所有图片占据的内容维持在一个范围内
如何加载高清巨图?
利用 BitmapRegionDecoder 加载超大图片
来源: http://mp.weixin.qq.com/s/vk5NJ1ZJ_FhQAdBeNswX4A