Nokia n1适合的谷歌语音助手手,有推荐的吗

中国领先的IT技术网站
51CTO旗下网站
如何在Android App上高效显示位图
为了创建具有视觉魅力的app,显示图像是必须的。学会在你的Android app上高效地显示位图,而不是放弃性能。
作者:小峰来源:码农网| 09:36
为了创建具有视觉魅力的app,显示图像是必须的。学会在你的Android app上高效地显示位图,而不是放弃性能。
在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) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
你可以手动设置inSampleSize,或使用显示器的尺寸计算。
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
final int height = options.outH
final int width = options.outW
int inSampleSize = 1;
if (height & reqHeight || width & reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
while ((halfHeight / inSampleSize) &= reqHeight
&& (halfWidth / inSampleSize) &= reqWidth) {
inSampleSize *= 2;
return inSampleS
即使在使用BitmapFactory时,图像解码在UI线程上完成。这可以冻结app,并导致ANR(&Application Not Responding应用程序没有响应&)警报。
这个容易解决,你只需要将解码过程放到工作线程上。一种方法是,正如Google指导中解释的那样:
class BitmapWorkerTask extends AsyncTask&Integer, Void, Bitmap& {
private final WeakReference&ImageView& imageViewR
private int data = 0;
public BitmapWorkerTask(ImageView imageView) {
imageViewReference = new WeakReference&ImageView&(imageView);
protected Bitmap doInBackground(Integer... params) {
data = params[0];
return decodeSampledBitmapFromResource(getResources(), data, 100, 100));
protected void onPostExecute(Bitmap bitmap) {
if (imageViewReference != null && bitmap != null) {
final ImageView imageView = imageViewReference.get();
if (imageView != null) {
imageView.setImageBitmap(bitmap);
每当对图像进行解码并放置在一个视图中的时候,Android操作系统默认重复整个渲染过程,浪费了宝贵的设备存储器。如果你打算在不同的地方展示相同的图像,或因为app生命周期或行为要多次重新加载,那么这可能会特别烦人。
为了避免占用过多的内存,推荐使用内存和磁盘缓存。接下来,我们将看到这些缓存之间的主要区别,以及为什么同时使用两者有用的原因。代码在这里显示的话太复杂了,所以请自行参阅以了解如何实现内存和磁盘的缓存。
内存缓存:图像存储在设备内存中。内存访问快速。事实上,比图像解码过程要快得多,所以将图像存储在这里是让app更快更稳定的一个好主意。内存缓存的唯一缺点是,它只存活于app的生命周期,这意味着一旦app被Android操作系统内存管理器关闭或杀死(全部或部分),那么储存在那里的所有图像都将丢失。请记住,内存缓存必须设置一个最大可用的内存量。否则可能会导致臭名昭著的outOfMemoryError。
磁盘缓存:图像存储在设备的物理存储器上(磁盘)。磁盘缓存可以一直存活于app启动期间,安全地存储图片,只要有足够的空间。缺点是,磁盘读取和写入操作可能会很慢,而且总是比访问内存缓存慢。由于这个原因,因此所有的磁盘操作必须在工作线程执行,UI线程之外。否则,app会冻结,并导致ANR警报。
每个缓存都有其优点和缺点,因此最好的做法是两者皆用,并从首先可用的地方读取,通过内存缓存开始。
最后的思考以及EpicBitmapRenderer
不知道你有没有注意到,正如我在本文开头所述,在Android app上显示图片真的很让人头疼。绝非看上去那么简单。
为了避免在每个项目中重复这些任务,我开发了一个100%免费又开源的Android库,EpicBitmapRenderer&。你可以在选择它,或在EpicBitmapRenderer网站了解更多。
EpicBitmapRenderer&易于使用,并在每个图像解码操作中自动化了所有这些恼人的任务,这样你就可以专注于app开发。
你只需要添加增加EpicBitmapRenderer&依赖在你的Gradle上(查看其他构建工具的替代品,)。
compile 'com.isaacrf.epicbitmaprenderer:epicbitmaprenderer:1.0'
在EpicBitmapRenderer&中解码图像是很容易的:只需要调用所需的解码方法并管理结果。看看下面这个例子,我们从URL获取图片并显示于ImageVIew上。
EpicBitmapRenderer.decodeBitmapFromUrl(
&/wp-content/themes/Workality-Lite-child/images/IsaacRF.png&,
new OnBitmapRendered() {
public void onBitmapRendered(Bitmap bitmap) {
ImageView imgView = findViewById(R.id.imgSampleDecodeUrl);
imgView.setImageBitmap(bitmap);
new OnBitmapRenderFailed() {
public void onBitmapRenderFailed(Exception e) {
Toast.makeText(MainActivity.this,
&Failed to load Bitmap from URL&,
Toast.LENGTH_SHORT).show();
这篇文章以及任何相关的源代码和文件,遵循 The Creative Commons Attribution-Share Alike 3.0 Unported License的授权许可。
译文链接:
英文原文:【责任编辑: TEL:(010)】
大家都在看猜你喜欢
头条外电头条原创原创
24H热文一周话题本月最赞
讲师:8人学习过
讲师:4人学习过
讲师:4人学习过
精选博文论坛热帖下载排行
《网管员世界》是国内唯一一家专门面向网管员职业的刊物。本书是2006年《网管员世界》各期内容的汇集,内容权威、全面、时效性强,贴近应用...
订阅51CTO邮刊7390人阅读
其它(16)
在我们平常开发的过程中在做引导页适配的时候,有时候会犯难,怎么样作图可以将各种不同尺寸分辨率的手机都适配好也就是不变形不拉伸,官方给的说法也只是做多套图去适配不同的分辨率,遇到全屏展示引导这种问题的时候就有些力不从心了。接下来我们就展示一下如何使用一张图来适配市面上的绝大部分手机:
这个办法是反编译微信得出的想法,微信的包里面只有一张的图,我们观察了微信在不同尺寸手机上的展示效果,它肯定是没有变形的,根据这个思路,我们发现它在适配不同的手机对图片做了缩放裁剪等处理。
接下来我们就实现一下如何对图片进行适当的缩放及裁剪:
大伙也都看到了,我们选择了两台具有代表性的设备:一台分辨率是800*480,一台是.这两台设备的高宽比是不一样的。
为了适配这两台代表性的设备,首先我们需要对图片进行等比缩放:
我们需要先行计算将要放大的图片的高度:
将要放大的图片的高度=原图的宽度*屏幕的高度/屏幕的宽度;
上面这个计算公式不用我说为什么吧,小学就学过的。至于原图放大会模糊的问题,一般屏幕的宽度最大也就是1080,只是魅族有些奇葩会是1152,如果担心这个问题的话,请美工给图的时候,直接按照1152的宽来就好了。
其次,我们为了良好的展示在节目上,面对于不同尺寸屏幕的手机,它们的高宽比是不一样的,所以我们的基本思想就是以宽度为基准,放大图片,然后以中心为准裁剪多余的部分(对于图片高度不足裁剪的手机,可能才疏学浅,还没见过)。
package com.sahadev.
import android.app.A
import android.graphics.B
import android.graphics.BitmapF
import android.graphics.P
import android.graphics.drawable.BitmapD
import android.os.B
import android.view.V
import android.view.ViewTreeObserver.OnGlobalLayoutL
import android.view.W
public class MainActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
scaleImage(this, findViewById(R.id.rootView), R.drawable.guide);
public static void scaleImage(final Activity activity, final View view, int drawableResId) {
// 获取屏幕的高宽
Point outSize = new Point();
activity.getWindow().getWindowManager().getDefaultDisplay().getSize(outSize);
// 解析将要被处理的图片
Bitmap resourceBitmap = BitmapFactory.decodeResource(activity.getResources(), drawableResId);
if (resourceBitmap == null) {
// 开始对图片进行拉伸或者缩放
// 使用图片的缩放比例计算将要放大的图片的高度
int bitmapScaledHeight = Math.round(resourceBitmap.getHeight() * outSize.x * 1.0f / resourceBitmap.getWidth());
// 以屏幕的宽度为基准,如果图片的宽度比屏幕宽,则等比缩小,如果窄,则放大
final Bitmap scaledBitmap = Bitmap.createScaledBitmap(resourceBitmap, outSize.x, bitmapScaledHeight, false);
view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
            @Override
            public boolean onPreDraw() {
                //这里防止图像的重复创建,避免申请不必要的内存空间
                if (scaledBitmap.isRecycled())
                    //必须返回true
                   
                // 当UI绘制完毕,我们对图片进行处理
                int viewHeight = view.getMeasuredHeight();
                // 计算将要裁剪的图片的顶部以及底部的偏移量
                int offset = (scaledBitmap.getHeight() - viewHeight) / 2;
                // 对图片以中心进行裁剪,裁剪出的图片就是非常适合做引导页的图片了
                Bitmap finallyBitmap = Bitmap.createBitmap(scaledBitmap, 0, offset, scaledBitmap.getWidth(),
                        scaledBitmap.getHeight() - offset * 2);
                if (!finallyBitmap.equals(scaledBitmap)) {//如果返回的不是原图,则对原图进行回收
                    scaledBitmap.recycle();
                    System.gc();
                }
                // 设置图片显示
                view.setBackgroundDrawable(new BitmapDrawable(context.getResources(), finallyBitmap));
               
            }
        });
最后看实际运行效果:
&1.&2.&3.&
2.SAMSUNG G0
3.HTC D316d 540*960
4.NUBIA X6
5.HUAWEI Y330-C00 480*800
怎么样,效果还不错吧,不过肉眼几乎看不出来它们是否变形,如果有疑问,可以使用PS对他们进行等比缩放,然后叠加测试效果。比例相同的分辨率显示效果是一致的,如果比例不同会有一部分偏差,所以在美工设计图片的时候,请不要在最顶部以及最底部设置特殊标志,以免被裁剪。
最后注意scaleImage()方法请不要被外部多次调用,否则该方法内会生成多个Bitmap对象,容易造成内存溢出。
PS:scaleImage()方法内部针对于绘制测量的多次调用做了处理,避免了重复绘制与重复测量造成的多次Bitmap对象创建,可以放心使用。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:393969次
积分:5204
积分:5204
排名:第4637名
原创:59篇
译文:115篇
评论:155条
我建了一个QQ群,欢迎对学习有兴趣的同学加入我们可以一起探讨、深究、掌握那些我们会用到的技术,让自己不至于太落伍。
《Android官方开发文档Training系列课程中文版》源文件的项目地址:
阅读:10114
文章:117篇
阅读:262727
(1)(1)(2)(8)(6)(13)(15)(5)(18)(18)(16)(33)(2)(1)(4)(7)(11)(14)(2)(2)(1)(2)(1)Android官方开发文档Training系列课程中文版:高效显示位图之位图缓存
原文地址:
往UI界面中加载单张图片的过程是很简单的,然而如果需要在某个时刻同时加载大量的图片,那么这事情就有些复杂了。在很多情况下,比如使用了ListView、GridView或者是ViewPager来展示一定数量的图片,在本质上这些情况下,屏幕的快速滑动会导致大量的图片被集中展示在屏幕上。
类似这样通过回收移除到屏幕之外的子View的会抑制内存的使用(也就是说它们本身不会滥用内存)。垃圾回收器还会释放你所加载的位图,假设你没有使用任何持久化引用的话。这真是极好的,但是为了保持流畅的UI效果,你可能需要在它们每次重新返回到屏幕的时候,对它们按照常规的方式重新处理。内存缓存及磁盘缓存可以在这里提供帮助,可以使这些组件快速的重新加载已经处理过的图片。
这节课将会讨论在加载多张图片的时候,如何通过使用内存缓存以及磁盘缓存来使UI界面更加流畅,响应速度更快。
使用内存缓存
内存缓存提供了一种快速访问位图的能力,不过这会花费宝贵的内存空间。类极其适合用来处理缓存图片的任务,它会将最近使用到的位图的引用存放在一个对象上,并会在超过内存设计大小之前将最后一个没有用到的成员给驱除。
Note: 在过去,使用或者是来缓存图片是最受欢迎的一种缓存方式,然而却并不推荐这么用。在 2.3之后,垃圾回收器对soft/weak引用的回收更加强制,这会使得这些引用几乎无效。此外,在Android 3.0之前,位图的字节数据被存储在本地内存中,可以预见这些数据是不会被释放的,这会导致程序很容易超过自身的内存限制,然后崩溃。
为了给选择合适的尺寸,有几个因素应该被考虑在内:
Activity或者程序在常规状态下的内存使用量是多少?
在同一时间最多会有多少图片集中显示在屏幕上?有多少内存需要为准备显示到屏幕上的图片所用?
设备屏幕的大小和尺寸分别是多少?在加载相同图片数量的情况下,像这种超高的密度(xhdpi)的设备与(hdpi)相比则需要更大的内存。
图片的尺寸多大?配置是什么?加载这个位图的时候需要花费的内存是多少?
图片的访问有多频繁?会比其它位图访问更频繁吗?如果是这样,可能你需要将它们永远保持在内存中了,或者甚至是有多个对象来为图片分组。
你可以在数量与质量之间取得平衡吗?某些时候存储大量的低质图片是很有用处的,可能会潜在的存在一些后台任务来加载一些高质量的版本。
这里特别没有指定尺寸或者配置,不过这适用所有的应用程序,这取决于对内存使用情况的分析,并需要找到一个适合的解决方案。缓存设置的太小会导致无意义的额外开销,缓存设置的太大会再次引起java.lang.OutOfMemory异常,应该将大小设置为应用的常规内存使用量之外的剩余内存之间。
下面是使用缓存位图的一个例子:
private LruCache mMemoryC
protected void onCreate(Bundle savedInstanceState) {
// Get max available VM memory, exceeding this amount will throw an
// OutOfMemory exception. Stored in kilobytes as LruCache takes an
// int in its constructor.
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// Use 1/8th of the available memory for this memory cache.
final int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache(cacheSize) {
protected int sizeOf(String key, Bitmap bitmap) {
// The cache size will be measured in kilobytes rather than
// number of items.
return bitmap.getByteCount() / 1024;
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
public Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
Note: 在这个例子中,有八分之一的内存被分配给了缓存。在正常的设备上(hdpi)这大概是4MB(32/8)左右。一个铺满了图片的GridView在全屏状态下的800*480的设备上所占的内存大概是1.5MB(800*480*4个字节),所以这可以在内存中存储大概2.5页的图像。
当加载一个位图到ImageView上的时候,首先要检查。如果发现了与之相匹配的,则会被用来立即更新到ImageView上,否则就会触发一个后台线程来处理图片:
public void loadBitmap(int resId, ImageView imageView) {
final String imageKey = String.valueOf(resId);
final Bitmap bitmap = getBitmapFromMemCache(imageKey);
if (bitmap != null) {
mImageView.setImageBitmap(bitmap);
mImageView.setImageResource(R.drawable.image_placeholder);
BitmapWorkerTask task = new BitmapWorkerTask(mImageView);
task.execute(resId);
BitmapWorkerTask中也需要对内存缓存进行添加或更新:
class BitmapWorkerTask extends AsyncTask {
// Decode image in background.
protected Bitmap doInBackground(Integer... params) {
final Bitmap bitmap = decodeSampledBitmapFromResource(
getResources(), params[0], 100, 100));
addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);
使用磁盘缓存
内存缓存对于最近浏览过的图像的快速加载非常有用,然而却不能将所有的图像都存放在内存缓存中。像GridView这样的组件在加载大数据集的时候可以轻易的将内存缓存填满。程序在运行的过程中可能会被其它任务打断,比如一个来电,这时,在后台的任务可能就会被杀死,内存缓存也会被销毁。一旦用户返回了界面,那么程序就需要再次重新处理每张图片。
那么磁盘缓存在这些情况下就很有帮助了,它可以存储处理过的图片,并会辅助提升图片的加载时间,在图片不再在内存缓存中存在的时候。当然,在磁盘上获取一张图片要比内存中要慢,并且还需要开启单独的工作线程,这和从磁盘上读取数据的时间一样,都不可预估。
Note:可能更适合用来存放被缓存过的图像,如果这些图像的访问更加频繁的话,就像在相册应用中的情况一样。
从Android Source中更新的示例代码使用了一个DiskLruCache的实现。下面是个更新后的版本,它对已有的内存缓存增加了磁盘缓存:
private DiskLruCache mDiskLruC
private final Object mDiskCacheLock = new Object();
private boolean mDiskCacheStarting =
private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
private static final String DISK_CACHE_SUBDIR = "thumbnails";
protected void onCreate(Bundle savedInstanceState) {
// Initialize memory cache
// Initialize disk cache on background thread
File cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR);
new InitDiskCacheTask().execute(cacheDir);
class InitDiskCacheTask extends AsyncTask {
protected Void doInBackground(File... params) {
synchronized (mDiskCacheLock) {
File cacheDir = params[0];
mDiskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE);
mDiskCacheStarting = // Finished initialization
mDiskCacheLock.notifyAll(); // Wake any waiting threads
class BitmapWorkerTask extends AsyncTask {
// Decode image in background.
protected Bitmap doInBackground(Integer... params) {
final String imageKey = String.valueOf(params[0]);
// Check disk cache in background thread
Bitmap bitmap = getBitmapFromDiskCache(imageKey);
if (bitmap == null) { // Not found in disk cache
// Process as normal
final Bitmap bitmap = decodeSampledBitmapFromResource(
getResources(), params[0], 100, 100));
// Add final bitmap to caches
addBitmapToCache(imageKey, bitmap);
public void addBitmapToCache(String key, Bitmap bitmap) {
// Add to memory cache as before
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
// Also add to disk cache
synchronized (mDiskCacheLock) {
if (mDiskLruCache != null && mDiskLruCache.get(key) == null) {
mDiskLruCache.put(key, bitmap);
public Bitmap getBitmapFromDiskCache(String key) {
synchronized (mDiskCacheLock) {
// Wait while disk cache is started from background thread
while (mDiskCacheStarting) {
mDiskCacheLock.wait();
} catch (InterruptedException e) {}
if (mDiskLruCache != null) {
return mDiskLruCache.get(key);
// Creates a unique subdirectory of the designated app cache directory. Tries to use external
// but if not mounted, falls back on internal storage.
public static File getDiskCacheDir(Context context, String uniqueName) {
// Check if media is mounted or storage is built-in, if so, try and use external cache dir
// otherwise use internal cache dir
final String cachePath =
Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||
!isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :
context.getCacheDir().getPath();
return new File(cachePath + File.separator + uniqueName);
Note:因为磁盘缓存的初始化需要磁盘操作,所以这个过程不应该放在UI线程中执行。然而,这也意味着在缓存初始化之前这是个访问的机会。为了做到这一点,需要有个lock对象来保证在缓存被初始化之前APP没有从磁盘缓存中读取数据。
内存缓存在UI线程中执行检查,磁盘缓存在后台线程中执行检查。磁盘操作绝不应该放入UI线程。当图像处理完毕后,最终被处理过的图片应当被添加到内存缓存及磁盘缓存中以便备用。
处理配置变更
如果在运行时发生了变更,比如屏幕的方向发生了改变,会引起Android销毁并重启运行中的Activity,你可能想要避免再一次处理图像,这样一旦配置发生了改变,可以使用户有一个流畅快速的用户体验。
幸运的是,你有一个非常赞的内存缓存方案:可以使用设置了setRetainInstance(true)的Fragment,它可以将缓存传入新的Activity实例。在activity重新创建的时候,这个被保留存在的Fragment会被重新附加在Activity上,你可以获得原先内存缓存的访问能力,这使得图像可以快速的被获得并被重新填充在ImageView对象中。
下面这个例子使用了引用LruCache的Fragment,并通过了配置更改的问题:
private LruCache mMemoryC
protected void onCreate(Bundle savedInstanceState) {
RetainFragment retainFragment =
RetainFragment.findOrCreateRetainFragment(getFragmentManager());
mMemoryCache = retainFragment.mRetainedC
if (mMemoryCache == null) {
mMemoryCache = new LruCache(cacheSize) {
... // Initialize cache here as usual
retainFragment.mRetainedCache = mMemoryC
class RetainFragment extends Fragment {
private static final String TAG = "RetainFragment";
public LruCache mRetainedC
public RetainFragment() {}
public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {
RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG);
if (fragment == null) {
fragment = new RetainFragment();
fm.beginTransaction().add(fragment, TAG).commit();
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
为了测试这项输出,试着在有和没有Fragment的情况下旋转设备。你应该会注意到这个过程几乎没有延迟。任何图像如果没有在内存缓存中找到,那么这就为磁盘缓存提供了用武之地,如果都没有的话,那么常规的处理方法就会出场。}

我要回帖

更多关于 适合蓝牙用的语音助手 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信