preface

This is my first blog to share in the Nuggets, recently in the Nuggets read a lot of big man’s articles, learned a lot of things, really can not help but want to share some of our daily work used in the optimization scheme, in fact, is also a discussion and learning process, I hope we can communicate more ~

To introduce myself

The first blog, always introduce yourself ~, there are alumni or other indirectly close to the side of the contact can be private chat exchange, the first 1/4 -> 1/3 life really do not have what intersection can also be familiar. Born in Jiangxi province, seven years of computer science in Moziqiao College of Arts and Sciences, Tianfu, graduated in the summer of 18, currently working in Beijing Haidian 768, “pulse” platform client development. Like playing games and singing and masturbating cats, but nothing else

background

Let’s start with a brief history of the OOM.

At the end of 18 years, our app version made a very big change, because time emergency number, business busy, didn’t also to gather together the number can make some people that we can work on time the quantity (regular) of each company, business line for some modules in the reconstruction and a lot of new demand during the development process, many details didn’t notice, The crash rate and OOM rate soared in the following month, and remained high. It’s on the order of two parts per thousand, which is very, very scary. Therefore, we spent a period of time, focusing on fixing OOM related problems, and directly made the main version crash rate to “one in ten thousand”, OOM rate to one in ten thousand this order of magnitude.

Take out OOM. What did we do?

Don’t talk nonsense, also don’t talk about those online can be found some conventional optimization methods to fill the number of words, I will in view of how to fix OOM this goal, will think of the process and the solution to the problem to share, hope that there will be a certain experience hit you, can play some help ~~


Open dry!!!!! The following content, I will use a level of title font ~ conspicuous some haha, after all, the front is wordy nonsense




Check for memory leaks

The first thing to fix OOM is to check for memory leaks. To troubleshoot memory leaks, the first step is to monitor and report memory leaks.

We have adopted LeakCanary, implementing a custom Service inherited from DisplayLeakService and overriding the afterDefaultHandling method to report a memory leak to Sentry.

The sample code is as follows:

public static class LeakReportService extends DisplayLeakService {   
    @SuppressWarnings("ThrowableNotThrown")    
    @Override    
    protected void afterDefaultHandling(@NonNull HeapDump heapDump, @NonNull AnalysisResult result, @NonNull String leakInfo) {        
        if(! result.leakFound || result.excludedLeak) {return;       
        }        
        try {            
            Exception exception = new Exception("Memory Leak from LeakCanary"); exception.setStackTrace(result.leakTraceAsFakeException().getStackTrace()); Sentry.capture(exception); } catch (Exception e) { e.printStackTrace(); }}}Copy the code

After a memory leak is reported to sentry, it is good to look directly at where the leak occurred. With sentry monitoring, there is no escape for most of the memory leaks in the project. Memory leaks are relatively simple, so I won’t spend a lot of time on them.

In addition to LeakCanary, we also used Android Studio’s built-in Profiler tool to analyze the internal cache, including problems with memory leaks and high memory spikes.

I don’t want to go into the profiler tool, but I want to give you some tips.

We can use Profiler to see the preview of bitmap images in the Java heap. This will help us to locate where the bitmap is leaking and where the bitmap is loading too much

Method: Find the corresponding Bitmap object, then ~, click on it, and then preview it as shown below:Copy the code



Second, the bottom-feeding strategy

What we do know is that when an Activity is nearing the end of its life cycle, we are most likely not going to use the Activity anymore, so we can clean up its references that might cause the Activity to leak, freeing up some of its resources. For example, we have TextWatcher in EditText, which is a very leaky case that comes up a lot in our project, and then we add a more aggressive backstop strategy,

Without further ado, get right to the code

private void traverse(ViewGroup root) {    
    final int childCount = root.getChildCount();    
    for (int i = 0; i < childCount; ++i) {        
        final View child = root.getChildAt(i);        
        if (child instanceof ViewGroup) {            
            child.setBackground(null);            
            traverse((ViewGroup) child);        
        } else {            
            if(child ! = null) { child.setBackground(null); }if (child instanceof ImageView) {               
                 ((ImageView) child).setImageDrawable(null);            
            } else if(child instanceof EditText) { ((EditText) child).cleanWatchers(); }}}}Copy the code

