安卓?jī)?nèi)存優(yōu)化是一個(gè)很重要的話題艳馒,有很多方面可以考慮诊笤,比如避免內(nèi)存泄漏兵迅、減少內(nèi)存抖動(dòng)幔虏、優(yōu)化圖片加載媚赖、使用緩存和對(duì)象池等矫限。下面我舉一些代碼案例催式,分別展示不合適的寫法和高性能的寫法徽曲。
歡迎評(píng)論區(qū)留言指正和補(bǔ)充临庇。
1. 避免使用枚舉類型反璃。
枚舉類型會(huì)占用更多的內(nèi)存昵慌,因?yàn)樗且粋€(gè)類對(duì)象,而不是一個(gè)基本類型淮蜈。如果需要定義一些常量斋攀,可以使用 static final int
或者 @IntDef
注解來代替。例如:
// 不合適的寫法
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 {}
這樣做可以節(jié)省內(nèi)存空間梧田,因?yàn)槊杜e類型會(huì)占用至少4個(gè)字節(jié)淳蔼,而 int
類型只占用4個(gè)字節(jié)。另外裁眯,使用注解可以保證類型安全和編譯時(shí)檢查鹉梨。
測(cè)試枚舉、數(shù)組和整型數(shù)本身占用內(nèi)存的大小
//測(cè)試代碼
public int type_int_a = 16;
public int type_int_b = 20;
public int[] type_int_array_a = {1,2,2};
public enum TypeEnumD{
first,
second,
third,
}
public TypeEnumD f = TypeEnumD.first;
public TypeEnumD s= TypeEnumD.second;
public TypeEnumD t = TypeEnumD.third;
測(cè)試結(jié)果
2. 避免在循環(huán)中創(chuàng)建對(duì)象俯画。
這會(huì)導(dǎo)致內(nèi)存抖動(dòng)和頻繁的GC,影響性能和用戶體驗(yàn)司草。如果需要在循環(huán)中使用對(duì)象艰垂,可以在循環(huán)外創(chuàng)建并復(fù)用,或者使用對(duì)象池來管理對(duì)象的生命周期埋虹。例如:
// 不合適的寫法
for (int i = 0; i < 100; i++) {
String s = new String("Hello"); // 每次循環(huán)都會(huì)創(chuàng)建一個(gè)新的字符串對(duì)象
// do something with s
}
// 高性能的寫法
String s = new String("Hello"); // 在循環(huán)外創(chuàng)建一個(gè)字符串對(duì)象
for (int i = 0; i < 100; i++) {
// do something with s
}
這樣做可以減少內(nèi)存分配和回收的次數(shù)猜憎,提高性能。如果對(duì)象的創(chuàng)建和銷毀成本較高搔课,可以考慮使用對(duì)象池來緩存和復(fù)用對(duì)象胰柑,例如 BitmapPool
3. 避免使用 String
連接符 +
來拼接字符串。
這會(huì)產(chǎn)生很多臨時(shí)的字符串對(duì)象爬泥,占用內(nèi)存空間柬讨,并觸發(fā)GC。如果需要拼接字符串袍啡,可以使用 StringBuilder
或者 StringBuffer
來代替踩官。例如:
// 不合適的寫法
String s = "Hello" + "World" + "!" // 這會(huì)創(chuàng)建三個(gè)字符串對(duì)象
// 高性能的寫法
StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append("World");
sb.append("!");
String s = sb.toString(); // 這只會(huì)創(chuàng)建一個(gè)字符串對(duì)象
這樣做可以避免不必要的字符串對(duì)象的創(chuàng)建,節(jié)省內(nèi)存空間境输,并提高字符串拼接的效率蔗牡。
4. 避免使用 System.gc()
來主動(dòng)觸發(fā)GC。
這會(huì)影響系統(tǒng)的自動(dòng)內(nèi)存管理機(jī)制嗅剖,并可能導(dǎo)致應(yīng)用卡頓或者OOM辩越。如果需要釋放內(nèi)存,可以通過合理地設(shè)計(jì)數(shù)據(jù)結(jié)構(gòu)和算法來減少內(nèi)存占用信粮,并及時(shí)釋放不再使用的對(duì)象的引用黔攒。例如:
// 不合適的寫法
System.gc(); // 強(qiáng)制調(diào)用GC
// 高性能的寫法
list.clear(); // 清空列表中的元素,并釋放引用
list = null; // 將列表對(duì)象置為null,讓GC自動(dòng)回收
這樣做可以讓系統(tǒng)根據(jù)內(nèi)存情況自動(dòng)調(diào)整GC策略亏钩,并避免不必要的GC開銷莲绰。
5. 避免在 onDraw()
方法中創(chuàng)建對(duì)象。
這會(huì)導(dǎo)致每次繪制都會(huì)分配內(nèi)存姑丑,造成內(nèi)存抖動(dòng)和GC蛤签。如果需要在 onDraw()
方法中使用對(duì)象,可以在構(gòu)造方法或者 onSizeChanged()
方法中創(chuàng)建并復(fù)用栅哀,或者使用靜態(tài)常量來代替震肮。例如:
// 不合適的寫法
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint(); // 每次繪制都會(huì)創(chuàng)建一個(gè)畫筆對(duì)象
paint.setColor(Color.RED);
canvas.drawCircle(getWidth() / 2, getHeight() / 2, 100, paint);
}
// 高性能的寫法
private Paint paint; // 在類中聲明一個(gè)畫筆對(duì)象
public MyView(Context context) {
super(context);
paint = new Paint(); // 在構(gòu)造方法中創(chuàng)建畫筆對(duì)象,并設(shè)置顏色
paint.setColor(Color.RED);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(getWidth() / 2, getHeight() / 2, 100, paint); // 復(fù)用畫筆對(duì)象
}
6. 避免使用 HashMap
來存儲(chǔ)少量的鍵值對(duì)留拾。
HashMap
的內(nèi)部實(shí)現(xiàn)需要維護(hù)一個(gè)數(shù)組和一個(gè)鏈表戳晌,會(huì)占用較多的內(nèi)存空間,并且可能導(dǎo)致內(nèi)存碎片痴柔。如果只需要存儲(chǔ)少量的鍵值對(duì)沦偎,可以使用 ArrayMap
或者 SparseArray
來代替。例如:
// 不合適的寫法
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);
這樣做可以節(jié)省內(nèi)存空間咳蔚,因?yàn)?ArrayMap
和 SparseArray
的內(nèi)部實(shí)現(xiàn)是使用兩個(gè)數(shù)組來存儲(chǔ)鍵和值豪嚎,沒有額外的開銷。另外谈火,它們還可以避免 HashMap
的擴(kuò)容和哈希沖突的問題侈询。
7. 避免使用 setXxx()
方法來設(shè)置視圖的屬性。
這會(huì)導(dǎo)致視圖的重新布局和重繪糯耍,消耗CPU和內(nèi)存資源扔字,并可能導(dǎo)致卡頓。如果需要?jiǎng)討B(tài)改變視圖的屬性温技,可以使用屬性動(dòng)畫來實(shí)現(xiàn)革为。例如:
// 不合適的寫法
view.setAlpha(0.5f); // 設(shè)置視圖的透明度,會(huì)觸發(fā)視圖的重繪
// 高性能的寫法
ObjectAnimator.ofFloat(view, "alpha", 0.5f).start(); // 使用屬性動(dòng)畫來設(shè)置視圖的透明度舵鳞,不會(huì)觸發(fā)視圖的重繪
這樣做可以避免不必要的視圖更新篷角,提高動(dòng)畫效果和流暢度。
8. 避免在 onCreate()
方法中初始化不必要的對(duì)象系任。
這會(huì)導(dǎo)致應(yīng)用啟動(dòng)時(shí)間變長(zhǎng),影響用戶體驗(yàn)虐块,并可能導(dǎo)致ANR俩滥。如果有些對(duì)象不需要在啟動(dòng)時(shí)就初始化,可以延遲到使用時(shí)再初始化贺奠,或者放到子線程中初始化霜旧。例如:
// 不合適的寫法
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
OkHttpClient client = new OkHttpClient(); // 在啟動(dòng)時(shí)就創(chuàng)建一個(gè)網(wǎng)絡(luò)客戶端對(duì)象,占用內(nèi)存空間,并可能影響啟動(dòng)速度
}
// 高性能的寫法
private OkHttpClient client; // 在類中聲明一個(gè)網(wǎng)絡(luò)客戶端對(duì)象
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
private OkHttpClient getClient() {
if (client == null) {
client = new OkHttpClient(); // 在需要使用時(shí)才創(chuàng)建網(wǎng)絡(luò)客戶端對(duì)象挂据,節(jié)省內(nèi)存空間以清,并提高啟動(dòng)速度
9. 避免使用 findViewById()
方法來查找視圖。
這會(huì)導(dǎo)致每次查找都會(huì)遍歷視圖樹崎逃,消耗CPU和內(nèi)存資源掷倔,并可能導(dǎo)致卡頓。如果需要使用視圖个绍,可以在 onCreate()
方法中使用 findViewById()
方法來獲取并保存到變量中勒葱,或者使用 ViewBinding
或者 ButterKnife
等庫來自動(dòng)綁定視圖。例如:
// 不合適的寫法
@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); // 每次調(diào)用都會(huì)查找視圖樹巴柿,影響性能
textView.setText("Hello World");
}
// 高性能的寫法
private TextView textView; // 在類中聲明一個(gè)視圖變量
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.text_view); // 在啟動(dòng)時(shí)就獲取并保存視圖對(duì)象凛虽,避免重復(fù)查找
}
@Override
protected void onResume() {
super.onResume();
textView.setText("Hello World"); // 復(fù)用視圖對(duì)象
}
這樣做可以避免不必要的視圖查找,提高性能和流暢度广恢。
10. 避免使用 VectorDrawable
來顯示矢量圖形凯旋。
VectorDrawable
的內(nèi)部實(shí)現(xiàn)是使用 Path
來繪制矢量圖形,這會(huì)消耗較多的CPU和內(nèi)存資源钉迷,并可能導(dǎo)致卡頓至非。如果需要顯示矢量圖形,可以使用 SVG
或者 WebP
等格式來代替篷牌。例如:
// 不合適的寫法
<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格式的圖片來顯示矢量圖形睡蟋,節(jié)省CPU和內(nèi)存資源,并提高繪制效率
這樣做可以避免不必要的矢量圖形繪制枷颊,提高性能和流暢度戳杀。