内存优化小结

内存泄漏(Memory Leak)

每个应用程序都需要内存来完成工作,为了确保Android 系统的每个应用都有足够的内存,Android 系统需要有效地管理内存分配。当内存不足时,Android 运行时就会触发GC,GC采用的垃圾标记算法为根搜索算法。

在这里插入图片描述
从上图可知,Obj4是可达的对象,表示它被引用,因此不会标记为可回收的对象。Obj5、Obj6和Obj7都是不可达的对象,其中Obj5和Obj6虽然相互引用,但是因为它们到GCRoots是不可达,所以它们仍旧被标记为可回收的对象。

内存泄露就是指没有用的对象到GC Roots是可达的(对象被引用),导致GC无法回收该对象。或者是已经不再使用某个对象,但是因为仍然有引用指向它,垃圾回收器就无法回收它,当然该对象占用的内存就无法被使用,这就造成了内存泄露

此时,如果Obj4 是一个没用的对象,但它仍与GCRoots是可达的,那么Obj4就会发生内存泄漏。

内存泄漏产生的原因,主要分为三大类:

  • 由开发人员自己编码造成的泄漏
  • 第三方框架造成的泄漏
  • 由Android 系统或者第三方ROM造成的泄漏

内存检测工具

  • Memory Profiler

内存性能分析器是 Android Profiler 中的一个组件,可帮助您识别可能会导致应用卡顿、冻结甚至崩溃的内存泄漏和内存抖动。

Memory Profiler

  • LeakCanary

LeakCanary是Android的内存泄漏检测库,来缩小每次泄漏的原因。

LeakCanary

  • Matrix ResourceCanary

Resource Canary主要是用来检测Activit级别的内存泄漏、以及重复创建的冗余Bitmap。

ResourceCanary

内存泄漏的场景

非静态内部类的静态实例

非静态内部类会持有外部类实例的引用,如果非静态内部类的实例是静态的,就会间接地长期维持着外部类的引用,阻止被系统回收。

    private static Object inner;
    
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.acttivity_test_resource_trace);

        Button btn=findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                createInnerClass();
            }
        });
    }
    
    private void createInnerClass(){
        inner=new Inner();
    }
    
    class Inner{}

当点击Button 时,会在createInnerClass() 方法里创建非静态内部类Inner的静态实例inner,该实例的生命周期会和应用程序一样长,并且一直持有当前Activity的引用,导致当前Activity无法被回收。

多线程相关的匿名内部类/非静态内部类

匿名内部类也会持有外部类实例的引用。多线程相关的Thread类和实现Runnable 接口的类等,它们的匿名内部类/非静态内部类如果做耗时操作就可能发生内存泄漏。

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

        Button btn=findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
               new Thread(new MyRunnable()).start();
               finish();
            }
        });
    }
    
    class MyRunnable implements Runnable {
        @Override
        public void run() {
            // 耗时较长任务
        }
    }

Runnable使用了匿名内部类,那么它将持有其所在Activity的隐式引用。如果任务在Activity销毁之前还未完成,那么将导致Activity的内存资源无法被回收,从而造成内存泄漏。解决方法就是使用静态内部类,这样便可以避免内存泄漏。

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

        Button btn=findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
               new Thread(new MyRunnable()).start();
               finish();
            }
        });
    }
    
    static class MyRunnable implements Runnable {
        @Override
        public void run() {
            // 耗时较长任务
        }
    }
Handler 内存泄漏

Handler的Message被存储在MessageQueue中,有些Message并不能马上处理掉,它们在MessageQueue中存在的时间会很长,这就会导致Handler无法被回收。如果Handler是非静态的,则Handler 也会导致引用它的Activity 或者Service 不能被回收。

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

        Button btn=findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mHandler.sendMessageDelayed(Message.obtain(),60000);
                finish();
            }
        });
    }

    Handler mHandler=new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
        }
    };

