An exhaustive list of Android memory optimization cases

Android memory optimization is a very important topic, and there are many aspects to consider, such as avoiding memory leaks, reducing memory jitter, optimizing image loading, using cache and object pools, etc. Below I will give some code examples to show inappropriate writing methods and high-performance writing methods.
Welcome to leave corrections and additions in the comment area.

1. Avoid using enumeration types.

The enumeration type takes up more memory because it is a class object rather than a primitive type. If you need to define some constants, you can use static final int or @IntDef annotation instead. For example:

// 不合适的写法
public enum Color {
    RED, GREEN, BLUE
}

// 高性能的写法
public static final int RED = 0;
public static final int GREEN = 1;
public static final int BLUE = 2;

@IntDef({RED, GREEN, BLUE})
@Retention(RetentionPolicy.SOURCE)
public @interface Color {}

Doing this can save memory space, because the enumeration type will occupy at least 4 bytes, while the int type only occupies 2 bytes. In addition, using annotations can ensure type safety and compile-time checking.

2. Avoid creating objects in loops.

This will cause memory thrashing and frequent GC, affecting performance and user experience. If you need to use objects in a loop, you can create and reuse them outside the loop, or use an object pool to manage the object's life cycle. For example:

// 不合适的写法
for (int i = 0; i < 100; i++) {
    String s = new String("Hello"); // 每次循环都会创建一个新的字符串对象
    // do something with s
}

// 高性能的写法
String s = new String("Hello"); // 在循环外创建一个字符串对象
for (int i = 0; i < 100; i++) {
    // do something with s
}

Doing so can reduce the number of memory allocation and recycling times and improve performance. If the cost of object creation and destruction is high, you can consider using an object pool to cache and reuse objects, such as BitmapPool

3. Avoid using the String concatenator + to concatenate strings.

This will generate a lot of temporary string objects, occupy memory space, and trigger GC. If you need to concatenate strings, you can use StringBuilder or StringBuffer instead. For example:

// 不合适的写法
String s = "Hello" + "World" + "!" // 这会创建三个字符串对象

// 高性能的写法
StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append("World");
sb.append("!");
String s = sb.toString(); // 这只会创建一个字符串对象

Doing so can avoid unnecessary creation of string objects, save memory space, and improve the efficiency of string concatenation.

4. Avoid using System.gc() to actively trigger GC.

This will affect the system's automatic memory management mechanism and may cause application lag or OOM. If you need to release memory, you can reduce memory usage by properly designing data structures and algorithms, and promptly release references to objects that are no longer used. For example:

// 不合适的写法
System.gc(); // 强制调用GC

// 高性能的写法
list.clear(); // 清空列表中的元素,并释放引用
list = null; // 将列表对象置为null,让GC自动回收

Doing so allows the system to automatically adjust the GC strategy based on memory conditions and avoid unnecessary GC overhead.

5. Avoid creating objects in onDraw() method.

This will cause memory to be allocated every time you draw, causing memory thrashing and GC. If you need to use an object in the onDraw() method, you can create and reuse it in the constructor or onSizeChanged() method, or use static constants instead. For example:

// 不合适的写法
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    Paint paint = new Paint(); // 每次绘制都会创建一个画笔对象
    paint.setColor(Color.RED);
    canvas.drawCircle(getWidth() / 2, getHeight() / 2, 100, paint);
}

// 高性能的写法
private Paint paint; // 在类中声明一个画笔对象

public MyView(Context context) {
    super(context);
    paint = new Paint(); // 在构造方法中创建画笔对象,并设置颜色
    paint.setColor(Color.RED);
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawCircle(getWidth() / 2, getHeight() / 2, 100, paint); // 复用画笔对象
}

This can avoid frequent creation and recycling of objects during the drawing process and improve drawing efficiency and smoothness.

6. Avoid using HashMap to store a small number of key-value pairs.

The internal implementation of HashMap requires maintaining an array and a linked list, which takes up more memory space and may cause memory fragmentation. If you only need to store a small number of key-value pairs, you can use ArrayMap or SparseArray instead. For example:

// 不合适的写法
HashMap<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);
map.put("c", 3);

// 高性能的写法
ArrayMap<String, Integer> map = new ArrayMap<>();
map.put("a", 1);
map.put("b", 2);
map.put("c", 3);

Doing this saves memory space because the internal implementation of ArrayMap and SparseArray uses two arrays to store keys and values, with no additional overhead. In addition, they can avoid HashMap expansion and hash collision problems.

