内存泄漏

1、内存泄露的本质

指当前对象在实际运行超出了其本身意义上生命周期范围的,从而导致本该处于内存可回收状态的但实际上却一直处于不可回收状态的内存占用非正常现象。

2、常见的两种现象:

(1)异步回调中持有M,异步回调生命周期不可控,或者本身的生命周期长于Activity,导致内存泄漏。常见于Activity中handler、异步线程

(2)静态属性持有了M的强引用。常见于单例模式强引用外部的非静态对象。

3、具体导致内存泄漏的几个方面

(1)Context

单例模式中,引用的context尽量用非activity类型的context,优先使用Application的Context

(2)Handler

泄漏一:activity内部类handler,发送延迟消息。消息未发送,主线程持有message,message持有handler,handler是activity内部类。

    private Handler handler = new Handler(Looper.getMainLooper());
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {

            }
        },20000);
        finish();
}

主线程 —> threadlocal —> Looper —> MessageQueue —> Message —> Handler —> Activity

泄漏二:子线程不回收。子线程生命周期长于外部类

        handler.post(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                test = "sss";
            }
        });
        finish();

Handler导致的一般是临时性泄漏,当任务执行完后,会被gc

(3)非静态内部类

非静态内部类默认持有外部类的引用,会导致内存泄漏。前提是内部类的生命周期要比外部类的生命周期长

如下不会发生内存泄漏,doSomething生命周期短于外部类

public class MainActivity2 extends AppCompatActivity {
    private MessageCall messageCall;
    
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        messageCall = new MessageCall();
        findViewById(R.id.doSomething).setOnClickListener(view -> {
            messageCall.doSomething();
        });
    }

    class MessageCall {
        public void doSomething(){
            System.out.println("doSomething");
        }
    }
}

改造messagecall,加入耗时方法。当runable未执行完,关闭activity,则会导致内存泄漏

class MessageCall {
        public void doSomething(){
            Runnable work = new Runnable() {
                @Override public void run() {
                    System.out.println("doSomething start");
                    SystemClock.sleep(20000);
                    System.out.println("doSomething end");
                }
            };
            new Thread(work).start();         
        }
    }

(4)Activity动态广播

finish、destory时取消广播。因为activity中动态注册的broadcastreceive,通过AMS强应用。

(5)Activity

Activity中尽量不要使用非静态内部类,可以使用静态内部类和WeakReference代替

4、内存分析工具

(1)LeakCanary

app的build.gradle中加入:

dependencies {
   debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
   releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
   testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
 }

Application中加入:

LeakCanary.install(this);

操作要检测的页面,查看结果。如果有内存泄漏

 

 (2)Profile Memory 常用工具

从Heap Dump中可以看到leaks的数量和具体发生泄漏的类。使用方便,快捷

(3)adb

adb shell dumpsys meminfo com.example.testclient 查看当前activity使用情况

小结,几个处理内存泄漏的方法:

(1)Context:单例模式引用长生命周期context

(2)Handler:handler.removeCallbacksAndMessages(null);

(3)广播、各种监听:适时注销

(4)非静态内部类:

  • 提取为外部类,单独定义类
  • 构造方法持有外部类的弱引用
  • 转化为静态类 + 外部类的弱引用
    private static class MyRunnable implements Runnable {

        private WeakReference<MainActivity> weakReference;
        public MyRunnable(MainActivity activity){
            weakReference = new WeakReference<>(activity);
        }

        @Override
        public void run() {
            MainActivity activity = weakReference.get();
            activity.initView();
        }
    }

 

posted @ 2022-11-24 11:50  随易来了  阅读(132)  评论(0编辑  收藏  举报