Android应用性能优化小结

废话多说无益,切入正题。

一、Android代码执行

Android开发者使用java,但是java使用jvm虚拟机,android使用dalvik虚拟机。Jvm是基于栈的虚拟机,dalvik基于寄存器的虚拟机。栈的执行效率更低,所需要的指令数更多。

在执行过程中,java被虚拟机先编译成java字节码,然后会被dex编译器编译成dalvik字节码,使用dalvik虚拟机来执行。应用在最终执行的过程中只包含dalvik字节码。

android2.2版本,google开始使用jit编译器。Dalvik JIT编译器吧dalvik字节码编译成本地代码(直接由Cpu执行,不需要虚拟机解释执行)。因为google称编码速度提高了3-6倍。本地代码可以为特定架构优化。

对过早的系统版本,我们的优化策略包括1、不做优化2、限制应用最低api等级3、禁掉一些非常耗资源的功能

 

 

二、算法优化

最有效最容易调试的方法是算法优化。

1.      使用迭代算法替换递归算法。原因:递归算法往往消耗更多的栈空间。在调用了过多递归的时候,往往会出现栈溢出。因为,我们一定要优先使用迭代算法

2.      对象的创建会消耗一定的资源。在程序中,应尽量减少对象的创建来增加速率。重复的计算伴随这对象的不断创建,对象可能是通过ndk调用,这样的话也会产生一定的消耗。

3.      在代码关键路径上要避免内存分配,如果必须要分配,也要减少分配数量

4.      使用预计算优化速率

5.      计算代价过高的情况下,使用缓存结果的方式以便再次使用时很快取出来。   这里如果key是个对象是可以使用hashmap,但是如果对象是个整数时,使用SparseArray效率会更高。原因是Hashmap使用Integer对象,而SparseArray使用的是int

6.      在缓存对象的时候,我们会考虑到内存消耗。因为缓存是有大小的。在android.util.LruCache3.1版本引入)中,综合当前的情况,通过覆写removeEldestEntry方法来优化缓存策略。一般有LRU(优先丢弃最近最少使用),MRU策略可以使用。

7.      必须要熟悉每个api发布时之间的区别,更高的api代表着有更多的优化可以使用。熟悉可以带来更高的效率

8.      Java的反射机制很好用,但是在性能至关重要的地方要避免使用反射,替代方法是在静态初始化里调用Class.forNameClass.getMethod确定指定方法是否存在。然后在性能要求高的地方只调用Method.invoke就好

9.      使用基于散列的数据结构,而且键是自定义的时候,一定要优化equals以及hashCode方法

 

三、数据库

要显示创建事务。这样的做法有两个特性1.原子提交2.性能更好

原子提交的好处是事务要么都完成,要么都没完成。因为事务只提交了一次,所以性能也就更好了。

使用一次性事务在任何大数据情况下都会有更好的体验.代码如下:



-------------------------------------------------------------------------------------------------

 

在多次提交的情况下,使用compileStatement可以让语句只编译一次。代码如下:

 

 

四、针对不同架构优化---ndk

市面上的大部分架构如下

1.      arm的版本从v1v7

2.      ARM7/ARM9/ARM10核心使用v5

3.      Cortex使用v7。包括A5\A8\A9\A15

4.      对我们来说,重要的是NDK支持一下三个应用程序二进制借口(ABI):1.armeabi

2.armeabi-v 7a

3.x86

              NDK使用方法:

1.      java中声明本地方法。写个native

2.      javac 编译

3.      javah 产生.h

4.      使用c/c++使用方法

5.      编译,加载本地库

 

这部分暂时了解不多。

值得注意的是

1.      JNI字符串需要转换成c/c++字符串

2.      c/c++字符串需要被释放

3.      GetStringChars----ReleaseStringChars

4.      GetStringUTFChars-----ReleaseStringUTFChars

5.      GetStringCritical----ReleaseStringCritical

6.      GetStringRegion  -----n/a

7.      GetStringUTFRegion ---n/a

8.      3-7来看。优先使用6\7,这样做避免了内存分配、复制String要用的一部分到预先分配的缓存区(更快)不需要释放字符串

9.      jni中调用java对象或类的域和方法。我们都需要得到域或方法的id、使用jni函数设置/获取域或者调用方法。出于性能的考虑,我们不会每次访问域或调用方法时都去获取一次域或者方法的id            注意。。。虚拟机加载类时域和方法的id就被设置了,只要类被加载,该id就有效。所以,        高效的方式是~~~在类被加载时就获取id,在静态初始化中获取。

 

 

五、使用汇编

1.      ARMv5:支持全部16位指令

2.      ARMv7:支持Thumb2指令集,分16位和32

3.      通过简化汇编代码,我们可以取得更好的效果

4.      考虑使用汇编,就是说要在几条指令,几条指令中徘徊,尼玛,这个很多蛋疼。这里不能仅仅只考虑指令数量的多少,而且还需要计算每条指令话费时间,才能得到更精确的耗时。不同的是不一样的指令在cpu中可能需要花费的指令周期也是不一样的。有时候为了最大化吞吐量还会打乱执行顺序。所以,结果不好确定。。。。。详细的话需要参考arm的技术参考手册。

参阅Cortex-A9技术参考手册,可以得到更多的指令周期计时的信息。

5.      在使用汇编的时候,减少操作数,也可以优化

6.      通过优化算法,可以减少使用基本操作的次数

7.      在不同的ARM结构中,每条指令所占的字节数也会不一样。我们应该因地制宜。

 

 

注意~!!使用汇编需要很重点的关注ARM指令集,详情参阅infocenter.arm.com

Ata. Androidapi中包含很多优化过的算法。在引用java的对象的时候,我们可以适当的查看下,android是否提供了相关的可替代的对象。

 