7. Avoid using the setXxx() method to set the properties of the view.

This will cause the view to be re-layout and re-drawn, consuming CPU and memory resources, and may cause lag. If you need to dynamically change the properties of a view, you can use property animation to achieve this. For example:

// 不合适的写法
view.setAlpha(0.5f); // 设置视图的透明度,会触发视图的重绘

// 高性能的写法
ObjectAnimator.ofFloat(view, "alpha", 0.5f).start(); // 使用属性动画来设置视图的透明度,不会触发视图的重绘

Doing this can avoid unnecessary view updates and improve animation effects and smoothness.

8. Avoid initializing unnecessary objects in the onCreate() method.

This will cause the application to take longer to start, affect the user experience, and may cause ANR. If some objects do not need to be initialized at startup, they can be delayed until use or initialized in a child thread. For example:

// 不合适的写法
@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);

   OkHttpClient client = new OkHttpClient(); // 在启动时就创建一个网络客户端对象,占用内存空间,并可能影响启动速度
}

// 高性能的写法
private OkHttpClient client; // 在类中声明一个网络客户端对象

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

private OkHttpClient getClient() {
   if (client == null) {
       client = new OkHttpClient(); // 在需要使用时才创建网络客户端对象,节省内存空间,并提高启动速度

9. Avoid using the findViewById() method to find views.

This will cause each lookup to traverse the view tree, consuming CPU and memory resources, and may cause lags. If you need to use a view, you can use the findViewById() method in the onCreate() method to obtain and save it to a variable, or use a library such as ViewBinding or ButterKnife to automatically bind the view. For example:

// 不合适的写法
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
}

@Override
protected void onResume() {
    super.onResume();
    TextView textView = findViewById(R.id.text_view); // 每次调用都会查找视图树,影响性能
    textView.setText("Hello World");
}

// 高性能的写法
private TextView textView; // 在类中声明一个视图变量

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    textView = findViewById(R.id.text_view); // 在启动时就获取并保存视图对象,避免重复查找
}

@Override
protected void onResume() {
    super.onResume();
    textView.setText("Hello World"); // 复用视图对象
}

Doing so avoids unnecessary view lookups and improves performance and smoothness.
###10. Avoid using VectorDrawable to display vector graphics.
The internal implementation of VectorDrawable uses Path to draw vector graphics, which consumes more CPU and memory resources and may cause lagging. If you need to display vector graphics, you can use formats such as SVG or WebP instead. For example:

// 不合适的写法
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24.0"
    android:viewportHeight="24.0">
    <path
        android:fillColor="#FF000000"
        android:pathData="M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2zM12,4c4.41,0 8,3.59 8,8s-3.59,8 -8,8 -8,-3.59 -8,-8 3.59,-8 8,-8zM6.5,9L10,12.5l-3.5,3.5L8,16l5,-5 -5,-5L6.5,9zM14,13h4v-2h-4v2z" />
</vector>

// 高性能的写法
<ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@drawable/ic_arrow_forward_24px.webp" /> // 使用WebP格式的图片来显示矢量图形,节省CPU和内存资源,并提高绘制效率

Doing so can avoid unnecessary drawing of vector graphics and improve performance and smoothness.

11. Avoid using AsyncTask to perform asynchronous tasks.

The internal implementation of AsyncTask uses a thread pool and a message queue to manage tasks, which takes up memory space and may cause memory leaks and concurrency issues. If you need to perform asynchronous tasks, you can use libraries such as RxJava or Coroutine instead. For example:

// 不合适的写法
private class MyTask extends AsyncTask<Void, Void, String> {

    private WeakReference<Context> contextRef;

    public MyTask(Context context) {
        contextRef = new WeakReference<>(context);
    }

    @Override
    protected String doInBackground(Void... params) {
        // do some background work
        return "result";
    }

    @Override
    protected void onPostExecute(String result) {
        Context context = contextRef.get();
        if (context != null) {
            // do something with result and context
        }
    }
}

// 高性能的写法
private fun doAsyncTask(context: Context) {
    CoroutineScope(Dispatchers.IO).launch {
        // do some background work
        val result = "result"
        withContext(Dispatchers.Main) {
            // do something with result and context
        }
    }
}

Doing so avoids unnecessary memory allocation and deallocation, avoids memory leaks and concurrency issues, and improves the management and scheduling of asynchronous tasks.

