做项目时,引导页面的ViewPager报了OOM异常,图片并不大,在清单文件Application节点中添加了两行代码就解决了这个问题
android:hardwareAccelerated="false"
android:largeHeap="true"
从Android3.0(API Level 11)开始,Android 2D渲染管道能够更好的支持硬件加速。硬件加速执行的所有的绘图操作都是使用GPU在View对象的画布上来进行的。因为启用硬件加速会增加资源的需求,因此这样的应用会占用更多的内存。
启用硬件加速的最容易的方法是给整个应用程序都打开全局硬件加速功能。
如果应用程序只使用标准的View和Drawable,那么打开全局硬件加速不会导致任何的不良的绘制效果。但是,因为硬件加速并不支持所有的2D图形绘制操作,所以对于那些使用定制的View和绘制调用的应用程序来说,打开全局硬件加速,可以会影响绘制效果。问题通常会出现在对那些不可见的元素进行了异常或错误的像素渲染。为了避免这种问题,android提供以下级别,以便可选择性的启用或禁止硬件加速:
1. Application
2. Activity
3. Window
4. View
如果应用程序执行了定制化的绘图,就要在实际的带有硬件加速的硬件设备上测试,以便发现问题。
控制硬件加速
能够用以下级别来控制硬件加速 Application级别
在应用的Android清单文件中,把下列属性添加到<application>元素中,来开启整个应用程序的硬件加速。
<application android:hardwareAccelerated="true" ...>
Activity级别
如果应用程序不能够正确的使用被打开的全局硬件加速,那么也可以对Activity分别进行控制。在<activity>元素中使用android:hardwareAccelerated属性,能够启用或禁止Activity级别的硬件加速。以下示例启用全局的硬件加速,但却禁止了一个Activity的硬件加速: <application android:hardwareAccelerated="true"> <activity ... />
<activity android:hardwareAccelerated="false" /> </application>
Window级别
如果需要更细粒度的控制,就可以使用下列代码来针对给定的窗口来启用硬件加速:
getWindow().setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
注意:当前不能在Window级别禁止硬件加速。
View级别
能够使用下列代码在运行时针对一个独立的View对象来禁止硬件加速:
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
注意:当前不能在View级别开启硬件加速。View层除了禁止硬件加速以外,还有其他的功能,更多的相关信息请看本文的“View层”。 判断一个View对象是否被硬件加速
有些时候,尤其是对于那些定制的View对象,应用程序知道当前的View对象是否被硬件加速是十分有益的。如果应用程序做了很多定制的绘图操作,并且不是所有的操作都会被新
的渲染管道所支持,那么这种判断就特别有用。
有两种不同的方法来检查应用程序是否被硬件加速了:
1. 如果一个View对象跟一个被硬件加速的窗口绑定,那么View.isHardwareAccelerated() 方法就会返回true;
2. 如果一个Canvas对象被硬件加速了,那么Canvas.isHardwareAccelerated()方法就会返回 true。
如果必须要在绘图代码中做这种检查,那么在可能的情况下,要使用Canvas.isHardwareAccelerated()方法来代替View.isHardwareAccelerated()方法。当一个View对象跟一个被硬件加速的窗口绑定的时候,它依然能够使用使用一个非硬件加速的Canvas对象。例如,把一个View对象绘制到缓存中的一个位图时就会发生这种情况。
HardAccelerated的作用:
当硬件加速被启用时,Android框架会采用一个新的绘图模式,这种模式利用显示列表把应用程序呈现在屏幕上,这样的速度更快。要充分理解显示列表,以及它们是如何影响应用程序的,对于理解Android是如何绘制没有硬件加速的View对象是有益的。下面分别介绍基于软件的和硬件加速的绘图模式。 基于软件的绘图模式
在软件的绘图模式中,View对象是通过以下两个步骤来绘制的: 1. 让View层次结构失效; 2. 绘制View层次结构。
无论何时,应用程序需要更新它的UI部分时,都回调用发生内容改变的View对象的invalidate()方法。无效的消息请求会在View对象层次结构中传递,以便计算出需要重绘的屏幕区域(脏区)。然后,Android系统会在View层次结构中绘制所有的跟脏区相交的区域。不幸的是,这种方法有两个缺点:
1. 这种模式在每个绘图传递中需要很多的代码执行。例如,如果应用程序调用了一个按钮
的invalidate()方法,并且该按钮位于另一个View对象的上方,那么即使该View对象没有变化,那么Android系统也要重新绘制它。
2. 第二个问题是这个种绘图模式能够隐藏应用程序中的bug。因为当View对象跟脏区相
交时,Android系统就会重新绘制它,所以即使没有调用View对象上的invalidate()方法,那么View对象内容的改变也可能会导致它被重绘。当发生这种情况时,就要依赖另一个被失效的View对象来获取正确的行为。这种行为能够改变每次你对应用程序的修改。因为这个原因,在修改影响View对象的绘图代码的数据和状态是,应该始终调用该定制View对象的invalidate()方法。
注意:在View对象的属性发生变化时,如背景色或TextView对象中的文本等,Android系统会自动的调用该View对象的invalidate()方法。 硬件加速绘图模式
这种模式下,Android系统依然会使用invalidate()方法和draw()方法来请求屏幕更新和展现View对象,但是实际的绘图处理是不同的,它会立即执行绘图命令,Android系统把这些命令记录在内部的显示列表中,列表中包含了View对象层次结构的绘图代码的输出。另一种优化是:Android系统只需要针对由invalidate()方法调用所标记的View对象的脏区进行记录和更新显示列表。没有失效的View对象能够通过重新发布先前被记录的显示列表来进行简单的重绘工作。这种绘图模式包含三个阶段: 1. 让View的层次结构失效; 2. 记录和更新显示列表; 3. 绘制显示列表。
使用这种模式,不能够依赖相交的脏区View的draw()方法来执行绘图工作。要确保Android系统记录一个View对象的显示列表,就必须调用invalidate()方法,如果忘记调用该方法,那么在变化发生后,View对象看上去会跟变化之前相同,这是一个比较容易发现的Bug。 使用显示列表对提升动画的性能也是有好处的,因为设置诸如透明度、旋转等属性时,不需要让目标View对象失效(系统会自动做这件事)。这种优化还适用于带有显示列表的View对象(应用程序被硬件加速时的任意View对象)。例如,假设有一个包含了一个Button对象的ListView对象的LinearLayout布局,那么LinearLayout布局的显示列表如下: 1. DrawDisplayList(ListView); 2. DrawDisplayList(Button)。
假设现在要改变ListView对象的不透明度,那么在调用ListView对象的setAlpha(0.5f)方法时,显示列表就包含了以下处理: 1. SaveLayerAlpha(0.5);
2. DrawDisplayList(ListView); 3. Restore;
4. DrawDisplayList(Button).
这里没有执行复杂的ListView对象的绘图代码。相反,系统只是比较简单的更新了LinearLayout对象的显示列表。在一个没有启用硬件加速的应用程序中,该列表(ListView)和它的父对象都会再次执行绘图代码。 不被硬件加速所支持的绘图操作
在硬件加速的时候,2D渲染管道支持大多数的通常用于Canvas的绘图操作,以及一些很少使用的操作。被用于渲染应用程序的所有的绘图操作都有发送给Android系统,默认的Widget和布局,以及一些常用的可视效果,如反射和瓷砖的纹理效果都是被支持的。以下列出了已知的不支持硬件加速的操作: 1. Canvas
chipPath() chipRegion() drawPicture() drawPosText() drawTextOnPath() drawVertives() 2. Paint
setLinearText() setMaskFilter() setRasterizer()
另外,还有一些操作行为会因启用了硬件加速而不同: 1. Canvas
clipRect():硬件加速会忽略XOR、Difference和ReverseDifference三种剪辑模式,3D变换不适用于剪辑矩形。
drawBitmapMesh():硬件加速会忽略颜色数组。 drawLines():硬件加速不支持抗锯齿处理。
setDrawFilter():硬件加速能够被设置,但是会被忽略。 2. Paint
setDither():硬件加速会忽略其设置。
setFilterBitmap():位图过滤是始终打开的。
setShadowLayer():该项设置只对文本有效。 3. ComposeShader
ComposeShader对象只能包含不同类型的着色器(例如,BitmapShader和LinearGradient,但是不能够包含两个BitmapShader对象的实例)。
ComposeShader对象不能够包含一个ComposeShader对象。
如果应用程序受到这些错误的功能或限制的影响,那么能够通过调用setLayerType(View.LAYER_TYPE_SOFTWARE, null)方法针对应用程序受到影响的部分来关闭硬件加速。这种方法,依然还能够在其他的地方利用硬件加速。
View层
在Android的所有版本中,通过使用View对象的绘图缓冲,或使用Canvas.saveLayer()方法,View对象都具有在屏幕外缓冲区呈现的能力。屏幕外缓冲区或层由多种用途。在呈现复杂的动画或使用组合效果时,它们能够获得更好的性能。例如,使用Canvas.saveLayer()方法暂时把一个View对象呈现在一个层中,然后使用不透明因子把该View对象合成到屏幕上,从而实现淡入淡出的效果。
从Android3.0(API Level 11)开始,用View.setLayerType()方法使用层的方式和时机会更多的控制。这个API需要两个参数:一个是层的类型,另一个是可选的,用于描述层应该如何被合成的Paint对象。能够使用这个Paint对象来进行颜色过滤、特殊的混合模式、或者层的透明度。View对象能够使用以下三种层类型:
LAYER_TYPE_NONE:View对象用普通的方式来呈现,并且不是由屏幕外缓存来返回的。这种类型是默认的行为;
LAYER_TYPE_HARDWARE:如果应用程序是硬件加速的,那么该View对象被呈现在硬件的一个硬件纹理中。如果没有被硬件加速,那么这种层类型的行为与LAYER_TYPE_SOFTWARE相同。
LAYER_TYPE_SOFTWARE:View对象会被呈现在软件的一个位图中。 使用哪种层的类型,依赖以下目标:
1. 性能:使用硬件层类型,把View呈现到一个硬件纹理中。一旦该View对象被呈现到一
个层中,那么它的绘图代码直到调用该View对象的invalidate()方法时才会被执行。对于某些动画,如alpha动画,就能够直接使用该层,这么做对于GPU来说是非常高效的。 2. 视觉效果:使用硬件或软件层类型和一个Paint对象,能够把一些特殊的视觉处理应用
给一个View对象。例如,使用ColorMatrixColorFilter对象绘制一个黑白相间的View对象。
3. 兼容性:使用软件层类型会强制把一个View对象呈现在软件中。如果View对象被硬件
加速(例如,如果整个应用程序都被硬件加速)发生呈现问题,那么使用软件层类型来解决硬件呈现管道的限制是一个容易的方法。 View层和动画
当应用程序被硬件加速的时候,硬件层能够传递更快、更平滑的动画。当播放具有复杂的绘图操作的动画时,以每秒60帧的速度播放不总是可能。这样能够通过使用硬件层把View对象呈现在硬件纹理中,可以缓解这种情况。硬件纹理能够被用于动画视图,这样在该View对象呈现动画时,就可以消除View对象所需要的重绘操作。直到View对象的属性发生变化时(invalidate()方法被调用),该View对象才会被重绘。如果在应用程序运行一个动画,并且没有获得想要的平滑结果,就要考虑在动画View上启用硬件层。
当一个View被硬件层返回时,通过层方法处理的某些属性会被合成到屏幕上。因为它们不
需要让View对象失效和重绘,所以设置这些属性是非常高效的。下面列出了影响层被合成的方式。调用这些属性设置器,会导致失效处理的优化,并且不会对目标View对象进行重绘:
1. alpha:改变层的透明度;
2. x,y,translation,translation:改变层的位置; 3. scaleX,scaleY:改变成的尺寸;
4. rotation,rotation,rotationY:改变3D空间中层的方向; 5. pivotX,pivotY:改变层的变换起源。
这些属性是在用ObjectAnimator对象给View对象设置动画时所使用的名称。如果想要访问这些属性,就要调用相应的set或get方法。例如,要修改alpha属性,就要调用setAlpha()方法。下面的代码展示了在3D空间中围绕Y轴旋转View对象的最有效的方法: view.setLayerType(View.LAYER_TYPE_HARDWARE, null); ObjectAnimator.ofFloat(view, "rotationY", 180).start(); 因为硬件层会消耗显示内存,因此强烈推荐只在动画播放期间启用硬件层,并且在动画播放结束后就禁用该硬件层。能够使用动画监听器来完成这种操作: View.setLayerType(View.LAYER_TYPE_HARDWARE, null);
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "rotationY", 180); animator.addListener(new AnimatorListenerAdapter() { @Override
public void onAnimationEnd(Animator animation) {
view.setLayerType(View.LAYER_TYPE_NONE, null); } });
animator.start();
更多的属性动画的信息,请看Property Animation(http://developer.android.com/guide/topics/graphics/prop-animation.html)
提示和技巧
切换到硬件加速的2D图形能够有效改善性能,但是依然要通过以下推荐的方式来设计应用程序,以便有效的使用GPU:
1. 减少应用程序中View对象的数量
系统绘制越多的View对象,就会越慢。这种情况也适用于软件呈现管道。减少View对象的有效方法之一就是优化UI。 2. 避免过度绘图
在彼此的顶部不要绘制太多的层。因为删除View时要同时删除遮盖在该View对象之上所有其他的不透明的View对象。如果需要绘制几个图层,要尽量在上面采用合成模式,考虑把它们合并成一个层。一个好的规则是:每帧的像素数不要大于屏幕上像素数的2.5倍(以位图的透明点阵数来计算)。 3. 不要在绘图方法中创建呈现对象
一个常见的错误时每次调用呈现方法时创建一个新的Paint对象或Path对象。这样就会强制频繁的运行垃圾回收,并且会绕过硬件管道中的缓存和优化。 4. 不要经常的编辑形状
对于复杂的形状,如路径和圆,是使用纹理掩码来呈现的。每次创建或修改路径,硬件通道都要创建一个新的掩码,这样会消耗大量的资源。
查询相关文档得知是为了让应用能申请使用更多的内存,我们知道安卓系统对于每个应用都有内存使用的限制,机器的内存限制,在/system/build.prop文件中配置的。
例如:
[java] view plain copy
- dalvik.vm.heapsize=128m
- dalvik.vm.heapgrowthlimit=64m
这里,heapgrowthlimit就是一个普通应用的内存限制,用ActivityManager.getLargeMemoryClass()获得的值就是这个。而heapsize是在manifest中设置了largeHeap=true之后,可以使用最大内存值。
设置largeHeap的确可以增加内存的申请量。但不是系统有多少内存就可以申请多少,而是由dalvik.vm.heapsize限制。
但是作为程序员的我们应该努力减少内存的使用,尽量想回收和复用的方法,而不是想方设法增大内存。当内存很大的时候,每次gc的时间也会长一些,性能会下降的。