百度工程师移动开发避坑指南——内存泄漏篇

news2024/11/26 4:26:20

图片

作者 | 启明星小组

在日常编写代码时难免会遇到各种各样的问题和坑,这些问题可能会影响我们的开发效率和代码质量,因此我们需要不断总结和学习,以避免这些问题的出现。接下来我们将围绕移动开发中常见问题做出总结,以提高大家的开发质量。本系列文章讲围绕内存泄漏、语言开发注意事项等展开。本篇我们将介绍Android/iOS常见的内存泄漏问题。

一、Android端

内存泄漏(Memory Leak),简单说就是不再使用的对象无法被GC回收,占用内存无法释放,导致应用占用内存越来越多,内存空间不足而出现OOM崩溃;另外因为内存可用空间变少,GC更加频繁,更容易触发FULL GC,停止线程工作,导致应用卡顿。

Android应用程序中的内存泄漏是一种常见的问题,以下是一些常见的Android内存泄漏:

1.1 匿名内部类

匿名内部类持有外部类的引用,匿名内部类对象泄露,从而导致外部类对象内存泄漏,常见Handler、Runnable匿名内部类,持有外部Activity的引用,如果Activity已经被销毁,但是Handler未处理完消息,导致Handler内存泄露,从而导致Activity内存泄露。

示例1:

public class TestActivity extends AppCompatActivity {

    private static final int FINISH_CODE = 1;

    private Handler handler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            if (msg.what == FINISH_CODE) {
                TestActivity.this.finish();
            }
        }
    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        handler.sendEmptyMessageDelayed(FINISH_CODE, 60000);
    }
}

示例2:

public class TestActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                TestActivity.this.finish();
            }
        }, 60000);
    }
}

示例1和示例2均为简单计时一分钟关闭页面,如果页面在之前被主动关闭销毁,Handler中仍有消息等待执行,就存在到Activity的引用链,导致Activity销毁后无法被GC回收,造成内存泄露;示例1为Handler匿名内部类,持有外部Activity引用:主线程 —> ThreadLocal —> Looper —> MessageQueue —> Message —> Handler —> Activity;示例2为Runnable匿名内部类,持有外部Activity引用:Message —> Runnable —> Activity.

修复方法1: 主要针对Handler,在Activity生命周期移除所有消息。

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

修复方法2: 静态内部类+弱引用,去掉强引用关系,可以修复类似匿名内部类造成内存泄露。

    static class FinishRunnable implements Runnable {

        private WeakReference<Activity> activityWeakReference;

        FinishRunnable(Activity activity) {
            activityWeakReference = new WeakReference<>(activity);
        }

        @Override
        public void run() {
            Activity activity = activityWeakReference.get();
            if (activity != null) {
                activity.finish();
            }
        }
    }
    
    new Handler().postDelayed(new FinishRunnable(TestActivity.this), 60000);

1.2 单例/静态变量

单例/静态变量持有Activity的引用,即使Activity已经被销毁,它的引用仍然存在,从而导致内存泄漏。

示例:

    static class Singleton {

        private static Singleton instance;

        private Context context;

        private Singleton(Context context) {
            this.context = context;
        }

        public static Singleton getInstance(Context context) {
            if (instance == null) {
                instance = new Singleton(context);
            }
            return instance;
        }
    }
    
    Singleton.getInstance(TestActivity.this);
 

调用示例中的单例,传递Context参数,使用Activity对象,即使Activity销毁,也一直被静态变量Singleton引用,导致无法回收造成内存泄露。

修复方法:

Singleton.getInstance(Application.this);

尽量使用Application的Context作为单例参数,除非一些需要需要Activity的功能,比如显示Dialog,如果非要使用Activity作为单例参数,可以参考匿名内部类修复方法,在合适时机比如Activity的onDestroy生命周期释放单例,或者使用弱引用持有Activity。

1.3 监听器

示例: EventBus注册监听未解绑,导致注册到EventBus一直被引用,无法回收。

public class TestActivity extends AppCompatActivity {
    
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EventBus.getDefault().register(this);
    }
}

