电话本内存泄露

一、问题背景

测试使用monkey工具,压力测试主题商店应用时,最后出现电话本内存泄露信息:

appCrashed_android.process.contacts_2016-12-30 00_34_36.txt
// CRASH: android.process.contacts (pid 5172)


// Short Msg: java.lang.OutOfMemoryError

 

 

刚开始看到这个问题,感觉一头雾水,为何测试主题商店应用,会导致电话本内存泄露呢? 这两个看起来八竿子打不着的应用, 会存在着怎样的纠葛,当时不知。

项目要继续,问题要解决。之前没有处理过OOM问题,只好查阅资料,询问同事协助处理。

 

 

二、问题分析

 

首先想确认问题的复现概率, 测试说基本上跑个两三个小时的monkey测试,就能复现,于是我自己也本地在尝试,果然能否复现。向导主题商店的测试内容主要为切换主题,于是就编写脚本,

压力测试切换主题,看能否使问题复现的时间缩短。

 

切换主题的脚本:

 

@echo off

adb remount
adb push 240699_Men.theme /sdcard/Themes/

pause

echo --------start to change themes-----

 ##循环切换主题, 1000次

FOR /l %%i IN (1,1,1000) DO (
adb shell am startservice -n com.nearme.themespace/com.nearme.themespace.services.ThemeApplyService --es THEME_PATH /sdcard/Themes/240699_Men.theme

##每次间隔8秒钟
ping -n 8 127.0.0.1>nul
echo %%i
)
pause

 

 

通过脚本运行,果然复现时间缩短了很多,很快就能复现。

 

接下来就是查找哪里出现的问题了,在网上找到一篇老外分析短信内存泄露的方法,分析中有如何使用脚本工具获取内存信息,很好用:

http://stevevallay.github.io/blog/2014/11/17/memory-leak/

文中说明为了获取内存信息,我们需要用到linux提供的 

procrank
procmem

这两个工具,一般我们的手机中不会自带这两个工具,需要去编译的版本中查找拷贝过来使用。

 

具体项目工具适配方法:
1、由于procmem和 Procrank 工具在我们的手机中一般没有放置,因此在调试各个项目时需要到各个项目编译的out下面的去找到对应的工具以及so库拷贝到目录中进行替换使用,
目录根据机器是32位还是 64位 有所不同:
out\target\product\msm8952_64\system\lib
out\target\product\msm8952_64\system\lib64


2、bat脚本也需要根据32位和64位push so库到对应目录, shell脚本需要根据需要调试的应用进程进行修改:

 

 

放置工具到手机中的bat脚本如下:

adb remount

adb shell rm -rf /sdcard/mem_info_log/

adb push libpagemap.so /system/lib64

adb push procrank /system/xbin
adb push procmem /system/xbin

adb shell chmod 777 /system/xbin/procrank
adb shell chmod 777 /system/xbin/procmem

pause

###放置到手机后台运行导出内存信息的shell脚本

adb push log.sh /system/bin

adb shell chmod 777 /system/bin/log.sh

adb shell sh /system/bin/log.sh &

pause

 

 

在手机中运行的shell脚本文件参照老外的写法如下:

#!/system/bin/sh
set `ps| grep android.process.contacts`
pidOfMms=$2
echo "pid of contacts is: $pidOfMms"

set `ps|grep com.nearme.themespace`
pidOfPhone=$2
echo "pid of themespace is: $pidOfPhone"

if [ ! -d "/sdcard/mem_info_log" ]; then
mkdir "/sdcard/mem_info_log"
echo "create new dir /sdcard/mem_info_log"
fi

while [ -d "/sdcard/mem_info_log" ]
do
echo "start capture mem_info_log"
t=`date +"%Y-%m-%d-%H-%M-%S"`
echo $$ > /sdcard/mem_info_log/pid.log
procrank > "/sdcard/mem_info_log/procrank"$t".log"
procmem -p $pidOfMms > "/sdcard/mem_info_log/procmem.contacts"$t".log"
procmem -p $pidOfPhone > "/sdcard/mem_info_log/procmem.themespace"$t".log"
dumpsys meminfo android.process.contacts > "/sdcard/mem_info_log/meminfo.contacts"$t".log"
dumpsys meminfo com.nearme.themespace > "/sdcard/mem_info_log/meminfo.themespace"$t".log"
cat /proc/$pidOfPhone/maps > "/sdcard/mem_info_log/maps.themespace"$t".log"
cat /proc/$pidOfMms/maps > "/sdcard/mem_info_log/maps.contacts"$t".log"
sleep 300
done

 

