Mobile Development 13 min read

Common Memory Leak Issues in Android and iOS Development

The article outlines frequent memory‑leak pitfalls in Android (such as anonymous inner classes, static singletons, unregistered listeners, unclosed streams, and lingering animations) and iOS (including block retain cycles, strong delegate references, NSTimer retention, unmanaged CoreFoundation objects, and dispatch_after), and provides concrete code‑level remedies to prevent out‑of‑memory crashes.

Baidu Geek Talk
Baidu Geek Talk
Baidu Geek Talk
Common Memory Leak Issues in Android and iOS Development

When writing mobile code, various pitfalls can affect development efficiency and code quality. This article summarizes typical memory‑leak problems in Android and iOS and provides concrete fixes.

Android side

Memory leak (Memory Leak) occurs when an object that is no longer used cannot be reclaimed by the GC, leading to OOM crashes and frequent full GCs.

1.1 Anonymous inner classes

Anonymous inner classes hold an implicit reference to the outer Activity. If a Handler or Runnable posted by the inner class outlives the Activity, the Activity cannot be GC‑ed.

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);
    }
}

Fix: remove all pending messages in onDestroy():

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

1.2 Singleton / static variables

A static singleton that stores an Activity context keeps the Activity alive after it is destroyed.

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);

Fix: use Application context instead of an Activity context. Singleton.getInstance(Application.this); 1.3 Listeners (e.g., EventBus)

Registering a listener without unregistering creates a strong reference chain.

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

1.4 File / database resources

Opening streams without closing them leaves resources allocated.

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);
    }
}

1.5 Animations

Animations that are not cancelled in onDestroy() keep the Activity from being released.

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 = 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 side

Even with ARC, improper reference handling can still cause leaks.

2.1 Block retain cycles

Blocks capture self strongly, forming a cycle: Activity → block → Activity.

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

Fix with weak‑strong dance:

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

2.2 Delegate retain cycles

Delegates should be weak to avoid mutual strong references.

@interface TestSubClass : NSObject
@property (nonatomic, strong) id<TestSubClassDelegate> delegate;
@end

Change the property to weak.

2.3 NSTimer strong reference

Timer retains its target, creating a cycle with the owning object.

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

Fix by using the block‑based API (iOS 10+) or a weak proxy:

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

or

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

2.4 Non‑reference memory (C allocations)

Resources allocated with CoreFoundation, CoreGraphics, or malloc are not managed by ARC and must be released manually.

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 Delayed execution

Using dispatch_after holds a strong reference to self until the block runs, which can delay deallocation.

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

Apply the weak‑strong pattern to avoid the leak.

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

By understanding these common patterns and applying the shown fixes, developers can significantly reduce memory‑leak bugs in both Android and iOS projects.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Mobile DevelopmentiOSAndroidbest practicesmemory leak
Baidu Geek Talk
Written by

Baidu Geek Talk

Follow us to discover more Baidu tech insights.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.