纯净、安全、绿色的下载网站

首页|软件分类|下载排行|最新软件|IT学院

当前位置:首页IT学院IT技术

Android内存优化 详解Android内存泄露及优化方案

我唱着黑色毛衣   2021-09-10 我要评论
想了解详解Android内存泄露及优化方案的相关内容吗我唱着黑色毛衣在本文为您仔细讲解Android内存优化的相关知识和一些Code实例欢迎阅读和指正我们先划重点:Android,内存优化,Android,内存泄露下面大家一起来学习吧

一、常见的内存泄露应用场景?

1、单例的不恰当使用

单例是我们开发中最常见和使用最频繁的设计模式之一所以如果使用不当就会导致内存泄露因为单例的静态特性使得它的生命周期同应用的生命周期一样长如果一个对象已经没有用处了但是单例还持有它的引用那么在整个应用程序的生命周期这个对象都不能正常被回收从而导致内存泄露

如:

public class App {
    private static App sInstance;

    private Context mContext;

    private App(Context context) {
        this.mContext = context;
    }

    public static App getInstance(Context context) {
        if (sInstance == null) {
            sInstance = new App(context);
        }
        return sInstance;
    }
}

调用getInstance(Context context)方法时传入的上下文如果为当前活动Activity或者当前服务的Service以及当前fragment的上下文当他们销毁时这个静态单例sIntance还会持用他们的引用从而导致当前活动、服务、fragment等对象不能被回收释放从而导致内存泄漏这种上下文的使用很多时候处理不当就会导致内存泄漏需要我们多注意编码规范

2、静态变量导致内存泄露

静态变量存储在方法区它的生命周期从类加载开始到整个进程结束一旦静态变量初始化后 它所持有的引用只有等到进程结束才会释放

如下代码:

public class MainActivity extends AppCompatActivity {
    private static Info sInfo;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (sInfo != null) {
            sInfo = new Info(this);
        }
    }
}

class Info {
    public Info(Activity activity) {
    }
}

Info 作为 Activity 的静态成员并且持有 Activity 的引用但是 sInfo 作为静态变量生命周期 肯定比 Activity 长所以当 Activity 退出后sInfo 仍然引用了 ActivityActivity 不能被回收 这就导致了内存泄露 在 Android 开发中静态持有很多时候都有可能因为其使用的生命周期不一致而导致内存泄露 所以我们在新建静态持有的变量的时候需要多考虑一下各个成员之间的引用关系并且尽量少地 使用静态持有的变量以避免发生内存泄露当然我们也可以在适当的时候讲静态量重置为 null 使其不再持有引用这样也可以避免内存泄露

3、非静态内部类导致内存泄露

非静态内部类(包括匿名内部类)默认就会持有外部类的引用当非静态内部类对象的生命周期 比外部类对象的生命周期长时就会导致内存泄露这类内存泄漏很典型的Handler的使用这么一说大家应该就很熟悉了吧大家都知道怎么处理这类内存泄漏

Handler的使用示例:

    private void start() {
        Message msg = Message.obtain();
        msg.what = 1;
        mHandler.sendMessage(msg);
    }

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == 1) {
                // ui更新
            }
        }
    };

Handler 消息机制mHandler 会作为成员变量保存在发送的消息 msg 中即 msg 持有 mHandler 的引用而 mHandler 是 Activity 的非静态内部类实例即 mHandler 持有 Activity 的引 用那么我们就可以理解为 msg 间接持有 Activity 的引用msg 被发送后先放到消息队列 MessageQueue 中然后等待 Looper 的轮询处理(MessageQueue 和 Looper 都是与线程相关联的 MessageQueue 是 Looper 引用的成员变量而 Looper 是保存在 ThreadLocal 中的)那么当 Activity 退出后msg 可能仍然存在于消息对列 MessageQueue 中未处理或者正在处理那么这样就会导致 Activity 无法被回收以致发生 Activity 的内存泄露

如何避免:
1、采用静态内部类+弱引用的方式

 private static class MyHandler extends Handler {
        private WeakReference<MainActivity> activityWeakReference;

        public MyHandler(MainActivity activity) {
            activityWeakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = activityWeakReference.get();
            if (activity != null) {
                if (msg.what == 1) {
                    // 做相应逻辑 
                }
            }
        }
    }

mHandler 通过弱引用的方式持有 Activity当 GC 执行垃圾回收时遇到 Activity 就会回收并释 放所占据的内存单元这样就不会发生内存泄露了但是 msg 还是有可能存在消息队列 MessageQueue 中

2、Activity 销毁时就将 mHandler 的回调和发送的消息给移除掉

  @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }

非静态内部类造成内存泄露还有一种情况就是使用 Thread 或者 AsyncTask异步调用:
如示例:
Thread :

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

AsyncTask:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new AsyncTask<Void, Void, Void>() {
            @Override
            protected Void doInBackground(Void... params) { 
                // UI线程处理
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return null;
            }
        }.execute();
    }
}

以上新建的子线程 Thread 和 AsyncTask 都是匿名内部类对象默认就隐式的持有外部 Activity 的引用 导致 Activity 内存泄露要避免内存泄露的话还是需要像上面 Handler 一样使用采用静态内部类+弱引用的方式(如上面Hanlder采用静态内部类+弱引用的方式)

4、未取消注册或回调导致内存泄露

比如我们在 Activity 中注册广播如果在 Activity 销毁后不取消注册那么这个刚播会一直存在 系统中同上面所说的非静态内部类一样持有 Activity 引用导致内存泄露因此注册广播后在 Activity 销毁后一定要取消注册

this.unregisterReceiver(mReceiver);

在注册观察则模式的时候如果不及时取消也会造成内存泄露比如使用 Retrofit+RxJava 注册网络请求的观察者回调同样作为匿名内部类持有外部引用所以需要记得在不用或者销毁的时候 取消注册

5、定时器Timer 和 TimerTask 导致内存泄露

当我们 Activity 销毁的时有可能 Timer 还在继续等待执行 TimerTask它持有 Activity 的引用不 能被回收因此当我们 Activity 销毁的时候要立即 cancel 掉 Timer 和 TimerTask以避免发生内存 泄漏

6、集合中的对象未清理造成内存泄露

这个比较好理解如果一个对象放入到 ArrayList、HashMap 等集合中这个集合就会持有该对象 的引用当我们不再需要这个对象时也并没有将它从集合中移除这样只要集合还在使用(而 此对象已经无用了)这个对象就造成了内存泄露并且如果集合被静态引用的话集合里面那 些没有用的对象更会造成内存泄露了所以在使用集合时要及时将不用的对象从集合 remove或 者 clear 集合以避免内存泄漏

7、资源未关闭或释放导致内存泄露

在使用 IO、File 流或者 Sqlite、Cursor 等资源时要及时关闭这些资源在进行读写操作时通常都 使用了缓冲如果及时不关闭这些缓冲对象就会一直被占用而得不到释放以致发生内存泄露 因此我们在不需要使用它们的时候就及时关闭以便缓冲能及时得到释放从而避免内存泄露

8、动画造成内存泄露

动画同样是一个耗时任务比如在 Activity 中启动了属性动画(ObjectAnimator)但是在销毁 的时候没有调用 cancle 方法虽然我们看不到动画了但是这个动画依然会不断地播放下去 动画引用所在的控件所在的控件引用 Activity这就造成 Activity 无法正常释放因此同样要 在 Activity 销毁的时候 cancel 掉属性动画避免发生内存泄漏

  @Override
    protected void onDestroy() {
        super.onDestroy();
        mAnimator.cancel();
    }

9、WebView 造成内存泄露

关于 WebView 的内存泄露因为 WebView在加载网页后会长期占用内存而不能被释放因此我 们在 Activity 销毁后要调用它的 destory()方法来销毁它以释放内存
另外在查阅 WebView 内存泄露相关资料时看到这种情况: Webview 下面的 Callback 持有 Activity 引用造成 Webview 内存无法释放即使是调用了 Webview.destory()等方法都无法解决问题(Android5.1 之后)

最终的解决方案是:在销毁 WebView 之前需要先将 WebView 从父容器中移除然后在销毁 WebView

   @Override
    protected void onDestroy() {
        super.onDestroy();
        // 先从父控件中移除
        WebView mWebViewContainer.removeView(mWebView);
        mWebView.stopLoading();
        mWebView.getSettings().setJavaScriptEnabled(false);
        mWebView.clearHistory();
        mWebView.removeAllViews();
        mWebView.destroy();
    }

总结

构造单例的时候尽量别用 Activity 的引用;
静态引用时注意应用对象的置空或者少用静态引用;
使用静态内部类+软引用代替非静态内部类;
及时取消广播或者观察者注册;
耗时任务、属性动画在 Activity 销毁时记得 cancel;
文件流、Cursor 等资源及时关闭; Activity 销毁时 WebView 的移除和销毁

下一篇继续:详解Android内存优化策略

ps:内存泄漏是开发中的一个痛点需要我们有很好的良好编码习惯奥里给!!!!!!!!!


相关文章

猜您喜欢

网友评论

Copyright 2020 www.sopisoft.net 【绿软下载站】 版权所有 软件发布

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 点此查看联系方式