Introduction:

It has been more than ten years since the development of smart phones, and the software and hardware of mobile phones have undergone earth-shaking changes, especially in the Android camp, from one or two hundred METERS at the beginning to 4G and 6G memory at every turn today. However, when most developers look at their anomaly reporting system, they still find various memory problems emerge endlessly, and various OOM versions contribute a lot to the crash rate. Android development to today is also more mature, a variety of new frameworks, new technologies are emerging in endlessly, and memory optimization has been an inevitable topic of Android development process. Just recently do memory optimization related work, here also to Android memory optimization related knowledge to do a summary.

Before starting the article, I recommend the “Android Performance Optimization model – the sixth season” translated by my company colleagues. Because of the limited space, I only make a brief summary of some content here. At the same time, if there is any incorrect content, please help correct it.

This article will summarize the knowledge related to Android memory optimization and finally analyze the case (Part 2 is the theoretical knowledge summary, you can also skip to part 3 to see the case) :

Android common memory problems and corresponding detection, solution. JOOX memory optimization case iv. Summary

In order to optimize the memory usage of the App, it is necessary to first understand the memory allocation and reclamation mechanism of the Android system.

Android memory allocation recycling mechanism

Refer to the memory reclamation mechanism of Android operating system [1], here is a brief summary:

From a macro point of view, Android system can be divided into three levels

  1. Application Framework,

  2. Dalvik virtual machine

  3. The Linux kernel.

Each of these three levels has its own memory-related work:

1. Application Framework

Anroid specifies five default reclaim priorities based on the components running in the process and their state:

  • Empty Process

  • Background process

  • Service process

  • Visible process

  • Foreground process

When the system needs to reclaim memory, empty processes will be reclaimed first, then background processes, and so on, and finally foreground processes will be reclaimed (normally foreground processes are the processes that interact with users, if even foreground processes need to be reclaimed, then the system is almost unavailable).

From this, there are a lot of process survival methods (increasing priority, mutual awakening, native survival, etc.), there are a variety of family barrel, and even a variety of unkillable process.

In Android, ActivityManagerService centrally manages memory allocation for all processes.

2. The Linux kernel

The easiest way to understand this is that ActivityManagerService scores all processes (stored in variable adj) and then updates this score to the kernel. It is up to the kernel to do the real memory recall (lowmemorykiller, Oom_killer). Here only roughly process, intermediate process is very complex, interested students can study together, source code in system ActivityManagerService. In Java.

3. Dalvik VM

Android process memory management analysis [3], the Android process memory management analysis.

Android has Native Heap and Dalvik Heap. In theory, the allocated space of Android Native Heap depends on the hardware RAM, and the Dalvik Heap of each process is limited in size. For specific strategies, see Android Dalvik Heap analysis [4].

Why is the Android App OOM? The amount of memory requested exceeds the maximum Dalvik Heap. There are also some “dark tech” memory optimizations, such as putting memory consuming operations in the Native tier, or breaking the Dalvik Heap limit per process by process.

Like native Java, Android Dalvik Heap divides the memory space of the Heap into three regions: Young Generation, Old Generation, and Permanent Generation.

The most recently allocated object is stored in the Young Generation area. When the object stays in the area for a certain amount of time, it is moved to the Old Generation area, and finally to the Permanent Generation area. The system performs different GC operations depending on the memory data type in memory.

When GC occurs, all threads are suspended. The amount of time it takes to perform GC also depends on which Generation it occurs in. Young Generation has the shortest time per GC operation, followed by Old Generation, and Permanent Generation has the longest.

The thread pauses during GC, which leads to stalling. Google has solved this problem in the new version of Android by optimizing the GC process in ART and revealing the ART details — Garbage collection[5]. It is said that the efficiency of memory allocation has been increased by 10 times. GC is two or three times more efficient (you can see how inefficient it was before), but the main thing is to optimize interrupt and blocking times, and frequent GC still leads to stalling.

Above is the knowledge of the Android system memory allocation and recovery, looking back on it, now all kinds of mobile phones manufacturer advocated ai, BuKa as 18 months, with a faster and faster, in fact, a large part of the Android memory optimization, by using some of the more mature based on the statistics, machine learning algorithm of data cleaning regularly, Clean up memory and even preload data into memory.

Ii. Common Android memory problems and corresponding detection, solutions

1. Memory leaks

