目录
在介绍内存泄露的时候我们首先需要了解一些基本的知识体系
基础知识介绍
什么是引用?
当我们创建一个对象的时候他在内存中的表现形式是这样的
new A(); 是创建对象, 那么a是什么呢???
a就是引用,他承接了new A(), 且引用了A(), a保存的是new A()创建的内存的首地址,所以, 引用大小≠ 所引用的对象创建内存的大小,也就是a实际保存的是对象的地址的大小,一般通常为4-8byte
是否可以直接被回收???
显然是不可能的,因为此时a正引用着new A(), 就导致new A()无法被回收,如果想要回收,那么就需要置空也就是 a = null,此时a不在引用着new A(),也就是引用链断掉了,new A()会被GC回收掉
LeakCanary的原理
1.debugAPK中清单文件的节点是怎么来的
当我们导入leakCanary依赖后,在程序运行到模拟器的时候,如果leakCanary发现了内存泄露,他会通知我们,但是我们在App模块中并没有添加过通知的权限,那么他的权限是哪里来的???
这是我们App模块中声明的清单文件,喏并没有对应的通知权限,也没有发现其他的什么配置,不急我们来解析一下debugAPK来看一下
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.quick.androidmemoryleak">
<application
android:allowBackup="true"
android:name=".MyApp"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AndroidMemoryLeak"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
找到对应的debugAPK文件,然后拖拽到AndroidStudio当中,然后找到清单文件
当我们点开清单文件后,我们发现一些有趣的事情,因为太多我这里就不截图了 :
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1"
android:versionName="1.0"
android:compileSdkVersion="32"
android:compileSdkVersionCodename="12"
package="com.quick.androidmemoryleak"
platformBuildVersionCode="32"
platformBuildVersionName="12">
<uses-sdk
android:minSdkVersion="24"
android:targetSdkVersion="32" />
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission
android:name="android.permission.POST_NOTIFICATIONS" />
<application
android:theme="@ref/0x7f100202"
android:label="@ref/0x7f0f001c"
android:icon="@ref/0x7f0d0000"
android:name="com.quick.androidmemoryleak.MyApp"
android:debuggable="true"
android:allowBackup="true"
android:supportsRtl="true"
android:extractNativeLibs="false"
android:fullBackupContent="@ref/0x7f120000"
android:roundIcon="@ref/0x7f0d0001"
android:appComponentFactory="androidx.core.app.CoreComponentFactory"
android:dataExtractionRules="@ref/0x7f120001">
<activity
android:name="com.quick.androidmemoryleak.LeakActivity"
android:exported="true" />
<activity
android:name="com.quick.androidmemoryleak.MainActivity"
android:exported="true">
<intent-filter>
<action
android:name="android.intent.action.MAIN" />
<category
android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name="leakcanary.internal.LeakCanaryFileProvider"
android:exported="false"
android:authorities="com.squareup.leakcanary.fileprovider.com.quick.androidmemoryleak"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@ref/0x7f120002" />
</provider>
<activity
android:theme="@ref/0x7f10044e"
android:label="@ref/0x7f0f003d"
android:icon="@ref/0x7f0d0002"
android:name="leakcanary.internal.activity.LeakActivity"
android:exported="true"
android:taskAffinity="com.squareup.leakcanary.com.quick.androidmemoryleak">
<intent-filter
android:label="@ref/0x7f0f0051">
<action
android:name="android.intent.action.VIEW" />
<category
android:name="android.intent.category.DEFAULT" />
<category
android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="file" />
<data
android:scheme="content" />
<data
android:mimeType="*/*" />
<data
android:host="*" />
<data
android:pathPattern=".*\.hprof" />
<data
android:pathPattern=".*\..*\.hprof" />
<data
android:pathPattern=".*\..*\..*\.hprof" />
<data
android:pathPattern=".*\..*\..*\..*\.hprof" />
<data
android:pathPattern=".*\..*\..*\..*\..*\.hprof" />
<data
android:pathPattern=".*\..*\..*\..*\..*\..*\.hprof" />
<data
android:pathPattern=".*\..*\..*\..*\..*\..*\..*\.hprof" />
</intent-filter>
</activity>
<activity-alias
android:theme="@ref/0x7f10044e"
android:label="@ref/0x7f0f003d"
android:icon="@ref/0x7f0d0002"
android:name="leakcanary.internal.activity.LeakLauncherActivity"
android:enabled="@ref/0x7f040003"
android:exported="true"
android:taskAffinity="com.squareup.leakcanary.com.quick.androidmemoryleak"
android:targetActivity="leakcanary.internal.activity.LeakActivity"
android:banner="@ref/0x7f0700a4">
<intent-filter>
<action
android:name="android.intent.action.MAIN" />
<category
android:name="android.intent.category.LAUNCHER" />
<category
android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
</activity-alias>
<activity
android:theme="@ref/0x7f10044f"
android:label="@ref/0x7f0f006e"
android:icon="@ref/0x7f0d0002"
android:name="leakcanary.internal.RequestPermissionActivity"
android:taskAffinity="com.squareup.leakcanary.com.quick.androidmemoryleak"
android:excludeFromRecents="true" />
<receiver
android:name="leakcanary.internal.NotificationReceiver" />
<provider
android:name="leakcanary.internal.MainProcessAppWatcherInstaller"
android:enabled="@ref/0x7f040006"
android:exported="false"
android:authorities="com.quick.androidmemoryleak.leakcanary-installer" />
<provider
android:name="leakcanary.internal.PlumberInstaller"
android:enabled="@ref/0x7f040005"
android:exported="false"
android:authorities="com.quick.androidmemoryleak.plumber-installer" />
<provider
android:name="androidx.startup.InitializationProvider"
android:exported="false"
android:authorities="com.quick.androidmemoryleak.androidx-startup">
<meta-data
android:name="androidx.emoji2.text.EmojiCompatInitializer"
android:value="androidx.startup" />
<meta-data
android:name="androidx.lifecycle.ProcessLifecycleInitializer"
android:value="androidx.startup" />
</provider>
</application>
</manifest>
奇怪了,这些权限和provider节点都是谁添加的呢??? 最终我们在从远程仓库下载下来的arr包中找到了对应的权限以及provider的声明
总结:为什么打包出来的debugApk文件会有读写权限的申请,以及多余的Provider因为我们导入依赖后,从远程仓库下载下来的arr包有对应的清单文件,我们当前的app模块也依赖了leakCanary,在打包apk的时候,Gradle会去把依赖的第三方文件的库里的配置文件的节点进行合并打包,所以,打包出来的apk文件才有一些我们并没有添加的权限
2.LeakCanary是怎么运行的
通过查看debug包我们发现LeakCanary声明了很多个内容提供者,首先我们要明确内容提供者的功能,我们之前的内容提供者都是为了给别的进程提供数据,访问系统的通讯录和相册之类的,跨进程来访问,但是LeakCanary的内容提供者不是为了这样做的,他是为了利用Activity的启动流程
当Application启动完成后会去启动App中注册的内容提供者,然后才会去启动Activity,在MainProcessAppWatcherInstaller这个LeakCanary的内容提供者类中我们发现了,他会去获取应用的Application对象,整体的流程就是利用Activity的启动流程,被动的去调用AppWatcher.manualInstall(application)这行代码
最终会调用到 ActivityWatcher的install方法中,通过registerActivityLifecycleCallbacks方法来注册通过LeakCanary内部实现的Application的内部接口来完成对Activity生命周期的监控
override fun install() {
application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
}
最后通过ActivityWatcher类中的该类型实现监控
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityDestroyed(activity: Activity) {
reachabilityWatcher.expectWeaklyReachable(
activity, "${activity::class.java.name} received Activity#onDestroy() callback"
)
}
}
那么问题又来了,是怎么样实现监控呢???
这个时候由引出来了一个 引用队列的知识,利用引用队列来检测对象是否被GC回收掉了,如果引用队列中有值则是被回收了,反之则是没有被回收
/**
* sr 引用了 string, 一起放到引用队列后,引用队列会存在一个 WeakReference,引用队列引用了sr,
* 引用队列与普通容器HasMap,LinkList不同的是当我们添加到队列中,我们就能在容器中找到对象,而引用队列是相反的也就是引用队列里面引用了sr,
* 但是里面并没有存在sr,只是一个很弱的引用引用了sr,当GC发生的时候sr引用的对象被回收掉不存在的时候,也就是sr为null,此时sr才会被添加到引用队
* 列中
*/
ReferenceQueue<String> queue = new ReferenceQueue<>();
String string = new String("hello");
String string2 = new String("hello");
WeakReference<String> sr = new WeakReference<String>(string, queue);
WeakReference<String> sr2 = new WeakReference<String>(string2,queue);
string = null;
string2 = null;
System.out.println(sr.get());// hello
WeakReference<String> k;
while ((k = (WeakReference<String>) queue.poll()) != null) {
System.out.println("回收了:" + k);
}
System.gc();// 通知GC进行回收
System.out.println(sr.get());// null
while ((k = (WeakReference<String>) queue.poll()) != null) {
System.out.println("回收了:" + k);
}
思考一个问题,引用队列这么设计的好处是什么???
引用队列的应用场景就是GC回收的时候,如果引用队列引用的对象释放内存之后才被添加到队列中,那么可以减少很多内存资源
1.静态变量引用上下文导致的内存泄露
使用静态变量引用上下文可能会导致内存泄漏的原因是静态变量在整个应用程序的生命周期内保持不变,而且在这种情况下,Activity 的实例将一直被引用,导致它无法被垃圾回收,从而造成内存泄漏。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
MyTestSingle.instances = this
}
}
class MyTestSingle() {
companion object {
var instances: Activity ? = null
}
}
改正:
使用弱引用即可
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
MyTestSingle.instances = this
}
}
class MyTestSingle() {
companion object {
var instance: WeakReference<MainActivity>? = null
}
}
2.非静态内部类造成的内存泄露
这段代码中发生内存泄露的原因是因为在MainActivity中持有了对InnerClass的引用,而InnerClass是一个非静态内部类,会默认持有对外部类OuterClass的引用。即使在onDestroy方法中将innerClass置空,由于非静态内部类会持有外部类的引用,因此即使将innerClass置空,仍然会造成内存泄露。为避免内存泄露,需要避免在Activity中持有对非静态内部类的引用,或者在不需要使用时及时释放对其的引用。
class OuterClass {
inner class InnerClass {
fun show() {
}
}
}
class MainActivity : AppCompatActivity() {
// 在MainActivity 中持有InnerClass的引用
private var innerClass: OuterClass.InnerClass? = OuterClass().InnerClass()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
innerClass?.show()
}
override fun onDestroy() {
super.onDestroy()
innerClass = null// 非静态内部类即使置空也会造成内存泄露
}
}
改正:
class OuterClass {
object InnerClass {
fun show() {
}
}
}
class MainActivity : AppCompatActivity() {
private var innerClass: OuterClass.InnerClass? = OuterClass.InnerClass
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
innerClass?.show()
}
override fun onDestroy() {
super.onDestroy()
//innerClass = null
}
这段代码中会造成Activity的泄露是因为InnerClass是一个静态内部类,而静态内部类不会持有外部类的引用。因此,在这段代码中并不会发生内存泄露,因为InnerClass是静态内部类,不会持有对OuterClass的引用。所以即使在onDestroy方法中不将innerClass置空,也不会造成内存泄露。
注意:
即使使用弱引用来引用非静态内部类仍然会造成内存泄露
在这段代码中,虽然使用了WeakReference来持有对InnerClass的引用,但仍然会发生内存泄露的原因是因为在InnerClass是一个非静态内部类,默认会持有对OuterClass的引用。即使使用了WeakReference来持有InnerClass的引用,但由于InnerClass会持有对OuterClass的引用,导致OuterClass实例无法被及时释放,从而导致内存泄露。为避免内存泄露,需要避免在Activity中持有对非静态内部类的引用,或者在不需要使用时及时释放对其的引用。
class OuterClass {
inner class InnerClass {
fun show() {
}
}
}
class MainActivity : AppCompatActivity() {
private var innerClass: WeakReference<OuterClass.InnerClass> = WeakReference(OuterClass().InnerClass())
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
innerClass?.get()?.show()
}
}
3.Android动画造成的内存泄露
上述代码我们将自定义View直接设置为View.GONE之后重新点击跳转Activity之后,然后再次设置为GONE,不断重复,我们发现仍然会造成内存泄露,即使重写setVisibility方法来关掉对应的属性动画还会造成内存泄露
改正:
public void setVisibility(int visibility) {
Log.d(TAG, "setVisibility");
super.setVisibility(View.INVISIBLE);
/**
* 即使进行了清除动画的操作,仍然会继续执行,因为,当前类还保存在内存中怎么办????
* 系统GC会帮我们关闭掉,在跳转页面的时候
*/
mShapeView.clearAnimation();
mShadowView.clearAnimation();
}
4.Handle造成的内存泄露
class MainActivity : AppCompatActivity() {
private val mHandle = Handler()
companion object {
const val TAG = "MainActivity"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
/**
* 利用Handle不断的发送任务执行
*/
mHandle.postDelayed(object : Runnable{
override fun run() {
Log.d(TAG, "=========")
mHandle.postDelayed(this, 1000L)
}
},1000L)
}
}
我们利用Handle创建了一个内存泄露的场景, 不断的去退出Activity然后重新进入Activity,发现我们的Activity并没有被回收,造成了Activity级别的内存泄露原因是它持有了对 Handler 实例的引用。由于 Handler 是一个非静态内部类,它会隐式地持有对外部类 MainActivity 的引用,导致 MainActivity 无法被回收,从而造成内存泄漏
改正:
使用静态类继承Handle,并且如果想要使用Activity的上下文的话,可以通过弱引用的方式接收Activity
5.单例类造成的内存泄露
在调用 getInstance() 方法的时候传入的context 参数是 Activity 等上下文,就会导致内存泄露,以 MainActivity 为例,当我们启动一个 Activity ,并调用 getInstance()方法去获取SingleClass 的单例,传入 Activity作为 context ,这样 SingleClass 类的单例 mSingleTest就持有了 Activity 的引用,当我们退出 Activity 时,该 Activity 就没有用了,但是因为 mSingleTest对象作为静态单例(在应用程序的整个生命周期中存在)会继续持有这个 Activity 的引用,导致这个Activity 对象无法被回收释放,这就造成了内存泄露。
class SingleClass private constructor(context: Context) {
private val mContext: Context = context
companion object {
@SuppressLint("StaticFieldLeak")
@Volatile
private var mSingleTest: SingleClass? = null
fun getInstance(context: Context): SingleClass? {
if (mSingleTest == null) {
synchronized(SingleClass::class.java) {
if (mSingleTest == null) {
mSingleTest = SingleClass(context)
}
}
}
return mSingleTest
}
}
fun show() {
Log.d(MainActivity.TAG, "用于测试是否造成了内存泄露")
}
}
class MainActivity : AppCompatActivity() {
companion object {
const val TAG = "MainActivity"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
SingleClass.getInstance(context = this)?.show()
}
}
改正:
如果需要再单例类中使用上下文可以使用,如下的方式
class SingleClass private constructor(context: Context) {
private val mContext: Context = context.applicationContext
companion object {
@SuppressLint("StaticFieldLeak")
@Volatile
private var mSingleTest: SingleClass? = null
fun getInstance(context: Context): SingleClass? {
if (mSingleTest == null) {
synchronized(SingleClass::class.java) {
if (mSingleTest == null) {
mSingleTest = SingleClass(context)
}
}
}
return mSingleTest
}
}
fun show() {
Log.d(MainActivity.TAG, "用于测试是否造成了内存泄露")
}
}
6.对应资源未关闭
FileOutputStream fos = new FileOutputStream(fileName);
fos.write(sb.toString().getBytes());
改正:
FileOutputStream fos = new FileOutputStream(fileName);
fos.write(sb.toString().getBytes());
fos.flush();
fos.close();