###12. Avoid using BitmapFactory to load images.
The internal implementation of BitmapFactory uses the nativeDecodeStream method to decode images, which consumes more memory space and may cause OOM. If you need to load images, you can use libraries such as Glide or Picasso instead. For example:

// 不合适的写法
ImageView imageView = findViewById(R.id.image_view);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image); // 这会创建一个原始大小的位图对象,占用内存空间,并可能导致OOM
imageView.setImageBitmap(bitmap);

// 高性能的写法
ImageView imageView = findViewById(R.id.image_view);
Glide.with(this).load(R.drawable.image).into(imageView); // 这会根据视图的大小和屏幕密度来加载合适大小的位图对象,节省内存空间,并避免OOM

Doing so can avoid the creation of unnecessary bitmap objects, save memory space, and improve the efficiency and quality of image loading.

13. Avoid using the Serializable interface to implement serialization.

The internal implementation of the Serializable interface uses the reflection mechanism to serialize and deserialize objects, which consumes more CPU and memory resources and may cause performance degradation. If you need to implement serialization, you can use Parcelable interface or libraries such as ProtoBuf instead. For example:

// 不合适的写法
public class User implements Serializable {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // getter and setter methods
}

// 高性能的写法
public class User implements Parcelable {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // getter and setter methods

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeInt(age);
    }

    public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>() {

        @Override
        public User createFromParcel(Parcel source) {
            return new User(source.readString(), source.readInt());
        }

        @Override
        public User[] newArray(int size) {
            return new User[size];
        }
    };
}

Doing so can avoid unnecessary reflection operations, save CPU and memory resources, and improve the efficiency of serialization and deserialization.

14. Avoid using LinkedList to store data.

The internal implementation of LinkedList uses a doubly linked list to store data, which takes up more memory space and may cause memory fragmentation. If you need to store data, you can use ArrayList or ArrayDeque instead. For example:

// 不合适的写法
LinkedList<String> list = new LinkedList<>();
list.add("a");
list.add("b");
list.add("c");

// 高性能的写法
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");

Doing this saves memory space because the internal implementation of ArrayList and ArrayDeque uses an array to store data, with no additional overhead. Additionally, they provide faster random access and iteration performance.

15. Avoid using StringTokenizer to split strings.

The internal implementation of StringTokenizer uses a character array to store strings, and each time the nextToken() method is called, a new string object is created, which consumes more CPU and memory resources and may cause GC. If you need to split the string, you can use the split() method or the Scanner class instead. For example:

// 不合适的写法
StringTokenizer st = new StringTokenizer("Hello World!");
while (st.hasMoreTokens()) {
    String token = st.nextToken(); // 每次调用都会创建一个新的字符串对象
    // do something with token
}

// 高性能的写法
String[] tokens = "Hello World!".split(" "); // 这只会创建一个字符串数组对象
for (String token : tokens) {
    // do something with token
}

Doing so can avoid unnecessary creation of string objects, save CPU and memory resources, and improve the efficiency of string splitting.

16. Avoid using SimpleDateFormat to format dates and times.

The internal implementation of SimpleDateFormat uses a Calendar object to store date and time, and each time the format() method is called, a new Date object is created, which consumes more CPU and memory resources and may cause GC. If you need to format dates and times, you can use libraries such as DateTimeFormatter or FastDateFormat instead. For example:

// 不合适的写法
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date();
String formattedDate = sdf.format(date); // 每次调用都会创建一个新的日期对象

// 高性能的写法
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime date = LocalDateTime.now();
String formattedDate = dtf.format(date); // 这不会创建任何新的对象

Doing so avoids the creation of unnecessary date objects, saves CPU and memory resources, and improves the efficiency of date and time formatting.

17. Avoid using SparseIntArray to store sparse matrices.

The internal implementation of SparseIntArray uses two arrays to store keys and values, which takes up more memory space and may lead to array expansion and copying. If you need to store sparse matrices, you can use libraries such as SparseMatrix or EJML instead. For example:

// 不合适的写法
SparseIntArray matrix = new SparseIntArray();
matrix.put(0, 1);
matrix.put(1, 2);
matrix.put(2, 3);

// 高性能的写法
SparseMatrix matrix = new SparseMatrix(3, 3);
matrix.set(0, 0, 1);
matrix.set(1, 1, 2);
matrix.set(2, 2, 3);

