优化Android上的Java代码【笔记】

简介: 优化Android上的Java代码【笔记】

前言:代码优化并不是应用开发的首要任务,而提供良好的用户体验并专注于代码的可维护性才是首要的任务。

1.Android如何执行代码


Android平台将Java代码编译成Java字节码,并通过dex编译器将其编译成Dalvik字节码,最终交由Dalvik虚拟机(JVM是基于栈的虚拟机,而Dalvik是基于寄存器的虚拟机)执行。在Android 2.2之后,引入了Dalvik JIT(实时编译器),它把Dalvik字节码编译成本地代码,由于本地代码直接由CPU执行,而绕过了虚拟机,并且可以将本地代码为特定架构进行优化,所以可以明显加快执行速度。(在manifest.xml文件里可以用Android:vmSafeMode启用或禁用JIT编译器。默认是启用的)


2.将递归转为迭代(以斐波那契数列为例)


3.数据类型的修正


在Java中,long是64位、int是32位、short是16位。所有整数类型都是有符号的。如斐波那契数列第92项是超出Long型范围的,那么我们怎么办?Java中有个类——java.math.BigInteger。该对象可以可以容纳任意大小的有符号整数,该类定义了所有基本的数学运算。(Java.math包除了BigInteger还定义了BigDecimal参考,而java.lang.Math提供了数学常数和运算函数。如果应用不需要双精度,使用Android的FloatMath效果更佳)


4.使用BigInteger带来的问题


它有三大缺点:


(1)BigInteger是不可变的,例如a=a.add(b),而不能简单的写成a.add(b),所以每次都会创建一个新的BigInteger对象;


(2)BigInteger使用BigInt和本地代码实现,每分配一个BigInteger对象都会额外创建一个BigInt对象,从Java调用JNI调用本地代码会产生一定的开销;


(3)数字越大,相加运算花费的时间也越长。


5.通过优化算法来减少分配内存数量


尽管我们需要BigInteger来确保结果的正确性,但也不必使用BigInteger来计算所有的n值。既然基本类型long可以容纳小于等于92项的结果,可以混合BigInteger和基本类型。


通过优化算法的同时,还可以使用BigInteger实现的预分配对象,比如BigInteger.ZERO、BigInteger.ONE、BigInteger.TEN。


6.通过类的static代码来预先计算结果


但带来的缺点是可能会造成较大的内存使用


7.缓存先前的计算结果


如果计算的代价过高,最好把过去的结果缓存起来,下次就可以很快的取出来。比如Java中的HashMap就可以充当缓存,不过,Android定义了SparseArray类,当键是整数时,它比HashMap效率更高。因为HashMap使用的是java.lang.Integer对象,而SparseArray使用的是基本类型int。因此使用HashMap会创建很多的Integer对象。但是换句话说,尽管使用HashMap回避SparseArray慢一些,但这样的好处是可以让代码不依赖与Android平台。(Android定义了多种类型的稀疏数组:SparseArray[键为整数,值为对象]、SparseBooleanArray[键为整数,值为boolean]、SparseIntArray[键为整数,值为整数])

android.util.LruCache<K,V>,该类是Android3.1引入的,还可以自己通过继承java.util.LinkedHashMap,并覆写removeEldestEntry来实现类似的功能。

8.注意API的等级


试图调用不存在的API将导致应用崩溃,你通常可以使用Build.VERSION.SDK_INT获得Android平台的API等级。可是不幸的是,这个字段是在Android1.6(API等级为4)中引入的,所以如果你试图在早于其的版本中使用该字段,则会导致崩溃。另一种选择是使用Build.VERSION.SDK,它是API等级1引入的,但这一字段现在已经废弃,版本字符串也没有归档。

可以用反射来检查是否存在SDK_INT字段,即判断该平台是不是Android1.6或跟高版本,参见Class.forName("Android.os.Build$VERSION").getField("SDK"),反射技术也可以用来确认平台是否存在特定方法,参见Class.forName("XXX")和Class.getMethod(“XXX”)。不过使用反射会使代码变慢,因此在性能至关重要的地方应尽量避免使用反射。替代的方法是在静态初始化代码中调用以上方法以确认指定方法是否存在,在性能要求较高的地方只调用Method.invoke()就好了。

9.数据结构


Java.util包中常见的数据结构,在此基础之上,Android为了解决性能问题还增加了一些自身的实现:

LruCache\SparseArray\sparseBooleanArray\SparseIntArray\Pair

Java还定义了Array类和Collections类。这两个类只包含静态方法,分别操作数组和集合。例如,使用Array.sort对数组排序,使用Arrays.binarySearch在有序数组中搜索值。

