Baidu Engineer's Guide to Avoiding Pitfalls in Mobile Development - Memory Leaks

picture

Author | Morning Star Team

It is inevitable to encounter various problems and pits when writing code daily. These problems may affect our development efficiency and code quality. Therefore, we need to constantly summarize and learn to avoid these problems. Next, we will summarize common problems in mobile development to improve the quality of everyone's development. This series of articles will focus on memory leaks, language development considerations, etc. In this article, we will introduce common memory leak problems in Android/iOS.

1. Android terminal

Memory leak (Memory Leak), simply put, is that objects that are no longer used cannot be reclaimed by GC, and the occupied memory cannot be released, causing the application to occupy more and more memory, and OOM crashes due to insufficient memory space; in addition, because the available memory space becomes less, GC is more frequent, and it is easier to trigger FULL GC, stop thread work, and cause the application to freeze.

Memory leaks in Android applications are a common problem, the following are some common Android memory leaks:

1.1 Anonymous inner class

The anonymous inner class holds the reference of the external class, and the anonymous inner class object leaks, which leads to the memory leak of the outer class object. The common Handler and Runnable anonymous inner classes hold the reference of the external Activity. If the Activity has been destroyed, but the Handler has not finished processing message, resulting in Handler memory leaks, resulting in Activity memory leaks.

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

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

Example 1 and Example 2 both simply time one minute to close the page. If the page was actively closed and destroyed before, there is still a message waiting to be executed in the Handler, and there is a reference chain to the Activity, which causes the Activity to be destroyed and cannot be recycled by the GC, resulting in memory loss. Leakage; Example 1 is an anonymous inner class of Handler, holding an external Activity reference: main thread —> ThreadLocal —> Looper —> MessageQueue —> Message —> Handler —> Activity; Example 2 is an anonymous inner class of Runnable, holding an external Activity reference : Message —> Runnable —> Activity.

Repair method 1: Mainly for Handler, remove all messages in the Activity life cycle.

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

Repair method 2: Static inner class + weak reference, removing the strong reference relationship, can repair memory leaks caused by similar anonymous inner classes.

    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 Singleton/static variable

A singleton/static variable holds a reference to the Activity, and even if the Activity has been destroyed, its reference still exists, causing a memory leak.

Example:

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

Call the singleton in the example, pass the Context parameter, and use the Activity object. Even if the Activity is destroyed, it will always be referenced by the static variable Singleton, resulting in failure to recycle and causing memory leaks.

Repair method:

Singleton.getInstance(Application.this);

Try to use the Context of Application as a singleton parameter, unless some functions that require Activity, such as displaying Dialog, if you must use Activity as a singleton parameter, you can refer to the anonymous inner class repair method, and release it at an appropriate time such as the onDestroy life cycle of Activity Singleton, or use weak reference to hold Activity.

1.3 Listeners

Example: The EventBus registered listener is not unbound, resulting in the registration to the EventBus being always referenced and unable to be recycled.

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

Repair method: Unbind in the life cycle of the corresponding registration monitor, and onCreate corresponds to onDestroy.

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

1.4 File/Database Resources

Example : When opening a file database or file, an exception occurs and it is not closed, causing resources to always exist, resulting in memory leaks.

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

Fix : Close the file stream in the finally code block to ensure that it can be executed after an exception occurs

    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 animation

Example: Android animation did not cancel the release of animation resources in time, resulting in memory leaks.

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

Fix : Cancel the animation when the page exits and destroys, and release animation resources in time.

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

2. IOS terminal

At present, we already have ARC (automatic reference counting) to replace MRC (manual reference counting), and the applied object will be released automatically when it is not strongly referenced. However, in the case of non-standard coding, the reference count cannot be reset to zero in time, and there is still a risk of introducing memory leaks, which may cause some very serious consequences. Taking the live broadcast scene as an example, if the ViewController of the live broadcast service cannot be released, the point statistics data dependent on the ViewController will be abnormal, and the user can still hear the live broadcast sound after closing the live broadcast page. It is very important to be familiar with memory leak scenarios and develop the habit of avoiding memory leaks. Here are some common iOS memory leaks and solutions.

2.1 Circular reference caused by block

Circular references introduced by blocks are the most common type of memory leak. A common reference cycle is object->block->object. At this time, the reference counts of both the object and the block are 1 and cannot be released.

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

In the sample code, self strongly references the member variable contentView, contentView strongly references actionBlock, and actionBlock strongly references self, which introduces a memory leak problem.

picture

To remove a circular reference is to remove a strong reference ring, and you need to replace a strong reference with a weak reference. like:

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

At this time, actionBlock weakly refers to self, the circular reference is broken, and it can be released normally.

picture

Or use the simpler writing provided by RAC:

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

It should be noted that it is not only self that may have a circular reference to the block, but all instance objects may have such problems, and this is easily overlooked during the development process. for example:

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

In this example, the circular reference between self and block is broken, and self can be released normally, but it should be noted that there is still a circular reference chain: tableView strongly references cell, cell strongly references block, and block strongly references tableView. This will also cause the tableView and cell to not be released.

picture

The correct way to write it is:

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

picture

2.2 Circular reference caused by 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

In the above example, TestSubClass uses the strong modifier for the delegate, which causes the TestClass instance and the TestSubClass instance to strongly reference each other after setting the proxy, resulting in a circular reference. In most cases, the delegate needs to use the weak modifier to avoid circular references.

2.3 NSTimer strong reference

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

The NSTimer instance will strongly refer to the incoming target, and there will be mutual strong references between self and timer. At this time, the status of the timer must be maintained manually. When the timer stops or the view is removed, the timer is actively destroyed to break the circular reference.

Solution 1 : Switch to the block method provided by iOS10 to avoid strong reference to target by NSTimer.

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

Solution 2 : Use NSProxy to solve the strong reference problem.

// 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 Non-reference type memory leaks

ARC's automatic release is implemented based on reference counting, and only maintains oc objects. The memory allocated directly by malloc in C language is not managed by ARC and needs to be released manually. Common operations such as using CoreFoundation, CoreGraphics framework to customize drawing, reading files, etc.

Such as generating UIImage through CVPixelBufferRef:

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 release problem

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

In the above example, the doSomething method is executed after a delay of 20 seconds using dispatch_after. This does not cause a memory leak of the self object. But assuming that self is a UIViewController, even if self has been removed from the navigation stack and no longer needs to be used, self will not be released until the block is executed, causing a phenomenon similar to memory leaks in the business.

In this long-term delayed execution, it is best to add a weakify-strongify pair to avoid strong holding.

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

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

Recommended reading [Technical Gas Station] series:

Baidu Programmer Development Pit Avoidance Guide (Go Language)

Baidu Programmer Development Pit Avoidance Guide (3)

Baidu Programmer Development Pit Avoidance Guide (Mobile Terminal)

Baidu Programmer Development Pit Avoidance Guide (Front End)

picture

{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/u/4939618/blog/8797752