Doing so can save memory space, because the internal implementation of SparseMatrix and EJML uses a linked list or a hash table to store non-zero elements, with no additional overhead. Additionally, they provide faster performance for matrix operations and transposes.

18. Avoid using JSONObject to parse JSON strings.

The internal implementation of JSONObject uses a HashMap to store key-value pairs, which takes up more memory space and may cause hash conflicts and expansion. If you need to parse JSON strings, you can use libraries such as Gson or Moshi instead. For example:

// 不合适的写法
String json = "{\"name\":\"Alice\",\"age\":18}";
JSONObject jsonObject = new JSONObject(json); // 这会创建一个哈希表对象,占用内存空间,并可能导致哈希冲突和扩容
String name = jsonObject.getString("name");
int age = jsonObject.getInt("age");

// 高性能的写法
String json = "{\"name\":\"Alice\",\"age\":18}";
Gson gson = new Gson();
User user = gson.fromJson(json, User.class); // 这会直接创建一个用户对象,节省内存空间,并提高JSON解析的效率
String name = user.getName();
int age = user.getAge();

Doing so can avoid unnecessary creation of hash table objects, save memory space, and improve the efficiency and quality of JSON parsing.

19. Avoid using Random class to generate random numbers.

The internal implementation of the Random class uses a linear congruential generator to generate random numbers, which results in low quality random numbers and can lead to concurrency issues. If you need to generate random numbers, you can use classes such as ThreadLocalRandom or SecureRandom instead. For example:

// 不合适的写法
Random random = new Random();
int n = random.nextInt(10); // 这会生成一个不太随机的整数,并且可能导致并发问题

// 高性能的写法
int n = ThreadLocalRandom.current().nextInt(10); // 这会生成一个更随机的整数,并且避免并发问题

Doing so improves the quality and safety of the random numbers and avoids concurrency issues.

20. Avoid using Log class to print logs.

The internal implementation of the Log class uses a PrintStream object to output logs to the console or file, which consumes more CPU and memory resources and may cause IO blocking and performance degradation. If you need to print logs, you can use libraries such as Timber or Logger instead. For example:

// 不合适的写法
Log.d("TAG", "Hello World!"); // 这会输出一条日志到控制台或者文件,消耗CPU和内存资源,并且可能导致IO阻塞和性能下降

// 高性能的写法
Timber.d("Hello World!"); // 这会输出一条日志到控制台或者文件,节省CPU和内存资源,并提高日志输出的效率和质量

Doing so can avoid unnecessary IO operations, save CPU and memory resources, and improve the efficiency and quality of log output.

Use static inner classes or weak references to avoid non-static inner classes holding references to external classes, causing memory leaks.

// 不合适的写法
public class MainActivity extends AppCompatActivity {

    private MyTask task;

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

        task = new MyTask();
        task.execute();
    }

    private class MyTask extends AsyncTask<Void, Void, Void> {
        // 这是一个非静态内部类,它会隐式地持有外部类的引用
        @Override
        protected Void doInBackground(Void... params) {
            // do some background work
            return null;
        }
    }
}

// 高性能的写法
public class MainActivity extends AppCompatActivity {

    private MyTask task;

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

        task = new MyTask(this);
        task.execute();
    }

    private static class MyTask extends AsyncTask<Void, Void, Void> {
        // 这是一个静态内部类,它不会持有外部类的引用
        private WeakReference<MainActivity> activityRef;

        public MyTask(MainActivity activity) {
            activityRef = new WeakReference<>(activity);
        }

        @Override
        protected Void doInBackground(Void... params) {
            // do some background work
            return null;
        }
    }
}

Doing this can avoid memory leaks, because if MainActivity is destroyed while MyTask is still running in the background, non-static inner classes will cause MainActivity to not be recycled, but static inner classes or weak references will not. This saves memory space and improves performance.

When using the singleton mode, be careful to use the Application's Context instead of the Activity's Context to prevent the Activity from being recycled.

// 不合适的写法
public class MySingleton {

    private static MySingleton instance;
    private Context context;

    private MySingleton(Context context) {
        this.context = context;
    }

    public static MySingleton getInstance(Context context) {
        if (instance == null) {
            instance = new MySingleton(context); // 这里使用了Activity的Context,会导致Activity无法被回收
        }
        return instance;
    }
}

// 高性能的写法
public class MySingleton {

    private static MySingleton instance;
    private Context context;

