zoukankan      html  css  js  c++  java
  • Android

    [译]Android内存泄漏的八种可能(上)

    Android防止内存泄漏的八种方法(下)

    Static Activities

    在类中定义了静态Activity变量,把当前运行的Activity实例赋值于这个静态变量。
    如果这个静态变量在Activity生命周期结束后没有清空,就导致内存泄漏。因为static变量是贯穿这个应用的生命周期的,所以被泄漏的Activity就会一直存在于应用的进程中,不会被垃圾回收器回收。

    static Activity activity;
    
        void setStaticActivity() {
          activity = this;
        }
    
        View saButton = findViewById(R.id.sa_button);
        saButton.setOnClickListener(new View.OnClickListener() {
          @Override public void onClick(View v) {
            setStaticActivity();
            nextActivity();
          }
        });

    Android提供了特殊的Set集合https://developer.android.com/reference/java/lang/ref/package-summary.html#classes
    允许开发者控制引用的“强度”。Activity对象泄漏是由于需要被销毁时,仍然被强引用着,只要强引用存在就无法被回收。

    可以用弱引用代替强引用。
    https://developer.android.com/reference/java/lang/ref/WeakReference.html.

    弱引用不会阻止对象的内存释放,所以即使有弱引用的存在,该对象也可以被回收。

    private static WeakReference<MainActivity> activityReference;
    
        void setStaticActivity() {
            activityReference = new WeakReference<MainActivity>(this);
        }

    Static Views

    类似的情况会发生在单例模式中,如果Activity经常被用到,那么在内存中保存一个实例是很实用的。正如之前所述,强制延长Activity的生命周期是相当危险而且不必要的,无论如何都不能这样做。

    特殊情况:如果一个View初始化耗费大量资源,而且在一个Activity生命周期内保持不变,那可以把它变成static,加载到视图树上(View Hierachy),像这样,当Activity被销毁时,应当释放资源。(译者注:示例代码中并没有释放内存,把这个static view置null即可,但是还是不建议用这个static view的方法)

    static view;
    
        void setStaticView() {
          view = findViewById(R.id.sv_button);
        }
    
        View svButton = findViewById(R.id.sv_button);
        svButton.setOnClickListener(new View.OnClickListener() {
          @Override public void onClick(View v) {
            setStaticView();
            nextActivity();
          }
        });

    由于View持有其宿主Activity的引用,导致的问题与Activity一样严重。弱引用是个有效的解决方法,然而还有另一种方法是在生命周期结束时清除引用,Activity#onDestory()方法就很适合把引用置空。

    private static View view;
    
    @Override
    public void onDestroy() {
        super.onDestroy();
        if (view != null) {
            unsetStaticView();
        }
    }
    
    void unsetStaticView() {
        view = null;
    }

    Inner Classes

    继续,假设Activity中有个内部类,这样做可以提高可读性和封装性。将如我们创建一个内部类,而且持有一个静态变量的引用,恭喜,内存泄漏就离你不远了(译者注:销毁的时候置空,嗯)。

    private static Object inner;
    
           void createInnerClass() {
            class InnerClass {
            }
            inner = new InnerClass();
        }
    
        View icButton = findViewById(R.id.ic_button);
        icButton.setOnClickListener(new View.OnClickListener() {
            @Override public void onClick(View v) {
                createInnerClass();
                nextActivity();
            }
        });

    内部类的优势之一就是可以访问外部类,不幸的是,导致内存泄漏的原因,就是内部类持有外部类实例的强引用。

    与上述两种情况相似,开发者必须注意少用非静态内部类,因为非静态内部类持有外部类的隐式引用,容易导致意料之外的泄漏。然而内部类可以访问外部类的私有变量,只要我们注意引用的生命周期,就可以避免意外的发生。

    private Object inner;
    
    void createInnerClass() {
        class InnerClass {
        }
        inner = new InnerClass();
    }
     

    Anonymous Classes

    相似地,匿名类也维护了外部类的引用。所以内存泄漏很容易发生,当你在Activity中定义了匿名的AsyncTsk
    。当异步任务在后台执行耗时任务期间,Activity不幸被销毁了(译者注:用户退出,系统回收),这个被AsyncTask持有的Activity实例就不会被垃圾回收器回收,直到异步任务结束。


    void startAsyncTask() {
            new AsyncTask<Void, Void, Void>() {
                @Override protected Void doInBackground(Void... params) {
                    while(true);
                }
            }.execute();
        }
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        View aicButton = findViewById(R.id.at_button);
        aicButton.setOnClickListener(new View.OnClickListener() {
            @Override public void onClick(View v) {
                startAsyncTask();
                nextActivity();
            }
        });

    Handler

    同样道理,定义匿名的Runnable,用匿名类Handler执行Runnable内部类会持有外部类的隐式引用,被传递到Handler的消息队列MessageQueue中,在Message消息没有被处理之前,Activity实例不会被销毁了,于是导致内存泄漏。

    void createHandler() {
            new Handler() {
                @Override public void handleMessage(Message message) {
                    super.handleMessage(message);
                }
            }.postDelayed(new Runnable() {
                @Override public void run() {
                    while(true);
                }
            }, Long.MAX_VALUE >> 1);
        }
    
    
        View hButton = findViewById(R.id.h_button);
        hButton.setOnClickListener(new View.OnClickListener() {
            @Override public void onClick(View v) {
                createHandler();
                nextActivity();
            }
        });

    Threads

    我们再次通过ThreadTimerTask来展现内存泄漏。

    void spawnThread() {
            new Thread() {
                @Override public void run() {
                    while(true);
                }
            }.start();
        }
    
        View tButton = findViewById(R.id.t_button);
        tButton.setOnClickListener(new View.OnClickListener() {
          @Override public void onClick(View v) {
              spawnThread();
              nextActivity();
          }
        });

    全部都是因为匿名类导致的。匿名类是特殊的内部类——写法更为简洁。当需要一次性特殊的子类时,Java提供的语法糖能让表达式最少化。这种很赞很偷懒的写法容易导致泄漏。正如使用内部类一样,只要不跨越生命周期,内部类是完全没问题的。但是,这些类是用于产生后台线程的,这些Java线程是全局的,而且持有创建者的引用(即匿名类的引用),而匿名类又持有外部类的引用。线程是可能长时间运行的,所以一直持有Activity的引用导致当销毁时无法回收。
    这次我们不能通过移除静态成员变量解决,因为线程是于应用生命周期相关的。为了避免泄漏,我们必须舍弃简洁偷懒的写法,把子类声明为静态内部类。
     
    但是,如果你坚持使用匿名类,只要在生命周期结束时中断线程就可以。
    private Thread thread;
    
    @Override
    public void onDestroy() {
        super.onDestroy();
        if (thread != null) {
            thread.interrupt();
        }
    }
    
    void spawnThread() {
        thread = new Thread() {
            @Override public void run() {
                while (!isInterrupted()) {
                }
            }
        }
        thread.start();
    }
  • 相关阅读:
    OpenJDK源码研究笔记(十二):JDBC中的元数据,数据库元数据(DatabaseMetaData),参数元数据(ParameterMetaData),结果集元数据(ResultSetMetaDa
    Java实现 LeetCode 257 二叉树的所有路径
    Java实现 LeetCode 257 二叉树的所有路径
    Java实现 LeetCode 257 二叉树的所有路径
    Java实现 LeetCode 242 有效的字母异位词
    Java实现 LeetCode 242 有效的字母异位词
    Java实现 LeetCode 242 有效的字母异位词
    Java实现 LeetCode 241 为运算表达式设计优先级
    Java实现 LeetCode 241 为运算表达式设计优先级
    Java实现 LeetCode 241 为运算表达式设计优先级
  • 原文地址:https://www.cnblogs.com/qlky/p/7325440.html
Copyright © 2011-2022 走看看