对于众多 android 程序员而言,在需求与应用性能之间,主要精力都会放到新需求的开发。随着项目复杂度的增加,应用性能越来越低,出现各种问题。程序员们奔波于各种“救火现场”,疲于奔命。本文作者 aritra roy 分享了自己在 android 应用程序开发过程中所遇以及思考,针对内存泄漏提炼出一套可以应用于开发中的方法论。也许会让你的开发效率事半功倍。
作者 | aritra roy,android 开发者
译者 | 罗昭成,责编 | 唐小引
封图 | csdn 付费下载自东方 ic
出品 | csdn(id:csdnnews)
以下为译文:
我们都知道,写一个 android 的应用很容易,但是要写一个高性能的应用可就不容易了。以我的个人经验来说,在 app 的开发过程中,主要的精力都会放在新功能、新模块、新组件的开发上。
开发过程中,看得见的 ui 比看不见的性能更能吸引我们的目光。所以我强制自己将“优化应用程序(如内存泄漏)”的优先级提高,并养成习惯。
长期以来,不关注性能,带来了很多的技术债。经过一年多的努力调整, 比起一年前,我有很多的心得体会。
对于很多开发者来说,内存泄漏都是一个老大难的问题。关于处理内存泄漏,你有可能会觉得太难,又或是太费时,又或者是觉得完全没有意义。但我要告诉你的是,事实并非如此。当你开始处理这些问题的时候,你会发现,这感觉超级棒。
在本篇文章中,我会以尽可能简单的方式讲解这些问题,即使你是一个初学者,也可以学习到如何构建一个高质量、高性能的应用。
垃圾回收
java 是一个非常强大的语言。在 android 中,我们几乎不会像 c / c++ 那样,手动分配和释放内存。因为 java 会自动清理内存。
让我们来思考一个问题,如果 java 内建的垃圾回收系统可以在我们不需要的时候自动回收内存,那我们为什么还需要关心内存呢?是因为垃圾回收机制不够完善吗?
当然不是,java 垃圾回收机制当然是完善的。垃圾回收机制是可以正常工作,但是,如果我们的应用程序出现 bug, 导致垃圾回收器不能正常检查出不需要的内存块,就会导致问题。
总体来说,是我们自己的代码错误导致垃圾回收不可用。不可否认,垃圾回收机制是 java 最伟大的设计之一。
关于垃圾回收器
在处理内存问题之前,你需要了解垃圾回收器的工作原理。它的概念非常简单,但在它背后,有着极其复杂的逻辑。但是你也别担心,我们也只关心一些简单的概念。
如图所示,android 或者 java 应用程序都有一个起点,从对象的初始化,并且调用方法。我们可以认为,这个点就是图中的 “gc roots”。有一些对象引用被 gc roots 直接持有,剩下的则由它们自己创建并持有。
如此,整个引用链构成了内存树。垃圾回收器从 gc roots 开始,直接或间接的链接着其它的对象。当整个遍历结束,还有一些不能被访问到的对象,就是变成了垃圾,这些对象就会被垃圾回收器回收。
内存泄漏
到现在,你已经知道了垃圾回收的概念,也知道了垃圾回收在 android 中是如何管理内存的。下面,我们将深入研究一下内存泄漏。
简单来说,内存泄漏是指你的对象已经使用结束,但是它却不能被释放掉。每个对象在完成它自己的生命周期过后,都需要从内存中清理出来。但是如果一个对象被另一个对象直接或间接的持有,垃圾回收器不能检查出它已经使用结束。朋友们,这样子就导致了内存泄漏。
值得庆幸的是,我们并不需要太担心所有的内存泄漏,因为并不是所有的内存泄漏都是有害的。有一些内存泄漏是无关痛痒(只泄漏几 kb 的内存),并且,在 android framwork 层也会有一些内存泄漏,但是你并不需要去修复,因为它们对 app 的性能影响微乎其微,你可以忽略。
但是有一些会引起 app 崩溃, 或者严重卡顿。这些都是需要你时刻注意的。
为什么要解决内存泄漏?
没有人会想使用一个又慢又占内存的应用。如果使用一段时间就会崩溃,你的用户也会“崩溃”掉,如果长时间出现这样子的问题,你的用户会毫不犹豫的卸载掉你的应用,并且再也不会使用它。
如果你的应用中存在内存泄漏,垃圾回收器不能回收不使用的内存,随着用户使用时间的增长,内存的占用会越来越多。如此下去,当系统不能在给它分配更多内存的时候,就会导致 outofmemoryerror, 然后应用程序会崩溃掉。
垃圾回收有利有弊,垃圾回收是一庞大的系统,在应用中,尽可能少的让垃圾回收器运行,这样对应用体验会更好。
随着你的应用使用的堆内存逐渐增加,short gc 就会触发,来保证立即清理无用对象。现在这些快速清理内存的 gc 运行在不同的线程中,这些 gc 不会导致你的应用变慢。
但是如果你的应用中存在严重的内存泄漏,short gc 没有办法回收内存,并且占用内存持续增加,这将会导致 larger gc 被触发。它会将整个应用程序挂起,阻塞大概 50~100ms,这会导致应用程序变慢并且有可能不能使用。
修复内存泄漏,减少对 app 的影响,给用户提供更好的体验。
如何发现内存泄漏?
现在,你已经认识到,你需要修复隐藏在你 app 中的内存泄漏。但是,我们如何才能找到它们呢?
android studio 为我们提供了一个非常强大的工具:monitors。
通过它,你能看到网络、cpu、gpu、内存的使用情况。
在调试运行 app 的时候,要密切关注内存监视器。内存泄漏的第一个现象就是,在使用的过程中,内存一直增加,不能减少,即使你把 app 退到后台也不能释放。内存分配监视器能够清楚的看到不同对象所占用的内存,可以清楚的知道哪个对象占用内存较多,需要处理。
但是,它本身还不够,它需要你指定时间,然后转存出对应的内存堆。这是一个很无趣的工作。
幸运的是,我们现在已经有更好的方式来实现。leakcanary, 一个和 app 一起运行的库,它会在内存泄漏的时候,转存出内存信息,然后给我们发送一个通知并给我们一个有用的栈信息。
常见的内存泄漏
从我的经验来看,有很多相似且经常出现内存泄漏的问题,你在你每天的开发中,都有可能会遇到它们。一衡水中学招生条件但你清楚了它们发生的时间、地点、原因 ,你就可以很轻松的修复它们。
未取消的 listener很多时候,你在 activity/fragment 中注册了一个 listener, 但是忘记取消注册了。如果你的运气不好,它很可能会引起一个严重的内存泄漏问题。一般来说,这些 listener 的 注册与取消注册是同步出现的,在你使用的时候需要注册,在不使用的时候需要取消注册。
举个例子,当我们的应用程序需要获取定位的时候,需要使用 locationmanager,你会从系统服务中拿到它,并且给其设置一个地理位置更新的回调:
private void registerlocationupdats {
mmanager = (locationmanager) getsystemrvice(context.location_rvice);mmanager.requestlocationupdates(locationmanager.gps_provider,timeunit.minutes.tomillis(1),
100,
this);
}
在代码中,可以看出来,使用了 activity 自己来实现了地理位置更新的回调。locationmanager 会持有这个回调的引用。当你退出了这个页面,android 系统会调用 ondestory ,但是垃圾回收器并不能清理掉它,因为 locationmanager 持有它的强引用其实我懂你。
当然,解决方案也很简单,就是在 ondestory 方法中,取消注册就可以了。
@override
public voidondestroy {
super.ondestroy;
if (mmanager != ) {
mmanager.removeupdates(this);
}}
内部类内部类在 java 和 android 开发中经常用到,非常简单,但是如果使用不当,也会造成严重的内存泄漏。让我们先来看一个简单的例子:
public class badactivity extends activity {
private textview mmessageview;
@override
protected voidoncreate(bundle savedinstancestate) {
super.oncreate(savedinstancestate);
tcontentview(r.layout.layout_bad_activity);mmessageview = (textview) findviewbyid(r.id.messageview);new longrunningtask.execute;
}private class longrunningtask extends asynctask<void, void, string> {
@override
protected string doinbackground(void... params) {
// 做一些耗时操作
return "am finally done!";
}
@override
protected voidonpostexecute(string result)在太行山上合唱 {
mmessageview.ttext(result);
}
}
}
这是一个很简单的 activity 页面,在页面启动的时候,在后台启动了一个耗时的任务(比如说,复杂的数据库查询或者是很慢的网络)。等到任务执行结束,把拿到的结果显示到页面上。看起来,这样做并没有问题。事实上,非静态的内部类会隐式的持有智慧团建初始密码是多少外部类的引用(在这里,就是 activity)。如果在耗时任务执行完之前,你旋转屏幕或者退出这个页面,垃圾回收器就不能从内存中清理掉 activity 的实例。这个简单的问题会导致很严重的内存泄漏问题。
当然,解决方案也非常地简单,如下:
public class goodactivity extends activity {
private asynctask mlongrunningtask;
private textview mmessageview;
@override
protected voidoncreate(bundle savedinstancestate) {
super.oncreate(savedinstancestate);
tcontentview(r.layout.layout_good_activity);mmessageview = (textview) findviewbyid(r.id.messageview);mlongrunningtask = new longrunningtask(mmessageview).execute;
}@override
protected voidondestroy {
super.ondestroy;
mlongrunningtask.cancel(true);
}private static class longrunningtask extends asynctask<void, void, string> {
private final weakreference<textview> messageviewreference;
publiclongrunningtask(textview messageview) {
this.messageviewreference = new weakreference<>(messageview);
}@override
protected string doinbackground(void... params) {
string message = ;if (!iscancelled) {
message = "i am finally done!";
}return message;
}@override
protected voidonpostexecute(string result) {
textview view = messageviewreference.get;if (view != ) {
view.ttext(result);}}}}
正如你看到的代码,首先我将非静态内部类改成了静态内部类,这样它就不会持有外部类的引用了。当然,使用静态的内部类,非静态的变量就不能访问了。所以我们需要将 textview 通过构造方法把它传过去。
在这里,我强烈推荐使用 weakreference ,它能更好的避免引起内存泄漏。你应该去学习 java 中关于不同引用类型的知识:
http://javarevisited.blogspot.in/2014/03/difference-between-weakreference-vs-溪谷生态园softreference-phantom-strong-reference-java.html
匿名内部类匿名内部类也是在开发过程中经常使用到的一个东西,它的定义和使用都非常的简洁。但以我的经验来看,匿名内部类造成了大量的内存泄漏的问题。
匿名内部类与非静态内部类相似,造成内部类的原因也和上面说的一样。你有可能在好多地方都使用了匿名内部类,如果使用不当,会严重影响 app 的性能。
public class moviesactivity extends activity {
private textview mnoofmoviesthisweek;
@override
protected voidoncreate(bundle savedinstancestate) {
super.oncreate(savedinstancestate);
tcontentview(r.layout.layout_movies_activity);mnoofmoviesthisweek = (textview) findviewbyid(r.id.no_of_movies_text_view);moviesrepository repository = ((moviesapp) getapplication).getrepository;repository.getmoviesthisweek.enqueue(new callback<list<movie>> {
@override
public voidonrespon(call<list<movie>> call,
respon<list<movie>> respon) {int numberofmovies = respon.body.size;
mnoofmoviesthisweek.ttext("no of movies this week: " + string.valueof(numberofmovies));
}@override
public voidonfailure(call<list<movie>> call, throwable t) {
// oops.
}
});
}
}
上面的例子中,我使用一个常用的网络库 retrofit 发送了一个网络请求,然后在 textview 中显示返回的结果。很明显,那个 callback 对象持有 activity 的引用。如果现在网络很慢,在网络响应回来之前,页面旋转或者关闭,就会导致 activity 泄漏。
我强烈建议,在需要的时候,尽量使用静态的内部类,而非匿名内部类。当然,我的意思不是不在使用匿名内部类,如果你需要使用匿名内部类,你需要注意引起内存泄漏的问题,保证不会出现问题。
bitmaps在应用中,你看到的所有图片都是 bitmap 对象,包含了所有的像素数据。现在这些 bitmap 数据非常的大,一个处理不好,就会引起 oom, 造成 app 崩溃。在 app 中使用的图片资源生成的 bitmap 会由系统进行管理,但是如果你需要自己处理 bitmap ,要记住,使用完过后要调用 bitmap.recycle 来释放资源。
本文发布于:2023-04-05 06:40:33,感谢您对本站的认可!
本文链接:https://www.wtabcd.cn/fanwen/zuowen/a7fe8f7430535852c8bdc78e5ee4fb61.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文word下载地址:csdn泄露数据(个人信息泄露处理方法).doc
本文 PDF 下载地址:csdn泄露数据(个人信息泄露处理方法).pdf
留言与评论(共有 0 条评论) |