Android应用程序如何避免内存泄漏以及如何检查泄漏原因_第1页
Android应用程序如何避免内存泄漏以及如何检查泄漏原因_第2页
Android应用程序如何避免内存泄漏以及如何检查泄漏原因_第3页
Android应用程序如何避免内存泄漏以及如何检查泄漏原因_第4页
Android应用程序如何避免内存泄漏以及如何检查泄漏原因_第5页
已阅读5页,还剩4页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、android应用程序如何避免内存泄漏以 及如何检查泄漏原因android的应用程序开发使用的java语言。java语言的gc机制使得在堆上分 配内存之后无需再手动的释放内存,而是等待垃圾收集器来收集无用的对彖以 凹收它们占用的内存。同时在android的进程管理机制中每一个单独的应用程 序在启动时都会创建一个新的linux进程来运行该程序,应用程序在运行屮分 配的内存也会在该应用程序退出时随着进程的销毁而释放,所以android中的 内存管理给开发人员造成的负担较轻。但应用程序还需耍在内存使用上注意不 要使应用程序占用大量内存,原因有如下两点:1应用程序占用的内存越少,android可以同时

2、放入内存程序就越多,用 户切换这些不同的程序所消耗的时间就越少,体验就越流畅。2. 如果应用程序在消耗光了所有的可用堆空间(16m到48m),那么再试图 在堆上分配新对象时就会引起oom (out of memory error)异常,此时应 用程序就会崩溃退出。所以在编写android应用程序时,仍然需要对应用程序屮内存的分配和使用多 加注意,特别是在存在后台线程、使用图片作为背景、在异步任务或者后台线 程中需要context ±下文对象的情况下,要注意避免出现对activity、view或 drawable等类的对象长期持有无用的reference,否则就会造成被引用的对象 无法

3、在gc时回收,而是长期占用堆空间,此时就发生了内存泄漏。持有context引用造成的泄漏f面介绍一下android开发文档中(avoi ding memory leak)的一个内存泄漏的 例子,该例子说明了 android应用程序中会引起内存泄漏的常见原因:长期保 持了对context对象的引用。在android应用程序中,很多操作都用到了 context对象,但是大多数都是用來加载和访问资源的。这就是为什么所有的 显示控件都需要一个context对象作为构造方法的参数。在android应用程序 中通常可以使用两种context对象:activity和application。当类或方法需 要c

4、ontext对象的时候常见的作法是使用第一个作为context参数。但这就意 味着view对象对整个activity保持引用,因此也就保持对activity内的所有 东西的引用,也就是整个view结构和它所有的资源都无法被及时的回收,而且 对activity的长期引用是比较隐蔽的。0verri deprotected void oncreate(bundle state) super. oncreate(state);textview label = new textview(this); labe1.settext("leaks are bad");setcontentv

5、iew(label); 当屏幕方向改变时android系统默认作法是会销毁当前的activity,然后创 建一个新的activity,这个新的activity会显示刚才的状态。在这样做的过 程android系统会重新加载ui用到的资源。现在假设的应用程序中有一个 比较人的bitmap类型的图片,每次旋转时都重新加载图片所用的时间较多。为 了提高屏幕旋转时activity的创建速度,最简单的方法是用静态变量的方法。private static drawable sbackground;©overrideprotcctcd void oncrcatc(bundle stato) supe