修复方法: 在对应注册监听的生命周期解绑,onCreate对应onDestroy。

    @Override
    protected void onDestroy() {
        super.onDestroy();
        EventBus.getDefault().unregister(this);
    }

1.4 文件/数据库资源

示例: 打开文件数据库或者文件,发生异常,未关闭,导致资源一直存在,导致内存泄漏。

    public static void copyStream(File inFile, File outFile) {
        try {
            FileInputStream inputStream = new FileInputStream(inFile);
            FileOutputStream outputStream = new FileOutputStream(outFile);
            byte[] buffer = new byte[1024];
            int len;
            while ((len = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

修复:在finally代码块中关闭文件流,保证发生异常后一定能执行到

    public static void copyStream(File inFile, File outFile) {
        FileInputStream inputStream = null;
        FileOutputStream outputStream = null;
        try {
            inputStream = new FileInputStream(inFile);
            outputStream = new FileOutputStream(outFile);
            byte[] buffer = new byte[1024];
            int len;
            while ((len = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            close(inputStream);
            close(outputStream);
        }
    }

    public static void close(Closeable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

1.5 动画

示例: Android动画未及时取消释放动画资源,导致内存泄露。

public class TestActivity extends AppCompatActivity {

    private ImageView imageView;
    private Animation animation;

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

        imageView = (ImageView) findViewById(R.id.image_view);
        animation = AnimationUtils.loadAnimation(this, R.anim.test_animation);
        imageView.startAnimation(animation);
    }
}

修复: 在页面退出销毁时取消动画,及时释放动画资源。

@Override
protected void onDestroy() {
    super.onDestroy();
    if (animation != null) {
        animation.cancel();
        animation = null;
    }
}

二、IOS端

目前我们已经有了ARC(自动引用计数)来替代MRC(手动引用计数),申请的对象在没有被强引用时会自动释放。但在编码不规范的情况下,引用计数无法及时归零,还是会存在引入内存泄露的风险,这可能会造成一些非常严重的后果。以直播场景举例,如果直播业务的ViewController无法释放,会导致依赖于ViewController的点位统计数据异常,且用户关闭直播页面后仍然可以听到直播声音。熟悉内存泄漏场景、养成避免内存泄露的习惯是十分重要的。下面介绍一些iOS常见内存泄漏及解决方案。

2.1 block引起的循环引用

block引入的循环引用是最常见的一类内存泄露问题。常见的引用环是对象->block->对象,此时对象和block的引用计数均为1,无法被释放。

[self.contentView setActionBlock:^{
    [self doSomething];
}];

例子代码中,self强引用成员变量contentView,contentView强引用actionBlock,actionBlock又强引用了self,引入内存泄露问题。

图片

解除循环引用,就是解除强引用环,需要将某一强引用替换为弱引用。如:

__weak typeof(self) weakSelf = self;
[self.contentView setActionBlock:^{
    __strong typeof(weakSelf) strongSelf = weakSelf;
    [strongSelf doSomething];
}];

此时actionBlock弱引用self,循环引用被打破,可以正常释放。

图片

或者使用RAC提供的更简便的写法:

@weakify(self);
[self setTaskActionBlock:^{
    @strongify(self);
    [self doSomething];
}];

需要注意的是,可能和block存在循环引用的不仅仅是self,所有实例对象都有可能存在这样的问题,而这也是开发过程中很容易忽略的。比如:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    Cell *cell = [tableView dequeueReusableCellWithIdentifier:@"identifer"];
    @weakify(self);
    cell.clickItemBlock = ^(CellModel * _Nonnull model) {
        @strongify(self);
        [self didSelectRowMehod:model tableView:tableView];
    };
    return cell;
}

这个例子中,self和block之间的循环引用被打破,self可以正常释放了,但是需要注意的是还存在一条循环引用链:tableView强引用cell,cell强引用block,block强引用tableView。这同样会导致tableView和cell无法释放。

图片

正确的写法为:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    Cell *cell = [tableView dequeueReusableCellWithIdentifier:@"identifer"];
    @weakify(self);
    @weakify(tableView);
    cell.clickItemBlock = ^(CellModel * _Nonnull model) {
        @strongify(self);
        @strongify(tableView);
        [self didSelectRowMehod:model tableView:tableView];
    };
    return cell;
}

图片

2.2 delegate引起的循环引用

@protocol TestSubClassDelegate <NSObject>

- (void)doSomething;

@end

@interface TestSubClass : NSObject

@property (nonatomic, strong) id<TestSubClassDelegate> delegate;

@end

@interface TestClass : NSObject <TestSubClassDelegate>

@property (nonatomic, strong) TestSubClass *subObj;

@end

上述例子中,TestSubClass对delegate使用了strong修饰符,导致设置代理后,TestClass实例和TestSubClass实例相互强引用,造成循环引用。大部分情况下,delegate都需要使用weak修饰符来避免循环引用。

2.3 NSTimer强引用

self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(doSomething) userInfo:nil repeats:YES];
[NSRunLoop.currentRunLoop addTimer:self.timer forMode:NSRunLoopCommonModes];

NSTimer实例会强引用传入的target,就会出现self和timer的相互强引用。此时必须手动维护timer的状态,在timer停止或view被移除时,主动销毁timer,打破循环引用。

解决方案1:换用iOS10后提供的block方式,避免NSTimer强引用target。

@weakify(self);
self.timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
    @strongify(self);
    [self doSomething];
}];

解决方案2:使用NSProxy解决强引用问题。

// WeakProxy
@interface TestWeakProxy : NSProxy

@property (nullable, nonatomic, weak, readonly) id target;

- (instancetype)initWithTarget:(id)target;

+ (instancetype)proxyWithTarget:(id)target;

@end

@implementation TestWeakProxy

- (instancetype)initWithTarget:(id)target {
    _target = target;
    return self;
}

+ (instancetype)proxyWithTarget:(id)target {
    return [[TestWeakProxy alloc] initWithTarget:target];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    if ([self.target respondsToSelector:[invocation selector]]) {
        [invocation invokeWithTarget:self.target];
    }
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    return [self.target methodSignatureForSelector:aSelector];
}

- (BOOL)respondsToSelector:(SEL)aSelector {
    return [self.target respondsToSelector:aSelector];
}

@end

// 调用
self.timer = [NSTimer timerWithTimeInterval:1 target:[TestWeakProxy proxyWithTarget:self] selector:@selector(doSomething) userInfo:nil repeats:YES];

2.4 非引用类型内存泄漏

ARC的自动释放是基于引用计数来实现的,只会维护oc对象。直接使用C语言malloc申请的内存,是不被ARC管理的,需要手动释放。常见的如使用CoreFoundation、CoreGraphics框架自定义绘图、读取文件等操作。

如通过CVPixelBufferRef生成UIImage:

CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
CIImage* bufferImage = [CIImage imageWithCVPixelBuffer:pixelBuffer];
CIContext *context = [CIContext contextWithOptions:nil];
CGImageRef frameCGImage = [context createCGImage:bufferImage fromRect:bufferImage.extent];
UIImage *uiImage = [UIImage imageWithCGImage:frameCGImage];
CGImageRelease(frameCGImage);
CFRelease(sampleBuffer);

2.5 延迟释放问题

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(20 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    [self doSomething];
});