Not only Android programmers, but also most programmers have encountered the problem of memory leakage. It can be said that most of the memory problems are caused by memory leakage. Android also has some very common memory leakage problems [6], here is a brief list:

  • Singletons (the main reason is that singletons are generally global and sometimes reference some variables with a short life cycle, so they cannot be released)

  • Static variables (again because of the long life cycle)

  • Handler memory leaks [7]

  • Anonymous inner classes (Anonymous inner classes reference external classes and cannot be released, such as various callbacks)

  • Resources are not closed when used up (BraodcastReceiver, ContentObserver, File, Cursor, Stream, Bitmap)

One of the best known components for Android memory leaks is LeakCanary, which is made by Square, the conscience of the Android open source community, which includes okHTTP, Retrofit, The principle is to monitor each activity, detect the reference in the background thread after the activity ondeStory, and then perform gc after a certain period of time. If the reference is still there after GC, The memory stack is then dumped and parsed for visual display. Use LeakCanary to quickly detect memory leaks in Android.

Under normal circumstances, the stability of the App should be greatly improved after most of the memory leakage problems are solved. However, sometimes the App itself has some functions that consume memory, such as live broadcasting, video playing and music playing. So what else can we do to reduce the memory usage and OOM?

2. Image resolution is related

Resolution adaptation problem. In many cases, images take up the majority of your App’s memory footprint. We know that can put the pictures in hdpi/xhdpi/xxhdpi different folders, through XML android: background set the background image, or through BitmapFactory. DecodeResource () method, Images are actually scaled by default. The actual functions called in the Java layer are either through the decodeResourceStream function in the BitmapFactory

public static Bitmap decodeResourceStream(Resources res, TypedValue value,
        InputStream is, Rect pad, Options opts) 

{

   if (opts == null)

   {        opts = new Options();    }

if (opts.inDensity == 0 && value ! = null)

   {

      final int density = value.density;

      if (density == TypedValue.DENSITY_DEFAULT)

      {           opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;       }

else if (density ! = TypedValue.DENSITY_NONE)

{ opts.inDensity = density; }}

if (opts.inTargetDensity == 0 && res ! = null)

   {        opts.inTargetDensity = res.getDisplayMetrics().densityDpi;    }    

   return decodeStream(is, pad, opts); }Copy the code

DecodeResource will scale the Bitmap according to the current device screen pixel density densityDpi value, so that the resolution of the parsed Bitmap matches the current device to achieve an optimal display, and the size of the Bitmap will be larger than the original. Check out Tencent Bugly’s detailed analysis of Android development pitfalls: How much memory do your Bitmaps take up?

Density, resolution, -hdPI and other RES directories

For example, for a 1280×720 image, if placed in XHDPI, the size of the XHDPI device is still 1280×720 and the size of the XXHPI device is 1920×1080. The size of the two cases in memory is: The difference between 3.68m and 8.29m is 4.61m, which is still a big difference for mobile devices.

Although there are advanced Image loading components such as Glide, Facebook Freso, or Universal image-loader, sometimes you need to manually get a bitmap or drawable. How do you reuse bitmaps as much as possible, especially in scenarios that might be called frequently (such as ListView’s getView)? First of all, it needs to be clear that the same image should be reused as much as possible. We can simply use WeakReference to make a Bitmap cache pool, or use similar image loading library to write a general bitmap cache pool, which can be implemented by referring to GlideBitmapPool[8].

Let’s also look at what the system does. For background images like android:background or Android: SRC set directly in XML, using ImageView as an example, we end up calling Resource. Java’s loadDrawable:

Drawable loadDrawable(TypedValue value, int id, Theme theme) throws NotFoundException 

{

   // Next, check preloaded drawables. These may contain unresolved theme    // attributes.    final ConstantState cs;

   if (isColorDrawable)

   {        cs = sPreloadedColorDrawables.get(key);    }else{        cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);    }    Drawable dr;

   if (cs != null) {        dr = cs.newDrawable(this);    } else if (isColorDrawable) {        dr = new ColorDrawable(value.data);    } else {        dr = loadDrawableForCookie(value, id, null);    }    ...

   

   return dr; }Copy the code