Handler 是非静态的匿名内部类的实例,它会隐性引用外部类Activity。点击Button时,Activity会结束,但是Handler 中的消息还没有被处理,因此Activity无法被回收。解决方案如下:

  MyHandler mHandler=new MyHandler();
    
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.acttivity_test_resource_trace);

        Button btn=findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mHandler.sendMessageDelayed(Message.obtain(),60000);
                finish();
            }
        });
    }
     // 一种解决方法:MyHandler 是静态内部类,它持有TestActivity对象使用了弱引用,这样就避免了内存泄漏。
    static class MyHandler  extends Handler{
        private WeakReference<TestActivity> mActivity;
        
        public MyHandler(TestActivity activity){
           mActivity=new  WeakReference<TestActivity>();
        }
        
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
        }
    }
    
    // 另一种解决方法:在onDestroy()方法中将Callbacks 和Messages 全部清除。采用这种方法,Handle中消息可能无法全部处理完
    @Override
    public void onDestroy(){
        if(mHandler!=null){
            mHandler.removeCallbackAndMessages(null);
        }
    }
未正确使用Context

对于不是必须使用Activity的Context 的情况(Dialog 的Context 必须使用Activity的Context),可以考虑使用Application Context来替代Activity的Context,这样可以避免Activity 泄漏。

public class AppSetting{
    private Context mAppContext;
    
    private static AppSetting mAppSetting=new AppSetting();
    
    pubic static AppSetting getInstance(){
        return mAppSetting;
    }
    
    public final void setup(Context context){
        mAppContext=context;
    }
}

mAppSetting作为静态对象,其生命周期长于Activity。当进行屏幕旋转旋转时,系统会销毁当前Activity。因为当前Activity调用了setup方法,并传入了Activity Context,使得Activity 被一个单例持有,导致垃圾收集器无法回收,进而产生了内存泄漏。解决办法就是使用Application的 Context。

public final void setup(Context context){
        mAppContext=context.getApplicationContext();
    }
静态View

使用静态View可以避免每次启动Activity都去读取并渲染View,但是静态View会持有Activity的引用,导致Activity无法被回收。解决的方法就是在onDestory 方法中将静态View 置为null。

private static Button btn;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.acttivity_test_resource_trace);

        btn=findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });
    }
    
    @Override
    public void onDestroy(){
        btn=null;
    }
WebView

WebView 存在内存泄漏的问题,在应用中只要使用一次WebView,内存就不会被释放掉。通常的解决办法就是为WebView单开一个进程,使用AIDL与应用的主程序进行通信。WebView进程可以根据业务需求,在合适的时机进行销毁。

资源对象未关闭

资源对象比如File、Cursor等,往往都使用了缓冲,会造成内存泄漏。因此,在资源对象不使用时,一定要确保它们已经关闭并将它们的引用置为Null,通常在finally 语句中进行关闭,防止出现异常时,资源未被释放的问题。

集合中对象没清理

通常把一些对象的引用加入到了集合中,当不需要该对象时,如果没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static 的话,那情况更加严重。

Bitmap 对象

临时创建的某个相对比较大的Bitmap对象,在经过变换得到 新的Bitmap 对象之后,应该尽快回收原始的Bitmap,这样能够更快释放原始Bitmap所占用的空间。避免静态变量持有比较大的Bitmap 对象或者其他大的数据对象,如果持有,要尽快置空该静态变量。

监听器未关闭

很多系统服务(比如TelephoneManager、SensorManager)需要register 和unregister 监听器,需要确保在合适的时候及时unregister监听器。自己手动添加的Listener,要记得在合适的时候及时移除这个Listener。

小结:内存泄漏最终导致内存溢出。

内存溢出(Out of Memory Error)

内存溢出是指当对象的内存占用已经超出分配内存的空间大小,这时未经处理的异常就会抛出。

内存溢出原因
  • 内存泄露导致

由于我们程序的失误,长期保持某些资源(如Context)的引用,垃圾回收器就无法回收它。该对象占用的内存就无法被使用,这就造成内存泄露。

  • 占用内存较多的对象

保存了多个耗用内存过大的对象(如Bitmap),造成内存超出限制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值