关于Android内存管理及性能优化的一些细节
jopen
11年前
1、使用优化过的数据容器。
在Android framework下,建议使用优化过的数据容器比如:SparseArray,SparseBooleanArray,LongSparseArray。通用的HashMap实现的内存使用率非常的低,因为他需要为每一个mapping创建一个分离的entry object。另外,SparseArray类避免了系统对有些key的自动装箱,因而带来了更高的效率。2、注意内存的开销。
注意你使用的语言和第三方库的成本和开销,要自始至终的将这些因素考虑在你的程序设计中。通常,有些事情表面上看着没什么问题但实际上的开销让人惊叹。比如:·枚举相对于静态常量来说,需要两倍甚至更多的内存。你应该完全避免在Android中使用枚举。
·每一个在java中的类(包括匿名内部类)使用大约500 bytes的代码量。
·每一个类的实例拥有12-16 bytes的RAM消耗。
·放置一个单独的实体到HashMap中,一个额外加的实体对象分配需要花费32 bytes。
</span></span>
3、关于代码的抽象
抽象是一个好的编程的基础,因为抽象可以提高代码的灵活性和可维护性。然而抽象也带来了一定的花销,一般情况下,他们有更多的代码需要执行,需要更多的时间和更多RAM来将这些代码映射到内存中。因此,如果你的抽象不能带来巨大的好处,你就应该割掉你的赘肉。</span>
4、避免依赖注入框架
虽然注入框架给我们带来了很多方便,但是在使用这些框架的时候,框架需要花费很多时间来扫描我们自己写的代码,甚至会将哪些你根本不需要的东西也加载到内存中。</span>
5、小心的使用扩展库
很多扩展库的代码不是针对手机环境开发的,可能在用到移动客户端的时候会导致很低的效率。因此在使用之前,需要评估一下其占用内存的大小。即使库针对手机开发,也会有潜在的危险,因为每一个Library做的事情不尽相同。比如,一个Library使用nano protobufs而另一个使用micro protobufs。现在,在你的app中就有两个protobuf。类似情况经常发生。
</span>
6、使用混淆器移除不必要的代码
ProGuard工具通过移除无用代码,使用语意模糊来保留类,字段和方法来压缩,优化和混淆代码。可以使你的代码更加完整,更少的RAM 映射页。</span>
7、使用多个进程(注意是process 不是 thread ok?)
如果这适合你的app,可能帮助你管理你的app的内存就是将你的app多个部分分配到多个进程中。该技术必须小心使用并且大多数应用不应该运行在多个进程下。这个技术的主要应用是后台工作跟天台工作一样重要的情况下。典型应用就是:当音乐播放器从服务器下载并长时间播放音乐,一般将其分为两个进程:一个是 UI,另一个位置后台服务的运行。</span>like this:
</span>
<service android:name=".PlaybackService" android:process=":background" />
process后面需要记住要有个":",这表示该进程属于你的app。
一般情况下,一块基本的空进程需要的内存大小在1.4m左右。
adb shell dumpsys meminfo com.example.android.apis:empty
8、基本性能优化方法的基本原则:
1)不要做你不必要的工作;2)不要申请不必要的内存;
例如,你明明知道一个方法返回一个String之后,你需要对这个String重新进行修改,那么就不要返回一个String,返回一个StringBuffer会是你更好的选择。
再比如,使用int比使用Integer占用更少的空间。这个大家肯定都是晓得的。
数组比一个Map拥有更好的性能。
如果你的方法不需要访问类字段,那么让你的方法是static的吧,这将会带来15%-20%速度的提升。
对于常量,请尽量使用static and final定义。如果使用final定义常量之后,会减少编译器在类生成时初始化<clinit>方法调用时对常量的存储,对于int型常量,将会直接使用其数值来进行替换,而对于String对象将会使用相对廉价的“string constant”指令来替换字段查找表。虽然这个方法并不完全对所有类型都有效,但是,将常量声明为static final绝对是一个好的做法。
避免Getters/Setters。虽然在一般的面向对象的设计模式中使用Getter和Setter是稀松平常的事情,但是在Android中使用getters/Setters是一个非常糟糕的主意,方法的调用相对于直接查找字段来说十分的昂贵。在没有JIT的情况下,直接对字段进行访问要比通过Getter访问快了近3倍。在有JIT的情况下,前者比后者快近7倍。
使用最新的循环方式。比如增强for。
避免使用浮点类型。在某些可以的情况下,将浮点替换成整型数据,然后进行计算会得到更精确的结果和更快的速度。</span>
小心使用Native Methods。这里需要纠正的是,Native 方法并不一定能提高你应用的速度,有些甚至会拖后腿,因为,首先来说就需要一部分开销在Java-native transition上,而且JIT并不能对其进行优化。另外你需要为每个你想要在其上运行的系统结构上进行编译;即便是同一个处理器上,你也可能需要多个版本,比如为G1上的ARM处理器编译的就不能很好的在Nexus One的ARM上运行。Native代码最主要的用途是,你已经有了很多native 代码,并且你迫切希望接入Android中。而不是使用Native Method来提高你应用中某部分代码的运行速度。
</span> 对于效率的提高除了使用遵守上面两条外基本准则外,选择合适的算法和数据结构也是非常关键的。</span>
9、关于UI上的一些问题
Hierarchy Viewer 通过他,可以看到你自己的Layout文件存在的问题。你可以看到你的Layout每一部分计算,布局,渲染所需要的时间。尽量的使你的Layout扁平话,深度最好保持 </span>在三层之内 。 RelativeLayout 是解决使用LinearLayout堆叠多层问题的利剑。那些为了方便 使 用LinearLayout的layout_weight属性 的哥们,需要重点注意,这个属性真的可以减慢measure速度。所以在使用之前,一定要再三考虑,是否真的不能通过其他方法来完成你要的效果?官方文档上 推荐使用RelativeLayout和GridLayout来避免Layout深度过深的问题 。
之前看文档,Google提供一个叫 ViewSub 的控件来优化那些不是必须要立即在UI上显示的控件,感兴趣的同学可以去看看。在API Level 1中就提供了这个东西,但是在实际开发中很少见到有人用或者提及(可能是我孤陋寡闻,公司就两个Android开发,另一个还要转IOS,我们俩的 Android技术就代表了我们公司的Android技术能力,想想真悲哀!),其实蛮好用的。
重用Layout。可以使用<include/> <merge/>将其他布局嵌入到当前布局中。
ListView的优化:ViewHolder的使用;AsyncTask的使用;针对ListView当前滑动状态,对图片数据的加载进行控制;(ListView在配以AsyncTask加载图片时需要注 意图片的加载完显示的位置以及图片的缓存问题,具体可以参考Google的 Demo )
10、将大消耗操作交给多个线程。
11、如果你的应用需要发送Broadcast但是又不希望别的应用获取到,或者你不希望处理别的应用发送的同样的action,那么请使用LocalBroadcastManager。
该类是在Android Support v4中提供的,用来在同一个应用内的不同组件之间发送Broadcast。好处上面说了,可以保证应用的私密性。会比全局广播有更高的效率,但是官方文档没有说明具体数值。具体使用方法:
LocalBroadcastManager.getInstance(this).registerReceiver( mStateReceiver, mStatusIntentFilter);
LocalBroadcastManager.getInstance(this).sendBroadcast(localBroadcastIntent);