安卓内存优化是一个很重要的话题,有很多方面可以考虑,比如避免内存泄漏、减少内存抖动、优化图片加载、使用缓存和对象池等。下面我举一些代码案例,分别展示不合适的写法和高性能的写法。
欢迎评论区留言指正和补充。
11. 避免使用 AsyncTask
来执行异步任务。
AsyncTask
的内部实现是使用一个线程池和一个消息队列来管理任务,这会占用内存空间,并可能导致内存泄漏和并发问题。如果需要执行异步任务,可以使用 RxJava
或者 Coroutine
等库来代替。例如:
// 不合适的写法
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
}
}
}
这样做可以避免不必要的内存分配和回收,避免内存泄漏和并发问题,并提高异步任务的管理和调度。
12. 避免使用 BitmapFactory
来加载图片。
BitmapFactory
的内部实现是使用 nativeDecodeStream
方法来解码图片,这会消耗较多的内存空间,并可能导致OOM。如果需要加载图片,可以使用 Glide
或者 Picasso
等库来代替。例如:
// 不合适的写法
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
这样做可以避免不必要的位图对象的创建,节省内存空间,并提高图片加载的效率和质量。
13. 避免使用 Serializable
接口来实现序列化。
Serializable
接口的内部实现是使用反射机制来序列化和反序列化对象,这会消耗较多的CPU和内存资源,并可能导致性能下降。如果需要实现序列化,可以使用 Parcelable
接口或者 ProtoBuf
等库来代替。例如:
// 不合适的写法
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];
}
};
}
这样做可以避免不必要的反射操作,节省CPU和内存资源,并提高序列化和反序列化的效率。
14. 避免使用 LinkedList
来存储数据。
LinkedList
的内部实现是使用一个双向链表来存储数据,这会占用较多的内存空间,并且可能导致内存碎片。如果需要存储数据,可以使用 ArrayList
或者 ArrayDeque
来代替。例如:
// 不合适的写法
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");
这样做可以节省内存空间,因为 ArrayList
和 ArrayDeque
的内部实现是使用一个数组来存储数据,没有额外的开销。另外,它们还可以提供更快的随机访问和迭代的性能。
15. 避免使用 StringTokenizer
来分割字符串。
StringTokenizer
的内部实现是使用一个字符数组来存储字符串,并且每次调用 nextToken()
方法都会创建一个新的字符串对象,这会消耗较多的CPU和内存资源,并可能导致GC。如果需要分割字符串,可以使用 split()
方法或者 Scanner
类来代替。例如:
// 不合适的写法
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
}
这样做可以避免不必要的字符串对象的创建,节省CPU和内存资源,并提高字符串分割的效率。
16. 避免使用 SimpleDateFormat
来格式化日期和时间。
SimpleDateFormat
的内部实现是使用一个 Calendar
对象来存储日期和时间,并且每次调用 format()
方法都会创建一个新的 Date
对象,这会消耗较多的CPU和内存资源,并可能导致GC。如果需要格式化日期和时间,可以使用 DateTimeFormatter
或者 FastDateFormat
等库来代替。例如:
// 不合适的写法
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); // 这不会创建任何新的对象
这样做可以避免不必要的日期对象的创建,节省CPU和内存资源,并提高日期和时间格式化的效率。
17. 避免使用 SparseIntArray
来存储稀疏矩阵。
SparseIntArray
的内部实现是使用两个数组来存储键和值,这会占用较多的内存空间,并且可能导致数组扩容和复制。如果需要存储稀疏矩阵,可以使用 SparseMatrix
或者 EJML
等库来代替。例如:
// 不合适的写法
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);
这样做可以节省内存空间,因为 SparseMatrix
和 EJML
的内部实现是使用一个链表或者一个哈希表来存储非零元素,没有额外的开销。另外,它们还可以提供更快的矩阵运算和转置的性能。
18. 避免使用 JSONObject
来解析JSON字符串。
JSONObject
的内部实现是使用一个 HashMap
来存储键值对,这会占用较多的内存空间,并且可能导致哈希冲突和扩容。如果需要解析JSON字符串,可以使用 Gson
或者 Moshi
等库来代替。例如:
// 不合适的写法
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();
这样做可以避免不必要的哈希表对象的创建,节省内存空间,并提高JSON解析的效率和质量。
19. 避免使用 Random
类来生成随机数。
Random
类的内部实现是使用一个线性同余发生器来生成随机数,这会导致随机数的质量不高,并且可能导致并发问题。如果需要生成随机数,可以使用 ThreadLocalRandom
或者 SecureRandom
等类来代替。例如:
// 不合适的写法
Random random = new Random();
int n = random.nextInt(10); // 这会生成一个不太随机的整数,并且可能导致并发问题
// 高性能的写法
int n = ThreadLocalRandom.current().nextInt(10); // 这会生成一个更随机的整数,并且避免并发问题
这样做可以提高随机数的质量和安全性,并避免并发问题。
20. 避免使用 Log
类来打印日志。
Log
类的内部实现是使用一个 PrintStream
对象来输出日志到控制台或者文件,这会消耗较多的CPU和内存资源,并且可能导致IO阻塞和性能下降。如果需要打印日志,可以使用 Timber
或者 Logger
等库来代替。例如:
// 不合适的写法
Log.d("TAG", "Hello World!"); // 这会输出一条日志到控制台或者文件,消耗CPU和内存资源,并且可能导致IO阻塞和性能下降
// 高性能的写法
Timber.d("Hello World!"); // 这会输出一条日志到控制台或者文件,节省CPU和内存资源,并提高日志输出的效率和质量
这样做可以避免不必要的IO操作,节省CPU和内存资源,并提高日志输出的效率和质量。