Android性能优化-内存泄漏(一)

本文详细介绍了Android应用中的内存管理原理,包括静态、栈式和堆式内存的特点与应用场景,重点讲解了内存泄漏的原因与解决方法,如单例模式、Handler、线程等场景下可能产生的内存泄漏及其解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Android每一个应用都是运行的独立的Dalivk虚拟机,根据不同的手机分配的可用内存可能只有(32M、64M等),所谓的4GB、6GB运行内存其实对于我们的应用不是可以任意索取

JVM内存管理


Java采用GC进行内存管理。深入的JVM内存管理知识,推荐《深入理解Java虚拟机》。关于内存泄漏我们要知道,JVM内存分配的几种策略。

1、静态的

静态的存储区,内存在程序编译的时候就已经分配好了,这块内存在程序整个运行期间都一直存在,它主要存放静态数据、全局的static数据和一些常量。

2、栈式的

在执行方法时,方法一些内部变量的存储都可以放在栈上面创建,方法执行结束的时候这些存储单元就会自动被注释掉。栈 内存包括分配的运算速度很快,因为内在在处理器里面。当然容量有限,并且栈式一块连续的内存区域,大小是由操作系统决定的,他先进后 出,进出完成不会产生碎片,运行效率高且稳定

3、堆式的

也叫动态内存 。我们通常使用new 来申请分配一个内存。这里也是我们讨论内存泄漏优化的关键存储区。GC会根据内存的使用情况,对堆内存里的垃圾内存进行回收。堆内存是一块不连续的内存区域,如果频繁地new/remove会造成大量的内存碎片,GC频繁的回收,导致内存抖动,这也会消耗我们应用的性能

我们知道可以调用 System.gc();进行内存回收,但是GC不一定会执行。面对GC的机制,我们是否无能为力?其实我们可以通过声明一些引用标记来让GC更好对内存进行回收。
开发时,为了防止内存溢出,处理一些比较占用内存并且生命周期长的对象时,可以尽量使用软引用和弱引用。

成员变量全部存储在堆中(包括基本数据类型,引用及引用的对象实体),因为他们属于类,类对象最终还是要被new出来的

局部变量的基本数据类型和引用存在栈中,应用的对象实体存储在堆中。因为它们属于方法当中的变量,生命周期会随着方法一起结束

内存泄漏的定义

当一个对象已经不需要使用了,本该被回收时,而有另外一个正在使用的对象持有它的引用,从而导致了对象不能被GC回收。这种导致了本该被回收的对象不能被回收而停留在堆内存中,就产生了内存泄漏

内存泄漏与内存溢出的区别

内存泄漏(Memory Leak)

进程中某些对象已经没有使用的价值了,但是他们却还可以直接或间接地被引用到GC Root导致无法回收。当内存泄漏过多的时候,再加上应用本身占用的内存,日积月累最终就会导致内存溢出OOM

内存溢出(OOM)

当 应用的heap资源超过了Dalvik虚拟机分配的内存就会内存溢出

Android开发常见的内存泄漏

1、单例造成的内存泄漏

  • 错误示例
    当调用getInstance时,如果传入的context是Activity的context。只要这个单例没有被释放,那么这个
    Activity也不会被释放一直到进程退出才会释放。

    public class CommUtil {
        private static CommUtil instance;
        private Context context;
        private CommUtil(Context context){
        this.context = context;
        }
    
    public static CommUtil getInstance(Context mcontext){
    if(instance == null){
        instance = new CommUtil(mcontext);
    }
    return instance;
    }
    
  • 解决方案

能使用Application的Context就不要使用Activity的Content,Application的生命周期伴随着整个进程的周期

2、非静态内部类创建静态实例造成的内存泄漏

非静态的内部类会持有外部类的一个隐式引用,只要非静态的内部类对象没有被回收,外部类的资源和视图都不会被回收(外部类会onDestory,只是它里面的资源和视图不被回收)。
- 错误示例

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if(mManager == null){
        //非静态的(匿名)内部类会持有外部类的一个隐式引用  
          new TestResource().requestData();
         //或者
        TestResource testResource = new TestResource();
        testResource.requestData();
        }

    }
    class TestResource {
        //异步去请求网络
        requestData();  
    }
  • 解决方案
    将非静态内部类修改为静态内部类。(静态内部类不会隐式持有外部类的引用)

       static class TestResource {
            //异步去请求网络
            requestData();  
        }
    