每当需要选择一个数据结构来解决问题时,最好将选择范围缩小到只有几个类,因为通常每个类为特定目的或为特定的服务而优化。例如,如果你不需要在数据结构内部处理同步,应该选择ArrayList而不是Vector。如果你使用基于散列的数据结构(例如HashMap),而且键是自定义的对象,那么你需要确保正确地覆写了类定义中的equal和hashCode方法。hashCode的低劣实现可以轻易地将散列的收益化为乌有。可以参考这个 链接,里面有实现hashCode()的很好示例。

10.响应能力


应用的性能不仅仅在于速度,也要能让用户真正感觉到快才行。例如,显得更快的方法有,应用可以延迟创建对象,知道需要时才创建,成为推迟初始化的技术。另外,在开发过程中,你很有可能要在关注性能的地方侦测执行缓慢的代码。

@优化的基本原则是保持应用的持续响应,可以考虑使用多线程。

@优化Activity的启动或者销毁序列是非常重要的:当一个Activity被销毁时,并创建一个新实例,会调用以下序列:onPause\onStop\onDestroy\onCreate\onStart\onResume。应用可以在manifest.xml文件中指定每个Activity元素的Android:configChanges属性,让它只接受自己想处理的配置变化。这会导致调用Activity的onConfigurationChanged(),而不是销毁。

@由于内存分配需要花时间,等到对象真正需要使用时才进行分配,也是一个很好的选择。当某个对象并不是立即就要使用时,推迟创建对象有着很明显的好处。

10.1降低布局复杂性,加速资源文件的展开


(1)使用RelativeLayout代替嵌套LinearLayouts,尽可能保持“扁平化”的布局。此外减少创建的对象数量,也会让事件的处理速度加快。

(2)使用ViewStub推迟对象创建,它可以在运行时展开资源,当ViewStub需要展现时,它被相应的资源展开替换,自己就成为了等待垃圾回收的对象。


10.2 使用StrictMode检测不良行为


例如遇到了下列两种情况:

(1)网络很慢(服务器很久都没有响应)

(2)文件系统的访问速度很慢

结论就是,不应该在主线程中进行网络操作或访问文件系统。通常情况下,在应用启动时,即当onCreate被调用时,启用StrictMode, 参考、 参考2

Android 3.0中引入了需要特别留意的方法有detectCustomSlowCall()和noteSlowCall(),他们都是用来检测应用中执行缓慢的代码或潜在缓慢的代码。

10.3 SQLite的性能提升


(1)如果SQL语句是简单的字符串,需要解释或编译才可以执行,当你执行execSQL语句时,SQLite内部是编译执行的。而事实证明,执行SQLite语句可能需要相当长的一段时间。除了编译,语句本身可能还需要创建。由于String是不可改变的,这可能会遇到如之前所说的创建了过多的BigInteger对象的问题。如果我们想创建一个完整的数据库,则需要大量的增删改的操作,而每个INSERT语句都会创建一个String对象并调用execSQL,在内部进行解析。一个显而易见的优化方法是,加快要执行的SQL语句字符串的创建速度。在这种情况下,使用+运算符来连接字符串不是最有效的方法,而使用StringBuilder对象,或调用String.format可以提高性能。【它们只是优化了传递给execSQL的字符串创建速度,这两种方法并不算是与SQL相关的优化】

如果所有的INSERT语句都十分相似,那么我们就可以考虑使用compileStatement让语句在循环外只编译一次。由于只进行一次的语句编译,并且绑定值是比编译更轻量的操作,所以这种方法明显快多了。Android还提供了其他的API,使用ContentValue对象把值插入到数据库中,它基本上包含了列名和值之间的绑定信息,并通过调用db.insert()方法来实现插入。该方法是最快实现且最灵活的选择。【Android3.0的android.database和android.database.sqlite包发生了许多变化。例如,Activity类中的managedQuery、startManagingCursor和stopManagingCursor方法已经废弃,由CursorLoader取而代之】

Android还提供了一些可以提高性能的类。例如,可以使用DatabaseUtils.InsertHelper在数据库中插入多行,这样就只需编译一次INSERT语句。

(2)虽然以上的例子中并没有显式地创建任何事务,但会自动为每个插入操作创建一个事务,并在每次插入后立即提交。显式创建事务有以下两个基本特性:

【1】原子提交【2】性能更好

抛开对性能的追求,第一个特性是很重要的。原子提交意味着数据库的所有修改要么全都完成,要么全都不做。事务不会只提交部分修改。最重要的一点是,有些数据库不是保存在内存中,而是存储在持久性存储上(比如SD卡或内部闪存),众所周知,访问持久性存储比访问易失性记忆体慢得多,所以数据库工作的大量时间都花费在此,一次性事务是解决此类问题的较好办法,事务处理的优越性能够得到更显著的体现。

