Technology Sharing

Android performance optimization memory optimization

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Android performance optimization memory optimization

Memory issues

  • Memory Thrashing
  • Memory Leaks
  • Memory leak

Memory Thrashing

Memory jitter refers to the creation and destruction of a large number of objects in a short period of time, resulting in frequent garbage collection (GC) activities. This frequent GC activity will occupy a large amount of CPU resources and may cause application lag or performance degradation.

Symptom: The memory curve is jagged.

Memory Leaks

A memory leak is when an application holds references to objects that are no longer needed, preventing them from being reclaimed by the garbage collector, thus occupying memory that could have been freed. Over time, memory leaks can cause less and less available memory, which can eventually cause the application to crash or degrade performance.

Memory leak

Memory overflow means that the application tries to allocate more memory space, but the system cannot satisfy the request because there is not enough memory space to allocate. This usually causes the application to throw an OutOfMemoryError exception.

Detection Tool

  • Memory Profiler
  • Memory Analyzer
  • LeakCanary

Memory Profiler

  • Memory Profiler is a memory analysis tool that comes with Android Studio.
  • Real-time chart showing program memory usage.
  • Identify memory leaks, thrashing, and more.
  • Provides the ability to capture heap dumps, force GC, and track memory allocations.

Memory Analyzer

  • Powerful Java Heap analysis tool to find memory leaks and memory usage.
  • Generate overall reports, analyze problems, etc.

LeakCanary

  • Automatic memory leak detection.
    • LeakCanary automatically detects leakage issues of these objects: Activity, Fragment, View, ViewModel, Service.
  • Official website: https://github.com/square/leakcanary

Memory management mechanism

Java

Java memory structure: heap, virtual machine stack, method area, program counter, local method stack.

Java memory recycling algorithm:

  • Mark-and-Sweep Algorithm:
    1. Mark objects that need to be recycled
    2. Collect all marked objects uniformly.
  • Copy algorithm:
    1. Divide the memory into two equal-sized blocks.
    2. When one memory block runs out, copy the surviving objects to another.
  • Mark-Clear Algorithm:
    1. The marking process is the same as the "mark-and-sweep" algorithm.
    2. The surviving objects move to one end.
    3. Clear the rest of the memory.
  • Generational collection algorithm:
    • Combine the advantages of multiple collection algorithms.
    • The survival rate of new generation objects is low, so a replication algorithm is used.
    • The survival rate of old generation objects is high, and the mark-sweep algorithm is used.

Disadvantages of the mark-and-sweep algorithm: The marking and sweeping efficiency is not high, and a large amount of discontinuous memory fragments will be generated.

Copy algorithm: simple to implement and efficient to run. Disadvantage: wastes half of the space.

Mark-sweep algorithm: avoids memory fragmentation caused by mark-sweep and avoids space waste caused by copy algorithms.

Android

Android memory is flexibly allocated, and the allocated value and maximum value are affected by the specific device.

The Dalvik recycling algorithm and the ART recycling algorithm are both garbage collection mechanisms used for memory management in the Android operating system.

Dalvik recycling algorithm:

  • Mark-and-sweep algorithm.
  • The advantage is that the implementation is simple. The disadvantage is that the application execution will be suspended during the marking and clearing phases, which will cause the application to freeze for a short time and affect the user experience.

Art recycling algorithm:

  • Compacting Garbage Collection algorithm. It is an improvement on the mark-sweep algorithm to reduce the pause time during garbage collection.
  • Concurrent marking: ART introduces a concurrent marking phase, which means it can be performed concurrently with the execution of the application. This reduces the pause time due to garbage collection.
  • Sweep and compact: During the sweep phase, ART not only clears unmarked objects, but also compacts memory, which means it moves live objects together to reduce memory fragmentation. This makes memory management more efficient and reduces the possibility of memory allocation failures.
  • Adaptive collection: ART also introduces the concept of adaptive collection, which means that it automatically adjusts the frequency and method of garbage collection according to the application's behavior and memory usage patterns. This allows ART to better adapt to different application needs.

