Android内存泄露整理

前言

内存泄漏(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来检测内存泄露,结果如下:

静态变量内存泄漏_com.example.testmemoryleak.png

再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);

}

}

}

非静态内部类内存泄漏_com.example.testmemoryleak.png

如上,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:实时监测内存泄漏的库

如何配置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.

https://stackoverflow.com/questions/46949622/android-studio-3-0-unable-to-resolve-dependency-for-appdexoptions-compileclas

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内存泄漏的常见情况和修改方法,平时我们进行代码书写时要注意这些条件,尽量避免内存泄露的发生;如果发生了要学会运用工具分析解决

参考: Android内存泄漏场景及解决方法

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,192评论 6 511
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,858评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,517评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,148评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,162评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,905评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,537评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,439评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,956评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,083评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,218评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,899评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,565评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,093评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,201评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,539评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,215评论 2 358

推荐阅读更多精彩内容

  • Android 内存泄漏总结 内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题。内存泄漏...
    _痞子阅读 1,637评论 0 8
  • 内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题。内存泄漏大家都不陌生了,简单粗俗的讲,...
    宇宙只有巴掌大阅读 2,363评论 0 12
  • 被文同时发布在CSDN上,欢迎查看。 APP内存的使用,是评价一款应用性能高低的一个重要指标。虽然现在智能手机的内...
    大圣代阅读 4,824评论 2 54
  • 内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题。内存泄漏大家都不陌生了,简单粗俗的讲,...
    DreamFish阅读 792评论 0 5
  • Android 内存泄漏总结 内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题。内存泄漏...
    apkcore阅读 1,222评论 2 7