内存优化二
内存泄漏
Handler内存泄漏分析
public class MainActivity extends AppCompatActivity { private final int MESSAGE_WHAT = 10000; Handler handler = new Handler() { @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); handler.sendEmptyMessageDelayed(MESSAGE_WHAT, 3 * 1000); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); handler.sendEmptyMessageDelayed(MESSAGE_WHAT, 5 * 1000); }}
在启动时,用Handler发空消息,然后在处理消息里面又发空消息。我们启动MainActivity 之后,1s之内点返回,然后在Profile里面点多次回收,按理说MainActivity 应该被GC回收掉,没有MainActivity 的实例。 但是实际上,如下图所示,还有一个MainActivity 实例。
可以看到,我截取的部分是点了很多次GC的,很多个垃圾桶的标志。但是这个MainActivity仍然有一个实例,说明MainActivity已经泄露了。
泄漏分析
这个按钮(dump the heap)能自动截取一段内存信息,生成一个文件。
生成完点这里保存到磁盘中,后缀名是hprof
然后使用sdk的工具hprof-conv,转一下,转成mat工具可以使用的。
.\hprof-conv.exe .\test1.hprof .\test2.hprof
prof-conv xxxxx.hprof yyyyy.hprof,其中xxxxx.hprof为原始文件,yyyyy.hprof为转换过后的文件。
使用mat工具打开test2.hprof文件。Mat:下载地址
打开之后,点一下下图位置
在ClassName的地方输入MainActivity,然后回车,找到MainActivity的所有实例。
然后排除软、若、虚引用,也就是只留强引用。
然后mat会打开新的标签页,用来展示这个MainActivity的引用信息
从图中我们可以看到MainActivity被MainActivity$1引用,然后一层一层到上面。哦了,分析到这里,我们也能从证据上看出来MainActivity因为MainActivity$1的强引用导致内存泄漏了。
理论分析
首先非静态内部类持有外部类引用,其次Handler发送Message,会把本身交给Message,Message放到Looper的Queue中。倒过来推就是,Looper中引用Queue,Queue引用Message,Message引用Handler,Handler引用MainActivity。所以MainActivity内存泄漏了。
解决办法
static修饰
private static final int MESSAGE_WHAT = 10000; static Handler handler = new Handler() { @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); handler.sendEmptyMessageDelayed(MESSAGE_WHAT, 3 * 1000); } };
经过垃圾回收,MainActivity已经被回收掉了,没有MainActivity的实例了。
缺点:static声明过多, 会造成类加载速度变慢,因为static是跟着类一起加载的。
软引用(这个在非静态内部类无法控制)
在Handler这个例子中没法测试,但是其它的可以试试的。
缺点:何时被回收不确定,如果在回收之后还需要用,就比较麻烦了。
销毁Activity时移除消息
public class MainActivity extends AppCompatActivity { private final int MESSAGE_WHAT = 10000; Handler handler = new Handler() { @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); handler.sendEmptyMessageDelayed(MESSAGE_WHAT, 3 * 1000); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); handler.sendEmptyMessageDelayed(MESSAGE_WHAT, 5 * 1000); } @Override protected void onDestroy() { super.onDestroy(); handler.removeMessages(MESSAGE_WHAT); }
在onDestroy回调方法中,移除Message,这样引用链就会在Queue–Message这里断开,下面的对象就可以回收了。从截图中可以看到不论MainActivity还是Handler都被正确回收了。
缺点:如果Handler写的比较多,这样的做法导致代码量比较多。
总结
内存泄漏的原因就是 短生命周期的实例,被长生命周期的实例引用,导致短生命周期的实例无法被回收。具体如何解决内存泄漏问题,不可一概而论,具体问题,具体分析,如果上面的例子中,Handler处理的事情很简单,那么即使MainActivity销毁,只要Handler的事情做完了,还是可以正常回收的。
public class MainActivity extends AppCompatActivity { private static final String TAG = MainActivity.class.getSimpleName(); private int MESSAGE_WHAT = 10000; Handler handler = new Handler() { @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); Log.d(TAG, "handleMessage: 我收到消息了"); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); handler.sendEmptyMessageDelayed(MESSAGE_WHAT, 5 * 1000); } @Override protected void onDestroy() { super.onDestroy(); }}
如上所示,Handler处理的事情很简单, 最终也是没有内存泄漏的。
所以没有绝对的解决方法,具体问题,具体分析
更多相关文章
- Android中Cannot draw recycled bitmaps
- android 设置seekBar 和Progress背景色
- java.lang.RuntimeException: Unable to start activity Compone
- LeakCanary原理分析
- Android内存泄漏的八种可能(上)
- 【Android(安卓)Developers Training】 59. 管理图片存储
- android 内存泄漏原因及解决方案
- Android中关于退出和Toast的引用
- Android之引用包含远程依赖库的aar(引用aar时找不到arr的远程依