一、问题描述
我在使用android手机,解锁屏幕时出现了卡顿现象,从解锁到菜单页面需要卡顿3~4秒。对于这一方面,从事android开发的我决定,测试分析解决这一问题。
二、分析问题
我们使用systrace,我这里是用的ddms录制trace.html,用chrom打开systrace提供了一些ui thread耗时cpu耗时等分析图,从图上分析可以看出,有很长一段时间,系统在进行gc,大约花费3~4s的时间,感觉可以查一下问题,从网上查询资料,知道是系统进行gc的时候是会阻塞当面界面的,表现上就是界面卡住了,但是gc是虚拟机系统行为,code无法控制,还得需要找源头,为何会有这么多垃圾需要回收。
三、gc回收原理与解析
那我们就先来搞懂gc的源头
gc原理
将内存中不再被使用的对象进行回收,gc中用于回收的方法称为收集器,由于gc需要消耗一些资源和时间,java在对对象的生命周期特征进行分析后,按照新生代、旧生代的方式来对对象进行收集,以尽可能的缩短gc对应用造成的暂停。
常见gc算法
在对象中添加一个引用计数器,每当一个地方引用它时,计数器就加1;当引用失效时,计数器就减1;当引用计数为0时就会被回收。但是它存在一个很大的问题就是循环引用:如下图,当实例化a时,a会持有实例b,b会持有c,c持有a。这样一来我们就会发现:如果需要回收a,除了释放初始实例化引用,还需要释放c的引用。但是由于abc互相引用,所以就造成谁也无法释放。主流的垃圾回收都没有采用这种判断方法,因为需要额外的工作来解决它。
可达性分析算法:
在java虚拟机中就是通过可达性分析法来判定对象是否存活的。思路是通过gc roots的对象作为起始点,然后从这些节点开始遍历所有引用链,如果某个对象没有gc roots直接或间接的连接的话,这个对象(白色节点)就被认为程序中不再使用可以被回收了。如下图:
代码示例:
const user1 = age: 11 const user2 = age: 12 const user3 = age: 13 const namelist = function fn() const num1 = 1 const num2 = 2 num3 = 3fn()
当函数调用过后,num1和num2在外部不能使用,引用数为0,会被回收 ;num3是挂载在window上的,所以不会被回收 ; 上面的user1、user2、user3被namelist引用,所以引用数不为0不会被回收 ;
上面无法回收循环应用对象举例:
function fn const obj1 = const obj2 = obj1.name = obj2 obj2.name = obj1 return #39;hello world#39;fn// obj1和obj2,因为互相有引用,所以计数器并不为0,fn调用之后依旧无法回收这两个对象
2、标记清除:
其分为标记和清除两个阶段。首先标记出所有死亡的对象,然后把所有死亡的的对象进行清除操作。如下图,我们可以清楚地看到,这种回收算法有一个很大的问题:造成很多的不连续内存碎片,这样一来,如果需要创建稍微大一点的对象,就很可能无法找到足够大的内存空间。这就需要整个再进行一次标记整理来解决这一问题。
3.标记整理:
算法分为标记-整理-清除阶段,首先需要先标记出存活的对象,然后把他们整理到一边,最后把存活边界外的内存空间都清除一遍。这个算法的好处就是不会产生内存碎片,但是由于整理阶段移动了对象,所以需要更新对象的引用。
4.标记复制:
算法分标记-复制两个阶段。首先会标记存活的对象,完成后,该算法会把存活的对象都复制到一块新的空内存里去。最后将原来的内存空间清空。过程如下图,这个算法最大的问题就是需要很大的内存,同时如果存活的对象非常多的话,标记和复制阶段都就会很慢。同时也涉及到了对象位置改变需要更新引用。尽管看起来问题很大,但是根据分代理论:弱分代假说里大多数对象生命周期短,这种情况下标记复制就很适合了(复制的存活对象少)。至于内存消耗太大的问题,java虚拟机通过将新生代分为一个eden区与2个survivo区,其中一个survivo区用来复制,这样一来极大地提高了内存空间利用率。
了解到gc的原理以及他的算法,我们就来看看如何解决问题。
四、js555888金沙的解决方案
打开著名的traceview工具,也是分析性能问题的好帮手。同样使用ddms工具上集成的方式,我使用的是mtk release的工具gat首先我用traceview看了一下,traceview主要是看一些方法的耗时和调用情况,以及消耗cpu的状态,从函数方法的调用来看,耗时最高的就是主线程,达到了4.7秒,图形上看,似乎这个binder操作耗费的时间有点高。
双击这一块看到信息,binder操作的inc cpu time占用14%,但是incl real time是73%的时间,达到了5秒多,这种情况主要是因为可能cpu的上下文切换、阻塞、gc等原因造成,与systrace上看出的问题一致,如图:
下面再录制一份log,找关键字activitymanager和binder看看情况,找到如下log:
01-21 14:07:49.951 1109 1285 i activitymanager: displayed com.android.settings/.subsettings: 5s544ms
可见,这个subsetting花了5秒半,相当长,循着时间往前看5s,看看是否有什么蛛丝马迹,找到了binder出错的信息:
01-21 14:07:43.931 4218 4218 e activitythread: activity com.android.settings.subsettings has leaked serviceconnection com.android.settings.password.chooselockgeneric$chooselockgenericfragment1 680 f 7 e 9 t h a t w a s o r i g i n a l l y b o u n d h e r e 01 minus; 2114 : 07 : 43.93142184218 e a c t i v i t y t h r e a d : a n d r o i d . a p p . s e r v i c e c o n n e c t i o n l e a k e d : a c t i v i t y c o m . a n d r o i d . s e t t i n g s . s u b s e t t i n g s h a s l e a k e d s e r v i c e c o n n e c t i o n c o m . a n d r o i d . s e t t i n g s . p a s s w o r d . c h o o s e l o c k g e n e r i c 1680f7e9 that was originally bound here 01-21 14:07:43.931 4218 4218 e activitythread: android.app.serviceconnectionleaked: activity com.android.settings.subsettings has leaked serviceconnection com.android.settings.password.chooselockgeneric1680f7e9thatwasoriginallyboundhere01minus;2114:07:43.93142184218eactivitythread:android.app.serviceconnectionleaked:activitycom.android.settings.subsettingshasleakedserviceconnectioncom.android.settings.password.chooselockgenericchooselockgenericfragment1 680 f 7 e 9 t h a t w a s o r i g i n a l l y b o u n d h e r e 01 minus; 2114 : 07 : 43.93142184218 e a c t i v i t y t h r e a d : a t a n d r o i d . a p p . l o a d e d a p k 1680f7e9 that was originally bound here 01-21 14:07:43.931 4218 4218 e activitythread: at android.app.loadedapk1680f7e9thatwasoriginallyboundhere01minus;2114:07:43.93142184218eactivitythread:atandroid.app.loadedapkservicedispatcher.01-21 14:07:43.931 4218 4218 e activitythread: at android.app.loadedapk.getservicedispatcher(loadedapk.java:1424)01-21 14:07:43.931 4218 4218 e activitythread: at android.app.contextimpl.bindservicecommon(contextimpl.java:1605)01-21 14:07:43.931 4218 4218 e activitythread: at android.app.contextimpl.bindservice(contextimpl.java:1557)01-21 14:07:43.931 4218 4218 e activitythread: at android.content.contextwrapper.bindservice(contextwrapper.java:684)01-21 14:07:43.931 4218 4218 e activitythread: at com.android.settings.password.chooselockgenericc h o o s e l o c k g e n e r i c f r a g m e n t . b i n d s e r v i c e ( c h o o s e l o c k g e n e r i c . j a v a : 297 ) 01 minus; 2114 : 07 : 43.93142184218 e a c t i v i t y t h r e a d : a t c o m . a n d r o i d . s e t t i n g s . p a s s w o r d . c h o o s e l o c k g e n e r i c chooselockgenericfragment.bindservice(chooselockgeneric.java:297) 01-21 14:07:43.931 4218 4218 e activitythread: at com.android.settings.password.chooselockgenericchooselockgenericfragment.bindservice(chooselockgeneric.java:297)01minus;2114:07:43.93142184218eactivitythread:atcom.android.settings.password.chooselockgenericchooselockgenericfragment.oncreate(chooselockgeneric.java:201)01-21 14:07:43.931 4218 4218 e activitythread: at android.app.fragment.performcreate(fragment.java:2489)01-21 14:07:43.931 4218 4218 e activitythread: at android.app.fragmentmanagerimpl.movetostate(fragmentmanager.java:1237)01-21 14:07:43.931 4218 4218 e activitythread: at android.app.fragmentmanagerimpl.addaddedfragments(fragmentmanager.java:2407)01-21 14:07:43.931 4218 4218 e activitythread: at android.app.fragmentmanagerimpl.executeopstogether(fragmentmanager.java:2186)01-21 14:07:43.931 4218 4218 e activitythread: at android.app.fragmentmanagerimpl.removeredundantoperationsandexecute(fragmentmanager.java:2142)01-21 14:07:43.931 4218 4218 e activitythread: at android.app.fragmentmanagerimpl.execpendingactions(fragmentmanager.java:2043)01-21 14:07:43.931 4218 4218 e activitythread: at android.app.fragmentmanagerimpl.executependingtransactions(fragmentmanager.java:799)01-21 14:07:43.931 4218 4218 e activitythread: at com.android.settings.settingsactivity.switchtofragment(settingsactivity.java:781)01-21 14:07:43.931 4218 4218 e activitythread: at com.android.settings.settingsactivity.launchsettingfragment(settingsactivity.java:439)01-21 14:07:43.931 4218 4218 e activitythread: at com.android.settings.settingsactivity.oncreate(settingsactivity.java:327)01-21 14:07:43.931 4218 4218 e activitythread: at android.app.activity.performcreate(activity.java:7023)01-21 14:07:43.931 4218 4218 e activitythread: at android.app.activity.performcreate(activity.java:7014)01-21 14:07:43.931 4218 4218 e activitythread: at android.app.instrumentation.callactivityoncreate(instrumentation.java:1215)01-21 14:07:43.931 4218 4218 e activitythread: at android.app.activitythread.performlaunchactivity(activitythread.java:2734)01-21 14:07:43.931 4218 4218 e activitythread: at android.app.activitythread.handlelaunchactivity(activitythread.java:2859)01-21 14:07:43.931 4218 4218 e activitythread: at android.app.activitythread.-wrap11(unknown source:0)01-21 14:07:43.931 4218 4218 e activitythread: at android.app.activitythreadh . h a n d l e m e s s a g e ( a c t i v i t y t h r e a d . j a v a : 1592 ) 01 minus; 2114 : 07 : 43.93142184218 e a c t i v i t y t h r e a d : a t a n d r o i d . o s . h a n d l e r . d i s p a t c h m e s s a g e ( h a n d l e r . j a v a : 106 ) 01 minus; 2114 : 07 : 43.93142184218 e a c t i v i t y t h r e a d : a t a n d r o i d . o s . l o o p e r . l o o p ( l o o p e r . j a v a : 164 ) 01 minus; 2114 : 07 : 43.93142184218 e a c t i v i t y t h r e a d : a t a n d r o i d . a p p . a c t i v i t y t h r e a d . m a i n ( a c t i v i t y t h r e a d . j a v a : 6518 ) 01 minus; 2114 : 07 : 43.93142184218 e a c t i v i t y t h r e a d : a t j a v a . l a n g . r e f l e c t . m e t h o d . i n v o k e ( n a t i v e m e t h o d ) 01 minus; 2114 : 07 : 43.93142184218 e a c t i v i t y t h r e a d : a t c o m . a n d r o i d . i n t e r n a l . o s . r u n t i m e i n i t h.handlemessage(activitythread.java:1592) 01-21 14:07:43.931 4218 4218 e activitythread: at android.os.handler.dispatchmessage(handler.java:106) 01-21 14:07:43.931 4218 4218 e activitythread: at android.os.looper.loop(looper.java:164) 01-21 14:07:43.931 4218 4218 e activitythread: at android.app.activitythread.main(activitythread.java:6518) 01-21 14:07:43.931 4218 4218 e activitythread: at java.lang.reflect.method.invoke(native method) 01-21 14:07:43.931 4218 4218 e activitythread: at com.android.internal.os.runtimeinith.handlemessage(activitythread.java:1592)01minus;2114:07:43.93142184218eactivitythread:atandroid.os.handler.dispatchmessage(handler.java:106)01minus;2114:07:43.93142184218eactivitythread:atandroid.os.looper.loop(looper.java:164)01minus;2114:07:43.93142184218eactivitythread:atandroid.app.activitythread.main(activitythread.java:6518)01minus;2114:07:43.93142184218eactivitythread:atjava.lang.reflect.method.invoke(nativemethod)01minus;2114:07:43.93142184218eactivitythread:atcom.android.internal.os.runtimeinitmethodandargscaller.run(runtimeinit.java:438)01-21 14:07:43.931 4218 4218 e activitythread: at com.android.internal.os.zygoteinit.main(zygoteinit.java:807)01-21 14:07:43.987 1109 3345 w activitymanager: unbind failed: could not find connection for android.os.binderproxy8d5f5b4
好巧,报错也是因为binder没有unbinder成功,与traceview的binder耗时,以及systrace的gc耗时的信息都有千丝万缕的关系,也许就是问题的所在,接下来就是解决这个错误,并验证的时刻。找到如下资料:
google android issue中有这个缺陷,缺陷详细信息在这里,using getapplicationcontext().bindservice instead of just bindservice on your activity solves the problem as it is using the higher level application context.先调用getapplicationcontext()获取其所属的activity的上下文环境才能正常bindservice,也就是在oncreate()方法中使用this.getapplicationcontext().bindservice((argshellip;))就可以了,否则bindservice将永远失败返回false。
那么就看log找到对应的地方,我们发现绑定binder的地方是用的getcontext.binder(xxxx),解绑的地方是用的getactivity.unbinder(xxx),我们通通换成getactivity.getapplicationcontext.binder(xxx) (unbinder),编译push验证,查看log如下:
line 2240: 01-21 15:29:12.629 1167 1325 i activitymanager: displayed com.android.settings/.subsettings: 2s672ms
android性能优化知识学习,获~可私信发送:核心笔记或手册即可获取!
五、文末
成功优化了大约3秒的时间,虽然还感觉有卡顿,但是已经好很多,因为为了方便调试,编译的是userdebug版本,使用user版本后效果还有提升,算是能够过的去了!
更多性能优化可前往私信哦。
郑重声明:此文内容为本网站转载企业宣传资讯,目的在于传播更多信息,与本站立场无关。仅供读者参考,并请自行核实相关内容。