App调试内存泄露之Context篇上.docx
- 文档编号:23842701
- 上传时间:2023-05-21
- 格式:DOCX
- 页数:10
- 大小:19.40KB
App调试内存泄露之Context篇上.docx
《App调试内存泄露之Context篇上.docx》由会员分享,可在线阅读,更多相关《App调试内存泄露之Context篇上.docx(10页珍藏版)》请在冰豆网上搜索。
App调试内存泄露之Context篇上
App调试内存泄露之Context篇(上)
Context作为最基本的上下文,承载着Activity,Service等最基本组件。
当有对象引用到Activity,并不能被回收释放,必将造成大范围的对象无法被回收释放,进而造成内存泄漏。
下面针对一些常用场景逐一分析。
1.CallBack对象的引用
先看一段代码:
@Override
protectedvoidonCreate(Bundlestate){
super.onCreate(state);
TextViewlabel=newTextView(this);
label.setText("Leaksarebad");
setContentView(label);
}
大家看看有什么问题吗?
没问题是吧,继续看:
privatestaticDrawablesBackground;
@Override
protectedvoidonCreate(Bundlestate){
super.onCreate(state);
TextViewlabel=newTextView(this);
label.setText("Leaksarebad");
if(sBackground==null){
sBackground=getDrawable(R.drawable.large_bitmap);
}
label.setBackgroundDrawable(sBackground);
setContentView(label);
}
有问题吗?
哈哈,先Hold住一下,先来说一下android各版本发布的历史:
/*2.22010-3-20,Froyo
2.32010-12-6,Gingerbread
3.02011-2-22,Honeycomb
4.02011-10-11IceCreamSandwich
*/
了解源码的历史,是很有益于我们分析android代码的。
好,开始分析代码。
首先,查看setBackgroundDrawable(Drawablebackground)方法源码里面有一行代码引起我们的注意:
publicvoidsetBackgroundDrawable(Drawablebackground){
//......
background.setCallback(this);
//......
}
所以sBackground对view保持了一个引用,view对activity保持了一个引用。
当退出当前Activity时,当前Activity本该释放,但是因为sBackground是静态变量,它的生命周期并没有结束,而sBackground间接保持对Activity的引用,导致当前Activity对象不能被释放,进而导致内存泄露。
所以结论是:
有内存泄露!
这是Android官方文档的例子:
http:
//android-
到此结束了吗?
我发现网上太多直接抄或者间接抄这篇文章,一搜一大片,并且吸引了大量的Android初学者不断的转载学习。
但是经过本人深入分析Drawable源码,事情发生了一些变化。
Android官方文档的这篇文章是写于2009年1月的,当时的AndroidSource至少是Froyo之前的。
Froyo的Drawable的setCallback()方法的实现是这样的:
publicfinalvoidsetCallback(Callbackcb){
mCallback=cb;
}
在GingerBread的代码还是如此的。
但是当进入HoneyComb,也就是3.0之后的代码我们发现Drawable的setCallback()方法的实现变成了:
publicfinalvoidsetCallback(Callbackcb){
mCallback=newWeakReference
}
也就是说3.0之后,Drawable使用了软引用,把这个泄露的例子问题修复了。
(至于软引用怎么解决了以后有机会再分析吧)
所以最终结论是,在android3.0之前是有内存泄露,在3.0之后无内存泄露!
如果认真比较代码的话,Android3.0前后的代码改进了大量类似代码,前面的Cursor篇里的例子也是在3.0之后修复了。
从这个例子中,我们很好的发现了内存是怎么通过回调泄露的,同时通过官方代码的update也了解到了怎么修复类似的内存泄露。
2.SystemService对象
通过各种系统服务,我们能够做一些系统设计好的底层功能:
//ContextImpl.java
@Override
publicObjectgetSystemService(Stringname){
ServiceFetcherfetcher=SYSTEM_SERVICE_MAP.get(name);
returnfetcher==null?
null:
fetcher.getService(this);
}
static{
registerService(ACCESSIBILITY_SERVICE,newServiceFetcher(){
publicObjectgetService(ContextImplctx){
returnAccessibilityManager.getInstance(ctx);
}});
registerService(CAPTIONING_SERVICE,newServiceFetcher(){
publicObjectgetService(ContextImplctx){
returnnewCaptioningManager(ctx);
}});
registerService(ACCOUNT_SERVICE,newServiceFetcher(){
publicObjectcreateService(ContextImplctx){
IBinderb=ServiceManager.getService(ACCOUNT_SERVICE);
IAccountManagerservice=IAccountManager.Stub.asInterface(b);
returnnewAccountManager(ctx,service);
}});
//......
}
这些其实就是定义在Context里的,按理说这些都是系统的服务,应该都没问题,但是代码到了各家厂商一改,事情发生了一些变化。
一些厂商定义的服务,或者厂商自己修改了一些新的代码导致系统服务引用了Context对象不能及时释放,我曾经碰到过Wifi,Storage服务都有内存泄露。
我们改不了这些系统级应用,我们只能修改自己的应用。
解决方案就是:
使用ApplicationContext代替Context。
举个例子吧:
//Forexample
mStorageManager=(StorageManager)getSystemService(Context.STORAGE_SERVICE);
改成:
mStorageManager=(StorageManager)getApplicationContext().getSystemService(Context.STORAGE_SERVICE);
3.Handler对象
先看一段代码:
publicclassMainActivityextendsQActivity{
//linttip:
ThisHandlerclassshouldbestaticorleaksmightoccur
classMyHandlerextendsHandler{
......
}
}
Handler泄露的关键点有两个:
1).内部类
2).生命周期和Activity不一定一致
第一点,Handler使用的比较多,经常需要在Activity中创建内部类,所以这种场景还是很多的。
内部类持有外部类Activity的引用,当Handler对象有Message在排队,则无法释放,进而导致Activity对象不能释放。
如果是声明为static,则该内部类不持有外部Acitivity的引用,则不会阻塞Activity对象的释放。
如果声明为static后,可在其内部声明一个弱引用(WeakReference)引用外部类。
publicclassMainActivityextendsActivity{
privateCustomHandlermHandler;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
mHandler=newCustomHandler(this);
}
staticclassCustomHandlerextendsHandler{
//内部声明一个弱引用,引用外部类
privateWeakReference
publicMyHandler(MyActivityactivity){
activityWeakReference=newWeakReference
}
//......
}
}
第二点,其实不单指内部类,而是所有Handler对象,如何解决上面说的Handler对象有Message在排队,而不阻塞Activity对象释放?
解决方案也很简单,在ActivityonStop或者onDestroy的时候,取消掉该Handler对象的Message和Runnable。
通过查看Handler的API,它有几个方法:
removeCallbacks(Runnabler)和removeMessages(intwhat)等。
//一切都是为了不要让mHandler拖泥带水
@Override
publicvoidonDestroy(){
mHandler.removeMessages(MESSAGE_1);
mHandler.removeMessages(MESSAGE_2);
mHandler.removeMessages(MESSAGE_3);
mHandler.removeMessages(MESSAGE_4);
//......
mHandler.removeCallbacks(mRunnable);
//......
}
上面的代码太长?
好吧,出大招:
@Override
publicvoidonDestroy(){
//Ifnull,allcallbacksandmessageswillberemoved.
mHandler.removeCallbacksAndMessages(null);
}
有人会问,当Activity退出的时候,我还有好多事情要做,怎么办?
我想一定有办法的,比如用Service等等.
4.Thread对象
同Handler对象可能造成内存泄露的原理一样,Thread的生命周期不一定是和Activity生命周期一致。
而且因为Thread主要面向多任务,往往会造成大量的Thread实例。
据此,Thread对象有2个需要注意的泄漏点:
1).创建过多的Thread对象
2).Thread对象在Activity退出后依然在后台执行
解决方案是:
1).使用ThreadPoolExecutor,在同时做很多异步事件的时候是很常用的,这个不细说。
2).当Activity退出的时候,退出Thread。
第一点,例子太多,建议大家参考一下afinal中AsyncTask的实现学习。
第二点,如何正常退出Thread,我在之前的博文中也提到过。
示例代码如下:
//ref
privatevolatileThreadblinker;
publicvoidstop(){
blinker=null;
}
publicvoidrun(){
ThreadthisThread=Thread.currentThread();
while(blinker==thisThread){
try{
thisThread.sleep(interval);
}catch(InterruptedExceptione){
}
repaint();
}
}
有人会问,当Activity退出的时候,我还有好多事情要做,怎么办?
请看上面Handler的分析最后一行。
(未完待续)
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- App 调试 内存 泄露 Context