Acvitiy销毁时,是否把所有引用都清除了?

首先我知道题目的答案肯定是否定的,因为假如finish()能一键清理所有引用的话,就不会有那么多memory leak之类的bug了。

我查到memory leak一般是由非静态内部类(non-static inner class)或非静态匿名类导致的,因为它们总是持有外部类的引用;如果外部类就是Activity,就会导致GC无法回收。

那问题来了,finish()既然能清除掉其他所有的引用,为什么就不能也清除掉非静态内部类持有的引用呢?

例如现在有10个View,它们本身就带有getContext()方法,说明它们都持有对其所在的Context的引用;然后这个context其实是一个Activity,里面还定义了个非静态内部类,那么这个内部类也持有该Activity的引用。那么现在执行finish(),Activity本该销毁,凭什么其他10个View的引用都清除了,就只剩下这个非静态内部类持有的引用不清除呢?

我的理解:finish是结束自身,但最终回收是由GC回收决定,虚拟机的回收机制是根可达计算方式,View是依附于Activity上的,可以看作Acitivity

可回收时View已经没有其他引用到的地方了,这样View 的回收是没有阻碍的(他们都在GC回收线程工作时被回收) ,非静态内部类持有外部类引用导致外部类无法被回收从而内存泄漏,这是因为这个非静态内部类没有办法回收,如自定义的handler,它还在工作,UI线程中持有handler的引用导致Activity不能回收,导致泄漏,这个时候是需要将它设为静态类的,如果你的View是有被Gc root引用到的话,它的activity同样也没法回收,finish的源码我没有具体去看,但看过虚拟机原理,这是我大致的了解,欢迎继续沟通

编写如下代码:

package com.example.myapplication;

import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

public class LeakActivity extends AppCompatActivity {

    private Handler handler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            context.button.setText("ddd");
            Log.i("handler", "dddd");
        }
    };

    Button button;
    LeakActivity context;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        context = this;

        button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                handler.sendEmptyMessageDelayed(0, 20000);
            }
        });
    }

    @Override
    public void finish() {
        super.finish();
        Log.i("finish", "true");
        context = null;
        handler = null;
    }
}

 

2021-02-07 18:40:46.440 3424-3424/com.example.myapplication E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.myapplication, PID: 3424
    java.lang.NullPointerException: Attempt to read from field 'android.widget.Button com.example.myapplication.LeakActivity.button' on a null object reference
        at com.example.myapplication.LeakActivity$1.handleMessage(LeakActivity.java:21)
        at android.os.Handler.dispatchMessage(Handler.java:107)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7356)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)

 

App检测到内存泄漏,同时以空指针报错,说明Activity为空不一定可以回收,延时任务还是在跑的。所以finish把引用清空其实是不对的。finish只是生命周期,不负责清空引用和延时任务。