首页 > 系统相关 >安卓内存分析(1)——常见内存泄漏场景

安卓内存分析(1)——常见内存泄漏场景

时间:2023-02-09 10:02:06浏览次数:36  
标签:泄漏 安卓 private 内存 context Activity public

安卓内存分析(1)——常见内存泄漏场景

问题背景

安卓日常开发和学习过程中,内存泄漏是一个重要的话题,并且内存泄漏相关的问题会经常发生在我们身边。那么,首先我们来看看内存泄漏的一些常见场景有哪些?

问题分析

1、单例导致的内存泄漏

单例模式在我们项目中经常会用到,比如,我们项目中使用ActivityManager单例,获取这个单例需要传入context对象。代码如下:

public class ActivityManager {
private static final String TAG = "ActivityManager";
 
    private Context context;
 
    private static ActivityManager instance;
 
    private ActivityManager(Context context) {
        this.context = context;
    }
 
    public static ActivityManager getInstance(Context context) {
        if (instance == null) {
            instance = new ActivityManager(context);
        }
        return instance;
    }
}

上面代码,在构造方法里传入context,如果不加注意,很容易因为生命周期的长短而引起内存泄漏。注意请在这里传入Application的context而不是Activity的context,因为如果传入的是Activity的context,当Activity退出时,此context并不会被回收,因为单例对象持有该context的引用,从而引起内存泄漏。如果传入的是Application的context,便不会引发内存泄漏,因为context的生命周期和Application一样长,所以正确的单例写法如下:

public class ActivityManager {
private static final String TAG = "ActivityManager";
 
    private Context context;
 
    private static ActivityManager instance;
 
    private ActivityManager(Context context) {
        this.context = context.getApplicationContext();
    }
 
    public static ActivityManager getInstance(Context context) {
        if (instance == null) {
            instance = new ActivityManager(context);
        }
        return instance;
    }
}

2、静态变量导致的内存泄漏

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

public class MainActivity extends AppCompatActivity {

    public static Util util;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        util = new Util(this);
    }
}

    class Util {
        private Context mContext;
        public Util(Context context) {
            this.mContext = context;
        }
    }

代码分析:util作为 Activity 的静态成员,并且持有 Activity 的引用,但是 util 作为静态变量,生命周期肯定比 Activity 长。所以当 Activity 退出后, util 仍然引用了 Activity , Activity 不能被回收,这就导致了内存泄露。 在 Android 开发中,静态持有很多时候都有可能因为其使用的生命周期不一致而导致内存泄露,我们尽量少地使用静态持有的变量,以避免发生内存泄露。当然,我们也可以在适当的时候将静态变量置为 null,使其不再持有引用,也可以避免内存泄露。

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

非静态内部类(包括匿名内部类)默认就会持有外部类的引用,当非静态内部类对象的生命周期比外部类对象的生命周期长时,就会导致内存泄露。非静态内部类导致的内存泄露在Android开发中有一种典型的场景就是使用Handler,很多开发者在使用Handler是这样写的,代码如下:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        start();
    }

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


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

代码分析:mHandler持有Activity引用,同时mHandler会作为成员变量保存在发送的消息msg中,即msg持有mHandler的引用。而mHandler是Activity的非静态内部类实例,即mHandler持有Activity的引用,那么我们就可以理解为msg间接持有Activity的引用。msg被发送后先放到消息队列MessageQueue中,然后等待Looper的轮询处理。那么当Activity退出后,msg可能仍然存在于消息对列MessageQueue中未处理或者正在处理,那么这样就会导致Activity无法被回收,以致发生Activity的内存泄露。同时可以再次看到,内存泄漏的本质还是:生命周期短的对象被生命周期长的对象应用。 通常在Android开发中如果要使用内部类,但又要规避内存泄露,一般都会采用静态内部类+弱引用的方式。代码如下:

public class MainActivity extends AppCompatActivity {
    MyHandler mHandler;

    public static class MyHandler extends Handler {

        private WeakReference<Activity> mActivityWeakReference;