    private MySingleton(Context context) {
        this.context = context.getApplicationContext(); // 这里使用了Application的Context,不会导致Activity无法被回收
    }

    public static MySingleton getInstance(Context context) {
        if (instance == null) {
            instance = new MySingleton(context);
        }
        return instance;
    }
}

Doing this can avoid memory leaks, because if the Activity is destroyed while MySingleton is still using its Context, the Activity cannot be recycled, but the Application's Context will not. This saves memory space and improves performance.

Use tools such as Proguard or R8 to obfuscate and compress code, reducing the number of methods and bytecode size. For example:

android {
  buildTypes {
    release {
      minifyEnabled true // 这里开启了代码混淆和压缩
      proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
      shrinkResources true // 这里开启了资源文件压缩
    }
  }
}

Doing so can reduce the size of the APK and improve the security and operating efficiency of the application.

Use the Lint tool to detect and remove useless resource files to reduce the size of your APK. For example:

android {
  lintOptions {
    checkReleaseBuilds true // 这里开启了Lint检查
    abortOnError true // 这里设置了如果发现错误就终止编译
  }
}

Doing so can reduce the size of the APK and improve the operating efficiency and quality of the application.

Use the inBitmap option to reuse the Bitmap memory space and reduce memory allocation. For example:

// 不合适的写法
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image, options); // 这里没有使用inBitmap选项,会导致每次都分配新的内存空间

// 高性能的写法
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false;
options.inBitmap = reusableBitmap; // 这里使用了inBitmap选项,会复用已有的内存空间,reusableBitmap是一个合适大小的位图对象
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image, options);

Doing so can reduce the number of memory allocation and recycling times and improve performance and fluency.

Use the inSampleSize option to scale the image proportionally to avoid loading overly large images. For example:

// 不合适的写法
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image, options); // 这里没有使用inSampleSize选项,会加载原始大小的图片,占用内存空间,并可能导致OOM

// 高性能的写法
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.drawable.image, options); // 这里先获取图片的原始宽高,不加载图片到内存中
int width = options.outWidth;
int height = options.outHeight;
int inSampleSize = 1; // 这里根据需要计算一个合适的缩放比例,例如根据视图的大小和屏幕密度等因素
options.inJustDecodeBounds = false;
options.inSampleSize = inSampleSize; // 这里使用inSampleSize选项,会按比例缩放图片,节省内存空间,并避免OOM
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image, options);

Doing this can avoid loading overly large images, save memory space, and improve image loading efficiency and quality.

Optimize layout files, reduce layout levels and redundant controls, and use include, merge, ViewStub and other tags to reuse and delay loading of layouts. For example:

<!-- 不合适的写法 -->
<LinearLayout android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Title" />

    <LinearLayout android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <ImageView android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/icon" />

        <TextView android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Content" />

    </LinearLayout>

</LinearLayout>

<!-- 高性能的写法 -->
<merge xmlns:android="http://schemas.android.com/apk/res/android">

    <TextView android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Title" />

    <ImageView android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/icon" />

    <TextView android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Content" />

</merge>

This can reduce layout levels and redundant controls, and improve the efficiency and smoothness of layout loading and rendering. If you want to reuse and lazy load layouts, you can use include, merge, ViewStub and other tags to achieve this.

Android study notes

Android performance optimization article: https://qr18.cn/FVlo89
Android Framework underlying principles article: https://qr18.cn/AQpN4J
Android vehicle article: https://qr18.cn/F05ZCM
Android reverse security study notes: https://qr18.cn/CQ5TcL
Android audio and video article: https://qr18.cn/Ei3VPD
Jetpack family bucket article (including Compose): https://qr18.cn/A0gajp
OkHttp source code analysis notes: https://qr18.cn/Cw0pBD
Kotlin article: https://qr18.cn/CdjtAF
Gradle article: https://qr18.cn/DzrmMB
Flutter article: https://qr18.cn/DIvKma
Eight knowledge bodies of Android: https://qr18.cn/CyxarU
Android core notes: https://qr21.cn/CaZQLo
Android interview questions from previous years: https://qr18.cn/CKV8OZ
The latest Android interview questions in 2023: https://qr18.cn/CgxrRy
Android vehicle development position interview exercises: https://qr18.cn/FTlyCJ
Audio and video interview questions:https://qr18.cn/AcV6Ap

Guess you like

Origin blog.csdn.net/weixin_61845324/article/details/132887667