Android官方建议,编写高效的代码的准则如下:
正确的选择数据结构很重要,对Java中常见的数据结构例如ArrayList和LinkedList,HashMap和HashSet等,需要做到对他们的联系和区别有比较深入的了解,方便再编码的时候做出正确的选择,下面我们再Android开发中使用SparseArray代替HashMap为例进行说明。SparseArray是Android平台特有的稀疏数组的实现,它是Integer到Object的一个映射,再特定场合可用于替代HashMap
//This is Arrays.binarySearch(), but dosen't do any argument validation.
static int binarySearch(int[] array, int size, int value){
int lo = 0;
int hi = size - 1;
while(lo <= hi){
final int mid = ( lo + hi) >>> 1;
final int midVal= array[mid];
if(midVal < value){
lo = mid + 1;
}else if(midVal > value){
hi = mid - 1;
}else{
return mid; // value found
}
}
return ~lo; //value not present
}
SparseArray家族目前有下面的四大类
//用于替代HashMap
SparseBooleanArray booleanArray = new SparseBooleanArray();
//用于替代HashMap
SparseIntArray intArray = new SparseIntArray();
//用于替代HashMap
SparseLongArray longArray = new SparseLongArray();
//用于替代HashMap
SparseArray
需要注意的几点如下:
SparseArray不是线程安全的
由于要进行二分查找,因此,SparseArray会对插入的数据按照Key值大小顺序插入。
SparseArray对删除操作做了优化,它并不会立即删除这个元素,而是通过设置标识位(DELETED)的方式,后面尝试重用。
再Android工程中运行Lint进行静态代码分析,会有一个名为AndroidLintUseSparseArrays的检查项,如果违规,它会提示
HashMap can be replaced with SparseArray
这样可以很轻松的找到工程可优化的地方。
Android代码中设计线程通信的地方通常会使用Handler,典型的代码结构如下。
public class HandlerActivity extends Activity{
//可能引入泄露的方法
private final Handler mLeakyHandler = new Handler(){
@Override
public void handleMessage(Message msg){
// ....
}
};
}
使用Android Lint分析这段代码,会违反检查项AndroidLintHandlerLeak,得到如下提示。
This Handler calss should be static or leaks might occour
那么产生内存泄漏的原因可能是什么呢?我们知道Handler是和Looper以及MessageQueue一起工作的,再Android中,一个应用启动以后,系统默认会创建一个为主线程服务的Looper对象,该Looper对象用于处理主线程的所有Message对象,它的生命周期贯穿于整个应用的生命周期。在主线程中使用的Handler都会默认绑定到这个Looper对象。在主线程中创建Handler对象时,它会立即关联主线程Looper对象的MessageQueue,这时发送到MessageQueue中的Message对象都会持有这个Handler对象的引用,这样在Looper处理消息时才能回调到Handler的handleMessage方法。因此,如果Message还没有被处理完成,那么Handler对象也就不会被垃圾回收。
在上面的代码中,将Handler的实例声明为HandlerActivity类的内部类。在Java语言中,非静态内部匿名类会持有外部类的一个隐式的引用,这样就可能会导致外部类无法被垃圾回收。因此,最终由于MessageQueue中的Message还没有处理完成,就会持有Handler对象的引用,而非静态Handler对象会持有外部类HandlerActivity的引用,这个Activity无法被垃圾回收从而导致内存泄漏。
一个明显会引入内存泄漏的例子
public class HandlerActivity extends Activity{
//可能引入泄露的方法
private final Handler mLeakyHandler = new Handler(){
@Override
public void handleMessage(Message msg){
//.....
}
};
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
//延迟5分钟发送消息
mLeakyHandler.postDelayed(new Runnable(){
@Override
public void run(){
//......
}
}, 1000*60*5);
}
}
由于消息延迟5分子发送,因此,当用户进入这个Activity并退出以后,在消息发送并处理完成之前,这个Activity是不会被系统回收的(系统内存确实不够的情况例外)
解决方案如下:
public class HandlerActivity extends Activity{
/*
声明一个静态的Handler内部类,并持有外部类的弱引用
*/
private static class InnerHandler extends Handler{
private final WeakReference
public InnerHandler(HandlerActivity activity){
mActivity = new WeakReference
}
@Override
public void handleMessage(Message msg){
HandlerActivity activity = mActivity.get();
if(activity != null){
//.....
}
}
}
private final InnerHandler mHandler = new InnerHandler(this);
/*
静态匿名内部类不会持有外部类的引用
*/
private static final Runnable sRunnable = new Runnable(){
@Override
public void run(){
//......
}
};
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
//延迟5分子发送消息
mHandler.postDelayed(sRunnable, 1000*60*5);
}
}
Context应该是每个入门Android的程序员第一个接触的概念,他代表当前上下文的环境,可以用来实现很多功能的调用,语句如下
/获取资源管理器对象,进而可以访问到例如String,color等资源
Resources resources = context.getResources();
//启动指定的Activity
context.startActivity(new Intent(this, MainActivity.class));
//获取各种系统服务
TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
//获取系统文件目录
File internalDir = context.getCacheDir();
File externalDir = context.getExternalCacheDir();
//更多...
可见,正确的理解Context的概念是很重要的,虽然在应用开发中随处可见Context的使用,但并不是所有的Context实力都具有相同的功能,在使用上需要区别对待,否则会很容易引入问题。总结下面的Context的种类
Context的种类
根据Context依托的组件以及用途的不同,我们可以将Context分为如下几种。
错误使用Context导致的内存泄漏
错误的使用Context可能会导致内存泄漏,典型的例子是在实现单例模式时使用Context,如下代码是可能会导致内存泄漏的单利实现。
public class SingleInstance{
private static SingleInstance sInstance;
private SingleInstance(Context context){
mContext = context;
}
public static SingleInstance getInstance(Context context){
if(sInstance == null){
sInstance = new SingleInstance(context);
}
return sInstance;
}
}
如果使用者调用getInstance时传入的Context是一个Activity或者是Service实例,那么在应用退出之前,由于单利模式一直存在,会导致对应的Activity或者Service被单利引用,从而不会被垃圾回收,Acitivty或者Service中关联其他View或者数据结构对象也不会被释放,从而导致内存泄漏。正确的做法是使用ApplicationContext,因为它是应用唯一的,而生命周期和应用一致的,正确的单例实现如下。
public class SingleInstance{
private Context mContext;
private static SingleInstance sInstance;
private SingleInstance(Context context){
mContext = context;
}
public static SingleInstance getInstance(Context context){
if(sInstance == null){
//获取到上下文对象
sInstance = new SingleInstance(context.getApplicationContext());
}
return sInstance;
}
}
不同的Context对比
不同组件中的Context能提供的功能不尽相同,总结如下
功能 | Application | Activity | Service | BroadcastReceiver | ContentProvider |
显示Dialog | NO | YES | NO | NO | NO |
启动Activity | NO[1] | YES | NO[1] | NO[1] | NO[1] |
实现layout Inflation | NO[2] | YES | NO[2] | NO[2] | NO[2] |
启动Service | YES | YES | YES | YES | YES |
绑定Service | YES | YES | YES | YES | NO |
发送Broadcast | YES | YES | YES | YES | YES |
注册Broadcast | YES | YES | YES | YES | NO[3] |
加载资源Resource | YES | YES | YES | YES | YES |
掌握Java的四种引用方式
掌握Java的四种引用类型对于写出内存使用良好的应用是很关键的
特别注意,在程序设计中一般很少使用弱引用与虚引用,使用软引用的情况较多,这是因为软引用可以加速JVM对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生。
避免创建非必要的对象
对象的创建需要内存分配,对象的销毁需要垃圾回收,这些都会在一定程度上影响应用的性能。因此一般来说,最好是重用对象而不是在每次需要的时候去创建一个功能相同的新对象,特别是注意不要在循环中重复创建相同的对象。
1. 简介
一般来说,我们最好的做法是重用对象,而不是每次使用都new出一个新的相同功能的对象,这样做很高效,特别是对于那些不可变类来说。
2. 例子
引用书中的一个很好的例子:比如有一个Person对象,对象中有一个字段是生日,生日是个常量,它不能被更改,现在需要完成一个方法,用这个方法我们可以获取到这个人是否出生在1946到1964年之间(也就是生育高峰期(baby boomer)),你会怎么做呢?
像下面这样?:
public class Person {
private final Date birthday;
public Person(Date birthday) {
this.birthday = birthday;
}
public boolean isBabyBoomer() {
Calendar c = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
c.set(1946, Calendar.JANUARY, 1, 0, 0, 0);//将时间设置为1946年1月1日0时0分0秒
Date boomStart = c.getTime();
c.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
Date boomEnd = c.getTime();
return birthday.compareTo(boomStart) >= 0 &&
birthday.compareTo(boomEnd) < 0;
}
}
isBabyBoomer在每次调用的时候都会创建一个Calendar 一个TimeZone和两个Date实例,这些都是不必要的,下面是改进版本:
public class Person {
private final Date birthday;
private static final Date BOOM_START;
private static final Date BOOM_END;
public Person(Date birthday) {
this.birthday = birthday;
}
static {
Calendar c = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
c.set(1946, Calendar.JANUARY, 1, 0, 0, 0);//将时间设置为1946年1月1日0时0分0秒
BOOM_START = c.getTime();
c.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
BOOM_END = c.getTime();
}
public boolean isBabyBoomer() {
return birthday.compareTo(BOOM_START) >= 0 &&
birthday.compareTo(BOOM_END) < 0;
}
}
3. 如果想要保证性能,则请尽量使用基本数据类型。
每次自动装箱(autoboxing)都会穿件一个多余的对象,比如:
Integer value = 0;
上面这段代码就是自动装箱,每次这样做都会穿件一个多余的Integer对象。
总结
本条的重点是避免创建多余的对象,提高重用性,但是需要注意对象的重用不能是盲目的,对于对象的重用,我们应该把目光集中在那些重量级的类,或是构造方法中需要做很多初始化的类的身上。对于一些比较轻量级的类,我们应该将代码的简洁和可维护性放在跟高的优先级上。
对常量使用static final修饰
对于基本数据类型和String类型的变量,建议使用static final修饰,因为final类型的常量会在会进入静态dex文件的与初始化部分,这是对基本数据类型和String类型常量的调用不会涉及类的初始化,而是直接调用字面量。
不要将视图控件声明为static,因为View对象会引用Activity对象,当Activity退出时其对象本身无法被销毁,会造成内存溢出
避免内部的Getters/Setters
在面向对象编程中,Getters/Setters的作用主要是对外屏蔽具体的变量定义,从而达到更好封装性。而如果是在类内部还是用Getters/Setters函数访问变量的话,会降低访问的速度。根据Android官方文档,在没有JIT(Just In Time)编译器时,直接访问变量的速度是调用Getter方法的3倍;在JIT编译时,直接访问变量的速度是调用Getter方法的7倍。当然,如果你的应用中使用了ProGuard的话,那么ProGuard会对Getters/Setters进行内联擦做,从而达到直接访问的效果。
数据类型的选择
字符串拼接用StringBuilder代替String,在非并发情况下用StringBuilder代替StringBuffer,如果你对字符串的长度有大致了解,如100字符左右,可以直接new StringBuilder(128)指定初始大小,减少空间不够时的再次分配。64位类型如long double的处理比32位如int慢,final类型存储在常量区中读取效率更高
数据结构选择
常见的数据结构选择如:
ArrayList和LinkedList
ArrayList、HashMap、LinkedHashMap、HashSet
HashMap、WeakHashMap
Collections.synchronizedMap和ConcurrentHashMap
Android也提供了一些性能更优的数据类型,如SparseArray、SparseBooleanArray、SparseIntArray、Pair
Sparse系列的数据结构是为key为int情况的特殊处理,采用二分查找及简单的数组存储,加上不需要泛型转换的开销,相对Map来说性能更优
代码的重构
代码重构是一项持之以恒的工作,需要依靠团队中每一个成员来维护代码库的高质量,如何有效的进行代码重构,推荐阅读《重构-改善既有的代码设计》和《代码整洁之道》
参考文章:
https://www.jianshu.com/p/2e699709aa79
https://www.aliyun.com/jiaocheng/32118.html