If the image is the same for different drawables, there will only be one bitmap stored in the bitmap State. When the drawable is retrieved, the sPreloadedDrawables will be stored in the bitmap state. The system retrieves the bitmap from the cache and constructs a drawable. By BitmapFactory. DecodeResource () is always return to bitmap to decode. So we can reuse the bitmap by getting the bitmap from the drawable using context.getResources().getDrawable. However, there are some holes here, like the bitmap we got, If we are doing something like recycle, but if we are using it elsewhere we will get a “Canvas: Trying to use a recycled bitmap Android.graphics.bitmap” exception.

3. Image compression

BitmapFactory can decode images with an option, which has some useful functions, such as:

  • InTargetDensity indicates the target pixel density when it is to be drawn

  • The value inSampleSize is an int. If it is less than 1, it will be treated as 1. If it is greater than 1, the bitmap width and height will be scaled down (1 / inSampleSize) and resolution will be treated as a multiple of 2. For example, if width=100, height=100, inSampleSize=2, then the bitmap is processed as width=50, height=50, width and height are reduced to 1/2, and the number of pixels is reduced to 1/4

  • InJustDecodeBounds literally means to parse only the edges of an image, sometimes just to get the size of the image, rather than loading the entire image directly.

  • InPreferredConfig uses ARGB_8888 by default, which takes up 4 bytes per pixel, and RGB_565, which takes up 2 bytes per pixel, saving 50% of memory.

  • InPurgeable and inInputShareable need to be used together. Bitmapfactory. Java has a comment in the source code, which basically means whether the bitmap can be reclaimed when the system is running out of memory. In fact, these two attributes have been ignored since 5.0, because the system believes that recycling and decoding may actually cause performance problems

  • This parameter is recommended by inBitmap to reuse image memory and reduce memory allocation. Before 4.4, only image memory areas of the same size can be reused. After 4.4, the original image can be reused as long as it is larger than the image to be decoded.

4. Cache pool size

Many image loading components now use not only soft references or weak references, but also LruCache, which is used by Glide by default, because soft references and weak references are difficult to control. LruCache allows for fine control, and the default cache pool setting is too large, resulting in memory waste. If the Settings are too small, the pictures will often be recycled, so it is necessary to calculate a reasonable initial value in memory according to the situation of each App, as well as the resolution of the device, and you can refer to Glide.

5. Memory jitter

What is memory jitter? In Android, memory jitter refers to the frequent allocation and collection of memory, and frequent gc can cause lag or OOM in severe cases.

A classic example of string concatenation is to create a large number of small objects (such as string concatenation logs in frequently called places), see String Optimization for Android [9].

Why does memory jitter cause OOM?

The main reason is that a large number of small objects are frequently created, which leads to memory fragmentation. Therefore, when the memory needs to be allocated, although there is still free memory to allocate, the system returns OOM directly because the memory is not continuous and cannot be allocated.

For example, when we take the subway, if you go to the subway without a bus card, the ticket machine will only support 5 yuan, 10 yuan, even if you have 10,000 one-piece tickets on you. . Of course you can exchange 5 or 10 yuan, but not so lucky in Android, the system will simply refuse to allocate memory and throw an OOM to you (some people say that Android does not defragment the free memory in the Heap, waiting to be verified).

other

Common data structure optimization, ArrayMap and SparseArray are Android system apis that are customized for mobile devices. It is used to replace HashMap to save memory under certain circumstances. For specific performance, see source analysis and performance comparison of HashMap, ArrayMap and SparseArray [10]. SparceArray should be used as far as possible to replace HashMap with key int. The memory savings are about 30%. For other types, ArrayMap’s memory savings are not really significant, in the 10% range, but lookups can be slow above 1000 data.

Enumerations are controversial on The Android platform. In earlier versions of Android, using enumerations resulted in packages that were 10 times larger than using int packages. In one example, using enumerations was discussed on StackOverflow. While enumerations are not a performance issue on The Android platform, it is recommended to be cautious about using enumerations because they can use up to twice as much memory as ints.

ListView reuse, as you all know, try to reuse conertView in getView, and because getView is going to be called a lot, you want to avoid generating objects a lot

Use multiple processes with caution. Nowadays, many apps are not single processes. In order to keep alive or improve stability, some processes are split. In fact, even empty processes occupy memory (about 1M).

Use the IDS of system resources, system components, images, and even controls whenever possible

Reduce the hierarchy of the view and use the ViewStub for pages that can be lazily initialized

