看完這篇文章,解决-APP-中-90-%-的內存异常問題,趕緊學起來

mb612e2e66047a8 2021-09-18 06:42:38 阅读数:949

看完 篇文章 文章 解决 app-
 @Override
protected void onDestroy() {
super.onDestroy();
if(null != handler){
handler.removeCallbacksAndMessages(null);
handler = null;
}
}
```
  1. 靜態變量

    示例:

    public class MainActivity extends AppCompatActivity {
    private static Police sPolice;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    if (sPolice != null) {
    sPolice = new Police(this);
    }
    }
    }
    class Police {
    public Police(Activity activity) {
    }
    }
    
    
    • 1.
    • 2.
    • 3.
    • 4.
    • 5.
    • 6.
    • 7.
    • 8.
    • 9.
    • 10.
    • 11.
    • 12.
    • 13.
    • 14.
    • 15.
    • 16.
    • 17.
    • 18.

這裏 Police 持有 activity 的引用,會造成 activity 得不到釋放,導致內存泄漏。

解决方法:

```
//1\. sPolice 在 onDestory()中 sPolice = null;
//2\. 在 Police 構造函數中 將强引用 to 弱引用;
```
  1. 非靜態內部類

參考 第二點 Handler 的處理方式

  1. 匿名內部類

示例:

public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(){
@Override
public void run() {
super.run();
}
};
}
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

很多初學者都會像上面這樣新建線程和异步任務,殊不知這樣的寫法非常地不友好,這種方式新建的子線程ThreadAsyncTask都是匿名內部類對象,默認就隱式的持有外部Activity的引用,導致Activity內存泄露。

解决方法:

//靜態內部類 + 弱引用
//單獨寫一個文件 + onDestory = null;

  • 1.
  • 2.
  1. 未取消注册或回調

示例:

public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
registerReceiver(mReceiver, new IntentFilter());
}
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// TODO ------
}
};
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

在注册觀察則模式的時候,如果不及時取消也會造成內存泄露。比如使用Retrofit + RxJava注册網絡請求的觀察者回調,同樣作為匿名內部類持有外部引用,所以需要記得在不用或者銷毀的時候取消注册。

解决方法:

//Activity 中實現 onDestory()反注册廣播得到釋放
@Override
protected void onDestroy() {
super.onDestroy();
this.unregisterReceiver(mReceiver);
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  1. 定時任務

示例:

public class MainActivity extends AppCompatActivity {
/**模擬計數*/
private int mCount = 1;
private Timer mTimer;
private TimerTask mTimerTask;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
mTimer.schedule(mTimerTask, 1000, 1000);
}
private void init() {
mTimer = new Timer();
mTimerTask = new TimerTask() {
@Override
public void run() {
MainActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
addCount();
}
});
}
};
}
private void addCount() {
mCount += 1;
}
}

  • 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.
  • 34.

當我們Activity銷毀的時,有可能Timer還在繼續等待執行TimerTask,它持有Activity 的引用不能被 GC 回收,因此當我們 Activity 銷毀的時候要立即cancelTimerTimerTask,以避免發生內存泄漏。

解决方法:

//當 Activity 關閉的時候,停止一切正在進行中的定時任務,避免造成內存泄漏。
private void stopTimer() {
if (mTimer != null) {
mTimer.cancel();
mTimer = null;
}
if (mTimerTask != null) {
mTimerTask.cancel();
mTimerTask = null;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
stopTimer();
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  1. 資源未關閉

示例:

ArrayList,HashMap,IO,File,SqLite,Cursor 等資源用完一定要記得 clear remove 等關閉一系列對資源的操作。

  • 1.

解决方法:

 用完即刻銷毀

  • 1.
  1. 屬性動畫

    示例:

動畫同樣是一個耗時任務,比如在 Activity 中啟動了屬性動畫 (ObjectAnimator) ,但是在銷毀的時候,沒有調用 cancle 方法,雖然我們看不到動畫了,但是這個動畫依然會不斷地播放下去,動畫引用所在的控件,所在的控件引用 Activity ,這就造成 Activity 無法正常釋放。因此同樣要在Activity 銷毀的時候 cancel 掉屬性動畫,避免發生內存泄漏。

解决方法:

 @Override
protected void onDestroy() {
super.onDestroy();
//當關閉 Activity 的時候記得關閉動畫的操作
mAnimator.cancel();
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  1. Android 源碼或者第三方 SDK

示例:

如果在開發調試中遇見 Android 源碼或者 第三方 SDK 持有了我們當前的 Activity 或者其它類,那麼現在怎麼辦了。

解决方法:

當前是通過 Java 中的反射找到某個類或者成員,來進行手動 = null 的操作。

內存抖動

什麼是內存抖動

內存頻繁的分配與回收,(分配速度大於回收速度時) 最終產生 OOM 。

也許下面的錄屏更能解釋什麼是內存抖動

看完這篇文章,解决-APP-中-90-%-的內存异常問題,趕緊學起來_程序員

可以看出當我點擊了一下 Button 內存就頻繁的創建並回收(注意看垃圾桶)。

那麼我們找出代碼中具體那一塊出現問題了勒,請看下面一段錄屏


mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
imPrettySureSortingIsFree();
}
});
/**
* 排序後打印二維數組,一行行打印
*/
public void imPrettySureSortingIsFree() {
int dimension = 300;
int[][] lotsOfInts = new int[dimension][dimension];
Random randomGenerator = new Random();
for (int i = 0; i < lotsOfInts.length; i++) {
for (int j = 0; j < lotsOfInts[i].length; j++) {
lotsOfInts[i][j] = randomGenerator.nextInt();
}
}
for (int i = 0; i < lotsOfInts.length; i++) {
String rowAsStr = "";
//排序
int[] sorted = getSorted(lotsOfInts[i]);
//拼接打印
for (int j = 0; j < lotsOfInts[i].length; j++) {
rowAsStr += sorted[j];
if (j < (lotsOfInts[i].length - 1)) {
rowAsStr += ", ";
}
}
Log.i("ricky", "Row " + i + ": " + rowAsStr);
}
}

  • 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.
  • 34.
  • 35.

看完這篇文章,解决-APP-中-90-%-的內存异常問題,趕緊學起來_程序員_02

最後我們之後是 onClick 中的 imPrettySureSortingIsFree() 函數裏面的 rowAsStr += sorted[j]; 字符串拼接造成的 內存抖動 ,因為每次拼接一個 String 都會申請一塊新的堆內存,那麼怎麼解决這個頻繁開辟內存的問題了。

其實在 Java 中有 2 個更好的 API 對 String 的操作很友好,相信應該有人猜到了吧。沒錯就是將 此處的 String 換成 StringBuffer 或者 StringBuilder,就能很完美的解决字符串拼接造成的內存抖動問題。

修改後

 /**
*&emsp;打印二維數組,一行行打印
*/
public void imPrettySureSortingIsFree() {
int dimension = 300;
int[][] lotsOfInts = new int[dimension][dimension];
Random randomGenerator = new Random();
for(int i = 0; i < lotsOfInts.length; i++) {
for (int j = 0; j < lotsOfInts[i].length; j++) {
lotsOfInts[i][j] = randomGenerator.nextInt();
}
}
// 使用StringBuilder完成輸出,我們只需要創建一個字符串即可, 不需要浪費過多的內存
StringBuilder sb = new StringBuilder();
String rowAsStr = "";
for(int i = 0; i < lotsOfInts.length; i++) {
// 清除上一行
sb.delete(0, rowAsStr.length());
//排序
int[] sorted = getSorted(lotsOfInts[i]);
//拼接打印
for (int j = 0; j < lotsOfInts[i].length; j++) {
sb.append(sorted[j]);
if(j < (lotsOfInts[i].length - 1)){
sb.append(", ");
}
}
rowAsStr = sb.toString();
Log.i("jason", "Row " + i + ": " + rowAsStr);
}
}

  • 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.

總結

只要養成這樣的習慣,至少可以避免 90 % 以上不會造成內存异常

**1. 數據類型: **不要使用比需求更占用空間的基本數據類型
2. 循環盡量用 foreach ,少用 iterator, 自動裝箱也盡量少用
3. 數據結構與算法的解度處理 (數組,鏈錶,棧樹,樹,圖)

數據量千級以內可以使用 Sparse 數組 (Key為整數),ArrayMap (Key 為對象) 雖然性能不如 HashMap ,但節約內存。

4. 枚舉優化

缺點:

每一個枚舉值都是一個單例對象,在使用它時會增加額外的內存消耗,所以枚舉相比與 Integer 和 String 會占用更多的內存

較多的使用 Enum 會增加 DEX 文件的大小,會造成運行時更多的 IO 開銷,使我們的應用需要更多的空間

特別是分 Dex 多的大型 APP,枚舉的初始化很容易導致 ANR

優化後的代碼:可以直接限定傳入的參數個數

 public class SHAPE {
public static final int TYPE_0=0;
public static final int TYPE_1=1;
public static final int TYPE_2=2;
public static final int TYPE_3=3;
@IntDef(flag=true,value={TYPE_0,TYPE_1,TYPE_2,TYPE_3})
@Target({ElementType.PARAMETER,ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE)
public @interface Model{
}
private @Model int value=TYPE_0;
public void setShape(@Model int value){
this.value=value;
}
@Model
public int getShape(){
return this.value;
}
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

5. static , static final 的問題

  • static 會由編譯器調用 clinit 方法進行初始化
  • static final 不需要進行初始化工作,打包在 dex 文件中可以直接調用,並不會在類初始化申請內存

基本數據類型的成員,可以全寫成 static final

6. 字符串的拼接盡量少用 +=

7. 重複申請內存問題

  • 同一個方法多次調用,如遞歸函數 ,回調函數中 new 對象
  • 不要在 onMeause() onLayout() ,onDraw() 中去刷新UI(requestLayout)

8. 避免 GC 回收將來要重新使用的對象 (內存設計模式對象池 + LRU 算法)

9. Activity 組件泄漏

  • 非業務需要不要把 activity 的上下文做參數傳遞,可以傳遞 application 的上下文
  • 非靜態內部類和匿名內部內會持有 activity 引用(靜態內部類 或者 單獨寫文件)
  • 單例模式中回調持有 activity 引用(弱引用)
  • handler.postDelayed() 問題
  • 如果開啟的線程需要傳入參數,用弱引接收可解决問題
  • handler 記得清除 removeCallbacksAndMessages(null)

10. Service 耗時操作盡量使用 IntentService,而不是 Service

最後

如果你覺得文章寫得不錯就給個贊唄?如果你覺得那裏值得改進的,請給我留言。一定會認真查詢,修正不足。謝謝。

希望讀到這的您能轉發分享和關注一下我,以後還會更新技術幹貨,謝謝您的支持!

轉發+點贊+關注,第一時間獲取最新知識點

新的開始

改變人生,沒有什麼捷徑可言,這條路需要自己親自去走一走,只有深入思考,不斷反思總結,保持學習的熱情,一步一步構建自己完整的知識體系,才是最終的制勝之道,也是程序員應該承擔的使命。

 CodeChina開源項目:《Android學習筆記總結+移動架構視頻+大廠面試真題+項目實戰源碼》

《系列學習視頻》
看完這篇文章,解决-APP-中-90-%-的內存异常問題,趕緊學起來_Android_03

《系列學習文檔》

看完這篇文章,解决-APP-中-90-%-的內存异常問題,趕緊學起來_程序員_04

《我的大廠面試之旅》

看完這篇文章,解决-APP-中-90-%-的內存异常問題,趕緊學起來_程序員_05

版权声明:本文为[mb612e2e66047a8]所创,转载请带上原文链接,感谢。 https://gsmany.com/2021/09/20210918064237810j.html