脚本中每隔五分钟会使用 

procrank
procmem

工具分别导出手机在压力测试过程中当前的内存信息,形成一个个文本放置在手机的一个目录下面,便于后续分析。

 

其中最终看到的有用信息在memInfo中:

Pss Private Private Swapped Heap Heap Heap
Total Dirty Clean Dirty Size Alloc Free
------ ------ ------ ------ ------ ------ ------
Native Heap 4160 3992 0 0 7936 6563 1372
Dalvik Heap 13589 13464 0 0 23033 22945 88
Dalvik Other 635 632 0 0
Stack 212 212 0 0
Ashmem 6 4 0 0
Other dev 4 0 4 0
.so mmap 797 80 28 0
.apk mmap 479 0 116 0
.ttf mmap 11 0 0 0
.dex mmap 3644 8 3632 0
.oat mmap 2844 0 796 0
.art mmap 1716 940 0 0
Other mmap 96 8 16 0
Unknown 110 104 0 0
TOTAL 28303 19444 4592 0 30969 29508 1460

App Summary
Pss(KB)
------
Java Heap: 14404
Native Heap: 3992
Code: 4660
Stack: 212
Graphics: 0
Private Other: 768
System: 4267

TOTAL: 28303 TOTAL SWAP (KB): 0

Objects
Views: 389 ViewRootImpl: 1
AppContexts: 7 Activities: 4
Assets: 3 AssetManagers: 2
Local Binders: 62 Proxy Binders: 27
Parcel memory: 9 Parcel count: 36
Death Recipients: 0 OpenSSL Sockets: 0

SQL
MEMORY_USED: 0
PAGECACHE_OVERFLOW: 0 MALLOC_SIZE: 0

 

看到了Activities的应用在持续增长,这样就能够确认应用确实存在OOM了,于是开始查对应Activity里面的代码。

最后发现在一个activity中存在了注册观察者处理,但是没有注销,其注册方式为:

getActivity().getContentResolver().registerContentObserver

问题的巧合之处就是原本正常的activity的生命周期其实每次就一次onCreate, 注册的调用是在这个方法中,如果是反复直接进入这个activity, 最多也就有一次引用,

因为再次进入是直接运行onstart的生命周期,不会在走入onCreate中了,所以如果反复测试电话本应用本身,此问题复现不了,反而是由于切换主题后,导致电话本又重新运行了

onCreate方法,这样每次都会运行注册,增加引用次数,导致后面出现引用过多,activity迟迟不能回收后导致OOM, 使用脚本观察,发现一般在15次左右就会出现OOM, 此时

电话本应用的内存已经接近120M, 这应该也是系统分配给一个应用的最大内存使用数值。

 

 

三、问题解决

 

当然发现问题原因后,解决起来就比较容易了,在onDestroy中增加注销即可:

getActivity().getContentResolver().unregisterContentObserver

 

 

随后再网上查阅了一下常用的OOM问题:

https://juejin.im/entry/5762b1d7816dfa00544680a0

 我这个问题就是属于

监听器没有注销造成的内存泄漏

所以注册和注销一定要记得成对使用。

 

问题过后又细想了一下为何这种注册没有注销会导致OOM, 于是猜测这种监听器的底层实现应该是使用了静态static 列表,将每个注册传递过来的activity或者其他参数

放入静态列表中,所以会导致无法回收。 并不是所有的用getActivity()作为参数传递过去之后都会导致OOM,比如初始化中获取资源文件,有时候也会使用这个方法传递参数

作为context, 这种就是没有问题的:

FeatureOption.init(getActivity());

 

不要风声鹤唳,以后所有的地方都不敢获取activity 传递参数了。

 

转载于:https://www.cnblogs.com/yangwubo/p/6609956.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值