LMK mechanism:

  • Low Memory Killer mechanism.
  • Its main function is to end some background processes according to certain priority strategies when the system memory is insufficient to free up memory and ensure the stability and responsiveness of the system.

solve

Memory jitter problem

Simulation problem code
public class ShakeActivity extends AppCompatActivity {

    private static Handler mHandler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            String str = "";
            for (int i = 0; i < 10000000; i++) {
                str += i;
            }
            mHandler.sendEmptyMessageDelayed(1, 30);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_shake);
    }

    public void startClick(View view) {
        mHandler.sendEmptyMessage(1);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
Use the Memory Profiler tool to detect

Memory Profiler allows you to view memory allocations, click "Record Java/Kotlin allocations".

insert image description here

What this means:

  • Java: Memory allocated by Java or Kotlin code.
  • Native: Memory allocated by C or C++ code.
  • Graphics: The memory used by the graphics buffer queue to display pixels to the screen (including GL surfaces, GL textures, etc.). CPU shared memory.
  • Stack: The memory used by the native stack and Java stack in your application. This is usually related to how many threads your application runs.
  • Memory used by the app to process code and resources such as dex bytecode, optimized or compiled dex code, .so libraries, and fonts.
  • Memory used by an app that the system is unsure how to categorize.
  • The number of Java/Kotlin objects allocated by the app. This number does not count objects allocated in C or C++.

The following means:

  • Allocations: By malloc() ornew The number of objects allocated by the operator.
  • Deallocations: By free() ordelete The number of objects deallocated by the operator.
  • Allocations Size: The total size of all allocations in the selected time period, in bytes.
  • Deallocations Size: The total size of memory released during the selected time period, in bytes.
  • Total Count: Allocations minus Deallocations.
  • Remaining Size: Allocations Size minus Deallocations Size.
  • Shallow Size: The total size of all instances in the heap, in bytes.

Analysis of the above picture:

insert image description here

The values ​​of Allocations and Deallocations in this area are relatively close, and the Shallow Size is relatively large, indicating that objects may be created and destroyed frequently.

insert image description here

After clicking, you can view the call stack information. Combined with the code, it can be inferred that there is a memory jitter problem in the Handler.

Optimization Tips
  • Avoid creating and destroying objects frequently.

Memory leak problem

Simulation problem code
public class CallbackManager {
    public static ArrayList<Callback> sCallbacks = new ArrayList<>();

    public static void addCallback(Callback callback) {
        sCallbacks.add(callback);
    }

    public static void removeCallback(Callback callback) {
        sCallbacks.remove(callback);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
public class LeakActivity extends AppCompatActivity implements Callback {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_leak);
        ImageView imageView = findViewById(R.id.imageView);
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.splash);
        imageView.setImageBitmap(bitmap);
        CallbackManager.addCallback(this);
    }

    @Override
    public void onOperate() {

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
Use LeakCanary tool to detect

Add dependent libraries:

debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.14'
  • 1

When a memory leak occurs, LeakCanary will generate relevant information and automatically dump it:

insert image description here

As can be seen from the above figure: LeakActivity has a memory leak and shows the reference chain relationship.

Of course, you can also generate hprof files and view specific information through the Profiler tool:

insert image description here

As shown in the figure above, there are 10 leaks, including LeakActivity. Click LeakActivity to view the object of the memory leak and check the reference chain, which shows that it is held by ArrayList.

Optimization Tips
  • Recycle collection elements in a timely manner.
  • Avoid static references to too many instances.
  • Use static inner classes.
  • Close resource objects in a timely manner.

Bitmap Optimization

If you do not release the image resources after using the Bitmap, it is easy to causeMemory leaks, leading to memory overflow

Bitmap memory model
  • Before API 10 (Android 2.3.3): Bitmap objects are placed in heap memory and pixel data is placed in local memory.
  • After API 10: all in heap memory.
  • After API 26 (Android 8.0): Pixel data is stored in local memory, so that the Bitmap pixel data of the native layer can be quickly released together with the objects of the Java layer.

Memory Recycling:

  • Before Android 3.0, you need to callBitmap.recycle()Recycle the Bitmap.
  • Starting from Android 3.0, the system provides smarter memory management, and in most cases there is no need to manually recycle Bitmap.

Bitmap pixel configuration:

ConfigSize in bytes (byte)illustrate
ALPHA_81Single transparent channel
RGB_5652Easy RGB color tone
ARGB_8888424-bit true color
RGBA_F168New in Android 8.0 (HDR)

Calculate the memory occupied by Btimap:

  • Bitmap#getByteCount()
  • getWidth() * getHeight() * 1 pixel occupies memory
Resource file directory

Resource file problem:

  • mdpi (medium density): approximately 160dpi, 1x resource.
  • hdpi (high density): approximately 240dpi, 1.5x resources.
  • xhdpi (extra high density): approximately 320dpi, 2x resources.
  • xxhdpi (extra-high density): approximately 480dpi, 3x resources.
  • xxxhdpi (extremely ultra-high density): approximately 640dpi, 4x resources.

Test code:

private void printBitmap(Bitmap bitmap, String drawable) {
    String builder = drawable +
            " Bitmap占用内存:" +
            bitmap.getByteCount() +
            " width:" +
            bitmap.getWidth() +
            " height:" +
            bitmap.getHeight() +
            " 1像素占用大小:" +
            getByteBy1px(bitmap.getConfig());
    Log.e("TAG", builder);
}

private int getByteBy1px(Bitmap.Config config) {
    if (Bitmap.Config.ALPHA_8.equals(config)) {
        return 1;
    } else if (Bitmap.Config.RGB_565.equals(config)) {
        return 2;
    } else if (Bitmap.Config.ARGB_8888.equals(config)) {
        return 4;
    }
    return 1;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
// 逻辑密度
float density = metrics.density;
// 物理密度
int densityDpi = metrics.densityDpi;
Log.e("TAG", density + "-" + densityDpi);

// 1倍图
Bitmap bitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.splash1);
printBitmap(bitmap1, "drawable-mdpi");

// 2倍图
Bitmap bitmap2 = BitmapFactory.decodeResource(getResources(), R.drawable.splash2);
printBitmap(bitmap2, "drawable-xhdpi");

// 3倍图
Bitmap bitmap3 = BitmapFactory.decodeResource(getResources(), R.drawable.splash3);
printBitmap(bitmap3, "drawable-xxhdpi");

// 4倍图
Bitmap bitmap4 = BitmapFactory.decodeResource(getResources(), R.drawable.splash4);
printBitmap(bitmap4, "drawable-xxxhdpi");

// drawable
Bitmap bitmap5 = BitmapFactory.decodeResource(getResources(), R.drawable.splash);
printBitmap(bitmap5, "drawable");
/*
        3.0-480
        drawable-mdpi Bitmap占用内存:37127376 width:2574 height:3606 1像素占用大小:4
        drawable-xhdpi Bitmap占用内存:9281844 width:1287 height:1803 1像素占用大小:4
        drawable-xxhdpi Bitmap占用内存:4125264 width:858 height:1202 1像素占用大小:4
        drawable-xxxhdpi Bitmap占用内存:2323552 width:644 height:902 1像素占用大小:4
        drawable Bitmap占用内存:37127376 width:2574 height:3606 1像素占用大小:4
         */
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

illustrate:

1dp on mdpi devices1px, 1dp on xhdpi devices2px, on xxhdpi devices 1dp==3px.

Therefore, the current device is xxhdpi, so the same picture is 858 wide in xxhdpi resources, 3 times wider in mdpi resources, and 1.5 times wider in xhdpi resources, which is 1287.

Optimization Tips
  • Set up multiple sets of image resources.
  • Select the appropriate decoding method.
  • Set up image caching.

Source code download