Android内存优化之内存泄漏的陋习

Android内存优化之内存泄漏的陋习

上篇写到内存泄漏的一些概念https://blog.csdn.net/Joson_Chou/article/details/83750514,说到内存泄漏的一些分析性的原因,这篇要说下具体的内存泄漏案例,在我们开发Android时很多人douh都会犯错的例子,特别是对于新手。

主要案例如下:

(1)BroadcastReciver在使用代码方式注册时,宿主(Activity、Fragment、Service等)退出时忘记反注册;

(2)单例模式下对象的释放问题;

(3)使用Handler产生的内存泄漏;

(4)线程的内存泄漏;

(5)数据库游标忘记关闭问题;

(6)构造Adapter时不习惯使用缓存的convertView;

(7)使用完Bitmap时没有调用recycle()方法区释放资源;

(8)WebView造成的内存泄漏;

(9)内部类持有外部类对象造成内存泄漏;

(10)第三方SDK内部内存泄漏;

 

BroadcastReciver内存泄漏

BroadcastRevicer广播接收器分两种注册方式,一种是通过AndroidManifest.xml静态方式注册,这类造成内存泄漏的方式比较hanj罕见,至少小编到现在也没遇到过;还有一种注册方式就是代码动态注册

registerReceiver(BroadcastRevicer,IntenFilter),大部分接触andrAndroid开发的人都会写广播,但是很多粗心人没注意到在使用完BroadcastReviver时进行fa反注册unregisterReceiver(BroadcastReciver);

此类型可延伸到一些常用的第三方开源框架,比如注解框架Butterknife使用完后要进行unbind、EventBus在退出时要进行unregister()操作。

 

单例对象内存泄漏

我们使用单例对象经常要传入一个Context对象,比如我们经常会用单例封装一个数据库操作类,这样方便我们不用频繁的创建对象,减少资源开销,由于单例的静态特性使得其生命周期和应用的生命周期一样长(JVM加载类的顺序,可参看此篇文章http://www.importnew.com/25295.html),所以在传入Context时如无特殊需求,可传入Application的context,直接调用context.getApplicationContext()进行传入。

 

Handler造成的内存泄漏

使用Handler我们再熟悉不过了,通过发送message来达到消息之间的传递,但是在使用Handler时我们需要在应用退出时需要手动进行消息队列MessageQueue清空,

 

mHandler.removeCallbacksAndMessages(null);

第二点就是我们在使用handler发送message对象,message对象强烈建议使用mHandler.obtainmessage()或者Message.obtain()方法来构造纤细对象,这样的方式好处就好在可以Message对象复用,通过在消息池里返回一个Message对象,避免重复new一个message对象造成资源浪费和内存消耗。

Handler内存泄漏就可能注意的比较少,先看一段代码:

在test()方法里我们希望能够将obj对象实例的操作放到post的某个线程的MessageQueue中,但是在上述所xi写的代码当中即使Handler所在的线程用完了obj对象了,但这个对象依然不会被GCqing清除,因为外部的Activityyi一直保持着这个对象的引用,在Java中,非静态内部类和匿名类内部类会隐式持有waib外部类对象的引用,这一点比较重要,相反静态内部类就不会存在引用外部类对象的情况,在解决这类问题时,我们可以以下这样的改进:

或者在不使用handler时进行手动置空 mHandler = null;

 

线程的内部泄漏

刚刚说到了匿名内部类会隐式持有外部类对象的引用,我们启动线程一般分以下方法:

(1)集成Thread类,实现run()方法;

(2)实现Runnable对象;

(3)集成AsyncTask,在doInBackGround()做耗时操作;

AsyncTask和Runnable都使用了匿名内部类,他们都将持有外部类对象的隐式引用,当我们activity退出时,如果我们的线程方法还没执行完,那么activity的内存资源将无法被GC回收,最直接的方法就是在退出时手动将线程关闭或者置为静态类型。

 

资源未关闭导致内存泄漏

上面提到了广播接收器未正确的反注册,与这类内存泄漏类型基本一致,都是未正确的使用API造成资源得不到回收,我们在使用数据库操作对象Cursor时,经常会忘记查询完后忘记关闭游标,正确使用如下面代码所示:

我们查询数据库数据时需在用完cursor对象时在finally模块里进行先cursor判空,为什么需要先判空,因为在查询数据时数据库可能不存在数据,直接返回一个null给cursor对象,在不为空时进行cursor.close();

在使用Bitmap时,我们需要注意的是Bitmap本身在内存中非常占内存,当Bitmap对象一多,会导致内存紧张,造成我们的Android系统异常卡顿,所以在使用完Bitmap对象需要手动释放资源bitmap.recycle();

使用WebView时我们需要在确认不再使用时将WebView进行destory(),避免占用内存;

构造Adapter未使用缓存convertView

我们以前经常会使用ListView(RecyclerView未出现时),通过传入一个Adapter来显示我们的数据,通过继承BaseAdapter来构造我们的Adapter,ListView会xian显示一定数量的item,当我们手指向上滑动ListView时,位于ListView的顶部item对象会被回收,然后被用来构造新出现的最下面的item对象,通过getView()方法完成,如果没有convertView,那么在给getView()时都会重新new一个item对象出来,这样会造成资源的浪费与内存开销,所以在使用Adapter时需要去缓存convertView。

 

第三方SDK内部泄漏

我们要完成一个成熟的APP,抛不开使用一些简单快捷的第三方库,比如EventBus等,但在使用的过程中我们偶尔会遇到一些作者都没察觉的bug,比如内存泄漏,而SDK内部又没有提供反注册或者让我们手动置空对象的方法,外部加上SDK的版本更新不上或者更新速度慢,我们就不得不fa发大招了:反编译或者反射置空;

(1)反编译:这种方法成功的几率比较少,但是可以让我们去接触Android内部究竟是怎么运行的,编译的时候Android都做了哪些操作,随便某度一搜,一大堆如何反编译的,我就随便扒个链接吧https://www.cnblogs.com/mfrbuaa/p/4588057.html

通过反编译得到源码,更改源码进行打包成一个jar包让我们的应用使用,从而达到解决内存泄漏的问题,小编以前试着用这种方法,内存泄漏确实也解决了,直接往源码里加反注册的方法,但是反编译会造成一些功能上不适配的功能,然后我又重新找了个方法,那就是反射。

(2)反射:反射获取对象进行置空,虽然会带来性能上的损耗,但大大增加了我们coding的能力,现在很多开源库之所以能做到这么灵活,其中一部分多亏与反射技术,反射也可在某度上一搜就一大堆,我就随便扒个链接吧https://www.cnblogs.com/wumingchen/p/5781844.html

 

以下是我解决SDK内部内存泄漏写的代码,只需要几行代码,就可以把对象置空O(∩_∩)O哈哈~

 

内存泄漏有很多情况,我没有依依列出来,但是万变不离其中,要解决内存泄漏,就先知道内存泄漏的原因,知道内存到达是怎么被回收的,然后一步一个脚印,同时大家可以在楼下指出我写的哪些不对,欢迎大家拍砖点赞收藏分享。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值