Android内存泄露案例和解析

Posted 逆水当行舟

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android内存泄露案例和解析相关的知识,希望对你有一定的参考价值。

使用过长对象生命周期

静态引用

静态变量存储在方法区,在类加载的时候被加载,除非类被卸载了,或者他会一直存活,直到App进程销毁,也就是说静态变量的生命期等于整个进程的生命期。

    private Context mContext;
    @OnClick(R.id.btnStaticTest)
    void testClick() 
        finish();
        Toast.makeText(mContext, "mContext泄漏", Toast.LENGTH_SHORT).show();
    

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mContext = this;
   

mContext持有了MainActivity的引用,因为mContext生命期长于MainActivity所以造成内存泄漏

单例

单例整个程序只保存一个对象,因为其也要通过静态变量来实现,所以他的生命期其实和静态变量一致也是整个进程的存活期。如果在内部持有了一个生命期较短对象的引用,而且没有手动释放的话,那么也会造成内存泄漏。

public class SingleInstance 
    private Context mContext;
    private static SingleInstance instance = new SingleInstance();

    public static SingleInstance getInstance() 
        return instance;
    

    public void setup(Context context) 
        this.mContext = context
    

解决办法

使用getApplicationContext代替context

例如在用Toast的时候,我们会尽量选用getApplicationContext(),就是利用Application的上下文生命期会贯穿整个App。

 Toast.makeText(getApplicationContext(), "mContext泄漏", Toast.LENGTH_SHORT).show();

有的使用,我们无法拿到getApplicationContext()因为这个方法是在ContextWrapper这个类里面,Activity继承了这个类他自然可以拿到。可以通过下面这种方式去获取:

public class XApplication extends Application 
    private static Context mContext;

    @Override
    public void onCreate() 
        super.onCreate();
        mContext = this;
        LeakCanary.install(this);
    
    public static Context getContext() 
        return mContext;
    

使用的时候XApplication.getContext()拿到Context,因为Application和静态变量生命期一致,所以不会造成内存泄漏,如果Application 在程序切换到后台被后台杀死,再切换回来重建肯定会执行他的onCreate()方法,再次给mContext赋值,所以不用担心XApplication.getContext()会报空指针异常。

手动控制释放

例如在Activity中会用到Activity的上下文mContext,那么就可以在onDestroy()释放引用

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        SingleInstance.getInstance().setContext(this);
    

    @Override
    protected void onDestroy() 
        super.onDestroy();
        SingleInstance.getInstance().setContext(null);
    

事实上,这种情况还是经常碰到的,例如注册和反注册广播,还有使用EventBus或者otto的时候

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        EventBus.getDefault().register(this);
    

    @Override
    protected void onDestroy() 
        super.onDestroy();
        EventBus.getDefault().unregister(this);
    

EventBus.getDefault()的实现

    static volatile EventBus defaultInstance;
    /** Convenience singleton for apps using a process-wide EventBus instance. */
    public static EventBus getDefault() 
        if (defaultInstance == null) 
            synchronized (EventBus.class) 
                if (defaultInstance == null) 
                    defaultInstance = new EventBus();
                
            
        
        return defaultInstance;
    

很明显看出他是一个懒汉式(Lazy Loading)的单例,通过unregister方法实现我们所说的手动释放。

使用非静态内部类

非静态内部类包括以下三种
- 成员内部类
- 局部内部类
- 匿名内部类
非静态内部类会直接持有外部类的引用,如果非静态内部类不被回收,他的外部类也不会被回收从而造成内存泄漏
例如:

成员内部类

    class A 
    

    private static A a;

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        a = new A();
    

虽然class A为空,但是因为他存在一个静态实例,这会导致A的类实例不会被GC回收,从而导致外部类MainActivity不会被回收。

匿名内部类

在点击事件的匿名内部类中,通过Handler.postDelayed()执行一个延迟任务,任务执行在Runnable匿名内部类中实现。

public class MainActivity extends AppCompatActivity 
    public static final String TAG = "MainActivity";
    public final static int DO_WORK = 1;


    private Handler mHandler = new Handler(new Handler.Callback() 
        @Override
        public boolean handleMessage(Message msg) 
            if (msg.what == DO_WORK) 
                //do something
            
            return false;
        
    );
    @BindView(R.id.btnHandler)
    Button btnHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        btnHandler.setOnClickListener(new View.OnClickListener() 
            @Override
            public void onClick(View v) 
                MainActivity.this.finish();
                mHandler.sendEmptyMessage(DO_WORK);
                mHandler.postDelayed(new Runnable() 
                    @Override
                    public void run() 
                        Toast.makeText(MainActivity.this, "btnHandler", Toast.LENGTH_SHORT).show();
                    
                , 15 * 1000);
            
        );
    

这里存在2处内存溢出:
1. finish()掉这个Activity,任务延迟15s,在延后的这15S过程中,造成了15S的内存泄漏。
2. 在public boolean handleMessage(Message msg)收到发送过来的消息,执行一些任务

