1、最近看到一篇关于Android性能调优的文章,里面提到了一个性能调优利器StrictMode,并且还是系统自带,不需要第三方引入。(之前一直没发现,惭愧)试着用它去检测了一下之前的代码,确实发现不少问题,特此记录分享下。
2.关于使用比较简单,建议在Applicaiton的oncreate方法中调用。
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());
ps:第一行setThreadPolicy()这个方法顾名思义是开启线程策略检测(detectAll就是开启下面的所有检测)
- 自定义的耗时调用 使用detectCustomSlowCalls()开启
- 磁盘读取操作 使用detectDiskReads()开启
- 磁盘写入操作 使用detectDiskWrites()开启
- 网络操作 使用detectNetwork()开启
第二行setVmPolicy()方法是开启虚拟机策略检测,就是各种泄露问题。
- Activity泄露 使用detectActivityLeaks()开启
- 未关闭的Closable对象泄露 使用detectLeakedClosableObjects()开启
- 泄露的Sqlite对象 使用detectLeakedSqlLiteObjects()开启
- 检测实例数量 使用setClassInstanceLimit()开启
3.下面有这样一行代码,拿到外部sd卡的路径,这行代码放在主线程执行,正常情况下,是不会报错的。但是实际上也属于违规代码。
@Override
public void onCreate() {
super.onCreate();
String path = Environment.getExternalStorageDirectory().getPath();
}
我们来看看报了什么提示:
大概能知道什么意思,就是说这句代码属于io操作,在主线程执行了,给出警告了。我相信很多人写着行代码都随手在主线程写了,当然这只是一个小例子,为了说明一下io操作在主线程执行的隐患。
解决办法:把这段代码放在子线程执行就不会报警告了,至于结果回调处理,就不多说了。
4.一个关于activity实例导致内存泄露的问题,有个需求是token失效的时候,app请求网络,后台会返回失效码,app这边需要从A页面跳转到登陆页面B,引导用户重新登陆进入C页面,所以,我在BaseActivity里面把当前activity用一个list维护起来,当token跳转到登陆页面的时候,把list里面的activity全部remove掉,确保当前list里面只有登陆页面的实例,(这里面有个小问题就是,用户不手动按返回键,而只是通过startactivity()跳转到另一个页面的时候,若没有finish,上一个activity是不会走ondestory的,所以这个实例实际上应该是在的),所以,当A页面跳转到登陆页面B的时候,需要手动调一下remove移除掉A,否则可能会导致A页面没有走ondestory方法,实例会一直存在。我用StrictMode打印日志的时候发现,从C主页跳转到A DataActivity时,A的实例一直增加,仔细查了下代码,发现,基类里面添加了A进去,但是,ondestory的时候没有调用remove当前activity的方法,导致,A实例一直增加。(即使用户是手动按返回键退出A页面,但因为A已经被List引用了,所以,A的资源还是无法全部释放,)
11-22 19:11:13.523 23937-23937/com.mzj.bd E/StrictMode: class com.mzj.bd.view.question.WebViewActivity; instances=2; limit=1
android.os.StrictMode$InstanceCountViolation: class com.mzj.bd.view.question.WebViewActivity; instances=2; limit=1
我们再来看看,Android studio自带的内存分析工具,分析的结果。下图右边可以看到,点了6次,就有6个实例了,明显的泄露了。
解决办法也很简单:
只需要在A的基类activity的ondestory方法里面remove掉list里面的实例就OK。
解决后无论跳转多少次,都不会出现多个实例的情况了。
5.关于百度地图mapview没有及时销毁导致的内存泄露问题。
(1)假如有一个加载百度地图的SignLocationActivity,销毁后必须伴随mapview的onDestroy()方法,否则,会造成LocationActivity的泄露,一直回收不了。即使SignLocationActivity已经走了生命周期的ondestory方法。我们先看下StrictMode替我们打出的信息。
11-23 10:13:21.864 16150-16150 E/StrictMode: class com.location.SignLocationActivity; instances=2; limit=1
android.os.StrictMode$InstanceCountViolation: class com.location.SignLocationActivity; instances=2; limit=1
at android.os.StrictMode.setClassInstanceLimit(StrictMode.java:1)
很明显,这是说SignLocationActivity的实例已经存在2个了,限制最多只能存在一个。查找下代码,发现SignLocationActivity的ondestory方法没有及时调用mapview的ondestory方法。导致mapview引用到SignLocationActivity的资源无法销毁。再看下此时的as打印的内存信息。(点了2次,并且手动gc一次,已经存在2个SignLocationActivity实例了,明显的泄露了)
再看看这个,Shallow Size指的是该对象本身占用内存的大小,Retained Size代表该对象被释放后,垃圾回收器能回收的内存总和。如果改实例能正常回收,理论上Shallow Size和Retained Size大小基本一致,这里的Retained Size 明显大于Shallow Size,说明,有很多其他地方引用的资源,得不到释放。原因也写的很清楚,是SignLocationActivity的mcontext上下文,被mapview引用,mapview没有销毁掉,SignLocationActivity资源得不到释放。
解决办法:在SignLocationActivity的ondestory方法调用mapview的ondestory方法。
@Override
protected void onDestroy() {
super.onDestroy();
if(mBaiduMap!=null){
mBaiduMap.clear();
}
if(signMapView!=null){
signMapView.onDestroy();
}
}
6.还有一个关于百度地图的内存泄露问题,在初始化LocationClient的时候,mLocationClient = new LocationClient(context);切记不要用当前activity的context,要用applicationContext,否则会出现下面的警告。
android.app.ServiceConnectionLeaked: Activity com.location.LocationActivity has leaked ServiceConnection com.baidu.location.b@fa723f1 that was originally bound here
at android.app.LoadedApk$ServiceDispatcher.<init>(LoadedApk.java:1344)