6、r. oncrcatc(statc);textview label = new textvi ew(thi s); labe 1. settext(z,leaks are bad");if (sbackground 二二 null) sbackground 二 getdrawable(r. drawable. large_bitmap); " label. setbackgrounddrawable(sbackground);setcontentvicw(labcl);这样的代码执行起来是快速的,但同时是错误的:这样写会一直保持着对 activity的引用。当一个drawa

7、ble对象附属于一个view时,这个view就相 当于drawable对象的一个冋调(引用)。在上面的代码片段中,就意味着 drawable和textview存在着引用的关系,而textview自己持有了对activity(context对象)的引用,这个activity又引用了相当多的东西。有两种简单的方法可以避免由引用context对象造成的内存泄露。首先第一个 方法是避免context对象超出它的作用范i韦i。上面的例子展示了静态引用的情 况,但是在类的内部,隐式的引用外部的类同样的危险。第二种方法是,使用 application对象。这个context对象会随着应用程序的存在而存在,而

8、不依 赖于activity的生命周期。如果你打算对context对象保持一个长期的引用, 请记住这个 application 对象。通过调用 context. getapplicationcontext () 或者activity. getapplicationo.方法,你可以很容易的得到这个对象。非静态内部类引起的内存泄漏java语言屮可以在一个类的内部定义inner class,这在很多时候减少了工作 量,而且使代码更加紧凑,但是由于非静态的inner class对象屮包含了产生 构造该inner class的包围类的引用,这样在android环境屮也会可能会因为对 包围类的长期引用而造成

9、内存泄漏。我们修改上一个例子的代码,将sbackground由静态成员变量改为普通成员变 量,这样就消除了该静态对彖持有activity的引用所造成的泄漏。z后我们在 该activity类中定一个非静态的内部类inner,并且对该activity定义一个 静态变inncrlnstanceo具体代码如下:public class memoryleak extends activity class inner int somcmcmbcr;private static inner innerlnstance;private drawab1e sbackground;/* called when t

10、he activity is first created. */ ©overridepublic void oncreate(bundle savedinstancestate) super oncreate(savedlnstancestate);textview 1abel 二 new textview(this);label. settext (z,leaks are bad,z);sbackground 二 getresources(). getdrawable(r. drawable, picl);if (innerinstance = null) innerlnstanc

11、e = new inner();label, setbackgrounddrawable(sbackground); setcontentview(label);上面例子代码在旋屏时同样会引起内存泄漏,原因是innerlnstance在第一次 构造时会持有当时activity的引用。当旋屏引起activity重新被构造,新的 activity对象执行oncreate ()函数时,作为activity类的静态成员变量 innerlnstance此时不为null,所以并不会被重新构造,但它还保留着之前 activity对象的引用,造成了该activity对彖无法被释放。在上而的例子屮 也就意味着至

12、少一个drawable对彖占用的内存没有被及时回收。对于上述两种 常见的内存泄漏,我们在开发屮需耍记住以下2点:1. 对activity的持久引用,对activity的引用应该和activity本身有相 同的生命周期,尽量使用application代替activity作为context来获 取资源,或者构造dialog或toasto2. 如果不能控制非静态的内部类的生命周期,尽量在activity中避免有非 静态的内部类。迥时在activity中使用静态的类时如果需更引用 activity,应该釆用 weakreference 弱引用来引用 activityo后台线程操作引起的泄漏在andro

13、id开发中常常需要在后台线程中执行查询或者后台操作。android本 身也提供了一些常见的异步任务类來简化多线程编程,譬如我们可以使用 asyncqueryhandler来执行后台査询任务,使用asynctask来执行异步的后台 任务。这些async前缀的异步任务类在执行时都会在ui线程之外新开了一个线 程来执行。我们在使用这些异步任务类的时,需要注意之前提到的不要将这些 异步任务类的派生类定义为activity的non-static inner class以避免之前 提到的inner class引起的内存泄漏。除此z外,还需要注意不要在这些异步 任务类屮持有activity的强引用,而应该采

14、用weakreference来保存 activity的引用。这样就不会出于后台线程在执行时activity对彖无法及时 释放。例如在联系人列表(contactslistactivity)中查询联系人的 queryhandler类,它作为asyncqueryhandler的派左类主要用来执彳亍查询联系 人数据库的操作,联系人较多的话查询操作用时就会较长,所以采用 asyncqueryhander在后台线程中执行查询。在该类的立义中就通过以下两点来 消除内存泄漏:1. 将 queryhander 定义为 static inner class, 避免 non-static inner class对彖

15、持有的contactslistacitivty对彖的引用2. 通过 weakreference 来保存 contactsij staci tivi ty 的引用。这样就保证了该后台查询线程不会持用contactslistactivity的强引用,从而 保证了即使后台查询线程正在执行的情况下,contactslistacitivity在响应 home键或者旋转屏幕时也能够被gc回收。private static class queryhandler extends asyncqueryhandler protected final weakreference<contactslistact

16、ivity> mactivity; protected boolean mloadingjoinsuggestions = false;©overrideprotected void onquerycomplete(int, token, object cookie, cursor cursor) final contactslistactivity activity 二 mactivity. get();if (activity !二 null && !a.ctivity. isfinishingo) 与我们正常使用的强引用类型不同,上述示例代码weakref

17、erence并不会增 加contactslistactivity的引用计数,在使用weakreference类型的 mactivity变屋时,也需要判断其get()返回的结果是否为null来判断 contactslistactivity对象是否还存在。在java语言中,有以下四种引用类 型,1. strong reference(强引用):通常我们编写的代码都是strong ref,于 此对应的是强可达性,只有去掉强可达,对象才被回收。2. soft reference (软引用):对应软可达性,只耍有足够的内存,就一直 保持对象,直到发现内存吃紧且没有strong ref时才冋收对彖。一般可

18、 用來实现缓存,通过java. lang. ref. softreference类实现。3. weak reference (弱引用):比sofl ref更弱,当发现不存在strong ref时,立刻回收对象而不必等到内存吃紧的时候。通过java. lang. ref. weakreference 和 java. util. weakhashmap 类实现。4. phantom reference (虚引用):根本不会在内存中保持任何对象,你只 能使用phantom ref本身。一般用于在进入final ize()方法后进行特殊 的清理过程,通过 java. lang. ref. phanto

19、mreference 实现。关于这p4种不同的类型,可以查看垃圾回收机制与引川类型文屮的详细说 明。在使用asynctask执行后台任务时,也要注意将其定义为static inner class,并且在其 doinbackgroundo 函数中检查 iscemccllcd()是否为 true, 从而可以尽快的退出后台线程。android开发手册中说明如果需要在后台执行 任务时,尽量采用asynctask并按照其规范重载相应的钩子函数。但java语言 中传统上使用runnable及thread来开起新的线程并执行后台任务的作法也是 可以的,只是android并不提倡这样作,特别是在应用程序中通过

20、thread在后 台执行耗时较长的任务。例如下面的示例代码:new thread () ©overridepublic void run () /do something或者runnable task = new runnable() public void run() /do something;thread t 二 new thread(task);t. start ();上述直接使用thread类创建线程来执行后台任务有很大的局限性,由于 android要求ui界面的更新必须是在ui主线程屮进行,所以如果后台线程屮 想要操作ui界面屮的元素(例如更新进度条)就必须通过handl

21、er来发送消 息,反而增加了代码的复杂性。如果使用标准的asynctask,则只需耍实现 onprogressupdateo钩子函数,就可以在后台任务执行的同时动态的更新ujl界 面。由于是onprogressupdate()钩子函数是在ut主线程屮执行,在该钩子函 数屮可以安全的操用ut界面。而.fl. asynctask在可以在oncancell ed()或 onpostexecuteo钩子函数中操用ui界面元素以通知用户后台任务的执行结 果,上述直接采用thread类的方法仍然必须通过handler才能更新用户界面。 线程之间通过handler通信引起的内存泄漏android 线程z间进

22、行通信时最常用的作法是通过接收消息的h标线程所持 有handler对象来创建message对象,然后再向h标线程发送该message。在 戸标线程中handler在执彳亍handlemessage ()时会根据相应message来执行相 应不同功能。另外一种作法是通过handler对彖向戸标线程直接发送runnable 对象来执彳了该runnable对象中不同的功能代码。在通过handler进彳亍通信时如 果不注意,也很有可能引起内存泄漏。例如线程之间通过message和runnable 对象进行通讯的例子。private drawable sbackground;priva.tc messa

23、ge msg;©overrideprotected void oncreate(bundie saved!nstancestate)super. oncreate(savedinstancestate);textvicw label 二 new textvicw(this);label, scttcxt("leaks are bad");sbackground = getresources(). getdrawable(r. drawable, picl); label. setbackgrounddrawable(sbackground);setcontentv

24、iew(label);otherthread t 二 new otherthread ('"other thread/z); t start ();handler h = new handler (tgetlooper() public void handlcmcssagc(message msg) log. d(tag,is + msg. tostring();log. d(tag, "thread " + thread, currentthread(). gelld() + " handied message");try msg =

25、 h- obtainmessage (101);h-sendmessage(msg);timeuni t. seconds, s 1 eep (3);h. post(new runnable() publ ic void run() log. d (tag, ,zhahahaz,);try timeunit. seconds, sleep(30); catch (interruptedexception e) / todo auto-generated catch blocke. printstacktrace ();log. d (tag, "thread " + thr

26、ead, curre nt thread (). gettd () + " waken up");); catch (interruptedexception e) / todo auto-generated catch blocke. printstacktracc();private static class otherthread extends handlerthread otherthread (string name) super (name);上述例了代码模拟了 ui线程同另一线程othcrthrcad z间通过handler进行通 讯的情景,ui线程会通过发

27、送message对象和runnable对象来委托 otherthread来执行不同的任务,这时message代表的任务是轻量级的任务, 其执行速度很快,这里就是打出两行log。通过runnable对象发送的任务是重 量级的任务,这个任务会执行30秒。在上述存在两处会引起内存泄漏的地方, 首先是在activity的msg成员变量在将message发送给otherthread后,仍然 是指向这个己发送的message,尽管otherthread能够很快的处理这一 message 对彖任务并随后释放对该message的引用。但是在ui线程中,在发送完该 message斤会长时间的执行其它任务(这里用

28、休眠3秒代表执行其它任务),而 在这3秒过程中曲于msg成员变量仍然是指向z前已经完成的message对彖。 这就使得该message对象迟迟不能释放。如果message带有大量的信息的话, 那么其占用的内存就一直不能被gc。另一处会引起内存泄漏的地方是发送给 otherthread的是非靜态的runnable inner class,所以该runnable对象就持 有当前activity的引用。曲于该runnable执行的任务比较重,所以在 otherthread完成前activity是无法被gc的。如果otherthread在执行 runnable任务时用户旋转了手机屏幕,那么旋屏后己经无

29、用的activity还会 存在将近30秒,也就是activity屮所有占用内存在30秒中无法被gc。为了 修正上述内存泄漏的问题,需要对例子屮的代码做如下修改:1. 在scndmessage完成z后显示的将msg成员变量置为null。msg 二 h. obtainmessage(lol);h.sendmessage(msg);msg = null;1. 将runnable对象改为static类型的内部类,或者不用runnable对彖来 进行线程间通讯而是将runnable的屮的功能代码转移到otherthread的 handler 对彖的 handlermessage 函数中。u1 线程和 o

30、therthread 只用 message对象来进行通讯。其它常见的引起内存泄漏的原因1. listadapter的getviewo函数中没有使用参数中的convertview. public view gctvicw(intposition,vicwconvcrtview, viewgroupparcnt)来向 listvicw 提供每一个 item 所需要的view对象。初始时listvicw会从bascadaptcr中根据当前 的屏幕布局实例化一定数量的view对象,同时listvicw会将这些view 对象缓存起來。当向上滚动listview时,原先位于最上面的list item的vi

31、ew对象会被冋收,然后被用来构造新出现的最下面的list itenio这个构造过程就是由getviewo方法完成的,getviewo的第二个 形参view convertview就是被缓存起来的listitem的view对象(初始 化时缓存中没有view对象则convertview是null) 0由此可以看出,如 果我们不去使用convertview, jfij是每次都在getviewo +重新实例化一 个view对彖的话,即浪费资源也浪费时间,也会使得内存占用越来越 大,直到最后引发gc。对于2.3版本的android还没有实现concurrent gc,所以在listview上下滚动时就会

32、由于虚拟机执行gc而卡住 listview的滚动,这时给用户的感觉就是界面不够流畅。2. bitmap对象不在使用时调用recycle()释放内存,有时我们会手工的操 作bitmap对象,如果-一个bitmap对象比较占内存,当它不在被使用 的时候,可以调用bitmap, recycle ()方法回收此对象的像素所占用的内 存。3. 查询得到的cursor未能及时关闭。eclipse中mat工具的使用如果在应用程序中发生了 00m界常导致的错误,我们需要首先定位一卜是否是 由于自身程序的原因。方法是重新打开之前出错退出的应用程序,然后在控制 台屮运行:$adb shell然后在打开的adb s

33、hell终端中执行:#procrank procreink就会输出类似卜'面的列表:pidvssrsspssusscmdline93568544k42160k19570k15840ksystem server100237600k35124k14912k12804k oms. home122133828k33828k12259k9440k com. android, phone253731916k31916k11510k9324k com. android. browser295628768k28768k9034k7152k com. hiapk. market 854268k268k8

34、9k84k/systcm/bin/sctviccmanagcr859444k444k86k28k/system/bi n/sdm920268k268k85k80k/system/bin/cbmon883404k404k84k28k/system/bin/sdm857276k276k81k76k/system/bin/debuggerd其中最左一例是当前正在运行所有进程的pid,接下來的四个字段分别为 vss : virtual set size虚拟耗用内存(包含共享库占用的内存)rss : resident set size实际使用物理内存(包含共享库占用的内存,如果有两个 进程使用一个占用的

35、空间为10m的so,那么这两个进程的rss就会分别增加 10m) pss : proportional set size实际使用的物理内存(比例分配共享库 占用的内存,如果有两个进程使用一个占用的空间为10m的so,那么这两个进 程的rss就会分别增加5m) uss : unique set size进程独自占用的物理内 存(不包含共享库占用的内存)在上面列表中找到z前应用程序所对应的进程 后,再重复z前引起00m错误的操作,如果发现该进程使用的内存不断的增加 (通常是uss段),那么该进程对应的应用程序就很可能存在内存泄漏。在初 步主位了内存泄漏z后,可以通过eclipse的mat插件来分析一下该进程屮内 存使

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论