        public MyHandler(Activity activity) {
            mActivityWeakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);

        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        start();
    }

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

非静态内部类造成内存泄露类似的场景还有包括使用Thread或者AsyncTask等。要避免内存泄露的原理是一样的,像上面Handler一样使用静态内部类+弱应用的方式,有兴趣的同学可以进一步深入研究。

4、未取消注册和回调导致的内存泄漏

比如我们在activity中注册了广播,在activity退出前,没有及时的取消,那么这个广播就会一直存在系统中,同时一直保留对我们activity的引用,这样也就导致了内存泄漏,代码如下:

public class MainActivity extends AppCompatActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        this.registerReceiver(mReceiver, new IntentFilter());
    }
 
    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
        }
    };
 
    @Override
    protected void onDestroy() {
        super.onDestroy();
        // this.unregisterReceiver(mReceiver); // activity ondestroy时不及时取消注册
    }
}

所以我们在使用广播时,一定要记得及时取消注册,不然很容易发生内存泄漏了。

问题总结

本文初步介绍了安卓开发过程中内存泄漏的部分常见场景,包括:单例导致的内存泄漏、静态变量导致的内存泄漏、非静态内部类导致的内存泄漏、未取消注册和回调导致的内存泄漏等,持续更新,有兴趣的同学可以进一步深入研究。

标签:泄漏,安卓,private,内存,context,Activity,public
From: https://blog.51cto.com/baorant24/6045858

相关文章

  • 4.1 内存的物理机制很简单
    计算机是进行数据处理的设备,而程序表示的就是处理顺序和数据结构。由于处理对象数据是存储在内存和磁盘上的,因此程序必须能自由地使用内存和磁盘。因此,大家有必要对内存和......
  • oracle判断数据块是否存在内存buffer cache中
    文档课题:oracle判断数据块是否存在内存buffercache中.数据库:oracle11.2.0.41、概念理论通过V$BH视图可以查询.V$BH保存着BufferCache中每个BLOCK的信息.如T1表在数据文件......
  • 虚拟内存跟物理内存之间的映射mmap\munmap
    #include<stdio.h>#include<sys/mman.h>intmain(void){/**创建虚拟内存的映射*void*mmap(void*__addr,size_t__len,int__prot,int__fla......
  • 亲测可食用!tiny11下载 精简瘦身的Win11版本 最低200MB内存配置即可启动
          精简瘦身的Win11版本最低200MB内存配置即可启动     最近在网上看到关于报道了tiny10的开发者NTDev,他将微软的win11操作系统提炼成一个8GB以......
  • 字符串与内存函数(2)
     本篇文章和大家继续分享一些与字符串和内存操作相关的函数,本次分享的函数包含有strerror函数,memcpy函数,memmove函数以及memcmp函数和memset函数。以上几个函数就是我们本......
  • 【大前端】使用html/css/javascript开发移动端(安卓,IOS)与桌面端(Win,mac,Linux)应用程序
    1、什么是大前端?传统上,Web应用可分为前端(在浏览器中执行的部分)和后端(在服务器中执行的部分)。前端工程师的职责是以Web技术(HTML、CSS、JavaScript、DOM、Ajax等)实现基于浏览......
  • JVM内存学习 2.0
    先说一下结果1.Linux的内存分配是惰性分配的.APP申明了kernel并不会立即进行初始化和使用.2.JVM的内存主要分为,堆区,非堆区,以及jvm使用的其他内存.比如直接内......
  • 如何扩展32位EXE程序的使用内存
    1以管理员身份运行Visualstudio的命令行 2、执行下面命令:editbin/LARGEADDRESSAWARE“C:\ProgramFiles\Skyline\TerraExplorerPro\TerraExplorer.exe”使用该......
  • 《程序是怎样跑起来的》·第五章 内存和磁盘的亲密关系
    0、开篇:(1)存储程序方式指的是什么?   在存储装置中保存程序,并逐一运行的方式(2)通过使用内存来提高磁盘访问速度的机制称为什么?   磁盘缓存(3)把磁盘的一部分作为......
  • 动态内存new与delete
    动态分配内存new&&delete使用堆区的内存有四个步骤:1)声明一个指针;2)用new运算符向系统申请一块内存,让指针指向这块内存;3)通过对指针解引用的方法,像使用变量一样使用这块内......