Android性能细节优化之---字符串连接篇

来源:互联网 发布:淘宝网店转让流程 编辑:程序博客网 时间:2024/05/29 14:26

在网上看了不少关于性能优化文章,大概思路就是加监控:监控内存泄露、监控ANR,这些监控的观点我感觉是属于被动式的只有真出问题了才能捕捉的到,也能解决一些突出问题。既然说到被动式了肯定还有主动式了,主动式可能认知有限并不能很好的说出来一些,我能想到的例如:Android lint、facebook的Infer、360火线、Godeyes等等这些都是比较宝贵的工具。如果我们把这些工具用上并解决了工具扫出来的问题是不是性能问题就解决了? 答案是的人请出门左转,以下内容对你已经是多余了。
性能问题套用今年流行语就是“反腐要先打老虎,在拍苍蝇,老虎不打苍蝇不绝”,性能也是,不把大的问题解决掉小问题就不会受重视。小问题多了也很恐怖的,而且你还无法发现,这是最头疼的问题接下来我将已小问题入手吧。先给大家介绍最正宗的文章吧google出品,从google翻译过来的文章性能优化,不方便科学上网的看这里优酷版


“天下大事必作于细,天下难事必作于易”老子说的,意思是:作大事必须从小事开始,天下的难事,必定从容易的作起。那我们就开始吧。


大家先看看这个代码有什么问题
public static final String MODULE_DEFAULT = “default”;
private static final String KEY_FIRST_LAUNCHER_REPORT = “first_launcher_report”;

public boolean getBoolean(String moduleName, String key, boolean defValue) {
return mSharedPreferences.getBoolean(moduleName + “_” + key, defValue);
}
不考虑SharedPreferences问题只看getBoolean参数,这段代码我觉得是大家最容易写,也是最顺手的写法。给你点考虑时间

1秒

2秒

……..

6秒

好,看来你也看不出来什么,我说下吧,这段代码 没有问题 那是不可能的,这段代码在字符串拼接的时候会生成3个String对象,不可思议吧,这样的写法在工程里肯定是非常多的,这真实婶儿能忍叔叔就不能忍了,在一团懵逼又不知道原因的情况下,去查了源码。请原谅我暂时的怂了。
在看了源码后发现在String.java开头他有这么一句话Strings are constant; their values cannot be changed after they are created. String buffers support mutable strings.Because String objects are immutable they can be shared. 意思是说:字符串是不可变得;他们的值在他们被赋值后不能改变。虽然String对象是不可变的但字符串缓冲区支持可变字符串。也就是一旦把字符串赋值给String对象,这个对象的内容就不能变了,可能好多人要问了,那为什么可以用+符号呢,写法这么方便。话不多说,看下图1
这里写图片描述

一目了然,在App编译后,“+”功能的实现逻辑是:先申请个StringBuilder对象然后用Append函数把字符串串起来,这就是“+”的逻辑,节省大家开发时间。然后我们又要问了那内存空间呢,是不是也增大了?看下图2
这是用Android Studio开发环境Android Monitor的Strat Allocation Tracking功能
这段代码是图1运行时内存申请的情况,也就是说getBoolean函数在运行期间占用394B的内存大小,为什么占用这么大内存呢,在看图3
这里写图片描述
StringBuilder继承自AbstractStringBuilder说白了他们也是char数组的管理类,我们结合图1看在new的时候没有传入参数,默认的构造函数是创建16个字符的char数组,而我们的getBoolean的字符串必然超出了16个字符,我们继续看的append函数它的实现在基类里面,看图4
这里写图片描述
也就是传进去的char[]大小和现有的大小加起来和最大容量对比超出了,就会申请更大的内存然后把字符串拷贝过去,以达到累加的目的。当字符串累加完后,因为当前对象是StringBuilder而我们想要的是String所以图1里面还有toString函数的调用,我们再看下图5
这里写图片描述
没错toString的实现就是new一个更大的String。
到这里就是整个字符串使用“+”的逻辑,里面涉及到的有对象申请空间、内存拷贝等工作,涉及到的Object个数远不是我们能看见的个数。所以不要小看这种写法工程里面用到的字符串拼接的个数绝对是个庞大的数子,每一处看起来可能影响不了什么但是基础的庞大,看起来就很恐怖了,更严重的情况是监控工具是监控不到这种地方的,所以当监控工具没有可泄露的地方时而程序却还在出现OOM等情况下,你绝对是懵逼状态,即使再怎么优化甚至达到重构的阶段,还是会出问题的,所以,性能细节优化我认为是很有必要的,这是一个App运行流畅的必须条件,积少成多,森林的茂密是由众多的树木组成,树木繁茂是由大量树叶子组成,叶子没了树木也就枯萎了,树木没了森林也就消失了。同样一个App也是靠大量的代码组成,代码出问题了App也就病入膏肓了。不说了……这是技术文章,和技术不相关话多了影响文章质量。
既然了解了整个过程我就说下我的修改思路,因为Android里面的所有API使用字符串的就是String,所以我们不使用String,修改如下
protected boolean getBoolean2(String a, String b) {
StringBuilder builder = new StringBuilder(a.length() + b.length() + 1);
builder.append(a).append(“_”).append(b);
return mSharedPreferences.getBoolean(builder.toString(), false);
}
我不用系统给我优化我自己来,和系统唯一不同的是构造函数我传入了字符串大小,这样在append时不需要再扩容了我一次性到位给你全部,也就不会涉及到内存的拷贝这些耗时操作。看下图6
这里写图片描述
整个getBoolean2占用内存102B是不是少了和getBoolean的394B相比少了3倍。
优化的方法还有很多,如果大家有更好的方法可以留言,我加入进来。大家共同进步。


原创粉丝点击