说明:这篇文章是参考了网络上的多篇文章汇总而成。
1、强制定义应用的堆内存大小
private final static int CWJ_HEAP_SIZE = 6* 1024* 1024 ;
VMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE);
2、控制Bitmap的大小
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;
3、显式回收不再使用的Bitmap对象
if(bitmapObject.isRecycled()==false) //如果没有回收
{
bitmapObject.recycle();
bitmap = null;
}
4、手动干涉GC处理
使用dalvik.system.VMRuntime类提供的setTargetHeapUtilization方法可以增强程序堆内存的处理效率。
private final static float TARGET_HEAP_UTILIZATION = 0.75f;
在程序onCreate时调用:
VMRuntime.getRuntime().setTargetHeapUtilization(TARGET_HEAP_UTILIZATION);
5、避免创建不必要的对象
创建太多的对象会造成性能低下。首先分配内存本身需要时间,其次虚拟机运行时堆内存使用量是有上限的,当使用量到达一定程度时会触发垃圾回收,垃圾回收会使得线程甚至是整个进程暂停运行。如果有对象频繁的创建和销毁,或者内存使用率很高,就会造成应用程序严重卡顿。
6、合理使用static成员
主要有三点需要掌握:
(1)如果一个方法不需要操作运行时的动态变量和方法,那么可以将方法设置为static的。
(2)常量字段要声明为“static final”,因为这样常量会被存放在dex文件的静态字段初始化器中被直接访问,否则在运行时需要通过编译时自动生成的一些函数来初始化。此规则只对基本类型和String类型有效。
(3)不要将视图控件声明为static,因为View对象会引用Activity对象,当Activity退出时其对象本身无法被销毁,会造成内存溢出。
7、避免内部的Getters/Setters
面向对象设计中,字段访问使用Getters/Setters通常是一个好的原则,但是在Android开发中限于硬件条件,除非字段需要被公开访问,否则如果只是有限范围内的内部访问(例如包内访问)则不建议使用Getters/Setters。无JIT时,直接字段访问大约比调用getter访问快3倍。有JIT时(直接访问字段开销等同于局部变量访问),要快7倍。
8、使用for-each循环
优先使用for-each循环通常情况下会获得更高的效率;除了一种情况,即对ArrayList进行遍历时,使用手动的计数循环效率要更高。
9、使用package代替private以便私有内部类高效访问外部类成员
私有内部类的方法访问外部类的私有成员变量和方法,在语法上是正确的,但是虚拟机在运行时并不是直接访问的,而是在编译时会在外部类中自动生成一些包级别的静态方法,执行时内部类会调用这些静态方法来访问外部类的私有成员。这样的话就多了一层方法调用,性能有所损耗。一种解决这个问题的方法就是将外部类的私有成员改为包级别的,这样内部类就可以直接访问,当然前提是设计上可接受。
10、避免使用浮点类型
在Android设备中浮点型大概比整型数据处理速度慢两倍,所以如果整型可以解决的问题就不要用浮点型。另外,一些处理器有硬件乘法但是没有除法,这种情况下除法和取模运算是用软件实现的。为了提高效率,在写运算式时可以考虑将一些除法操作直接改写为乘法实现,例如将“x / 2”改写为“x * 0.5”。
11、了解并使用库函数
Java标准库和Android Framework中包含了大量高效且健壮的库函数,很多函数还采用了native实现,通常情况下比我们用Java实现同样功能的代码的效率要高很多。所以善于使用系统库函数可以节省开发时间,并且也不容易出错。
i.在处理字符串的时候,不要吝惜使用String.indexOf(),String.lastIndexOf()等特殊实现的方法。这些方法都是使用C/C++实现的,比起Java循环快10到100倍。
ii.System.arraycopy方法在有JIT时,比自行编码的循环快9倍。
iii.android.text.format包下的Formatter类,提供了IP地址转换、文件大小转换等方法;DateFormat类,提供了各种时间转换,都是非常高效的方法。
iv. TextUtils类
对于字符串处理Android提供了一个简单实用的TextUtils类,如果处理比较简单的内容不用去思考正则表达式不妨试试TextUtils。
v.高性能的MemoryFile类。
通过MemoryFile类可以实现高性能的文件读写操作。MemoryFile适用于哪些地方呢?对于I/O需要频繁操作的,主要是和外部存储相关的I/O操作,MemoryFile通过将本地或SD卡上的文件,分段映射到内存中进行修改处理,这样就用高速的RAM代替了ROM或SD卡,性能提高不少,对于Android手机而言同时还减少了电量消耗。该类是通过JNI的方式直接在C底层执行。
12、关闭Cursor对象
Cursor cursor = null;
try {
cursor = getContentResolver().query(...);
if (cursor != null && cursor.moveToNext()) {
... ...
}
} catch (Exception e) {
... ...
} finally {
if (cursor != null) {
cursor.close();
}
}
在onCreate()中打开,在onDestroy()中关闭;
在onStart()中打开,在onStop()中关闭;
在onResume()中打开,在onPause()中关闭;
13、及时释放不需要的对象的引用
1. 静态成员变量
有时因为一些原因(比如希望节省Activity初始化时间等),将一些对象设置为static的,比如:
private static TextView mTv;
mTv = (TextView) findViewById(...);
而且没有在Activity退出时释放mTv的引用,那么此时mTv本身,和与mTv相关的那个Activity的对象也不会在GC时被释放掉,Activity强引用的其他对象也无法被释放掉,这样就造成了内存泄漏。如果没有充分的理由,或者不能够清楚的控制这样做带来的影响,请不要这样写代码。
2. 正确注册/注销监听器对象
经常要用到一些XxxListener对象,或者是XxxObserver、XxxReceiver对象,然后用registerXxx方法注册,用unregisterXxx方法注销。
(1) registerXxx和unregisterXxx方法的调用通常也和Cursor的打开/关闭类似,在Activity的生命周期中成对的出现即可:
在onCreate()中register,在onDestroy()中unregister;
在onStart()中register,在onStop()中unregister;
在onResume()中register,在onPause()中unregister;
(2) 没有unregister
有一段代码,在Activity中定义了一个PhoneStateListener的对象,将其注册到TelephonyManager中:TelephonyManager.listen(l,PhoneStateListener.LISTEN_SERVICE_STATE);但是在Activity退出的时候没有注销掉这个监听,即没有调用以下方法:
TelephonyManager.listen(l,PhoneStateListener.LISTEN_NONE);
因为PhoneStateListener的成员变量callback,被注册到了TelephonyRegistry中,TelephonyRegistry是后台的一个服务会一直运行着。所以如果不注销,则callback对象无法被释放,PhoneStateListener对象也就无法被释放,最终导致Activity对象无法被释放。
3. 适当使用SoftReference、WeakReference
如果要写一个缓存之类的类(例如图片缓存),建议使用SoftReference,而不要直接用强引用,例如:
private final ConcurrentHashMap<Long, SoftReference<Bitmap>> mBitmapCache = new ConcurrentHashMap<Long, SoftReference<Bitmap>>();
当加载的图片过多,应用可用堆内存不足的时候,就可以自动的释放这些缓存的Bitmap对象。
4. 构造Adapter时,使用缓存的convertView
以构造ListView的BaseAdapter为例,在BaseAdapter中提供了以下方法:
public View getView(int position,View convertView,ViewGroup parent)
来向ListView提供每一个item所需要的view对象。初始时ListView 会从BaseAdapter中根据当前的屏幕布局实例化一定数量的view对象,同时ListView会将这些view对象缓存起来。当向上滚动ListView 时,原先位于最上面的list item的view对象会被回收,然后被用来构造新出现的最下面的list item。这个构造过程就是由getView()方法完成的,getView()的第二个形参View convertView就是被缓存起来的 list item的view对象(初始化时缓存中没有view对象则convertView 是null)。由此可以看出,如果我们不去使用convertView,而是每次都在getView()中重新实例化一个View对象的话,既浪费资源也浪费时间,也会使得内存占用越来越大。
14、采用硬件加速
在android 3.0及以上版本中,为androidmanifest.xml文件的application标签添加android:hardwareAccelerated="true"。
15、View设置缓存属性
android:setDrawingCache="true"。
16、设置Activity的Window的背景图为空
getWindow().setBackgroundDrawable(null)。
17、尽量采用文件操作
文件操作的速度比数据库的操作要快10倍左右。
18、快速滑动时不显示图片
当快速滑动列表时(SCROLL_STATE_FLING),item中的图片或获取需要消耗资源的view,可以不显示出来;而处于其他两种状态(SCROLL_STATE_IDLE 和SCROLL_STATE_TOUCH_SCROLL),则可以将那些view显示出来。
19、主线程中不做耗时操作
1)主线程不要进行网络处理;
2)主线程不要进行数据库处理;
3)主线程不要进行文件处理。
20、避免static成员变量引用资源耗费过多的实例
比如Context。Android界面比较多,基本都需要引用到context,平时用时不注意,在当前Activity直接传this,长期的结果就会出现较多释放不掉的类,导致内存泄露。我们经常使用到两种context:
A)Activity.this周期为当前Activity生命周期;
B)getApplicationContext()周期为整个应用生命周期;
第一种情况是因为传入的Activity被static的类所引用,不管里面有没有界面显示,传入的Activity都释放不了,解决的办法就是传入getApplicationContext(),类似的如果不需要Activity来显示界面或者做只有Activity才能做的事情,那只需要传入getApplicationContext() 就能满足要求。
第二种情况,就是在当前界面传入Activity.this给Dialog,在finish时没有调用dialog的dismiss()方法,这样也会导致Activity释放不了。
21、使用WeakReference代替强引用
JDK 1.2版本开始,把对象的引用分为4种级别,从而使程序能更加灵活地控制对象的生命周期。这4种级别由高到低依次为:强引用、软引用、弱引用和虚引用。
i. 强引用(StrongReference)
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。
ii. 软引用(SoftReference)
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
iii. 弱引用(WeakReference)
在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。弱引用可以保持对对象的引用,同时允许GC在必要时释放对象,回收内存。对于那些创建便宜但耗费大量内存的对象,即希望保持该对象,又要在应用程序需要时使用,同时希望GC必要时回收时,可以考虑使用弱引用。
iv. 虚引用(PhantomReference)
与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
22、避免在循环中构造大量的对象
临时对象共用,减少临时对象的创建。尽量避免在经常调用的方法,循环中new对象,由于系统不仅要花费时间来创建对象,而且还要花时间对这些对象进行垃圾回收和处理,在我们可以控制的范围内,最大限度的重用对象,最好能用基本的数据类型或数组来替代对象。与循环不相关的操作也应尽量放在循环外面。如:
for(int i=0; terminal=x.length;i<terminal;i++)
{
x[i] = x[i]/scaleA *scaleB;
}
应该改成:
double scale = scaleB*scaleA;
for(int i=0; terminal=x.length;i<terminal;i++)
{
x[i] = x[i]/scale ;
}
23、把多维数组分成多个一维数组
二维数组占用的内存空间比一维数组多得多,大概10倍以上。
24、尽量使用局部变量
调用方法时传递的参数以及在调用中创建的临时变量都保存在栈(Stack)中,速度较快。其他变量,如静态变量,实例变量等,都在堆(Heap)中创建,速度较慢。如下面一段代码:
for(int i =0; i <this.mCount; i++) {
dumpItem(this.mItems);
}
应该改成这样:
int count = this.mCount;
Item[] items = this.mItems;
for(int i =0; i < count; i++) {
dumpItems(items);
}
如果你要多次访问一个变量,也最好先为它建立一个本地局部变量,例如:
protected void drawHorizontalScrollBar(Canvas canvas, int width, int height) {
if(isHorizontalScrollBarEnabled()) {
int size = mScrollBar.getSize(false);
if(size <=0) {
size = mScrollBarSize;
}
mScrollBar.setBounds(0, height - size, width, height);
mScrollBar.setParams(computeHorizontalScrollRange(), computeHorizontalScrollOffset(), computeHorizontalScrollExtent(),false);
mScrollBar.draw(canvas);
}
}
这里有4次访问成员变量mScrollBar,如果将它缓存到本地,4次成员变量访问就会变成4次效率更高的栈变量访问。另外就是方法的参数与本地局部变量的效率相同。
25、不要在for循环的第二个条件中调用任何方法
如下面方法所示,在每次循环的时候都会调用getCount()方法,这样做比你在一个int先把结果保存起来开销大很多。
for(int i =0; i < this.getCount(); i++) {
dumpItems(this.getItem(i));
}
26、使用final修饰符
1)值固定的变量;
2)不会被继承的类;
3)不会被重载的方法。
如果一个类是final的,则该类所有方法都是final的。java编译器会寻找机会内联(inline)所有的final方法(这和具体的编译器实现有关)。此举能够使性能平均提高50%。
27、使用实体类比接口好
假设有一个HashMap对象,有以下两种方式声明:
Map map1 = new HashMap();
HashMap map2 = new HashMap();
按照传统的观点Map会更好些,因为这样你可以改变他的具体实现类,只要这个类继承自Map接口。传统的观点对于传统的程序是正确的,但是它并不适合嵌入式系统。调用一个接口的引用会比调用实体类的引用多花费一倍的时间。如果HashMap完全适合你的程序,那么使用Map就没有什么价值。
28、避免使用枚举
枚举变量非常方便,但不幸的是它会牺牲执行的速度并大幅增加文件体积。例如:
public class Foo {
public enum Shrubbery { GROUND, CRAWLING, HANGING }
}
会产生一个900字节的.class文件(Foo$Shubbery.class)。在它被首次调用时,这个类会调用初始化方法来准备每个枚举变量。每个枚举项都会被声明成一个静态变量并被赋值。然后将这些静态变量放在一个名为“$VALUES”的静态数组变量中。而这么一大堆代码,仅仅是为了使用三个整数。这样:Shrubbery shrub =Shrubbery.GROUND;会引起一个对静态变量的引用,如果这个静态变量是final int,那么编译器会直接内联这个常数。
29、关闭资源对象
对SQLiteOpenHelper、SQLiteDatabase、Cursor、I/O操作等都应该记得显式关闭。
30、布局用Java完成比XML快
一般情况下对于Android程序布局往往使用XML文件来编写,这样可以提高开发效率,但是考虑到代码的安全性以及执行效率,可以通过Java代码自行创建,虽然Android编译过的XML是二进制的,但是加载XML解析器的效率对于资源占用还是比较大的,Java处理效率比XML快得多,但是对于一个复杂界面的编写,可能需要一些嵌套考虑,如果你思维灵活的话,使用Java代码来布局你的Android应用程序是一个更好的方法。
31、避免在异步线程中循环刷新界面
应用开发中自定义View的时候,交互部分千万不要写成线程不断刷新界面显示,而要根据TouchListener事件主动触发界面的更新。
32、优化ListView的item
ListView中item的布局至关重要,必须尽可能的减少使用的控件和布局层次。RelativeLayout是绝对的利器,通过它可以减少布局的层次。同时要尽可能的复用控件,这样可以减少ListView的内存使用,减少滑动时gc次数。ListView的背景色与cacheColorHint设置相同颜色,可以提高滑动时的渲染性能。
getView方法中不能做复杂的逻辑计算,特别是数据库和网络访问操作,否则会严重影响滑动时的性能。费时的操作尽量放在该方法外部共用。
33、避免频繁网络请求
访问server端时,建立连接本身比传输需要更多的时间,如非必要,不要将一次交互可以做的事情分成多次交互(这需要与Server端协调好)。有效管理Service后台服务,如果程序后台有一个service不停地去服务器上更新数据,在不更新数据的时候就让它sleep,这种方式是非常耗电的,通常情况下,可以使用AlarmManager来定时启动服务。如下所示,每30分钟执行一次。
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, MyService.class);
PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, 0);
long interval = DateUtils.MINUTE_IN_MILLIS * 30;
long firstWake = System.currentTimeMillis() + interval;
am.setRepeating(AlarmManager.RTC,firstWake,interval, pendingIntent);
34、数据量大时使用GZIP压缩
在进行大数据量传输时,尽量使用GZIP方式进行压缩,可以减少网络流量,一般是压缩前数据大小的30%左右。
HttpGet request = new HttpGet("http://example.com/gzipcontent");
HttpResponse resp = new DefaultHttpClient().execute(request);
HttpEntity entity = response.getEntity();
InputStream compressed = entity.getContent();
InputStream rawData = new GZIPInputStream(compressed);
35、数据库操作优化
1)相对于封装过的ContentProvider而言,使用原始SQL语句执行效率高,比如使用方法rawQuery、execSQL的执行效率比较高。
2)对于需要一次性修改多个数据时,可以考虑使用SQLite的事务方式批量处理。
SQLiteDatabase database = new SQLiteDatabase();
if (database.isOpen())
{
database.beginTransaction();
try {
...数据库操作
}
database.setTransactionSuccessful();
} finally {
database.endTransaction();
}
database.close();
}
3)批量插入多行数据使用InsertHelper或者bulkInsert方法。
4)使用SQLiteStatement的executeInsert()和executeUpdateDelete()方法替代SQLiteDatabase的execSQL()方法。
36、使用SparseArray替代HashMap
SparseArray是android里为<Interger,Object>这样的Hashmap而专门写的类,目的是提高效率,其核心是折半查找函数(binarySearch)。在Android中,当我们需要定义
HashMap<Integer,E> hashMap = new HashMap<Integer,E>();
时,可以使用如下的方式来取得更好的性能:
SparseArray<E> sparseArray = new SparseArray<E>();
37、用迭代替代递归调用
递归算法的效率并不好,虽然代码简洁,尤其在没有多少内存可用的嵌入式系统开发中,主要是因为递归算法往往要消耗大量的栈空间。它会产生过多的方法调用,可能导致栈溢出,让应用崩溃,因此尽量用迭代替代递归调用。
38、合理使用包装类型
虽然包装类型和基本类型在使用过程中可以相互转换,但它们两者所产生的内存区域是完全不同的,基本类型数据产生和处理都在栈中处理,包装类型是对象,是在堆中产生实例。在集合类对象,有对象方面需要的处理适用包装类型,其他的处理提倡使用基本类型。
39、减少不必要的对象创建
如
A a =newA();
if(i==1){list.add(a);}
应该改为
if(i==1){
A a =new A();
list.add(a);
}
40、在finally块中释放资源
程序中使用到的资源应当被释放,以避免资源泄漏。这最好在finally块中去做。不管程序执行的结果如何,finally块总是会执行,以确保资源的正确关闭。
41、使用移位来代替“/”的操作
“/”是一个代价很高的操作,使用移位的操作将会更快和更有效。
如:
int num = a /4;
int num = a /8;
应该改为:
int num = a >>2;
int num = a >>3;
但注意的是使用移位应添加注释,因为移位操作不直观,比较难理解。
42、使用移位来代替“*”的操作
同样的,对于“*”操作,使用移位的操作将会更快和更有效。
如:
int num = a *4;
int num = a *8;
应该改为:
int num = a <<2;
int num = a <<3;
43、合并try/catch块
将几个方法调用放在一个try/catch块中,而不是为每个方法调用实现几个try/catch块。如:
try{
Some.method1();
}catch(method1Exception e){
}
try{
Some.method2();
}catch(method2Exception e){
}
try{
Some.method3();
}catch(method3Exception e){
}
应该写为:
try{
Some.method1();
Some.method2();
Some.method3();
}catch(method1Exception e){
}catch(method2Exception e){
}catch(method3Exception e){
}
44、慎用new关键词创建类的实例
用new关键词创建类的实例时,构造函数链中的所有构造函数都会被自动调用。如果一个对象实现了Cloneable接口,我们可以调用它的clone()方法。Clone()方法不会调用任何类构造函数。在使用设计模式的场合,如果用Factory模式创建对象,则改用clone()方法创建新的对象实例非常简单。例如,下面是Factory模式的一个典型实现:
private static Credit BaseCredit = new Credit();
public static Credit getNewCredit()
{
return (Credit)BaseCredit.clone();
}
45、布局性能优化
布局的性能优化之所以重要,因为以下两个方面:
·布局文件是一个xml文件,inflate布局文件其实就是解析xml,根据标签信息创建相应的布局对象并做关联。xml中的标签和属性设置越多,节点树的深度越深,在解析时要执行的判断逻辑、函数的嵌套和递归就越多,所以时间消耗越多;
·inflate操作只是布局影响的第一个环节,一个界面要显示出来,在requestLayout后还要执行一系列的measure、layout、draw的操作,每一步的执行时间都会受到布局本身的影响。而界面的最终显示是所有这些操作完成后才实现的,所以如果布局质量差,会增加每一步操作的时间成本,最终显示时间就会比较长。
(1)布局层次尽量少
在达到同样布局效果的前提下,xml文件中树的深度尽量的浅。
·使用RelativeLayout来代替LinearLayout实现相同的布局效果;
·如果布局树的A节点只有一个子节点B,而B只有一个子节点C,那么B通常是可以去掉的;
·合理的使用<merge>标签,如果布局X可以被include到Y中,那么需要考虑X的根节点是否可以设置为<merge>,这样在解析时会将<merge>的子节点添加到Y中,而<merge>本身不会添加。
(2)使用ViewStub延迟加载视图
ViewStub是一个没有尺寸大小并且不会在布局中嵌套或渲染任何东西的轻量级的视图。如果界面中有一部分视图控件不需要立即显示,则可以将其写到一个单独的layout文件中,用ViewStub标签代替,当要真正显示这部分内容时再通过ViewStub将视图加载进来。