㈠ 常见的内存泄漏原因及解决方法
(Memory Leak,内存泄漏)
当一个对象已经不需要再使用本该被回收时,另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,这就产生了内存泄漏。
内存泄漏是造成应用程序OOM的主要原因之一。我们知道android系统为每个应用程序分配的内存是有限的,而当一个应用中产生的内存泄漏比较多时,这就难免会导致应用所需要的内存超过系统分配的内存限额,这就造成了内存溢出从而导致应用Crash。
因为内存泄漏是在堆内存中,所以对我们来说并不是可见的。通常我们可以借助MAT、LeakCanary等工具来检测应用程序是否存在内存泄漏。
1、MAT是一款强大的内存分析工具,功能繁多而复杂。
2、LeakCanary则是由Square开源的一款轻量级的第三方内存泄漏检测工具,当检测到程序中产生内存泄漏时,它将以最直观的方式告诉我们哪里产生了内存泄漏和导致谁泄漏了而不能被回收。
由于单例的静态特性使得其生命周期和应用的生命周期一样长,如果一个对象已经不再需要使用了,而单例对象还持有该对象的引用,就会使得该对象不能被正常回收,从而导致了内存泄漏。
示例:防止单例导致内存泄漏的实例
这样不管传入什么Context最终将使用Application的Context,而单例的生命周期和应用的一样长,这样就防止了内存泄漏。???
例如,有时候我们可能会在启动频繁的Activity中,为了避免重复创建相同的数据资源,可能会出现如下写法:
这样在Activity内部创建了一个非静态内部类的单例,每次启动Activity时都会使用该单例的数据。虽然这样避免了资源的重复创建,但是这种写法却会造成内存泄漏。因为非静态内部类默认会持有外部类的引用,而该非静态内部类又创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,从而导致Activity的内存资源不能被正常回收。
解决方法 :将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,就使用Application的Context。
示例:创建匿名内部类的静态对象
1、从Android的角度
当Android应用程序启动时,该应用程序的主线程会自动创建一个Looper对象和与之关联的MessageQueue。当主线程中实例化一个Handler对象后,它就会自动与主线程Looper的MessageQueue关联起来。所有发送到MessageQueue的Messag都会持有Handler的引用,所以Looper会据此回调Handle的handleMessage()方法来处理消息。只要MessageQueue中有未处理的Message,Looper就会不断的从中取出并交给Handler处理。另外,主线程的Looper对象会伴随该应用程序的整个生命周期。
2、 java角度
在Java中,非静态内部类和匿名类内部类都会潜在持有它们所属的外部类的引用,但是静态内部类却不会。
对上述的示例进行分析,当MainActivity结束时,未处理的消息持有handler的引用,而handler又持有它所属的外部类也就是MainActivity的引用。这条引用关系会一直保持直到消息得到处理,这样阻止了MainActivity被垃圾回收器回收,从而造成了内存泄漏。
解决方法 :将Handler类独立出来或者使用静态内部类,这样便可以避免内存泄漏。
示例:AsyncTask和Runnable
AsyncTask和Runnable都使用了匿名内部类,那么它们将持有其所在Activity的隐式引用。如果任务在Activity销毁之前还未完成,那么将导致Activity的内存资源无法被回收,从而造成内存泄漏。
解决方法 :将AsyncTask和Runnable类独立出来或者使用静态内部类,这样便可以避免内存泄漏。
对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,从而造成内存泄漏。
1)比如在Activity中register了一个BraodcastReceiver,但在Activity结束后没有unregister该BraodcastReceiver。
2)资源性对象比如Cursor,Stream、File文件等往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。它们的缓冲不仅存在于 java虚拟机内,还存在于java虚拟机外。如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄漏。
3)对于资源性对象在不使用的时候,应该调用它的close()函数将其关闭掉,然后再设置为null。在我们的程序退出时一定要确保我们的资源性对象已经关闭。
4)Bitmap对象不在使用时调用recycle()释放内存。2.3以后的bitmap应该是不需要手动recycle了,内存已经在java层了。
初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的View对象,同时ListView会将这些View对象缓存起来。当向上滚动ListView时,原先位于最上面的Item的View对象会被回收,然后被用来构造新出现在下面的Item。这个构造过程就是由getView()方法完成的,getView()的第二个形参convertView就是被缓存起来的Item的View对象(初始化时缓存中没有View对象则convertView是null)。
构造Adapter时,没有使用缓存的convertView。
解决方法 :在构造Adapter时,使用缓存的convertView。
我们通常把一些对象的引用加入到了集合容器(比如ArrayList)中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。
解决方法 :在退出程序之前,将集合里的东西clear,然后置为null,再退出程序。
当我们不要使用WebView对象时,应该调用它的destory()函数来销毁它,并释放其占用的内存,否则其长期占用的内存也不能被回收,从而造成内存泄露。
解决方法 :为WebView另外开启一个进程,通过AIDL与主线程进行通信,WebView所在的进程可以根据业务的需要选择合适的时机进行销毁,从而达到内存的完整释放。
1、在涉及使用Context时,对于生命周期比Activity长的对象应该使用Application的Context。凡是使用Context优先考虑Application的Context,当然它并不是万能的,对于有些地方则必须使用Activity的Context。对于Application,Service,Activity三者的Context的应用场景如下:
其中,NO1表示Application和Service可以启动一个Activity,不过需要创建一个新的task任务队列。而对于Dialog而言,只有在Activity中才能创建。除此之外三者都可以使用。
2、对于需要在静态内部类中使用非静态外部成员变量(如:Context、View ),可以在静态内部类中使用弱引用来引用外部类的变量来避免内存泄漏。
3、对于不再需要使用的对象,显示的将其赋值为null,比如使用完Bitmap后先调用recycle(),再赋为null。
4、保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期。
5、对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏:
1)将内部类改为静态内部类
2)静态内部类中使用弱引用来引用外部类的成员变量
Android内存泄漏总结
㈡ android开发什么叫内存泄露
下面图片是解决内存泄露的例子。例子来自android学习手册,android学习手册,里面有源码。android学习手册包含9个章节,108个例子,源码文档随便看,例子都是可交互,可运行,源码采用android studio目录结构,高亮显示代码,文档都采用文档结构图显示,可以快速定位。360手机助手中下载,图标上有贝壳
在android程序开发中,当一个对象已经不需要再使用了,本该被回收时,而另外一个正在使用的对象持有它的引用从而导致它不能被回收,这就导致本该被回收的对象不能被回收而停留在堆内存中,内存泄漏就产生了。
内存泄漏有什么影响呢?它是造成应用程序OOM的主要原因之一。由于android系统为每个应用程序分配的内存有限,当一个应用中产生的内存泄漏比较多时,就难免会导致应用所需要的内存超过这个系统分配的内存限额,这就造成了内存溢出而导致应用Crash。
了解了内存泄漏的原因及影响后,我们需要做的就是掌握常见的内存泄漏,并在以后的android程序开发中,尽量避免它。下面小编搜罗了5个android开发中比较常见的内存泄漏问题及解决办法,分享给大家,一起来看看吧。
一、单例造成的内存泄漏
Android的单例模式非常受开发者的喜爱,不过使用的不恰当的话也会造成内存泄漏。因为单例的静态特性使得单例的生命周期和应用的生命周期一样长,这就说明了如果一个对象已经不需要使用了,而单例对象还持有该对象的引用,那么这个对象将不能被正常回收,这就导致了内存泄漏。
如下这个典例:
public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context;
}
public static AppManager getInstance(Context context) {
if (instance != null) {
instance = new AppManager(context);
}
return instance;
}
}
这是一个普通的单例模式,当创建这个单例的时候,由于需要传入一个Context,所以这个Context的生命周期的长短至关重要:
1、传入的是Application的Context:这将没有任何问题,因为单例的生命周期和Application的一样长 ;
2、传入的是Activity的Context:当这个Context所对应的Activity退出时,由于该Context和Activity的生命周期一样长(Activity间接继承于Context),所以当前Activity退出时它的内存并不会被回收,因为单例对象持有该Activity的引用。
所以正确的单例应该修改为下面这种方式:
public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context.getApplicationContext();
}
public static AppManager getInstance(Context context) {
if (instance != null) {
instance = new AppManager(context);
}
return instance;
}
}
这样不管传入什么Context最终将使用Application的Context,而单例的生命周期和应用的一样长,这样就防止了内存泄漏。
二、非静态内部类创建静态实例造成的内存泄漏
有的时候我们可能会在启动频繁的Activity中,为了避免重复创建相同的数据资源,会出现这种写法:
public class MainActivity extends AppCompatActivity {
private static TestResource mResource = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(mManager == null){
mManager = new TestResource();
}
//...
}
class TestResource {
//...
}
}
这样就在Activity内部创建了一个非静态内部类的单例,每次启动Activity时都会使用该单例的数据,这样虽然避免了资源的重复创建,不过这种写法却会造成内存泄漏,因为非静态内部类默认会持有外部类的引用,而又使用了该非静态内部类创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。正确的做法为:
将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,请使用ApplicationContext 。
三、Handler造成的内存泄漏
Handler的使用造成的内存泄漏问题应该说最为常见了,平时在处理网络任务或者封装一些请求回调等api都应该会借助Handler来处理,对于Handler的使用代码编写一不规范即有可能造成内存泄漏,如下示例:
public class MainActivity extends AppCompatActivity {
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//...
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
loadData();
}
private void loadData(){
//...request
Message message = Message.obtain();
mHandler.sendMessage(message);
}
}
这种创建Handler的方式会造成内存泄漏,由于mHandler是Handler的非静态匿名内部类的实例,所以它持有外部类Activity的引用,我们知道消息队列是在一个Looper线程中不断轮询处理消息,那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏,所以另外一种做法为:
public class MainActivity extends AppCompatActivity {
private MyHandler mHandler = new MyHandler(this);
private TextView mTextView ;
private static class MyHandler extends Handler {
private WeakReference<Context> reference;
public MyHandler(Context context) {
reference = new WeakReference<>(context);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = (MainActivity) reference.get();
if(activity != null){
activity.mTextView.setText("");
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView)findViewById(R.id.textview);
loadData();
}
private void loadData() {
//...request
Message message = Message.obtain();
mHandler.sendMessage(message);
}
}
创建一个静态Handler内部类,然后对Handler持有的对象使用弱引用,这样在回收时也可以回收Handler持有的对象,这样虽然避免了Activity泄漏,不过Looper线程的消息队列中还是可能会有待处理的消息,所以我们在Activity的Destroy时或者Stop时应该移除消息队列中的消息,更准确的做法如下:
public class MainActivity extends AppCompatActivity {
private MyHandler mHandler = new MyHandler(this);
private TextView mTextView ;
private static class MyHandler extends Handler {
private WeakReference<Context> reference;
public MyHandler(Context context) {
reference = new WeakReference<>(context);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = (MainActivity) reference.get();
if(activity != null){
activity.mTextView.setText("");
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView)findViewById(R.id.textview);
loadData();
}
private void loadData() {
//...request
Message message = Message.obtain();
mHandler.sendMessage(message);
}
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
}
使用mHandler.removeCallbacksAndMessages(null);是移除消息队列中所有消息和所有的Runnable。当然也可以使用mHandler.removeCallbacks();或mHandler.removeMessages();来移除指定的Runnable和Message。
四、线程造成的内存泄漏
对于线程造成的内存泄漏,也是平时比较常见的,如下这两个示例可能每个人都这样写过:
//——————test1
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(10000);
return null;
}
}.execute();
//——————test2
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(10000);
}
}).start();
上面的异步任务和Runnable都是一个匿名内部类,因此它们对当前Activity都有一个隐式引用。如果Activity在销毁之前,任务还未完成, 那么将导致Activity的内存资源无法回收,造成内存泄漏。正确的做法还是使用静态内部类的方式,如下:
static class MyAsyncTask extends AsyncTask<Void, Void, Void> {
private WeakReference<Context> weakReference;
public MyAsyncTask(Context context) {
weakReference = new WeakReference<>(context);
}
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(10000);
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
MainActivity activity = (MainActivity) weakReference.get();
if (activity != null) {
//...
}
}
}
static class MyRunnable implements Runnable{
@Override
public void run() {
SystemClock.sleep(10000);
}
}
//——————
new Thread(new MyRunnable()).start();
new MyAsyncTask(this).execute();
这样就避免了Activity的内存资源泄漏,当然在Activity销毁时候也应该取消相应的任务AsyncTask::cancel(),避免任务在后台执行浪费资源。
五、资源未关闭造成的内存泄漏
对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。
㈢ Android技术分享|Android 中部分内存泄漏示例及解决方案
内存泄漏:
举例:
请注意以下的例子是虚构的
内存抖动
源自Android文档中的 Memory churn 一词,中文翻译为内存抖动。
指快速频繁的创建对象从而产生的性能问题。
引用Android文档原文:
Java内存泄漏的根本原因是 长生命周期 的对象持有 短生命周期 对象的引用就很可能发生内存泄漏。
尽管短生命周期对象已经不再需要,但因为长生命周期依旧持有它的引用,故不能被回收而导致内存泄漏。
静态集合类引起的内存泄漏
如果仅仅释放引用本身(tO = null), ArrayList 依然在引用该对象,GC无法回收。
监听器
在Java应用中,通常会用到很多监听器,一般通过 addXXXXListener() 实现。但释放对象时通常会忘记删除监听器,从而增加内存泄漏的风险。
各种连接
如数据库连接、网络连接(Socket)和I/O连接。忘记显式调用 close() 方法引起的内存泄漏。
内部类和外部模块的引用
内部类的引用是很容易被遗忘的一种,一旦没有释放可能会导致一系列后续对象无法释放。此外还要小心外部模块不经意的引用,内部类是否提供相应的操作去除外部引用。
单例模式
由于单例的静态特性,使其生命周期与应用的生命周期一样长,一旦使用不恰当极易造成内存泄漏。如果单利持有外部引用,需要注意提供释放方式,否则当外部对象无法被正常回收时,会进而导致内存泄漏。
集合类泄漏
如集合的使用范围超过逻辑代码的范围,需要格外注意删除机制是否完善可靠。比如由静态属性 static 指向的集合。
单利泄漏
以下为简单逻辑代码,只为举例说明内存泄漏问题,不保证单利模式的可靠性。
AppManager 创建时需要传入一个 Context ,这个 Context 的生命周期长短至关重要。
1. 如果传入的是 Application 的 Context ,因为 Application 的生命周期等同于应用的生命周期,所以没有任何问题。
2. 如果传入的是 Activity 的 Context ,则需要考虑这个 Activity 是否在整个生命周期都不会被回收了,如果不是,则会造成内存泄漏。
非静态内部类创建静态实例造成的内存泄漏
应该将该内部类单独封装为一个单例来使用。
匿名内部类/异步线程
Runnable都使用了匿名内部类,将持有MyActivity的引用。如果任务在Activity销毁前未完成,将导致Activity的内存无法被回收,从而造成内存泄漏。
解决方法:将Runnable独立出来或使用静态内部类,可以避免因持有外部对象导致的内存泄漏。
Handler造成的内存泄漏
Handler属于TLS(Thread Local Storage)变量,生命周期与Activity是不一致的,容易导致持有的对象无法正确被释放
当Android应用程序启动时,该应用程序的主线程会自动创建一个Looper对象和与之关联的MessageQueue。
当主线程中实例化一个Handler对象后,它就会自动与主线程Looper的MessageQueue关联起来。所有发送到MessageQueue的Messag都会持有Handler的引用,所以Looper会据此回调Handle的handleMessage()方法来处理消息。只要MessageQueue中有未处理的Message,Looper就会不断的从中取出并交给Handler处理。
另外,主线程的Looper对象会伴随该应用程序的整个生命周期。
在Java中,非静态内部类和匿名类内部类都会潜在持有它们所属的外部类的引用,但是静态内部类却不会。
当该 Activity 被 finish() 掉时,延迟执行任务的 Message 还会继续存在于主线程中,它持有该 Activity 的 Handler 引用,所以此时 finish() 掉的 Activity 就不会被回收了从而造成内存泄漏(因 Handler 为非静态内部类,它会持有外部类的引用,在这里就是指 SampleActivity)。
避免不必要的静态成员变量
对于BroadcastReceiver、ContentObserver、File、Cursor、Stream、Bitmap等资源的使用,应在Activity销毁前及时关闭或注销。
不使用WebView对象时,应调用`destroy()`方法销毁。
㈣ 关于Android EditText导致的内存泄漏的问题
泄露信息如下:
====================================
HEAP ANALYSIS RESULT
====================================
1 APPLICATION LEAKS
References underlined with "~~~" are likely causes.
Learn more at https://squ.re/leaks.
Displaying only 1 leak trace out of 4 with the same signature
Signature:
┬───
│ GC Root: System class
│
├─ android.os.AsyncTask class
│ Leaking: NO (a class is never leaking)
│ ↓ static AsyncTask.SERIAL_EXECUTOR
│ ~~~~~~~~~~~~~~~
├─ android.os.AsyncTask$SerialExecutor instance
│ Leaking: UNKNOWN
│ ↓ AsyncTask$SerialExecutor.mTasks
│ ~~~~~~
├─ java.util.ArrayDeque instance
│ Leaking: UNKNOWN
│ ↓ ArrayDeque.elements
│ ~~~~~~~~
├─ java.lang.Object[] array
│ Leaking: UNKNOWN
│ ↓ Object[].[0]
│ ~~~
├─ android.os.AsyncTask$SerialExecutor$1 instance
│ Leaking: UNKNOWN
│ Anonymous class implementing java.lang.Runnable
│ ↓ AsyncTask$SerialExecutor$1.val$r
│ ~~~~~
├─ android.widget.TextView$3 instance
│ Leaking: UNKNOWN
│ Anonymous class implementing java.lang.Runnable
│ ↓ TextView$3.this$0
│ ~~~~~~
├─ androidx.appcompat.widget.AppCompatEditText instance
│ Leaking: YES (View.mContext references a destroyed activity)
│ View not part of a window view hierarchy
│ View.mAttachInfo is null (view detached)
│ View.mID = R.id.input_edit
│ View.mWindowAttachCount = 1
│ mContext instance of RoomMainActivity with mDestroyed = true
│ ↓ View.mContext
╰→ RoomMainActivity instance
Leaking: YES (ObjectWatcher was watching this because RoomMainActivity
received Activity#onDestroy() callback and Activity#mDestroyed is true)
key = cb388a5c-0ff2-452f-a9b0-6ea7d03a5105
watchDurationMillis = 37631
retainedDurationMillis = 32630
mApplication instance of ChatApplication
mBase instance of androidx.appcompat.view.ContextThemeWrapper
====================================
原因分析:
1、引用链结构:AsyncTask的SerialExecutor执行器引用了EditText对象,而EditText对象中的mContext引用到了RoomMainActivity中context,导致RoomMainActivity 无法销毁。
查看源码得知在TextView中有updateTextServicesLocaleAsync()方法,调用了AsyncTask.execute()向其中传入了匿名内部类Runnable,而持有了控件对象。
2、解决方法:因为在源码层面无法修改源码,在引用端切断引用链。
给EditText使用Application的上下文,在EditText使用的页面退出销毁时移除EditText控件,包括置空它的监听器、清除它的焦点。
import android.content.Context;
import android.os.Build;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.ViewGroup;
import androidx.appcompat.widget.AppCompatEditText;
/**
* 关于Android EditText导致的内存泄漏的问题
*/
public class NoMemoryLeakEditTextextends AppCompatEditText {
public NoMemoryLeakEditText(Context context) {
super(context.getApplicationContext());
}
public NoMemoryLeakEditText(Context context, AttributeSet attrs) {
super(context.getApplicationContext(), attrs);
}
public NoMemoryLeakEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context.getApplicationContext(), attrs, defStyleAttr);
}
public void clearMemoryLeak(TextWatcher watcher, ViewGroup container) {
clearFocus();
setOnTouchListener(null);
setOnClickListener(null);
setOnDragListener(null);
setOnKeyListener(null);
setOnLongClickListener(null);
setOnEditorActionListener(null);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
(null);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
setOnScrollChangeListener(null);
}
setOnFocusChangeListener(null);
removeTextChangedListener(watcher);
container.removeView(this);
}
}
最后在页面销毁的地方调用clearMemoryLeak方法
㈤ Android ValueAnimator --内存泄漏
不是成功来得太慢,只是下的功夫还不够
最近在写一个关于贝塞尔曲线水波纹渣尺的小DEMO,发现关闭当前视图时报出内存泄漏的问兆游题,检查下内存泄如猜高漏信息,经过排查发现是ValueAnimator的监听事件引起的,
先上解决方法:
在Activity/Fragment销毁时调用如下方法:
找到问题就好办了,查源码
检查发现在ValueAnimator start()方法中有个 addAnimationCallback(0); 调用,继续检查发现
这里就发现问题了, AnimationHandler.getInstance(); 这个是单例获取,所以在退出Activity时,ValueAnimator没有被释放,找到问题就好办了,就在Activity或者Fragment被销毁时,主动把动画监听取消掉。最后上一下我Demo的效果图 得意
㈥ 排查内存泄漏最简单和直观的方法
内存泄漏无疑会严重影响用户体验,一些本应该废弃的资源和对象无法被释放,导致手机内存的浪费,app使用的卡顿,那么如何排查内存泄漏呢?
当然,首先我门有google的官方文档可以参考:
大部分博客的方法也来自于此。总的来说,就是使用android studio 的monitor memory功能监测app主进程占用的内存,触发GC操作,而后观察内存的占用情况,如果在使用的过程中内存不断增加,没有回落, 很有可能 发生了内存泄漏,这时候就需要导出内存分配的具体详情进行深入分析了。
但是事实上,通过观察这个内存曲线的曾场来或者是观察allocate tracker中的allocate data数值的增长来检测是否有内存泄漏问题,真的很玄,因为往往内存泄漏发生了,但是GC仍然可以通过回收其他对象的方式腾出空间,导致这个数据的变化基本看不出来,甚至是减小的,所以我觉得这种方式, 就像是让你用手掌去感知婴儿的体温,去检测确定这个婴儿有没有发烧一样,非常不靠谱不准确。
那么,重点来了,我的方法,简单直观,保准你一学就会!
先说一个terminal指令:
这条指令是用来查询这个进程所占用的内存的具体详情的,通过这条指令可以看到当前app在手机中占用的具体的堆内存大小,view的数量, activity的数量 ,等等。如下图:
其中activity数目是非常关键的一个信息,可以帮助我们快速地检测出内存泄漏。我们可以反复地进入退出需要测试的目标activity,如果在反复进入退出之后,用terminal执行上面的语句查询当前的内存情况,如果发现activity数量一直在增长,那么内存泄露一定是发生了!
内存泄漏已经发生,如何定位原因呢?
如下图,在android studio中开始memory monitor,点击init GC,反复进入退出发生了内存泄漏的activity,这时候点击生成内存文件,这之后android studio会自动打开生成的.hprof文件。选中该文件转化成标准的hrof文件。
用MAT工具打开生成的.hprof文件,点击如下所示的图标,可以看到内存中的对象列表。
考虑到大内存的泄漏都是因为Activity被destroy之后却仍然被其他对象持有而造成的,因此首先解决棘手问题,直接搜索Activity,如下。发现有Activity的实例个数是3,跟实际不符,明显这个activity导致内存泄漏了,按照如图的方式找到它的引用,也就是导致内存泄漏的幕后兇手!
可以看到这个例子中的内存泄漏是由一个HandlerThread引发的,那么找到这个问题的位置,在合适的地方(如ondestroy)将这个handler thread释放即可。
如下图所示: 在android studio中打开生成的hprof文件,在右侧边栏会出现的Analyzer Tasks工具,点击执行图标,即可出现检测分析的结果,得到哪些activity被泄漏了,这些被泄漏的activity被谁引用了。
可以看到内存泄漏由AsyncHandler引起,需要在activity生命周期结束的时候进行释放。
方法2不用安装MAT工具,更加便捷哦~
有问题可以留言,谢谢您的阅读~~
㈦ 使用ConnectivityManager的内存泄漏隐患
Android里面内存泄漏问题最突出的就是Activity的泄漏,而泄漏的根源大多在于单例的使用,也就是一个静态实例持有了Activity的引用。静态变量的生命周期与应用(Application)是相同的,而键启Activity生命周期通常比它短,也就会造成在Activity生命周期结束后,还被引用导致无法被系统回收释放。
生成静态引用内存泄漏可能有两种情况:
这个主要讲下系统级的情况,这样的情况可能也有很多,举个最近发现的问题ConnectivityManager。
通常我们获取系统服务时采用如下方式:
在Android6.0系统上,如果这里的Context如果是Activity的实例,那么即使你什么也不干也会造成内存泄漏。
用LeakCanary可以直接看到内存泄漏:
一步一步来分析下。
先从Context的getSystemService方法开始,我们知道Activity是从ContextWrapper继承而来的,ContextWrapper中持有一个mBase实例,这个实例指向一个ContextImpl对象,同时ContextImpl对象持有一个OuterContext对象,对于Activity来说,这个OuterContext就是Activity对象。所以调用getSystemService最终会调用到ContextImpl的getSystemService方法。
在6.0上 ContextImpl 的getSystemService方法调用 SystemServiceRegistry 来完成。
SystemServiceRegistry提供ConnectivityManager的实例。
在6.0上, ConnectivityManager 实现为单例:
精彩的部分来了,ConnectivityManager 持庆亮贺有了一个Context的引用:
这个Context在ConnectivityManager 创建时传入,这个Context在中由ContextImpl对象转换为OuterContext,与就是Activity对象,所以最终ConnectivityManager的单实例持有了Activity的实例引用。这样即使Activity退出后仍然无法释放,导致誉派内存泄漏。
这个问题仅在6.0上出现,在5.1上ConnectivityManager实现为单例但不持有Context的引用,在5.0有以下版本ConnectivityManager既不为单例,也不持有Context的引用。
其他服务没认真研究,不确定有没有这个问题。不过为了避免类似的情况发生,最好的解决办法就是: