Android メモリ リークのトラブルシューティングの分析と一般的な解決策

メモリリークとは:

Android の開発プロセスで、不要になったオブジェクトを再利用する必要がある場合、使用中の別のオブジェクトがその参照を保持していて再利用できないため、再利用されるべきオブジェクトが失敗する原因となります。ヒープ メモリ、メモリ リークが発生します。

メモリリークの危険?

これは、アプリケーションの OOM の主な原因の 1 つです。Android システムによって各アプリケーションに割り当てられるメモリは限られているため、アプリケーションで多くのメモリ リークが発生すると、必然的にアプリケーションが必要とするメモリが割り当てられたメモリを超えてしまいます。メモリリークを引き起こし、アプリケーションのクラッシュを引き起こしたクォータ。

メモリ リークのトラブルシューティング:

1. adb コマンド adb shell dumpsys meminfo package name を使用して、現在のアクティビティ数を表示します。ページを開いたり閉じたりしてページを確認します。ページを閉じた直後はガベージ コレクションが実行されないため、テストのために Android Studio に付属のプロファイラーを使用し、強制ガベージ コレクションをクリックします。アクティビティ数が最初と同じなら正常、アクティビティ数が増えればメモリリークです。

ここに画像の説明を挿入
ここに画像の説明を挿入

2. AS の Profiler を使用して問題をさらにトラブルシューティングし、[Dump Java heap] をクリックしてヒープ割り当てをエクスポートします。

ここに画像の説明を挿入

一般的なメモリ リークの状況:

1. 静的アクティビティ (Activity Context) とビュー

静的変数 Activity と View はメモリ リークを引き起こします. 次のコードでは、Activity の Context と TextView が静的オブジェクトに設定されており、メモリ リークが発生しています. context と textView のインスタンスのライフサイクルは、インスタンスのライフサイクルと同じであるためです
.アプリケーションであり、現在のアクティビティ (MemoryTestActivity) 参照を保持します。MemoryTestActivity が破棄され、その参照が保持されると、再利用されないため、メモリ リークが発生します。

public class MemoryTestActivity extends AppCompatActivity {
    
    

    private static Context context;
    private static TextView textView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_memory_test);
        context = this;
        textView = new TextView(this);
    }
}

2.シングルトンによるメモリリーク

Android のシングルトン モードは、開発で頻繁に使用されるモードです。不適切な使用はメモリ リークにつながる可能性があります。シングル ケースのライフ サイクルはアプリケーションのライフ サイクルと同じです。つまり、シングルトンはアプリケーションと同じオブジェクトを保持する必要があります。アプリケーションのライフ サイクルと矛盾するオブジェクトを保持することはできません。例: アクティビティ (コンテキスト) コンテキスト:

public class TestManager {
    
    

    private static TestManager manager;
    private Context context;

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

    /**
     * 如果传入的context是activity,service的上下文,会导致内存泄漏
     * 原因是我们的manger是一个static的静态对象,这个对象的生命周期和整个app的生命周期一样长
     * 当activity销毁的时候,我们的这个manger仍然持有者这个activity的context,就会导致activity对象无法被释放回收,就导致了内存泄漏
     */
    public static TestManager getInstance(Context context) {
    
    
        if (manager == null) {
    
    
            manager = new TestManager(context);
        }
        return manager;
    }
}

解決策: TestManager シングルトン モードで使用されるコンテキスト Context を変更します. TestManager シングルトン モードは ApplicationContext を参照します. TestManager シングルトン モードはアプリケーション ライフ サイクルと同じです. ApplicationContext はアプリケーション ライフ サイクルと同じです.メモリリークがないこと。

public class TestManager {
    
    

    private static TestManager manager;
    private Context context;

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

    //正确写法
    public static TestManager getInstance(Context context) {
    
    
        if (manager == null) {
    
    
            manager = new TestManager(context.getApplicationContext());
        }
        return manager;
    }
}

3.スレッドによるメモリリーク

匿名スレッドの内部クラスはアクティビティを暗黙的に参照します. 時間のかかるタスクを実行するとき, アクティビティは常に暗黙的に参照されます. アクティビティが閉じられると, 匿名スレッドの内部クラスはアクティビティを暗黙的に参照し、できません.時間内にリサイクルされます。

public class MemoryTestActivity extends AppCompatActivity {
    
    

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

