Android中怎样避免创建不必要的对象
前言
随着在app中分配更多的对象,你就得实施定期的强制垃圾收集,会导致用户体验产生小卡顿现象。并发垃圾处理器在android 2.3中引入,但是总是应该避免不必要的工作,因此应该在不必要的时候避免创建对象实例。
在编程开发中,内存的占用是我们经常要面对的现实,通常的内存调优的方向就是尽量减少内存的占用。
android设备不像pc那样有着足够大的内存,而且单个app占用的内存实际上是比较小的。所以避免创建不必要的对象对于android开发尤为重要。
本文会介绍一些常见的避免创建对象的场景和方法,其中有些属于微优化,有的属于编码技巧,当然也有确实能够起到显著效果的方法。
使用单例
单例是我们常用的设计模式,使用这种模式,我们可以只提供一个对象供全局调用。因此单例是避免创建不必要的对象的一种方式。
单例模式上手容易,但是需要注意很多问题,最重要的就是多线程并发的情况下保证单例的唯一性。当然方式很多,比如饿汉式,懒汉式double-check
等。
这里介绍一个很极客的书写单例的方式。
public class singleinstance { private singleinstance() { } public static singleinstance getinstance() { return singleinstanceholder.sinstance; } private static class singleinstanceholder { private static singleinstance sinstance = new singleinstance(); } }
在java中,类的静态初始化会在类被加载时触发,我们利用这个原理,可以实现利用这一特性,结合内部类,可以实现上面的代码,进行懒汉式创建实例。
避免进行隐式装箱
自动装箱是java 5 引入的一个特性,即自动将原始类型的数据转换成对应的引用类型,比如将int
转为integer
等。
这种特性,极大的减少了编码时的琐碎工作,但是稍有不注意就可能创建了不必要的对象了。比如下面的代码
integer sum = 0; for(int i=1000; i<5000; i++){ sum+=i; }
上面的代码sum+=i
可以看成sum = sum + i
,但是+这个操作符不适用于integer对象,首先sum进行自动拆箱操作,进行数值相加操作,最后发生自动装箱操作转换成integer对象。
其内部变化如下
int result = sum.intvalue() + i; integer sum = new integer(result);
由于我们这里声明的sum为integer类型,在上面的循环中会创建将近4000个无用的integer对象,在这样庞大的循环中,会降低程序的性能并且加重了垃圾回收的工作量。因此在我们编程时,需要注意到这一点,正确地声明变量类型,避免因为自动装箱引起的性能问题。
另外,当将原始数据类型的值加入集合中时,也会发生自动装箱,所以这个过程中也是有对象创建的。如有需要避免这种情况,可以选择 sparsearray
, sparsebooleanarray
, sparselongarray
等容器。
谨慎选用容器
java和android提供了很多编辑的容器集合来组织对象。比如 arraylist
, contentvalues
, hashmap
等。
然而,这样容器虽然使用起来方便,但也存在一些问题,就是他们会自动扩容,这其中不是创建新的对象,而是创建一个更大的容器对象。这就意味这将占用更大的内存空间。
以hashmap为例,当我们put key
和value
时,会检测是否需要扩容,如需要则双倍扩容
@override public v put(k key, v value) { if (key == null) { return putvaluefornullkey(value); } //some code here // no entry for (non-null) key is present; create one modcount++; if (size++ > threshold) { tab = doublecapacity(); index = hash & (tab.length - 1); } addnewentry(key, value, hash, index); return null; }
关于扩容的问题,通常有如下几种方法
1.预估一个较大的容量值,避免多次扩容
2.寻找替代的数据结构,确保做到时间和空间的平衡
用好launchmode
提到launchmode必然和activity有关系。正常情况下我们在manifest中声明activity,如果不设置launchmode就使用默认的standard模式。
一旦设置成standard,每当有一次intent请求,就会创建一个新的activity实例。举个例子,如果有10个撰写邮件的intent,那么就会创建10个composemailactivity的实例来处理这些intent。结果很明显,这种模式会创建某个activity的多个实例。
如果对于一个搜索功能的activity,实际上保持一个activity示例就可以了,使用standard模式会造成activity实例的过多创建,因而不好。
确保符合常理的情况下,合理的使用launchmode,减少activity的创建。
activity处理onconfigurationchanged
这又是一个关于activity对象创建相关的,因为activity创建的成本相对其他对象要高很多。
默认情况下,当我们进行屏幕旋转时,原activity会销毁,一个新的activity被创建,之所以这样做是为了处理布局适应。当然这是系统默认的做法,在我们开发可控的情况下,我们可以避免重新创建activity。
以屏幕切换为例,在activity声明时,加上
<activity android:name=".mainactivity" android:label="@string/app_name" android:theme="@style/apptheme.noactionbar" android:configchanges="orientation" >
然后重写activity的onconfigurationchanged
方法
public void onconfigurationchanged(configuration newconfig) { super.onconfigurationchanged(newconfig); if (newconfig.orientation == configuration.orientation_portrait) { setcontentview(r.layout.portrait_layout); } else if (newconfig.orientation == configuration.orientation_landscape) { setcontentview(r.layout.landscape_layout); } }
注意字符串拼接
字符串这个或许是最不起眼的一项了。这里主要讲的是字符串的拼接
log.i(logtag, "oncreate bundle=" + savedinstancestate);
这应该是我们最常见的打log的方式了,然而字符串的拼接内部实际是生成stringbuilder
对象,然后挨个进行append,直至最后调用tostring方法的过程。
下面是一段代码循环的代码,这明显是很不好的,因为这其中创建了很多的stringbuilder
对象。
public void implicitusestringbuilder(string[] values) { string result = ""; for (int i = 0 ; i < values.length; i ++) { result += values[i]; } system.out.println(result); }
降低字符串拼接的方法有
1.使用string.format替换
2.如果是循环拼接,建议显式在循环外部创建stringbuilder使用
减少布局层级
布局层级过多,不仅导致inflate过程耗时,还多创建了多余的辅助布局。所以减少辅助布局还是很有必要的。可以尝试其他布局方式或者自定义视图来解决这类的问题。
提前检查,减少不必要的异常
异常对于程序来说,在平常不过了,然后其实异常的代码很高的,因为它需要收集现场数据stacktrace。但是还是有一些避免异常抛出的措施的,那就是做一些提前检查。
比如,我们想要打印一个文件的每一行字符串,没做检查的代码如下,是存在filenotfoundexception
抛出可能的。
private void printfilebyline(string filepath) { try { fileinputstream inputstream = new fileinputstream("textfile.txt"); bufferedreader br = new bufferedreader(new inputstreamreader(inputstream)); string strline; //read file line by line while ((strline = br.readline()) != null) { // print the content on the console system.out.println (strline); } br.close(); } catch(filenotfoundexception e) { e.printstacktrace(); } catch (ioexception e) { e.printstacktrace(); } }
如果我们进行文件是否存在的检查,抛出filenotfoundexception
的概率会减少很多,
private void printfilebyline(string filepath) { if (!new file(filepath).exists()) { return; } try { fileinputstream inputstream = new fileinputstream("textfile.txt"); bufferedreader br = new bufferedreader(new inputstreamreader(inputstream)); string strline; //read file line by line while ((strline = br.readline()) != null) { // print the content on the console system.out.println (strline); } br.close(); } catch(filenotfoundexception e) { e.printstacktrace(); } catch (ioexception e) { e.printstacktrace(); } }
上述的检查是一个不错的编码技巧,建议采纳。
不要过多创建线程
在android中,我们应该尽量避免在主线程中执行耗时的操作,因而需要使用其他线程。
private void testthread() { new thread() { @override public void run() { super.run(); //do some io work } }.start(); }
虽然这些能工作,但是创建线程的代价远比普通对象要高的多,建议使用handlerthread
或者threadpool
做替换。
使用注解替代枚举
枚举是我们经常使用的一种用作值限定的手段,使用枚举比单纯的常量约定要靠谱。然后枚举的实质还是创建对象。好在android提供了相关的注解,使得值限定在编译时进行,进而减少了运行时的压力。相关的注解为intdef和stringdef。
如下以intdef为例,介绍如何使用
在一个文件中如下声明
public class appconstants { public static final int state_open = 0; public static final int state_close = 1; public static final int state_broken = 2; @intdef({state_open, state_close, state_broken}) public @interface doorstate {} }
然后设置书写这样的方法
private void setdoorstate(@appconstants.doorstate int state) { //some code }
当调用方法时只能使用 state_open
, state_close
和 state_broken
。使用其他值会导致编译提醒和警告。
选用对象池
在android中有很多池的概念,如线程池,连接池。包括我们很长用的handler.message
就是使用了池的技术。
比如,我们想要使用handler发送消息,可以使用 message msg = new message()
,也可以使用 message msg = handler.obtainmessage()
。使用池并不会每一次都创建新的对象,而是优先从池中取对象。
使用对象池需要需要注意几点
1.将对象放回池中,注意初始化对象的数据,防止存在脏数据
2.合理控制池的增长,避免过大,导致很多对象处于闲置状态
谨慎初始化application
android应用可以支持开启多个进程。 通常的做法是这样
<service android:name=".networkservice" android:process=":network" />
通常我们在application的 oncreate 方法中会做很多初始化操作,但是每个进程启动都需要执行到这个oncreate方法,为了避免不必要的初始化,建议按照进程(通过判断当前进程名)对应初始化.
public class myapplication extends application { private static final string logtag = "myapplication"; @override public void oncreate() { string currentprocessname = getcurrentprocessname(); log.i(logtag, "oncreate currentprocessname=" + currentprocessname); super.oncreate(); if (getpackagename().equals(currentprocessname)) { //init for default process } else if (currentprocessname.endswith(":network")) { //init for netowrk process } } private string getcurrentprocessname() { string currentprocessname = ""; int pid = android.os.process.mypid(); activitymanager manager = (activitymanager) this.getsystemservice(context.activity_service); for (activitymanager.runningappprocessinfo processinfo : manager.getrunningappprocesses()) { if (processinfo.pid == pid) { currentprocessname = processinfo.processname; break; } } return currentprocessname; } }
总结
上面的一些知识就是关于android中如何避免创建多余对象的总结.欢迎提出意见和观点,共同进步。希望本文的内容能帮助到大家。