Data correlation: The use of protobuf for serialized data can save 30% memory compared to XML, and the use of shareprefercnce should be cautious, because for the same SP, the whole XML file will be loaded into memory, sometimes in order to read a configuration, hundreds of K data will be read into memory, database fields as thin as possible, only read the required fields.

Dex optimization, code optimization, careful use of external libraries, somebody feels how much code in memory has nothing to do, actually there will be a sort of relationship, is now a little bit big project at more than one million lines of code, dex is more the norm, not only takes up ROM space, actually run time needs to be loaded dex is memory (M), Sometimes a whole library is introduced to use a function in some library. Consider extracting the necessary parts, turning on the ProGuard optimization code, and using Facebook Redex to optimize the dex(which seems to have a lot of holes).

Three cases,

JOOX is a core product of IBG and has become the no.1 music App in 5 countries and regions since its launch in 2014. Southeast Asia is the main distribution region of JOOX. In fact, there are many low-end models in these regions, so memory optimization of the App is imperative.

The above mentioned Android memory allocation and reclamation mechanism also lists common memory problems, but when we are given a task to optimize memory, where should we start? Here is a memory optimized share.

1. Fix most of the memory leaks first.

No matter how the current MEMORY usage of the App is, it is better to recycle things that are not needed in theory to avoid wasting user memory and reduce OOM. In fact, since JOOX has been connected to LeakCanary, memory leak detection has been done for each release, and over several iterations, JOOX has fixed dozens of leaks.

2. View the memory usage through MAT to optimize the memory usage.

After JOOX fixed a series of memory leaks, memory usage remained high, leaving MAT to see where it was. There are numerous online tutorials on the use of MAT. Two TUTORIALS on the use of MAT are simply recommended [11], and mat-Memory Analyzer Tool is used for advanced use [12].

Click on Android Studio to dump the current memory snapshot. Since the hprof file directly dumped by Android Sutdio is a little different from the standard hprof file, we need to convert it manually. Use SDK /platform-tools/hprof-conv.exe to convert the original hprof-conv file hprof-new hprof.hprof file. You only need to enter the original file name and the target file name to convert, and after converting, you can open it directly with MAT.

Here is the hprof file for JOOX to open the App and manually perform multiple gc.

Here we are looking at the Dominator Tree (the list of objects in memory that occupy the most memory).

  • Shallo Heap: The amount of memory occupied by the object itself, not including the memory of the object referenced by it.

  • Retained Heap: The value of the Retained Heap is calculated by superimposing the size of all the Retained set objects. Or, the heap size of all other freed objects (including recursively freed objects) due to X being freed.

At first glance, there are three 8M objects, which add up to 24M.

List Objects -> With incoming References check with incoming References. Outgoing references check with incoming References

In this way, we can see that the three pictures are flash screen, App main background and App drawer background respectively.

There are really two problems here:

  • The original images are actually 1280×720, but they are all scaled to 1920×1080 on a 1080p phone

  • In fact, this image should be recycled after the flash screen is displayed, but due to historical reasons (related to JOOX exit mechanism), this image is resident in the background, resulting in unnecessary memory usage.

Optimization method: We moved these three images from XHDPI to XXHDPI (of course, we need to see if the image display effect has a great impact), and recycled the flash screen after the flash screen display. Optimization results:

Optimized 17M from 8.29×3= 24.87m to 3.68×2= 7.36m. May sometimes take a lot of effort to optimize a lot of code can not optimize hundreds of K, so in many cases when memory optimization optimization picture or more immediate results).

In the same way, we found that for some default images, the actual display requirements are not very high (the image is relatively simple, and the image loads successfully in most cases), such as the background image of the banner below:

About 1.6m before optimization, and about 700K after optimization.

At the same time, we also found another problem with the default image. Due to historical reasons, the image loading library we used required a bitmap for setting the default image interface. As a result, we used BitmapFactory decode to decode a bitmap for almost every adapter. Not only did not reuse, but also saved multiple copies, which not only caused memory waste, but also caused sliding to occasionally stall. Here we also use the global bitmap cache pool for the default image. As long as the App uses the same bitmap globally, it reuses the same bitmap.

In addition, for the picture seen from MAT, sometimes it will be difficult to confirm which picture it is because you can’t see the corresponding ID in the project. Here, stackOverflow has a method to restore the picture directly with the original data through GIM.