(3)我们可以使用限制数据库访问的方式来加快查询的速度。数据库查询仅会返回一个游标对象,然后用它来遍历结果。很明显如果查询时选择正确的参数,只读取需要的数据将使性能得到可观的提升,如果将所有行为作为一个事务处理则会更快,如果只需要一定数量的行,指定调用查询的限制参数,可以进一步减少数据库的访问时间。【还可以考虑使用SQLite的FTS(全文检索)扩展,它支持更多高级搜索特性(使用索引)。 参考】


目录
相关文章
|
3天前
|
Linux 编译器 Android开发
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
在Linux环境下,本文指导如何交叉编译x265的so库以适应Android。首先,需安装cmake和下载android-ndk-r21e。接着,下载x265源码,修改crosscompile.cmake的编译器设置。配置x265源码,使用指定的NDK路径,并在配置界面修改相关选项。随后,修改编译规则,编译并安装x265,调整pc描述文件并更新PKG_CONFIG_PATH。最后,修改FFmpeg配置脚本启用x265支持,编译安装FFmpeg,将生成的so文件导入Android工程,调整gradle配置以确保顺利运行。
24 1
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
|
12天前
|
Java 测试技术 应用服务中间件
常见 Java 代码缺陷及规避方式(下)
常见 Java 代码缺陷及规避方式(下)
38 0
|
14天前
|
Java
Java中ReentrantLock释放锁代码解析
Java中ReentrantLock释放锁代码解析
25 8
|
1天前
|
存储 Java API
Android系统 文件访问权限笔记
Android系统 文件访问权限笔记
23 1
|
6天前
|
缓存 移动开发 Android开发
构建高效Android应用:从优化用户体验到提升性能表现
【4月更文挑战第18天】 在移动开发的世界中,打造一个既快速又流畅的Android应用并非易事。本文深入探讨了如何通过一系列创新的技术策略来提升应用性能和用户体验。我们将从用户界面(UI)设计的简约性原则出发,探索响应式布局和Material Design的实践,再深入剖析后台任务处理、内存管理和电池寿命优化的技巧。此外,文中还将讨论最新的Android Jetpack组件如何帮助开发者更高效地构建高质量的应用。此内容不仅适合经验丰富的开发者深化理解,也适合初学者构建起对Android高效开发的基础认识。
5 0
|
7天前
|
Java 开发者
Java中多线程并发控制的实现与优化
【4月更文挑战第17天】 在现代软件开发中,多线程编程已成为提升应用性能和响应能力的关键手段。特别是在Java语言中,由于其平台无关性和强大的运行时环境,多线程技术的应用尤为广泛。本文将深入探讨Java多线程的并发控制机制,包括基本的同步方法、死锁问题以及高级并发工具如java.util.concurrent包的使用。通过分析多线程环境下的竞态条件、资源争夺和线程协调问题,我们提出了一系列实现和优化策略,旨在帮助开发者构建更加健壮、高效的多线程应用。
7 0
|
8天前
|
SQL 缓存 Java
Java数据库连接池:优化数据库访问性能
【4月更文挑战第16天】本文探讨了Java数据库连接池的重要性和优势,它能减少延迟、提高效率并增强系统的可伸缩性和稳定性。通过选择如Apache DBCP、C3P0或HikariCP等连接池技术,并进行正确配置和集成,开发者可以优化数据库访问性能。此外,批处理、缓存、索引优化和SQL调整也是提升性能的有效手段。掌握数据库连接池的使用是优化Java企业级应用的关键。
|
10天前
|
Java 程序员 编译器
Java中的线程同步与锁优化策略
【4月更文挑战第14天】在多线程编程中,线程同步是确保数据一致性和程序正确性的关键。Java提供了多种机制来实现线程同步,其中最常用的是synchronized关键字和Lock接口。本文将深入探讨Java中的线程同步问题,并分析如何通过锁优化策略提高程序性能。我们将首先介绍线程同步的基本概念,然后详细讨论synchronized和Lock的使用及优缺点,最后探讨一些锁优化技巧,如锁粗化、锁消除和读写锁等。
|
11天前
|
Java 编译器
Java并发编程中的锁优化策略
【4月更文挑战第13天】 在Java并发编程中,锁是一种常见的同步机制,用于保证多个线程之间的数据一致性。然而,不当的锁使用可能导致性能下降,甚至死锁。本文将探讨Java并发编程中的锁优化策略,包括锁粗化、锁消除、锁降级等方法,以提高程序的执行效率。
13 4
|
12天前
|
Java
代码的魔法师:Java反射工厂模式详解
代码的魔法师:Java反射工厂模式详解
26 0