移动开发避坑指南——内存泄漏

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

一、Android端

GEEK TALK

内存泄漏(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, , 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, , 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);    }}

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

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

二、IOS端

GEEK TALK

目前我们已经有了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];});

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

外卖点餐APP开发需要哪些功能

uni-app框架&#xff1a;使用Vue.js开发跨平台应用的前端框架&#xff0c;编写一套代码&#xff0c;可编译到Android、小程序等平台。 框架支持:springboot/Ssm/thinkphp/django/flask/express均支持 前端开发:vue.js 可选语言&#xff1a;pythonjavanode.jsphp均支持 运行软件…

LeetCode_101(对称二叉树)

1.递归 public boolean isSymmetric(TreeNode root) {if(root null){return true;}return deepCheck(root.left,root.right);}boolean deepCheck(TreeNode left, TreeNode right){//递归的终止条件是两个节点都为空//或者两个节点中有一个为空//或者两个节点的值不相等if(lef…

RocketMQ 事件驱动:云时代的事件驱动有啥不同?

作者&#xff1a;林清山&#xff08;隆基&#xff09; 前言&#xff1a; 从初代开源消息队列崛起&#xff0c;到 PC 互联网、移动互联网爆发式发展&#xff0c;再到如今 IoT、云计算、云原生引领了新的技术趋势&#xff0c;消息中间件的发展已经走过了 30 多个年头。 目前&a…

如何应对MySQL单表数据量过大:垂直分表与水平分表策略解析

话接上回&#xff0c;单表最大数据建议两千万&#xff0c;那如果开发一个项目&#xff0c;预计注册量达到一个亿怎么办。 单表内放这么多数据&#xff0c;MYSQL底层B树的层级结构就可能会变得很高&#xff0c;磁盘io次数变多&#xff0c;性能会大幅度降低。所以考虑数据库分表…

01-Git 之快速入门操作本地仓库

https://learngitbranching.js.org/?localezh_CN在线练习git 1. Git 安装好Git以后, 先检查是否已经绑定了用户名和邮箱 git config --list1.1 为什么要使用版本控制&#xff1f; 从个人角度&#xff1a; 在做项目时&#xff0c;如果一点点去改代码会很乱&#xff0c;不利…

顺序表(C语言版)

前言&#xff1a;本篇文章我们来详细的讲解一下顺序的有关知识&#xff0c;这篇文章中博主会以C语言的方式实现顺序表。以及用顺序表去实现通讯录的代码操作。 目录 一.顺序表的概念 二.顺序表的分类 1.静态顺序表 三.动态顺序表的实现 1.顺序表的初始化 2.顺序表的尾插…

python 重载内置函数吗

python中是不支持函数重载的&#xff0c;但在python3中提供了这么一个装饰器functools.singledispatch&#xff0c;它叫做单分派泛函数&#xff0c;可以通过它来完成python中函数的重载&#xff0c;让同一个函数支持不同的函数类型&#xff0c;它提供的目的也正是为了解决函数重…

智能物联网远传冷水表管理系统

智能物联网远传冷水表管理系统是一种基于物联网技术的先进系统&#xff0c;旨在实现对冷水表的远程监测、数据传输和智能化管理。本文将从系统特点、构成以及带来的效益三个方面展开介绍。 系统特点 1.远程监测&#xff1a;系统可以实现对冷水表数据的远程监测&#xff0c;无…

累积分布函数图(CDF)的介绍、matlab的CDF图绘制方法(附源代码)

在对比如下两个误差的时候&#xff0c;怎么直观地分辨出来谁的误差更低一点&#xff1f;&#xff1a; 通过这种误差时序图往往不容易看出来。 但是如果使用CDF图像&#xff0c;以误差绝对值作为横轴&#xff0c;以横轴所示误差对应的累积概率为纵轴&#xff0c;绘制曲线图&am…

Python开源工具库使用之词云Wordcloud

文章目录 前言一、基本使用1.1 文本生成词云1.2 配置项 二、进阶用法2.1 自定义形状2.2 自定义着色2.3 自定义词频2.4 中文 三、实际案例3.1 工作报告词云3.2 周杰伦歌词词云 四、总结4.1 优点和局限性4.2 展望未来发展 参考 前言 当我们需要将大量文本数据可视化展示时&#…

不得不学的Zabbix监控系统,最细搭建详解

目录 一、为什么要做监控 二、Zabbix介绍 三、zabbix组成 四、Zabbix主要功能 五、zabbix 监控原理 六、zabbix运行机制 七、Zabbix的监控方式 7.1 主动模式 7.2 被动模式 八、Zabbix监控系统监控对象 九、Zabbix的优缺点 9.1Zabbix的优点 9.2Zabbix的缺点 9.3za…

ELK日志分析系统之Zookeeper

一、Zookeeper简介 ZooKeeper是一种为分布式应用所设计的高可用、高性能且一致的开源协调服务&#xff0c;它提供了一项基本服务&#xff1a;分布式锁服务。分布式应用可以基于它实现更高级的服务&#xff0c;实现诸如同步服务、配置维护和集群管理或者命名的服务。 Zookeepe…

腾讯云服务器CVM标准型S8实例CPU内存、网络和存储性能测评

腾讯云第八代云服务器标准型S8实例基于全新优化虚拟化平台&#xff0c;CPU采用Intel Emerald Rapids 全新处理器&#xff0c;睿频3.0GHz&#xff0c;内存采用最新DDR5&#xff0c;默认网络优化&#xff0c;最高内网收发能力达4500万pps&#xff0c;最高内网带宽可支持120Gbps。…

61、ARM/串口通信相关学习20240415

一、串口通信&#xff1a;实现PC端串口助手与开发板的字符串通信。 代码&#xff1a; main&#xff1a; #include "uart4.h"int main(){uart4_config();//char a;char s[64];while (1){//a getchar();//putchar(a1);gets(s);puts(s);}return 0;}usrt4.c&#xff…

protobuf 编码原理

简介 Protocol Buffers&#xff08;protobuf&#xff09;&#xff0c;它是 Google 开发的一种数据序列化协议&#xff08;与 XML、JSON 类似&#xff09;。 优点&#xff1a; 效率高&#xff1a;Protobuf 以二进制格式存储数据&#xff0c;比如 XML 和 JSON 等文本格式更紧凑…

【虚幻引擎】DTProjectSettings 蓝图获取基本项目配置插件使用说明 获取项目命名,项目版本,公司名,公司识别名,主页,联系方式

本插件可以使用蓝图获取到项目的一些基本配置&#xff0c;如获取&#xff1a;公司名、公司识别名、版权声明、描述、主页、许可条款、隐私政策、项目ID、项目命名、项目版本、支持联系方式、项目显示标题、项目调试标题信息、应保留窗口宽高比、使用无边框窗口、以VR启动、允许…

CommunityToolkit.Mvvm笔记---Ioc

使用MVVM模式提高应用程序代码库中的模块化程度的最常用模式是使用某种形式的反转控制&#xff08;Ioc&#xff09;。其中最常见的解决方案是使用依赖关系注入&#xff0c;该解决方案存在于创建多个注入后端类的服务&#xff08;即以参数的形式传递给 viewmodel 构造函数&#…

携程景点详情API:电商发展新引擎,推动旅游智能化升级

随着信息技术的快速发展&#xff0c;旅游行业正迎来一场深刻的智能化升级。作为电商发展的新引擎&#xff0c;携程景点详情API以其丰富的数据资源和高效的服务能力&#xff0c;正逐渐成为推动旅游智能化升级的重要力量。本文将深入探讨携程景点详情API在电商发展中的作用&#…

xhci 数据结构

xhci 数据结构 xhci 数据结构主要在手册上有详细的定义&#xff0c;本文根据手册进行归纳总结&#xff1a; 重点关注的包括&#xff1a; device contexttrb ringtrb device context设备上下文 设备上下文数据结构由xHC管理&#xff0c;用于向系统软件报告设备配置和状态信息。…

【个人博客搭建】(2)项目分层结构

1、在解决方案这右击&#xff0c; 2、填写项目名称。&#xff08;位置使用默认即可&#xff09; 3、选择框架版本。&#xff08;最好同创建webapi一个版本吧&#xff09; 4、创建后进入该界面。会生成默认的一个Class类。&#xff08;后修改名称或删除都可&#xff09; 5、然后…