In fact, there is a disadvantage of JOOX. In many parts of JOOX, complicated pictures are used, and some parts also need blur and animation, which are all memory consuming operations. After the Material Design is developed, many apps are revised according to MD Design, and the default background is usually adopted. The default images are usually solid colors. Not only does the App look bright and light, but it actually saves a lot of memory, which JOOX has optimized for low-end models.

3. We also analyzed OOM on Bugly and found that some OOM can be avoided.

This place is the banner bit of our home page. Theoretically, the App will cache the default background image as soon as it is opened. However, in fact, after using it for a period of time, In order to decode this background image, OOM changed to global cache.

The following is the legendary memory jitter

The actual code is as follows: string concatenation is performed for logging. Once this function is called frequently, memory jitter is likely to occur. Here we have changed to using StringBuilder for optimization in our new version.

There are also some strange situations, which happened when we scanned the song file headers. Some file headers were hundreds of meters in size, so we applied for too much memory at one time, so we directly caught out of memory error, which cannot be repaired temporarily.

4. At the same time, we adjusted some logic codes, for example, the third TAB (Live TAB) of our App home page carried out data delay loading and timed recycling.

Here because the page in addition to the large and round banner, practical strong reference images will have more than one, if this time cut to other pages are listening to music, etc, this page has been cached in the background, the actual is a waste of memory consumption, in order to optimize the experience at the same time, we can’t directly by setting the home page of the viewpager cache pages, Because it will be recycled, often lead to affect experience, so we are not visible on the page after a period of time, remove adapter data (just empty in the adapter data, actual data returned from the network load is still there, here only to remove the interface for the reference of the picture), when the page is displayed again to use, according to the data is already loaded This reduces the use of images in many cases and does not affect the experience.

5. Finally, we also encountered a strange problem. There was such a report on our Bugly report

We’ve seen a discussion on StackOverflow about how frequent calls to system.gc () can cause kernel state transitions to timeout in some cases, such as screen breaks, or in some power-saving modes. There seems to be no good solution to this problem except to optimize memory and minimize manual calls to system.gc ().

The optimization results

After starting the App, we switch to my music interface, stay for 1 minute, and obtain the App memory usage after multiple GC

Before optimization:

After the optimization:

The results of many tests are similar, and only one of them is captured here, with an optimization effect of 28M.

Of course, different scenarios have different memory footprint, and the above results are the result of multiple manual gc triggers. The test results may vary considerably when other third-party tools are used without manual GC.

As for the background of various pictures in JOOX mentioned above, we have made dynamic optimization for different models, and set a solid color background for especially low-end models. The final optimization results are as follows:

The average memory size decreased by 41M.

This summary is mainly from the picture, there is a little logic optimization, has basically reached the optimization goal.

Four,

Memory optimization is still an important topic at present. We can check memory usage through various memory leak detection components, MAT. The Memory Monitor tracks Memory changes throughout the App, the Heap Viewer checks the current Memory snapshot, and the Allocation Tracker tracks the source of Memory objects. The crash reporting platform is used to Monitor and optimize the App Memory from various aspects. The above is just a list of some common cases. Of course, each App has different functions, logic and architecture, causing different memory problems. Only by mastering the use of tools and finding the problems, can the right remedy be applied.


References are cited

1. The memory reclamation mechanism of the Android operating system www.ibm.com/developerwo…

2. Alibaba’s Android share memory optimization www.infoq.com/cn/presenta…

3. The process of the Android memory management analysis blog.csdn.net/gemmem/arti…

4. Android Dalvik Heap analysis blog.csdn.net/cqupt_chen/…

5. Details reveal ART – Garbage collection www.cnblogs.com/jinkeep/p/3…

6. Android performance optimization of common memory leak blog.csdn.net/u010687392/…

7. Android App memory leaks of Handler blog.csdn.net/zhuanglongh…

8. GlideBitmapPool github.com/amitshekhar…

9. The Android performance optimization of String blog.csdn.net/vfush/artic article…

10. A HashMap, ArrayMap, SparseArray source analysis and performance comparison www.jianshu.com/p/7b9a1b386…

11. USES tutorial blog.csdn.net/itomge/arti MAT…

12. The MAT – Memory Analyzer Tool using advanced www.lightskystreet.com/2015/09/01/…



If you think our content is good, please forward it to moments and share it with your friends