上述例子中,使用dispatch_after延迟20秒后执行doSomething方法。这并不会造成self对象的内存泄漏问题。但假设self是一个UIViewController,即使self已经从导航栈中移除,不需要再使用了,self也会在block执行后才会被释放,造成业务上出现类似内存泄露的现象。

在这种长时间的延时执行中,最好也加入weakify-strongify对,避免强持有。

@weakify(self);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(20 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    @strongify(self);
    [self doSomething];
});

----------  END  ----------

推荐阅读【技术加油站】系列:

百度程序员开发避坑指南(Go语言篇)

百度程序员开发避坑指南(3)

百度程序员开发避坑指南(移动端篇)

百度程序员开发避坑指南(前端篇)

图片

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/538947.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Prompting】ChatGPT Prompt Engineering开发指南(6)

ChatGPT Prompt Engineering开发指南&#xff1a;Expanding/The Chat Format Expanding自定义对客户电子邮件的自动回复提醒模型使用客户电子邮件中的详细信息 The Chat Format总结内容来源 在本教程中&#xff0c;第一部分学习生成客户服务电子邮件&#xff0c;这些电子邮件是…

前端(HTML)

网络传输三大基石:URL,HTTP,HTML 前端使用URL利用HTTP协议去向服务器端发送请求某个资源&#xff0c;服务器端响应浏览器一个HTML页面&#xff0c;浏览器对HTML页面解析 HTML的标准结构: 【1】先用普通文本文档新建一个文本&#xff0c;将文本的后缀改为.html 或者 .htm 我…

