前言
内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃(OOM)等严重后果
Android发生内存泄漏的常见情况
静态变量
静态变量的生命周期和应用的生命周期一样长。如果静态变量持有某个Activity的context,则会引发对应Activity无法释放,导致内存泄漏。如果持有application的context,就没有问题(以下例子是指Activity销毁时没有释放的情况)
常见的有:
- 单例模式:内部实现是静态变量和方法
- 静态的View:view默认持有Activity的context
- 静态Activity
package com.example.testmemoryleak;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import com.squareup.leakcanary.LeakCanary;
public class MainActivity extends AppCompatActivity {
private Button btn;
private Button btn1;
private static Context StaticVarible;
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn = (Button)findViewById(R.id.button);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MainActivity.this.finish();
}
});
StaticVarible = this;
LeakCanary.install(getApplication());
}
private static class NoLeakHandler extends Handler{
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
}
}
这里我用了开源框架LeakCanary来检测内存泄露,结果如下:
再ActivityDestory时讲静态变量置空即可
@Override
protected void onDestroy() {
StaticVarible = null;
super.onDestroy();
}
匿名内部类或者非静态内部类
常见的包括
Handler,AsyncTask,TimerTask等,一般在处理多线程任务的时候
非静态的内部类和匿名内部类都会隐式地持有其外部类的引用(否则怎么访问外部类的非静态成员呢?),静态的内部类不会持有外部类的引用
Java中的类可以是static吗?答案是可以。在java中我们可以有静态实例变量、静态方法、静态块。类也可以是静态的。
java允许我们在一个类里面定义静态类。比如内部类(nested class)。把nested class封闭起来的类叫外部类。在java中,我们不能用static修饰顶级类(top level class)。只有内部类可以为static。
静态内部类和非静态内部类之间到底有什么不同呢?下面是两者间主要的不同。
(1)内部静态类不需要有指向外部类的引用。但非静态内部类需要持有对外部类的引用。
(2)非静态内部类能够访问外部类的静态和非静态成员。静态类不能访问外部类的非静态成员。他只能访问外部类的静态成员。
(3)一个非静态内部类不能脱离外部类实体被创建,一个非静态内部类可以访问外部类的数据和方法,因为他就在外部类里面。
那么修改方法 1.静态内部类 2.销毁前及时处理非静态内部类
package com.example.testmemoryleak;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import com.squareup.leakcanary.LeakCanary;
public class MainActivity extends AppCompatActivity {
private Button btn;
private Button btn1;
private static Context StaticVarible;
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn = (Button)findViewById(R.id.button);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MainActivity.this.finish();
}
});
btn1 = (Button)findViewById(R.id.button1);
btn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mHandler = new Handler(){
@Override
public void handleMessage(Message msg){
super.handleMessage(msg);
}
};
Message message = Message.obtain();
message.what = 1;
mHandler.sendMessageDelayed(message,5*60*1000);
/*
mHandler = new NoLeakHandler();
Message message = Message.obtain();
message.what = 1;
mHandler.sendMessageDelayed(message,5*60*1000);
Log.i("weijuncheng", String.valueOf(mHandler.hasMessages(1)));
*/
}
});
StaticVarible = this;
LeakCanary.install(getApplication());
}
@Override
protected void onDestroy() {
//mHandler.removeCallbacksAndMessages(null);
StaticVarible = null;
super.onDestroy();
}
private static class NoLeakHandler extends Handler{
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
}
}
如上,Activity销毁时非静态内部类mHandler中还有未处理的消息,造成无法释放其引用的Activity对象
修改方案:
1. 使用静态内部类(必要时结合WeakReference,弱引用方式)
private static class NoLeakHandler extends Handler{
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
}
mHandler = new NoLeakHandler();
Message message = Message.obtain();
message.what = 1;
mHandler.sendMessageDelayed(message,5*60*1000);
Log.i("weijuncheng", String.valueOf(mHandler.hasMessages(1)));
静态内部类延伸
静态内部类也可以用来实现单例,保证线程安全,效率高;其主要原理为:Java中静态内部类可以访问其外部类的静态成员属性,同时,1.静态内部类只有当被调用的时候才开始首次被加载(懒加载),2.利用了classloader的机制来保证初始化instance时只有一个线程(多个线程就不是静态类了),所以也是线程安全的,同时没有性能损耗(加synchronized同步锁)
与饿汉方式不同的地方在,饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化(相当于一个在static语句中初始化,相当于在<clinit>中初始化;另一个相当于在静态内部类的<clinit>中进行单例的初始化)
https://www.cnblogs.com/zhaoyan001/p/6365064.html
代码示例如下
public class Singleton {
private Singleton() {}
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstance.INSTANCE; //lazy-loading
}
}
2. 销毁前及时处理非静态内部类
@Override
protected void onDestroy() {
mHandler.removeCallbacksAndMessages(null);
super.onDestroy();
}
监听器
当使用Activity的context注册监听,不再需要监听时没有取消注册
资源对象
资源对象未关闭:BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源,使用后未关闭会导致内存泄漏。因为资源性对象往往都用了一些缓冲,缓冲不仅存在于 java虚拟机内,还存在于java虚拟机外。如果仅仅是把它的引用置null,而不关闭它们,也会造成内存泄漏
容器
容器中的对象没有清理:集合一般占用内存较大,不及时关闭会导致内存紧张(不会导致内存泄漏,而会导致可用内存大大减少)
WebView
检测、分析内存泄漏的工具
MemoryMonitor:随时间变化,内存占用的变化情况
MAT:输入HRPOF文件,输出分析结果
- Histogram:查看不同类型对象及其大小
- DominateTree:对象占用内存及其引用关系
如何配置LeakCanary开源库
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.example.testmemoryleak"
minSdkVersion 15
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
debug{
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'
// Optional, if you use support library fragments:
debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3'
}
配置LeakCanary踩过的坑
1. Unable to resolve dependency for ':app@dexOptions/compileClasspath': Could not resolve project : library_Name.
With Android Studio 2.3(AS) the project works fine and i can able to run the App. After updating the AS to Android Studio 3.0. i too got the error as below for libraries and build types.
Unable to resolve dependency for ':app@dexOptions/compileClasspath': Could not resolve project : library_Name. Unable to resolve dependency for ':app@release/compileClasspath': Could not resolve project : library_Name.
To Solve the issue, simply.
What ever the
buildTypes{ debug{ ... } release{ ... } }
2.Could not resolve all dependencies for configuration ':app:debugRuntimeClasspath'.
org.gradle.api.UncheckedIOException: Failed to capture fingerprint of input files for task ':app:preDebugBuild' property 'compileManifests' during up-to-date check
https://my.oschina.net/u/616133/blog/2221453 (这样操作就行,为什么?允许内置的maven仓库?)
File --> Setting --->Build,Execution,Deployment --> Gradle -->Android Studio 勾选 “Enable embedded Maven repository”
如何避免内存泄漏
不要在匿名内部类中进行异步操作
将非静态内部类转为静态内部类 + WeakReference(弱引用)的方式
在 Activity 回调 onDestroy 时或者 onStop 时
- 移除消息队列 MessageQueue 中的消息
- 静态变量置null
- 停止异步任务
- 取消注册
使用Context时,尽量使用Application 的 Context
尽量避免使用static 成员变量。另外可以考虑lazy初始化
为webView开启另外一个进程,通过AIDL与主线程进行通信,WebView所在的进程可以根据业务的需要选择合适的时机进行销毁,从而达到内存的完整释放
及时关闭资源。Bitmap 使用后调用recycle()方法
总结
大体说明了Android内存泄漏的常见情况和修改方法,平时我们进行代码书写时要注意这些条件,尽量避免内存泄露的发生;如果发生了要学会运用工具分析解决