3、Handler造成的内存泄漏

  • 错误示例
    mHandler是Handler的非静态匿名内部类的实例,所以它持有外部类Activity的引用,我们知道消息队列是在一个Looper线程中不断轮询处理消息,那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏。

     private MyHandler mHandler = new MyHandler(this);
        private TextView mTextView ;
        private static class MyHandler extends Handler {
            private WeakReference<Context> reference;
            public MyHandler(Context context) {
                reference = new WeakReference<>(context);
            }
            @Override
            public void handleMessage(Message msg) {
                MainActivity activity = (MainActivity) reference.get();
                if(activity != null){
                    activity.mTextView.setText("");
                }
            }
        }
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = (TextView)findViewById(R.id.textview);
        loadData();
    }
    
    private void loadData() {
    
        Message message = Message.obtain();
        mHandler.sendMessage(message);
    }
    
  • 解决方案
    创建一个静态Handler内部类,然后对Handler持有的对象使用弱引用,这样在回收时也可以回收Handler持有的对象,这样虽然避免了Activity泄漏,不过Looper线程的消息队列中还是可能会有待处理的消息,所以我们在Activity的Destroy时或者Stop时应该移除消息队列中的消息

       private MyHandler mHandler = new MyHandler(this);
        private TextView mTextView ;
        private static class MyHandler extends Handler {
            private WeakReference<Context> reference;
            public MyHandler(Context context) {
                reference = new WeakReference<>(context);
            }
            @Override
            public void handleMessage(Message msg) {
                MainActivity activity = (MainActivity) reference.get();
                if(activity != null){
                    activity.mTextView.setText("");
                }
            }
        }
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = (TextView)findViewById(R.id.textview);
        loadData();
    }
    
    private void loadData() {
        //...request
        Message message = Message.obtain();
        mHandler.sendMessage(message);
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }
    }
    

4、线程造成的内存泄漏

  • 错误示例
    异步任务和Runnable都是一个匿名内部类,因此它们对当前Activity都有一个隐式引用。如果Activity在销毁之前,任务还未完成, 那么将导致Activity的内存资源无法回收,造成内存泄漏

    new AsyncTask<Void, Void, Void>() {
        @Override
        protected Void doInBackground(Void... params) {
            SystemClock.sleep(10000);
            return null;
        }
    }.execute();
    
    
    new Thread(new Runnable() {
        @Override
        public void run() {
            SystemClock.sleep(10000);
        }
    }).start();
    
  • 解决方案
    使用 静态内部类,避免了Activity的内存资源泄漏,当然在Activity销毁时候也应该取消相应的任务AsyncTask::cancel(),避免任务在后台执行浪费资源

       static class MyAsyncTask extends AsyncTask<Void, Void, Void> {
            private WeakReference<Context> weakReference;
    
            public MyAsyncTask(Context context) {
                weakReference = new WeakReference<>(context);
            }
    
            @Override
            protected Void doInBackground(Void... params) {
                SystemClock.sleep(10000);
                return null;
            }
    
            @Override
            protected void onPostExecute(Void aVoid) {
                super.onPostExecute(aVoid);
                MainActivity activity = (MainActivity) weakReference.get();
                if (activity != null) {
                    //...
                }
            }
        }
        static class MyRunnable implements Runnable{
            @Override
            public void run() {
                SystemClock.sleep(10000);
            }
        }
        new Thread(new MyRunnable()).start();
        new MyAsyncTask(this).execute();
    

5、资源未关闭造成的内存泄漏

  • 错误示例
    对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏

  • 解决方案
    在Activity销毁时及时关闭或者注销

6、使用了静态的Activity和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(); 
      } 
    }); 
    
    
    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(); 
      } 
    });
    
  • 解决方案

应该及时将静态的应用 置为null,而且一般不建议将View及Activity设置为静态

7、注册了系统的服务,但onDestory未注销

  • 错误示例

    SensorManager sensorManager = getSystemService(SENSOR_SERVICE);
    Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
    sensorManager.registerListener(this,sensor,SensorManager.SENSOR_DELAY_FASTEST);
    
  • 解决方案

    //不需要用的时候记得移除监听
    sensorManager.unregisterListener(listener);
    

8、不需要用的监听未移除会发生内存泄露

  • 错误示例

    //add监听,放到集合里面
    tv.getViewTreeObserver().addOnWindowFocusChangeListener(new ViewTreeObserver.OnWindowFocusChangeListener() {
        @Override
        public void onWindowFocusChanged(boolean b) {
            //监听view的加载,view加载出来的时候,计算他的宽高等。
        }
    });
    
  • 解决方案

    //计算完后,一定要移除这个监听
    tv.getViewTreeObserver().removeOnWindowFocusChangeListener(this);
    

tv.setOnClickListener();//监听执行完回收对象,不用考虑内存泄漏
tv.getViewTreeObserver().addOnWindowFocusChangeListene,add监听,放到集合里面,需要考虑内存泄漏

本篇文章来自: http://www.jianshu.com/p/402225fce4b2#

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值