We do some resource and reference clearing in the onDestory() method of the base BaseActivity class

Third, the memory peak is too high

After we removed all the memory leaks that can be fixed, we did not find any improvement in the data and OOM rate remained high for a week. Therefore, we began to suspect the problem of high memory peak value. In our project, there are not only some native modules, but also mixed H5 and RN modules. When an instance of the ReactActivity is started, the memory peak is always very, very high, and the project has a message flow presentation, which contains a large number of images. This is also the reason for the high memory peak (the Bitmap object is too large and too many).

We’ve brought up our old friend, Profiler, which is a great tool for analyzing bitmap objects. You can see the size, preview the image, and find out layer by layer who is referring to it by going to instance. For example, in this example, the reference is referenced by Fresco ~ directly in CountingMemoryCache.





In fact, we still need to pay attention to the allocation of Bitmap objects and illegal holding caused by the memory peak problem, if a Bitmap object is 3M, and then hold dozens or hundreds of Bitmap objects in memory, this who can bear, low-end machine directly OOM long ago.

Check for Bitmap allocation problems

We currently use two image loading frameworks in the project, UIL and Fresco. UIL is a new framework that I’ve been talking about for a long time

1. Problems with UIL loading pictures in our project:

  • ARGB_8888 is passed in most places without a proper Config. There is no need to change it to 565 to reduce the memory footprint by half
  • When loadImage is performed with UIL, no targetSize is passed in, which directly results in the UIL decoding Bitmap objects with screen size. Imagine a very small avatar View holding a Bitmap object with screen size.
  • MemoryCache (false) many places do not need to store a memoryCache, such as splash screen ads, which will not be used once the app is started, and can be loaded when memoryCache(false)
  • There are a lot of places where you don’t need a disk cache, such as publishing dynamic images, selecting images from a gallery, and no need to save a disk cache, because the images themselves are local images. Direct diskCache (false)


2. The use of Fresco in RN pages

You can see from the code that when an RN page is destroyed, the Fresco memory cache is emptied,

Go straight to code diagram:



If the code sees this, it seems that Fresco doesn’t need to worry, since it will empty Fresco’s memory cache and cause memory spikes to be too high. If the reader sees this and thinks so, it is a mistake. Without further ado, let’s get right to the picture.



The logic of Fresco source code will not be analyzed in this article. I will use a separate section to analyze the specific source code

The reason WHY I am so sensitive to the Fresco GIF cache is because of the Profiler. When I used the Profiler to check the allocation of bitmaps in memory, I found that hundreds of Loading maps were not destroyed. The Bitmap of each frame is about 360K), and the more pages open, the more Bitmap Loading will be. (This is because we have a Loading animation for every RN page.)

0.3m * 100 = 30M, not a few… A little scary, to be honest

So, to kill them, there’s a reflex, and normally you don’t need a reflex. Just clear the object from ImagePipelineFactory

public static void clearAnimationCache() {    
if(frescoAnimationCache == null) {if native and Rn initialize Fresco at the same time, Will cause the Fresco "internal storage figure CountingMemoryCache not Fresco. GetImagePipelineFactory () getBitmapCountingMemoryCache () / / temporary use the method of reflection, Try {Class imagePipelineFactoryClz = class.forname ("com.facebook.imagepipeline.core.ImagePipelineFactory");            
        Field mAnimatedFactoryField = imagePipelineFactoryClz.getDeclaredField("mAnimatedFactory");            
        mAnimatedFactoryField.setAccessible(true);            
        AnimatedFactoryV2Impl animatedFactoryV2 = (AnimatedFactoryV2Impl) mAnimatedFactoryField.get(Fresco.getImagePipelineFactory());            
        Class animatedFactoryV2ImplClz = Class.forName("com.facebook.fresco.animation.factory.AnimatedFactoryV2Impl");            
        Field mBackingCacheField = animatedFactoryV2ImplClz.getDeclaredField("mBackingCache");            
        mBackingCacheField.setAccessible(true);            
        frescoAnimationCache = (CountingMemoryCache) mBackingCacheField.get(animatedFactoryV2);        
    } catch (Exception e) {            
        Log.e("FrescoUtil", e.getMessage(), e); }}if(frescoAnimationCache ! = null) { frescoAnimationCache.clear(); } Fresco.getImagePipelineFactory().getBitmapCountingMemoryCache().clear(); Fresco.getImagePipelineFactory().getEncodedCountingMemoryCache().clear(); }Copy the code