chatgpt赋能Python-mac版的python怎么用

Mac版Python的使用指南 Python是一种高级编程语言&#xff0c;常用于Web开发、数据分析、机器学习等领域。在Mac系统上&#xff0c;Python的安装和使用非常方便。本文将介绍如何在Mac上安装和使用Python并演示几个常见的Python用例。 Python在Mac上的安装 Mac电脑自带Python…

springboot+jsp+javaweb学生信息管理系统 05hs

springboot是基于spring的快速开发框架, 相比于原生的spring而言, 它通过大量的java config来避免了大量的xml文件, 只需要简单的生成器便能生成一个可以运行的javaweb项目, 是目前最火热的java开发框架 &#xff08;1&#xff09;管理员模块&#xff1a;系统中的核心用户是管…

蓝牙耳机什么牌子的好用?发烧友实测2023年蓝牙耳机排名

从AirPods入坑蓝牙耳机开始&#xff0c;断断续续已经买过二十多款蓝牙耳机了&#xff0c;我每天都会逛逛数码板块&#xff0c;最近看到了2023年蓝牙耳机排名&#xff0c;为检验是否名副其实&#xff0c;我购入了排名前五的品牌进行了一个月的测试&#xff0c;接下来我就来分享一…

k8s系列(四)——资源对象

k8s系列四——资源对象 pod概念 思考&#xff1a;为什么k8s会引出pod这个概念&#xff0c;容器不能解决么&#xff1f; 我的理解&#xff1a;一组密切相关的服务使用容器的话&#xff0c;如果他们的镜像不在一个容器里的话&#xff0c;那么就需要配置反向代理进行通信&#xf…

Packet Tracer – 配置中继

Packet Tracer – 配置中继 地址分配表 设备 接口 IP 地址 子网掩码 交换机端口 VLAN PC1 NIC 172.17.10.21 255.255.255.0 S2 F0/11 10 PC2 NIC 172.17.20.22 255.255.255.0 S2 F0/18 20 PC3 NIC 172.17.30.23 255.255.255.0 S2 F0/6 30 PC4 NIC 1…

java 根据指定字段排序(mysql)

需求&#xff1a; 查询数据的时候&#xff0c;由前端指定字段和排序方式进行排序。 这时候要怎么做呢&#xff1f; 要定义一个相应的类&#xff0c;排序的时候&#xff0c;是动态拼接的。 要考虑多个字段&#xff0c;不同排序方式的情况。 处理 OrderField import io.swagge…

基于matlab的ADC输入动态范围测量代码

如图&#xff0c;本文主要分享基于matlab的ADC输入数据有效位分析的代码。 fidfopen(C:\Users\Administrator\Desktop\Test.txt,r); % numptinput(Data Record Size (Number of Points)? );% fclkinput(Sampling Frequency (MHz)? ); numpt16384; fclk50; numbit14; [v1]fs…

SDK案例记录

目前的极简配置 注意事项 默认的属性配置中&#xff0c;大多采用环境变量的形式&#xff0c;方便不同设备通用 比如“常规”->“输出目录”为 $(SolutionDir)..\bin\win_msvc2017$(Platform)\$(Configuration)\案例运行前的配置&#xff08;除MwBatchSimPlugin&#xff0…

华丽家族股东大会21项议案全被否