    //匿名内部类持有MemoryTestActivity实例引用,当耗时匿名线程内部类执行完成以后MemoryTestActivity实例才会回收;
    public void anonymousInnerClass() {
    
    
          new AsyncTask<Void, Void, Void>(){
    
    
            @Override
            protected Void doInBackground(Void... voids) {
    
    
                //执行异步处理
                SystemClock.sleep(120000);
                return null;
            }
        }.execute();
    }
}

解決策: AsyncTask の匿名内部クラスを静的クラスに変更し、Activity の暗黙的な参照を削除し、MemoryTestActivity が破棄された時点で非同期タスク staticAsyncTask.cancel(true) をキャンセルして、非同期タスクの実行が更新されないようにします。 MemoryTestActivity インスタンスの UI を破棄します。

public class MemoryTestActivity extends AppCompatActivity {
    
    
    
    private StaticAsyncTask staticAsyncTask;
    
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_memory_test);

        staticAsyncTask = new StaticAsyncTask(this);
        staticAsyncTask.execute();
    }

    private static class StaticAsyncTask extends AsyncTask<Void, Void, Void> {
    
    
        private WeakReference<Context> weakReference;

        public StaticAsyncTask(Context context) {
    
    
            weakReference = new WeakReference<Context>(context);
        }

        @Override
        protected Void doInBackground(Void... voids) {
    
    
            //执行异步处理
            SystemClock.sleep(120000);
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
    
    
            super.onPostExecute(aVoid);

            MemoryTestActivity activity = (MemoryTestActivity) weakReference.get();
            if (activity != null) {
    
    
                //异步任务执行完成,执行UI处理
            }
        }
    }

    @Override
    protected void onDestroy() {
    
    
        super.onDestroy();
        staticAsyncTask.cancel(true);
    }
}

4. 非静的内部クラスの静的インスタンスの作成によるメモリ リーク

public class MemoryTestActivity extends AppCompatActivity {
    
    

    private static TestResource testResource;

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

        testResource = new TestResource();
    }

    class TestResource{
    
    
        //资源类
    }

}

このように、Activity 内に非静的な内部クラスの singleton を作成し、Activity が開始されるたびに singleton のデータが使用されるようにすることで、作成の繰り返しを回避できますが、この書き方ではメモリ リークが発生するため、非静的内部クラスのデフォルトは、外部クラスへの参照を保持し、非静的内部クラスを使用して静的インスタンスを作成するためです. インスタンスのライフサイクルは、アプリケーションと同じくらい長く、静的インスタンスの原因となります.インスタンスが常にアクティビティの参照を保持するため、結果としてアクティビティ メモリ リソースを正常に回復できません。解決策
: 内部クラスを静的内部クラスとして設定するか、内部クラスを抽象化してシングルトンをカプセル化します。Context を使用する必要がある場合は、 ApplicationContext を使用してください。

5. Handler によるメモリリーク

ハンドラーの使用によるメモリ リークは比較的一般的です. ネットワーク タスクの処理や一部のリクエスト コールバックのカプセル化などの API は、ハンドラーの助けを借りて処理する必要があります. ハンドラーに使用される非標準コードは、次の例に示すように、メモリ リークを引き起こす可能性があります:

    private Handler mHandler = new Handler(){
    
    
        @Override
        public void handleMessage(Message msg) {
    
    
            //处理UI显示
        }
    };

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

    //loadData()方法在子线程中执行
    private void loadData() {
    
    
        Message message = Message.obtain();
        //模拟线程延迟120秒发送Message
        mHandler.sendMessageDelayed(message, 120000);
    }
}

この方法で Handler を作成すると、メモリ リークが発生する可能性があります. mHandler は Handler の非静的匿名内部クラスのインスタンスであるため、外部クラス Activity への参照を保持しています. メッセージ キューが継続的にメッセージのポーリングと処理を行っていることがわかっています.ルーパー スレッドでは、アクティビティが終了すると、メッセージ キューにはまだ未処理のメッセージまたは処理中のメッセージがあります (たとえば、上記の例では、時間のかかるタスクがサブスレッドで処理され、アクティビティが終了し、実行が完了する前に破棄されます)、メッセージ キュー内のメッセージは残りますが、mHandler インスタンスの参照があり、mHander はアクティビティへの参照を保持しているため、アクティビティのメモリを時間内に回復できず、メモリ リークが発生します。

public class MemoryTestActivity extends AppCompatActivity {
    
    
    private Handler handler = new StaticHandler(this);

    private static class StaticHandler extends Handler {
    
    

        WeakReference<Context> weakReference;

        public StaticHandler(Context context) {
    
    
            weakReference = new WeakReference<>(context);
        }

        @Override
        public void handleMessage(Message msg) {
    
    
            //处理UI显示
            MemoryTestActivity activity = (MemoryTestActivity) weakReference.get();
            if (activity != null) {
    
    

            }
        }
    }

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

    //loadData()方法在子线程中执行
    private void loadData() {
    
    
        Message message = Message.obtain();
        //模拟线程延迟120秒发送Message
        handler.sendMessageDelayed(message, 120000);
    }

    @Override
    protected void onDestroy() {
    
    
        super.onDestroy();
        handler.removeCallbacksAndMessages(null);
    }
}

静的な Handler 内部クラスを作成し、次に Handler が保持するオブジェクトに弱いアプリケーションを使用して、リサイクル中に Handler が保持するオブジェクトもリサイクルできるようにし、アクティビティ リークを回避します。アクティビティの破棄または停止時にメッセージ キュー内のメッセージを削除する必要があります;
handler.removeCallbacksAndMessages(null); メッセージ キュー内のすべてのメッセージとスレッドを削除します;
解決策の概要:

  • プログラム ロジックによるメンテナンス
    • アクティビティを閉じるときにバックグラウンド スレッドを停止します。スレッドを停止することは、ハンドラと外部接続ラインを切断することと同じであり、アクティビティは適切なタイミングで自然にリサイクルされます。
    • ハンドラーが遅延メッセージによって参照されている場合は、対応するハンドラーの removeCallbacks() メソッドを使用して、メッセージ キューからメッセージ オブジェクトを削除します。
  • ハンドラーを静的クラスとして宣言する
    • Java では、非静的内部クラスと匿名内部クラスは外部クラスへの参照を暗黙的に保持し、静的内部クラスは外部クラスへの参照を保持しません。静的クラスは外部クラスのオブジェクトを保持しないため、Activity は自由に再利用できます; Handler は外部クラスのオブジェクトの参照を保持しなくなるため、プログラムはオブジェクトを操作することを許可しませんハンドラーのアクティビティ。したがって、ハンドラーにアクティビティ (WeakReference) への弱参照を追加する必要があります。

6.アニメーション

プロパティアニメーションには一種の無限ループアニメーションがあります.Activityでこの種のアニメーションを再生し、onDestroy()でアニメーションを停止しないと、アニメーションが再生され続けます.このとき、Activityはアクティビティが失敗する原因となるビュー。このような問題を解決するには、onDestroy() メソッドで objectAnimator.cancel() を呼び出してアニメーションを停止します。

public class MemoryTestActivity extends AppCompatActivity {
    
    
    
    private TextView textView;
    private ObjectAnimator objectAnimator;

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

        textView = (TextView)this.findViewById(R.id.textView2);
        objectAnimator = ObjectAnimator.ofFloat(textView, "rotation", 0, 360);
        objectAnimator.setRepeatCount(ValueAnimator.INFINITE);
        objectAnimator.start();
    }

    @Override
    protected void onDestroy() {
    
    
        super.onDestroy();

    }
}

アニメーションを停止する onDestroy() メソッドで objectAnimator.cancel() が呼び出されないため、アニメーションを実行するビューは常にアクティビティを参照し、アクティビティの破棄に失敗します。解決策: で objectAnimator.cancel() を呼び出し
ますアニメーションを停止する onDestroy() メソッド。

7. サードパーティ製ライブラリの不適切な使用


1. EventBus や RxJava などの一部のサードパーティ オープン ソース フレームワークを使用する場合、Activity が破棄される前にサブスクライブを解除しないと、メモリ リークが発生します 2.ライフ サイクルで相対的に登録およびキャンセルする必要があります(onCreate->onDestory | onResume->onPause ... )

8. クローズされていないリソースによるメモリ リーク

BroadcastReceiver、ContentObserver、File、Cursor、Stream、Bitmap などのリソースを使用するには、Activity が破棄されたときにそれらを閉じるかログアウトする必要があります。そうしないと、これらのリソースがリサイクルされず、メモリ リークが発生します。

おすすめ

転載: blog.csdn.net/u011106915/article/details/126738716