如何在 Android App 上高效显示位图
vuuo4281
8年前
<p>本文由自动化采集完成,质量不符合直接发布的条件,请审核员严格按照文章处理标准流程来审核。</p> <p><a href="/misc/goto?guid=4959748512835942275" rel="nofollow,noindex">http://www.linuxprobe.com/submit</a></p> <p>为了创建具有视觉魅力的app,显示图像是必须的。学会在你的Android app上高效地显示位图,而不是放弃性能。</p> <p>本章目录结构</p> <ul> <li>在Android上显示图像的痛苦</li> <li>最后的思考以及EpicBitmapRenderer</li> </ul> <h2>在Android上显示图像的痛苦</h2> <p>当工作于开发视觉魅力的app时,显示图像是必须的。问题是,Android操作系统不能很好地处理图像解码,从而迫使开发者要小心某些任务以避免搞乱性能。</p> <p>Google写了一个有关于高效显示位图的完整指南,我们可以按照这个指南来理解和解决在显示位图时Android操作系统的主要缺陷。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/1b9dae6452c38763760fa1c302ab45d0.png"></p> <h2>Android app性能杀手</h2> <p>按照Google的指南,我们可以列出一些我们在Android apps上显示图像时遇到的主要问题。</p> <h3>降低图像采样率</h3> <p>无论视图大小,Android总是解码并全尺寸/大小显示图像。因为这个原因,所以如果你试图加载一个大图像,那就很容易使你的设备出现outOfMemoryError。</p> <p>为了避免这种情况,正如Google所说的那样,我们应该使用</p> <p>BitmapFactory</p> <p>解码图像,为</p> <p>inSampleSize</p> <p>参数设置一个值。图象尺寸由inSampleSize划分,减少存储器的使用量。</p> <pre> <code class="language-java">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); }</code></pre> <p>你可以手动设置inSampleSize,或使用显示器的尺寸计算。</p> <pre> <code class="language-java">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; }</code></pre> <h3>异步解码</h3> <p>即使在使用BitmapFactory时,图像解码在UI线程上完成。这可以冻结app,并导致ANR(“Application Not Responding应用程序没有响应”)警报。</p> <p>这个容易解决,你只需要将解码过程放到工作线程上。一种方法是 使用异步任务 ,正如Google指导中解释的那样:</p> <pre> <code class="language-java">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); } } } }</code></pre> <h3>图像缓存</h3> <p>每当对图像进行解码并放置在一个视图中的时候,Android操作系统默认重复整个渲染过程,浪费了宝贵的设备存储器。如果你打算在不同的地方展示相同的图像,或因为app生命周期或行为要多次重新加载,那么这可能会特别烦人。</p> <p>为了避免占用过多的内存,推荐使用内存和磁盘缓存。接下来,我们将看到这些缓存之间的主要区别,以及为什么同时使用两者有用的原因。</p> <ul> <li>内存缓存:图像存储在设备内存中。内存访问快速。事实上,比图像解码过程要快得多,所以将图像存储在这里是让app更快更稳定的一个好主意。内存缓存的唯一缺点是,它只存活于app的生命周期,这意味着一旦app被Android操作系统内存管理器关闭或杀死(全部或部分),那么储存在那里的所有图像都将丢失。请记住,内存缓存必须设置一个最大可用的内存量。否则可能会导致臭名昭著的outOfMemoryError。</li> <li>磁盘缓存:图像存储在设备的物理存储器上(磁盘)。磁盘缓存可以一直存活于app启动期间,安全地存储图片,只要有足够的空间。缺点是,磁盘读取和写入操作可能会很慢,而且总是比访问内存缓存慢。由于这个原因,因此所有的磁盘操作必须在工作线程执行,UI线程之外。否则,app会冻结,并导致ANR警报。</li> </ul> <p>每个缓存都有其优点和缺点,因此最好的做法是两者皆用,并从首先可用的地方读取,通过内存缓存开始。</p> <h2>最后的思考以及EpicBitmapRenderer</h2> <p>不知道你有没有注意到,正如我在本文开头所述,在Android app上显示图片真的很让人头疼。绝非看上去那么简单。</p> <p>为了避免在每个项目中重复这些任务,我开发了一个100%免费又开源的Android库,</p> <p>EpicBitmapRenderer</p> <p>。你可以在 EpicBitmapRenderer GitHub repo 选择它,或在EpicBitmapRenderer网站了解更多。</p> <p>EpicBitmapRenderer</p> <p>易于使用,并在每个图像解码操作中自动化了所有这些恼人的任务,这样你就可以专注于app开发。</p> <p>你只需要添加增加</p> <p>EpicBitmapRenderer</p> <p>依赖在你的Gradle上。</p> <pre> <code class="language-java">compile 'com.isaacrf.epicbitmaprenderer:epicbitmaprenderer:1.0'</code></pre> <p>在</p> <p>EpicBitmapRenderer</p> <p>中解码图像是很容易的:只需要调用所需的解码方法并管理结果。看看下面这个例子,我们从URL获取图片并显示于ImageVIew上。</p> <pre> <code class="language-java">//Sample 3: Decode Bitmap from URL (Async) 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(); } });</code></pre> <h2>许可证</h2> <p>这篇文章以及任何相关的源代码和文件,遵循 The Creative Commons Attribution-Share Alike 3.0 Unported License的授权许可。</p> <p> </p> <p>来自:http://www.linuxprobe.com/如何在-android-app-上高效显示位图.html</p> <p> </p>