六、NDK小技巧
1、内联函数:函数调用也会带来降低效率的问题,因为调用函数实际上将程序执行顺序转移到函数所存放在内存中某个地址,将函数的程序内容执行完后,再返回到转去执行该函数前的地方。这种转移操作要求在转去前要保护现场并记忆执行的地址,转回后先要恢复现场,并按原来保存地址继续执行。因此,函数调用要有一定的时间和空间方面的开销,于是将影响其效率。特别是对于一些函数体代码不是很大,但又频繁地被调用的函数来讲,解决其效率问题更为重要。引入内联函数实际上就是为了解决这一问题.只要在函数定义的头前加上关键字inline即可
2、把一个函数的循环展开,比如将100次循环改成25次,在循环体内执行4次。这个需要实验过才知道有没有效果。不一定都有效。这依赖当前的系统架构
3、预加载内存
1.GCC__builtin_prefetch();
2.arm汇编代码中使用pldpldw指令,pli指令(适用于armv7及以上)

这种优化不一定有效,因为一些cpu会自动预加载内存

4、使用LDM/STM代替LDR/STD指令。汇编不舒服,一笔带过。

 

七、内存使用

性能主要取决于三个因素:

1.      CPU如何操纵特定的数据类型

2.      数据和指令需要占用多少存储空间

3.      数据在内存中的布局

 

在性能比较下,

1.      使用更短的数据类型,性能更优

2.      避免类型转换

3.      注意数组之类的在释放时,要设置成null

4.      因为对象是在队中分配的,在数组中只能存储对象的引用。所以如果要提高命中率,可以考虑并拢数组,即将一个类型的数据都组合在一起。

5.      在关键路径,或者在主线程中,要避免内存回收。特别是在游戏中

6.      在任何时候,都要有推迟初始化的习惯

 

八、多线程和同步

在很多情况下,应用处理的顺序

1.      UI线程中收到事件

2.      在非UI线程中处理相应事件

3.      UI线程根据处理结果刷新

 

线程的优先级

1-10.MIN_PRIORITY(1)   NORM_PRIORITY(5)  MAX_PRIORITY(10)

调用Thread.setPriority()可以改变线程优先级

 

Linux优先级

使用android.os.ProcessProcess.setThreadPriority。它定义了8个优先级

1.      THREAD_PRIORITY_AUDIO(-16)

2.      THREAD_PRIORITY_BACKGROUND(10)

3.      THREAD_PRIORITY_DEFAULT(0)

4.      THREAD_PRIORITY_DISPLAY(-4)

5.      THREAD_PRIORITY_FOREGROUND(-2)

6.      THREAD_PRIORITY_LOWEST(19)

7.      THREAD_PRIORITY_URGENT_AUDIO(-19)

8.      THREAD_PRIORITY_URGENT_DISPLAY(-8)

 

如果要调整线程优先级,可以使用  优先级老化算法

 

关于优化

1. 在多线程环境中,我们会经常使用AsyncTask来进行多线程环境的操作

我们要注意线程中必须了解的HandlerLooper
值得注意的是使用Message.obtainHandler.obtainMessage来获得message对象更优化。因为这是从全局的消息池中取出Message对象,这比每次单独创建新实例获取Message效率更高

2. 使用HandlerThread
多线程环境必须要注意同步,保证语句的原子性

3. 多线程的环境,要注意并发的情况。当多个线程同时只读访问相同的数据时,充分考虑到吞吐量,比较常用的是ReentrantReadWriteLock.WriteLockReentrantReadWriteLock.ReadLock

4. 未来的世界属于多核。我们应该利用多核优势。使用分治算法

5. 在某些情况下,可以考虑将子问题的缓存共享。(这里有可能会发生将问题分解成子问题后,性能没得到提成,很有可能是因为数据之间有依赖,不得不进行同步,线程在等待上花费了很多时间)。因此,最好使用多线程执行无关的任务,避免同步需求,或执行偶尔或者频率低的定期同步

6. 使用多线程。要考虑到内存泄漏问题。

 

九、科学的功耗

任何一个操作都伴随着CPU的运行。一个优秀的应用应该为用户考虑这一点

要做到这一点需要考虑以下

1.      避免执行做无用功的代码,比如一些广播的注册可以延后到需要的时候才做

2.      充分的给用户设置的权利

3.      给用户选择图片质量的权利,图片下载也会伴随的流量,伴随着更大的消耗

4.      软件的监听操作应该让用户选择监听的频率

5.      使用被动定位服务

6.      通过获取历史最新结果提供用户更好的体验

7.      充分利用wakelock功能,建议使用带超时功能的WakeLock.acquire

 

十、布局

1.      横向布局,减少布局层数。这能提高应用反映速度

2.      减少布局中创建的对象。使用尽可能少的对象来布局

3.      在布局嵌套时,考虑上层的布局方式,看是否能用merge来减少一层布局

4.      使用推迟初始化布局----ViewStub

ViewStub只需要在代码中,findViewById 然后inflate就可以

5.      考虑图片压缩,使用压缩过的图片可以减少内存占用

6.      OpenGl的环境下,应该充分考虑消隐,以及多种纹理方式,只有当有需要的时候才去渲染

 

十一、RenderScript

在很多纹理渲染的时候,我们可以使用RenderScript,这是一种高性能的3D渲染和计算操作的新框架。

优势:

1.      平台独立

2.      易于并行执行

3.      可以使用cpugpu、或者其他处理器

缺点:

1.      android独有,不通用

2.      学习有一定难度

3.      api数量有限,动画效果难了就不要考虑

 

RenderScript会被不断的优化,所以,我们应该重视,google帮你做优化

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值