持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第25天,点击查看活动详情
目录
3、案例1:非静态的内部类错误使用,导致 Activity内存泄露
步骤2:写主界面 MainActivity、布局activity_main
(2)方式2:当Activity销毁的时候,将线程的任务退出
一、前言
今天来复习一下有关内存泄露的知识,一个是把之前知识整理一下,如有一些工具版本更新会写一些当下的使用介绍。另外会参考网上比较好的博文进行修正改进一些知识点
二、内存泄露_内存溢出_内存抖动
首先我们必须对内存泄露有一个最简单的了解,就是一些对象有着有限的生命周期,当这些对象所要做的事情完成了,我们希望他们会被回收掉。但是如果有一系列对象对这个对象的引用,那么在我们期望这个对象生命周期结束的时候被GC回收的时候,它是不会被回收的,它还会占用内存,导致堆内存直升不降,这就造成了内存泄露。
内存泄漏: 指对象不再使用,本该被回收,却因为有其他正在使用的对象持有该对象的引用,而无法被GC回收,它占用内存,导致堆内存直升不降的情况。
内存溢出: 我们可以把app的堆内存空间想成了一个杯子,内存就是里面的水。当用户重复这个操作或者有多个不同Activity内存泄漏时,app运行一段时间堆内存超过系统规定的最大值 heapSize,杯子满了就会发生内存溢出(OOM)
内存抖动:是因为短时间内大量的对象被创建然后又被马上释放,瞬间产生大量的对象会严重占用内存区域,这块内存区域就是Young Generiation内存区域。当达到它的阀值,剩余空间不够的时候,就会触发GC(垃圾回收机制),这样刚产生的对象很快就被回收,每一次分配对象占用很少内存,但是它们叠加在一起就会造成Heap(堆内存)的压力,从而触发更多其他类型的GC,这个操作有可能会影响到帧率,并使得用户感知到性能问题。
内存溢出,内存抖动,内存泄漏这三者中这里面最严重的现象还是OOM也就是内存溢出,在开发过程中提到内存首先会想到内存溢出OOM,内存泄漏,然后才是内存抖动,内存抖动三者中严重程度比较轻。
内存溢出就是分配的内存不足以让你做有些操作,就是堆内存上有些内存没有被释放从而它会失去控制,造成程序使用的内存越来越少,导致系统运行速度减慢,严重程度下OOM,会造成整个程序的崩溃,所以为了提高APP的质量提高用户体验,我们必须避免与解决OOM。
三、LeakCanary 内存泄漏检测库-使用介绍
内存泄露分析工具:使用 square公司的 LeakCanary 分析activity的内存泄露
1、github上搜索 LeakCanary 工具
可在 github上搜索 LeakCanary 工具,可以看到 leakcanary 的源码:GitHub - square/leakcanary: A memory leak detection library for Android.
LeakCanary is a memory leak detection library for Android. LeakCanary是一个用于Android的内存泄漏检测库。
2、集成方式
集成方式,点击链接:LeakCanary ,如下图,
That’s it, there is no code change needed! 就这样,不需要修改代码!
集成,目前最新版本 2.2 ,只需在 build.gradle 添加如下依赖:
// debug Implementation because LeakCanary should only run in debug builds.
// 调试实现,因为LeakCanary只应在调试生成中运行。
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.2'
另外添加依赖以后,有可能报如下异常,很可能是网络问题,try again 即可
当然上面还有更新的提示:
To upgrade from LeakCanary 1.6, follow the upgrade guide. 要从LeakCanary 1.6升级,请遵循升级指南。
3、案例1:非静态的内部类错误使用,导致 Activity内存泄露
情形一:在Activity中启动子线程,子线程执行耗时操作
下面我们来看一下案例:
步骤1:新建一个 Module(模块/组件)
我们可以在项目上,新建一个Module:File--->New--->New Module
然后一直默认。。。,完成如下:
步骤2:写主界面 MainActivity、布局activity_main
布局activity_main
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="innerClass"
android:text="非静态内部类的错误使用" />
</LinearLayout>
复习一下快捷键:option + enter
option + enter,会在 MainActivity 里面,自动生成方法: public void innerClass(View view) { }
主界面 MainActivity
package com.yyh.testleakdemo;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void innerClass(View view) {
startActivity( new Intent(MainActivity.this, InnerClassActivity.class));
}
}
步骤3:写业务逻辑:InnerClassActivity
子线程执行耗时操作
- 在Activity中启动子线程,子线程执行耗时操作,
- 但是当Activity退出的时候,如果线程的任务还没执行完,那么该线程对象就不会死掉,
- 该线程由于是Activity的内部对象,因此对外部类(Activity)有一个隐式的强引用,
- 从而导致Activity也无法被GC回收掉,最后导致Activity一定时间的泄露。
package com.yyh.testleakdemo;
import android.os.Bundle;
import android.os.SystemClock;
import androidx.appcompat.app.AppCompatActivity;
public class InnerClassActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_inner_class);
errorDemoMethod();
}
/**
* 非静态内部类的错误使用
*/
private void errorDemoMethod() {
/**
* 内部类对象,对外部类对象有一个隐式的强引用
* 大家可复习一下:java 中的四种引用类型
*/
new Thread(new Runnable() {
@Override
public void run() {
//模拟耗时操作
SystemClock.sleep(100*1000);
}
}).start();
}
}
4、效果展示
在夜神模拟器上,运行Module模块:testleakdemo
点击 Button跳转到一个 Activity,执行耗时线程操作,然后点击返回按钮 Activity退出,
这个时候 InnerClassActivity 里面的耗时线程操作还没执行完,
然后我们可以看到 LeakCanary 工具有内存泄露的相关提示
四、分析小结、解决方式
1、分析小结
在Activity中启动子线程,子线程执行耗时操作,但是当Activity退出的时候,如果线程的任务还没执行完,那么该线程对象就不会死掉,该线程由于是Activity的内部对象,因此对外部类(Activity)有一个隐式的强引用,从而导致Activity也无法被GC回收掉,最后导致Activity一定时间的泄露。
2、解决方式
解决该泄露的方式:就是我们应该谨慎在Activity中开辟耗时的子线程,如果使用了那么也应该保证当Activity销毁的时候,将线程的任务退出。或者我们可以自定义一个静态的类继承Thread,然后再使用。因为只有非静态的内部类对象才会对外部类有隐式的强引用。
(1)方式1:自定义一个静态的类继承Thread
package com.yyh.testleakdemo;
import android.os.Bundle;
import android.os.SystemClock;
import androidx.appcompat.app.AppCompatActivity;
/**
* 自定义一个静态的类继承Thread
*/
public class Correct1Activity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_correct1);
MyThread myThread = new MyThread();
myThread.start();
}
public static class MyThread extends Thread{
@Override
public void run() {
//模拟耗时操作
SystemClock.sleep(100*1000);
}
}
}
(2)方式2:当Activity销毁的时候,将线程的任务退出
package com.yyh.testleakdemo;
import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
/**
* 当Activity销毁的时候,将线程的任务退出
*/
public class Correct2Activity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_correct2);
correctDemoMethod();
}
private Thread thread;
private void correctDemoMethod() {
thread = new Thread(new Runnable() {
@Override
public void run() {
//模拟耗时操作
//SystemClock.sleep(100*1000);
//有关 SystemClock.sleep 和 thread.sleep 区别,
//网上资料很多,这里就不多讲了
try {
thread.sleep(100*1000);
} catch (InterruptedException e) {
e.printStackTrace();
Log.v("InnerClassActivity@@@","InterruptedException");
return;
}
}
});
thread.start();
}
@Override
protected void onDestroy() {
super.onDestroy();
//thread.stop();//Caused by: java.lang.UnsupportedOperationException
thread.interrupt();
}
}
有关 SystemClock.sleep 和 thread.sleep 区别,可参考博文:
SystemClock.sleep(long ms)与Thread.sleep(long millis)分析_wtu178的博客-CSDN博客
(3)方式3:使用线程池
其实,项目中会集成个性化的线程池,保证不开启过多的线程进行多线程操作(硬件层CPU 支持的核心数有限,过多的线程只能更加分割 cpu 时间片,无法达到更好的效果),也会有自己的新线程创建、管理和结束操作。在工作中我们将Android Thread分两种线程进行管理,即 UI Thread (最多 Thread 数由 cpu 核心数决定)和 Bkg Thread (最大Thread数为1)。
这块知识点有时间再整理一下。。。
五、其他
square公司,对我们 android来说,是一个大名鼎鼎的公司,它开源了相当多的开源框架。
比如说:LeakCanary(内存泄漏检测库)、OKHttp(网络请求库)、Picasso(图片库)、Retrofit(网络请求接口的封装)
square开源官网:Square Open Source
然后写博文的时候,偶然看到这篇博文,这哥们挺逗的😆
Android之超级开源公司square(picasso、okhttp、leakcanary、retrofit)简单介绍
这句话说的挺对:官方文档写的超级明白!所以一定要从官方的一手资料学习,注意二手资料容易带你到沟里!
然后我也顺便搜了搜,大牛JakeWharton的相关网址:
JakeWharton的github
JakeWharton (Jake Wharton) · GitHub
Butterknife(黄油刀)——View注入框架:将Android视图和回调绑定到字段和方法
GitHub - JakeWharton/butterknife: Bind Android views and callbacks to fields and methods.
JakeWharton的博客:
六、参考资料
深入剖析Android内存泄露原理-视频个人笔记_simplebam-CSDN博客
请别只做拿来主义者,如果觉得写的不错、对你有用,留下你的足迹:点赞 或 评论 支持下!
一直被模仿从未被超越,你们的支持是我们这些写博客博主们的动力!我们将继续分享干货!