Another bottom line

To prevent spikes from getting too high, we also set up a thread that periodically monitors memory usage in real time, and if there is a memory emergency, we simply empty the UIL/Fresco cache

private static Handler lowMemoryMonitorHandler; private static final int MEMORY_MONITOR_INTERVAL = 1000 * 60; /** * enable low memory monitor, if low memory response */ public static voidstartMonitorLowMemory() {
        HandlerThread thread = new HandlerThread("thread_monitor_low_memory"); thread.start(); lowMemoryMonitorHandler = new Handler(thread.getLooper()); lowMemoryMonitorHandler.postDelayed(releaseMemoryCacheRunner, MEMORY_MONITOR_INTERVAL); } private static Runnable releaseMemoryCacheRunner = new;} private static Runnable releaseMemoryCacheRunner = newRunnable() {
        @Override
        public void run() {
            long alreadyUsedSize = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
            long maxSize = Runtime.getRuntime().maxMemory();
            if(Double.com pare said (alreadyUsedSize, maxSize * 0.8) = = 1) {BitmapUtil. ClearMemoryCaches (); } lowMemoryMonitorHandler.postDelayed(releaseMemoryCacheRunner, MEMORY_MONITOR_INTERVAL); }};Copy the code

Five, large map investigation and optimization

I think we will not think of, in the registration page of our APP, there will be a picture rotation control, it rotation of five or six single 6M+ Bitmap… Of course, this is not the only case for mega graphs. There are other places where we Profiler to find large Bitmap objects and then preview them to determine where they are used.

Just optimize it out. The worst 8888 -> 565 will take up half of the memory


How to say,, OOM this thing, haven’t zha stalemate, did not.


Six, summarized

In the middle of the night, I wanted to share and record something, so I just wrote this blog without any details, typesetting and good language organization. I just wanted to share

To summarize what we’ve done to fix OOM:

  1. Check for memory leaks, including common Context leaks, singleton leaks, EditText TextWatcher leaks, and so on. Find and fix them
  2. Last-ditch solution:
  • In the Activity onDestory, traverse the View tree, clearing backGround, Drawable, EditText TextWatcher, and so on
  • Optimization of peak memory. Memory leaks can lead to memory spikes, which are OOM’s big pot, for example when there is not enough available memory to allocate a Bitmap object, and most of the memory spikes on Android are caused by loading images. Now many apps have information flow display, there may be a lot of nine-grid display pictures, and the Bitmap object itself can be very large.
    • Optimize use of UIL
      • MemoryCache option. Not all image loads require the UIL to plug a memoryCache, such as flash screens
      • Imageloader.getinstance ().displayImage(), the Option passed in should not be brainless ARGB_8888.
      • When calling the displayImage, it is best to pass an ImageSize as a targetSize. This size can be the size of your ImageView. If the size of the View itself is uncertain, you can pass an approximate value, such as the standard size of several heads in our app. Just pass MaxAvatarSize
    • The optimization of Fresco”
      • RN uses Fresco to load images. When the RN Activity is destroyed, the default Fresco cache will be empty, but the GIF cache will not be clear. Clean it manually. Each RN page in our project had a Loading GIF, so we suffered a big loss.
  • Keep a HandlerThread running in the background and empty the UIL and Fresco memory cache to calm the world
  • You need to monitor memory leaks, OOM, Crash, and ANR
  • I can’t remember any other details. I lost my mind at 4:00 in the morning

    There will be a separate article about Fresco source code analysis, the best way to use the Profiler, and the optimization of App startup speed. There will be more in the future. Contents covered include but are not limited to:

    • Share some design ideas of mainstream framework
    • Problems and potholes in work projects
    • Some experience of wading in puddles at work
    • Good code
    • Bad code
    • The design of the bad
    • Programmer’s journey from hair to rain alarm
    • .









    My Jane booksZou Ah Tao Tao Tao’s brief book

    My CSDN, Tao Tao’s CSDN

    I’m the nuggetsThe nuggets of Zou Ah Tao Tao Tao