Android內存泄露整理

前言

內存泄漏(Memory Leak)是指程序中己動態(tài)分配的堆內存由于某種原因程序未釋放或無法釋放,造成系統(tǒng)內存的浪費,導致程序運行速度減慢甚至系統(tǒng)崩潰(OOM)等嚴重后果

Android發(fā)生內存泄漏的常見情況

靜態(tài)變量

靜態(tài)變量的生命周期和應用的生命周期一樣長。如果靜態(tài)變量持有某個Activity的context,則會引發(fā)對應Activity無法釋放,導致內存泄漏。如果持有application的context,就沒有問題(以下例子是指Activity銷毀時沒有釋放的情況)

常見的有:

  • 單例模式:內部實現(xiàn)是靜態(tài)變量和方法
  • 靜態(tài)的View:view默認持有Activity的context
  • 靜態(tài)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來檢測內存泄露,結果如下:

靜態(tài)變量內存泄漏_com.example.testmemoryleak.png

再ActivityDestory時講靜態(tài)變量置空即可

@Override

protected void onDestroy() {

StaticVarible = null;

super.onDestroy();

}

匿名內部類或者非靜態(tài)內部類

常見的包括

Handler,AsyncTask,TimerTask等,一般在處理多線程任務的時候

非靜態(tài)的內部類和匿名內部類都會隱式地持有其外部類的引用(否則怎么訪問外部類的非靜態(tài)成員呢?),靜態(tài)的內部類不會持有外部類的引用

Java中的類可以是static嗎?答案是可以。在java中我們可以有靜態(tài)實例變量、靜態(tài)方法、靜態(tài)塊。類也可以是靜態(tài)的。
java允許我們在一個類里面定義靜態(tài)類。比如內部類(nested class)。把nested class封閉起來的類叫外部類。在java中,我們不能用static修飾頂級類(top level class)。只有內部類可以為static。
靜態(tài)內部類和非靜態(tài)內部類之間到底有什么不同呢?下面是兩者間主要的不同。
(1)內部靜態(tài)類不需要有指向外部類的引用。但非靜態(tài)內部類需要持有對外部類的引用。
(2)非靜態(tài)內部類能夠訪問外部類的靜態(tài)和非靜態(tài)成員。靜態(tài)類不能訪問外部類的非靜態(tài)成員。他只能訪問外部類的靜態(tài)成員。
(3)一個非靜態(tài)內部類不能脫離外部類實體被創(chuàng)建,一個非靜態(tài)內部類可以訪問外部類的數(shù)據(jù)和方法,因為他就在外部類里面。

那么修改方法 1.靜態(tài)內部類 2.銷毀前及時處理非靜態(tài)內部類


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

}

}

}

非靜態(tài)內部類內存泄漏_com.example.testmemoryleak.png

如上,Activity銷毀時非靜態(tài)內部類mHandler中還有未處理的消息,造成無法釋放其引用的Activity對象

修改方案:

1. 使用靜態(tài)內部類(必要時結合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)));

靜態(tài)內部類延伸

靜態(tài)內部類也可以用來實現(xiàn)單例,保證線程安全,效率高;其主要原理為:Java中靜態(tài)內部類可以訪問其外部類的靜態(tài)成員屬性,同時,1.靜態(tài)內部類只有當被調用的時候才開始首次被加載(懶加載),2.利用了classloader的機制來保證初始化instance時只有一個線程(多個線程就不是靜態(tài)類了),所以也是線程安全的,同時沒有性能損耗(加synchronized同步鎖)

與餓漢方式不同的地方在,餓漢式方式是只要Singleton類被裝載就會實例化,沒有Lazy-Loading的作用,而靜態(tài)內部類方式在Singleton類被裝載時并不會立即實例化,而是在需要實例化時,調用getInstance方法,才會裝載SingletonInstance類,從而完成Singleton的實例化(相當于一個在static語句中初始化,相當于在<clinit>中初始化;另一個相當于在靜態(tài)內部類的<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. 銷毀前及時處理非靜態(tài)內部類


@Override

protected void onDestroy() {

mHandler.removeCallbacksAndMessages(null);

super.onDestroy();

}

監(jiān)聽器

當使用Activity的context注冊監(jiān)聽,不再需要監(jiān)聽時沒有取消注冊

資源對象

資源對象未關閉:BraodcastReceiver,ContentObserver,F(xiàn)ile,Cursor,Stream,Bitmap等資源,使用后未關閉會導致內存泄漏。因為資源性對象往往都用了一些緩沖,緩沖不僅存在于 java虛擬機內,還存在于java虛擬機外。如果僅僅是把它的引用置null,而不關閉它們,也會造成內存泄漏

容器

容器中的對象沒有清理:集合一般占用內存較大,不及時關閉會導致內存緊張(不會導致內存泄漏,而會導致可用內存大大減少)

WebView

檢測、分析內存泄漏的工具

MemoryMonitor:隨時間變化,內存占用的變化情況

MAT:輸入HRPOF文件,輸出分析結果

  • Histogram:查看不同類型對象及其大小
  • DominateTree:對象占用內存及其引用關系

LeakCanary:實時監(jiān)測內存泄漏的庫

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

如何避免內存泄漏

不要在匿名內部類中進行異步操作

將非靜態(tài)內部類轉為靜態(tài)內部類 + WeakReference(弱引用)的方式

在 Activity 回調 onDestroy 時或者 onStop 時

  • 移除消息隊列 MessageQueue 中的消息
  • 靜態(tài)變量置null
  • 停止異步任務
  • 取消注冊

使用Context時,盡量使用Application 的 Context

盡量避免使用static 成員變量。另外可以考慮lazy初始化

為webView開啟另外一個進程,通過AIDL與主線程進行通信,WebView所在的進程可以根據(jù)業(yè)務的需要選擇合適的時機進行銷毀,從而達到內存的完整釋放

及時關閉資源。Bitmap 使用后調用recycle()方法

總結

大體說明了Android內存泄漏的常見情況和修改方法,平時我們進行代碼書寫時要注意這些條件,盡量避免內存泄露的發(fā)生;如果發(fā)生了要學會運用工具分析解決

參考: Android內存泄漏場景及解決方法

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • Android 內存泄漏總結 內存管理的目的就是讓我們在開發(fā)中怎么有效的避免我們的應用出現(xiàn)內存泄漏的問題。內存泄漏...
    _痞子閱讀 1,702評論 0 8
  • 內存管理的目的就是讓我們在開發(fā)中怎么有效的避免我們的應用出現(xiàn)內存泄漏的問題。內存泄漏大家都不陌生了,簡單粗俗的講,...
    宇宙只有巴掌大閱讀 2,492評論 0 12
  • 被文同時發(fā)布在CSDN上,歡迎查看。 APP內存的使用,是評價一款應用性能高低的一個重要指標。雖然現(xiàn)在智能手機的內...
    大圣代閱讀 4,971評論 2 54
  • 內存管理的目的就是讓我們在開發(fā)中怎么有效的避免我們的應用出現(xiàn)內存泄漏的問題。內存泄漏大家都不陌生了,簡單粗俗的講,...
    DreamFish閱讀 869評論 0 5
  • Android 內存泄漏總結 內存管理的目的就是讓我們在開發(fā)中怎么有效的避免我們的應用出現(xiàn)內存泄漏的問題。內存泄漏...
    apkcore閱讀 1,306評論 2 7

友情鏈接更多精彩內容