修改方式:有两种方案:

  1. 手动取消 。 在关闭Activity时(finish/onStop等函数中),取消还在排队的Message:
    mHandler.removeCallbacksAndMessages(null);

  2. 使用弱引用。 使用WeakReference截断StrongReference。问题的症结既然是内部类持有外部类对象的引用,那我不用内部类就行了,直接使用静态成员类。但mHandler又需要与Activity对象交互,那就来个WeakReference,指向外部Activity对象。

public class MainActivity extends AppCompatActivity 
    public static final String TAG = "MainActivity";
    public final static int DO_WORK = 1;

    private MyHandler mHandler;
    @BindView(R.id.btnHandler)
    Button btnHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHandler = new MyHandler(this);
        ButterKnife.bind(this);
        btnHandler.setOnClickListener(new View.OnClickListener() 
            @Override
            public void onClick(View v) 
                MainActivity.this.finish();
                mHandler.sendEmptyMessage(DO_WORK);
                mHandler.postDelayed(new Runnable() 
                    @Override
                    public void run() 
                        Toast.makeText(MainActivity.this, "btnHandler", Toast.LENGTH_SHORT).show();
                    
                , 15 * 1000);
            
        );
    

//方案一
    @Override
    protected void onDestroy() 
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
        if (mHandler != null) 
            mHandler = null;
        
    
//方案二
    private static class MyHandler extends Handler 
        private WeakReference<AppCompatActivity> wr;

        public MyHandler(AppCompatActivity aty) 
            wr = new WeakReference<>(aty);
        


        @Override
        public void handleMessage(Message msg) 
            super.handleMessage(msg);
            if (wr.get() != null) 
                if (msg.what == DO_WORK) 
                //dosomething
                
            
        
    


耗时操作

直接开启子线程,匿名内部类

public class MainActivity extends AppCompatActivity 
    public static final String TAG = "MainActivity";
    private static int count = 0;

    @BindView(R.id.btnHandler)
    Button btnHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        new Thread(new Runnable() 
            @Override
            public void run() 
                while (true) 
                    try 
                        Thread.sleep(1000);
                     catch (InterruptedException e) 
                        e.printStackTrace();
                    
                    count++;
                    Log.d(TAG, "run: " + count);
                
            
        ).start();
    

根据我们刚才分析的,使用弱引用来解除,Thread对Activity持有的引用

public class MainActivity extends AppCompatActivity 
    public static final String TAG = "MainActivity";
    private static int count = 0;

    @BindView(R.id.btnHandler)
    Button btnHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        new MyThread(new Runnable() 
            @Override
            public void run() 
                while (true) 
                    try 
                        Thread.sleep(1000);
                     catch (InterruptedException e) 
                        e.printStackTrace();
                    
                    count++;
                    Log.d(TAG, "run: " + count);
                
            
        ).start();
    

    private class MyThread extends Thread 
        private WeakReference<Runnable> wr;

        public MyThread(Runnable runnable) 
            super(runnable);
            wr = new WeakReference<Runnable>(runnable);
        

        @Override
        public synchronized void start() 
            if (wr.get() == null) 
                return;
            
            super.start();
        
    

但是运行后发现,这样做没有作用。因为我们开始建立的Thread的时候有两个匿名内部类,修改后Runnable还是持有Activity的引用。
这里我们可以利用,线程池的特性在Activity停止的时候取消任务

public class MainActivity extends AppCompatActivity 
    public static final String TAG = "MainActivity";
    private static int count = 0;

    @BindView(R.id.btnHandler)
    Button btnHandler;
    private Future future;

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        Callable<Boolean> callable = new Callable() 
            @Override
            public Object call() throws Exception 
                while (true) 
                    try 
                        Thread.sleep(1000);
                     catch (InterruptedException e) 
                        e.printStackTrace();
                        return false;
                    
                    count++;
                    Log.d(TAG, "run: " + count);

                
            
        ;
        ExecutorService es = Executors.newSingleThreadExecutor();
        future = es.submit(callable);
    

    @Override
    protected void onDestroy() 
        super.onDestroy();
        future.cancel(true);
    

注意:不要使用Runnable取代Callable,这并不能中断任务执行,下面是错误的范例

        Runnable runnable = new Runnable() 
            @Override
            public void run() 
                
                    while (true) 
                        try 
                            Thread.sleep(1000);
                         catch (InterruptedException e) 
                            e.printStackTrace();
                        
                        count++;
                        Log.d(TAG, "run: " + count);
                    
                
            
        ;
        ExecutorService es = Executors.newSingleThreadExecutor();
        future = es.submit(runnable);

这样写 并不能取消执行

以上是关于Android内存泄露案例和解析的主要内容,如果未能解决你的问题,请参考以下文章

Android面试-Android性能优化和内存优化APP启动速度一线大厂的实战案例解析

5.JVM系列-堆内内存泄露案例分析解决

杂想之一个C++内存泄露案例

如何定位Android NDK开发中遇到的错误

如何使用MAT分析Android应用内存泄露

使用新版Android Studio检测内存泄露和性能