5月17日晚间&#xff0c;A股上市公司华丽家族发布关于收到上海证券交易所监管工作函的公告&#xff0c;交易所对相关事项提出监管要求。 在此之前&#xff0c;华丽家族当天召开股东大会&#xff0c;21项股东大会议案全部未通过。历史上&#xff0c;股东大会议案全部被否的情形…

『python爬虫』24. selenium之无头浏览器-后台静默运行(保姆级图文)

目录 1. 无头浏览器2. 分析被爬取数据的网页结构3. 完整代码总结 欢迎关注 『python爬虫』 专栏&#xff0c;持续更新中 欢迎关注 『python爬虫』 专栏&#xff0c;持续更新中 1. 无头浏览器 一般性的selenium会打开浏览器页面&#xff0c;展示图形化页面给我们看&#xff0c;我…

Spring Boot注入Servlet、Filter、Listener 注解方式和使用RegistrationBean二种方式 加源码分析

目录 Spring Boot 注入Servlet、Filter、Listener 官方文档 基本介绍 应用实例1-使用注解方式注入 创建/Servlet_.java 修改Application.java , 加入ServletComponentScan 完成测试 创建Filter_.java 创建static/css/t.css, 作为测试文件 完成测试, 注意观察后台 注…

【数据结构】--- 博主拍了拍你并向你扔了一“棵”二叉树(概念+结构)

文章目录 前言&#x1f31f;一、树概念及结构&#xff1a;&#x1f30f;1.1树的概念&#xff1a;&#x1f30f;1.2树的相关概念&#xff1a;&#x1f30f;1.3树的表示&#xff1a;&#x1f4ab;1.3.1左孩子右兄弟表示法&#xff1a;&#x1f4ab;1.3.2双亲表示法&#xff1a; &…

Golang每日一练(leetDay0069) 数字范围按位与、快乐数

目录 201. 数字范围按位与 Bitwise-and-of-numbers-range &#x1f31f;&#x1f31f; 202. 快乐数 Happy Number &#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Rust每日一练 专栏 Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每…

美团前高级测试工程师教你如何使用web自动化测试

一、自动化测试基本介绍 1 自动化测试概述&#xff1a; 什么是自动化测试&#xff1f;一般说来所有能替代人工测试的方式都属于自动化测试&#xff0c;即通过工具和脚本来模拟人执行用例的过程。 2 自动化测试的作用 减少软件测试时间与成本改进软件质量 通过扩大测试覆盖率…

python随机生成数据并用双y轴绘制两条带误差棒的折线图

python绘图系列文章目录 往期python绘图合集: python绘制简单的折线图 python读取excel中数据并绘制多子图多组图在一张画布上 python绘制带误差棒的柱状图 python绘制多子图并单独显示 python读取excel数据并绘制多y轴图像 python绘制柱状图并美化|不同颜色填充柱子 Python绘制…

IC验证学习笔记(AHB-RAM)08addr、bsize都随机,主要做地址偏移操作

rkv_ahbram_haddr_word_unaligned_virt_seq: 对addr和bsize都随机化操作 ifndef RKV_AHBRAM_HADDR_WORD_UNALIGNED_VIRT_SEQ_SV define RKV_AHBRAM_HADDR_WORD_UNALIGNED_VIRT_SEQ_SVclass rkv_ahbram_haddr_word_unaligned_virt_seq extends rkv_ahbram_base_virtual_sequenc…

深入探讨桥梁建筑中地质工程与仪器仪表应用

近期&#xff0c;随着桥梁建筑行业的不断发展&#xff0c;地质工程与仪器仪表应用成为了热议的话题。在桥梁、建筑、水利工程等领域&#xff0c;渗压计、MCU自动化测量单元、应变计、测缝计、固定测斜仪等各种先进的仪器仪表技术正在广泛应用&#xff0c;为工程施工和监测提供了…

什么是pytest自动化测试框架?如何安装和使用呢?赶快收藏起来

一、pytest是什么&#xff1f; pytest是一款强大的Python测试工具&#xff0c;可以胜任各种类型或级别的软件测试工作。实际上&#xff0c;越来越多的项目在使用pytest。因为pytest会提供更丰富的功能&#xff0c;包括assert重写、第三方插件&#xff0c;以及其他测试工具无法比…