Androidインタビューのキーポイント:一般的なメモリリークと最適化ソリューション

役に立たないオブジェクト(もう使用する必要のないオブジェクト)が他のオブジェクトによって参照されている場合、そのオブジェクトはシステムによって再利用できないため、ヒープ内のオブジェクトによって占有されているメモリユニットを解放できず、メモリスペースの浪費が発生します。状況はメモリリークです。

Android開発では、プログラミングの習慣が悪いと、開発したアプリでメモリリークが発生する可能性があります。Android開発インタビューでの一般的なメモリリークシナリオと最適化ソリューションを次に示します。

シングルトンはメモリリークを引き起こします

シングルトンモードはAndroid開発でよく使用されますが、不適切に使用するとメモリリークが発生します。シングルトンの静的な性質により、そのライフサイクルはアプリケーションのライフサイクルと同じになるため、オブジェクトが役に立たなくなっても、シングルトンがそのオブジェクトへの参照を保持している場合、アプリケーションのライフサイクル全体で通常は使用できません。メモリリークにつながるリサイクル。

public class AppSettings {

    private static AppSettings sInstance;
    private Context mContext;

    private AppSettings(Context context) {
        this.mContext = context;
    }

    public static AppSettings getInstance(Context context) {
        if (sInstance == null) {
            sInstance = new AppSettings(context);
        }
        return sInstance;
    }
}

この単一の場合、上記のコードのような、我々は呼び出す場合getInstance(Context context)メソッドを受信する際contextのパラメータActivityService例えばコンテキストとしては、メモリリークにつながります。

Activity例えば、我々が開始Activityし、呼び出しgetInstance(Context context)得るための方法をAppSettings渡す、単一のケースActivity.thisのようにcontext、このようなAppSettingsシングルトンクラスsInstance保持するためにActivity、我々は終了したときに参照しActivityたときActivityので、しかし、無駄がないsIntance静的な単一のようたとえば、(アプリケーションのライフサイクル全体に存在する)はこのActivity参照を保持し続けるためActivityオブジェクトをリサイクルおよび解放できず、メモリリークが発生します。

このようなシングルトンによって引き起こされるメモリリークを回避するために、contextパラメータをグローバルコンテキストに変更できます。

private AppSettings(Context context) {
    this.mContext = context.getApplicationContext();
}

グローバルコンテキストApplication Contextシングルトンのライフサイクルである限り、アプリケーションのコンテキストであるため、メモリリークが回避されます。

シングルトンパターンはアプリケーションのライフサイクルに対応しているため、シングルトンを構築するときに使用されるActivityコンテキストは避けようとしますが、使用されるコンテキストは避けApplicationます。

静的変数はメモリリークを引き起こします

静的変数はメソッド領域に格納され、そのライフサイクルはクラスのロードからプロセス全体の終わりまで始まります。静的変数が初期化されると、それが保持する参照は、プロセスが終了するまで解放されません。

たとえば、次の状況は、Activity繰り返し作成infoされるのを避けるためにsInfo、静的変数として使用されます。

public class MainActivity extends AppCompatActivity {

    private static Info sInfo;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (sInfo != null) {
            sInfo = new Info(this);
        }
    }
}

class Info {
    public Info(Activity activity) {
    }
}

InfoActivity静的メンバー、およびホールドActivity参照しますが、sInfo静的変数として、ライフサイクルは間違いなくよりも長いですActivityだから、後にActivity出て行く、それはsInfoまだ参照されているActivityActivityメモリリークにつながる、再利用することはできません。

Androidの開発では、静的な保持は、使用のライフサイクルが一貫していないためにメモリリークにつながることがよくあります。したがって、静的な保持を作成するときは、各メンバー間の参照関係を考慮して、最善を尽くす必要があります。メモリリークを回避するために、静的に保持された変数は慎重に使用してください。もちろん、適切なタイミングで静的値をnullにリセットして、参照を保持しないようにすることもできます。これにより、メモリリークを回避することもできます。

非静的内部クラスはメモリリークを引き起こします

非静的内部クラス(匿名内部クラスを含む)は、デフォルトで外部クラスへの参照を保持します。非静的内部クラスオブジェクトのライフサイクルが外部クラスオブジェクトのライフサイクルよりも長い場合、メモリリークが発生します。

非静的内部クラスによって引き起こされるメモリリークは、Android開発の一般的なシナリオで使用されHandlerます。多くの開発者Handlerは次のように記述します。

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

    private void start() {
        Message msg = Message.obtain();
        msg.what = 1;
        mHandler.sendMessage(msg);
    }

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == 1) {
                // 做相应逻辑
            }
        }
    };
}

参照がmHandler静的変数として保持されていない場合、ActivityライフサイクルがActivity長くならない可能性があり、必ずしもメモリリークが発生するわけではないと言う人もいますが、そうでないことは明らかです。

おなじみのHandlerすべてのノウハウをメッセージングは、mHandlerメンバ変数として送信されたメッセージに保存されますmsg、そのmsg保留mHandlerの参照が、mHandlerあるActivity非静的内部クラスのインスタンスは、そのmHandlerホールドActivityし、我々は次のように理解することができるの参照、msg間接保有のActivity参照。msgメッセージが最初にキューに送信された後MessageQueueと待機のためのLooperポーリング処理(MessageQueueおよびLooperスレッドに関連付けられ、MessageQueueそれはLooperメンバ変数の参照、Looperに格納されていますThreadLocal)。次いで、後Activity出、それがmsgまだであってもよいMessageQueue未処理または処理されるメッセージキューにつながる、ActivityActivityメモリリーク再利用することができません

一般に、Android開発で内部クラスを使用したいが、メモリリークを回避したい場合は、通常、静的内部クラス+弱い参照を使用します

public class MainActivity extends AppCompatActivity {

    private Handler mHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHandler = new MyHandler(this);
        start();
    }

    private void start() {
        Message msg = Message.obtain();
        msg.what = 1;
        mHandler.sendMessage(msg);
    }

    private static class MyHandler extends Handler {

        private WeakReference<MainActivity> activityWeakReference;

        public MyHandler(MainActivity activity) {
            activityWeakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = activityWeakReference.get();
            if (activity != null) {
                if (msg.what == 1) {
                    // 做相应逻辑
                }
            }
        }
    }
}

mHandler弱い参照によって保持されますActivity。GCがガベージコレクションを実行Activityすると、占有されているメモリユニットを再利用して解放します。このようにして、メモリリークは発生しません。

上記のアプローチは、回避しないんActivityて送信する。得られたメモリリークを参照があるmsgもはやActivityunheldが、msgそれはまだメッセージキューに存在してもよいMessageQueueことが優れているので、コールバック、および送信されたメッセージを移動するためにActivity、それが場合れる破壊します。mHandler取り除く。

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

非静的内部クラスがメモリリークを引き起こす別の状況は、Threadまたはを使用することAsyncTaskです。

たとえば、Activity :のnew子スレッドを直接使用しますThread

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 模拟相应耗时逻辑
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

または、新しいAsyncTask非同期タスクを直接作成します。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new AsyncTask<Void, Void, Void>() {
            @Override
            protected Void doInBackground(Void... params) {
                // 模拟相应耗时逻辑
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return null;
            }
        }.execute();
    }
}

多くの初心者は、上記のこの新しいスレッドと非同期タスクを気に入るはずです。このような言い回しはあまりわかりにくいため、新しい子スレッドThreadAsyncTask匿名の内部クラスオブジェクト、Activity参照に暗黙的に保持されるデフォルトの外部Activityメモリにつながります道を譲る。メモリリークを回避するにHandler、上記のように静的内部クラス+弱いアプリケーション使用する必要があります(コードはリストされていませんHanlder上記の正しい記述を参照しください)。

未登録またはコールバックによるメモリリーク

たとえばActivity、で放送に登録した場合Activity、破棄後に登録をキャンセルしないと、この新しい放送は常にシステムに存在し、上記の非静的内部クラスのようなActivity参照を保持し、メモリリークを引き起こします。したがって、ブロードキャストがActivity破棄れた後、登録をキャンセルする必要があります。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        this.registerReceiver(mReceiver, new IntentFilter());
    }

    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            // 接收到广播需要做的逻辑
        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        this.unregisterReceiver(mReceiver);
    }
}

観測モードを登録する際に、時間内にキャンセルしないと、メモリリークの原因にもなります。たとえばRetrofit+RxJava、ネットワークリクエストの登録に使用されるオブザーバーコールバックは、外部参照も匿名の内部クラスとして保持するため、使用または破棄されていない場合は、登録をキャンセルすることを忘れないでください。

TimerとTimerTaskはメモリリークを引き起こします

TimerまたTimerTask、Androidでは通常、無限カルーセルの実現など、タイミングや周期的なタスクを実行するために使用されますViewPager

public class MainActivity extends AppCompatActivity {

    private ViewPager mViewPager;
    private PagerAdapter mAdapter;
    private Timer mTimer;
    private TimerTask mTimerTask;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
        mTimer.schedule(mTimerTask, 3000, 3000);
    }

    private void init() {
        mViewPager = (ViewPager) findViewById(R.id.view_pager);
        mAdapter = new ViewPagerAdapter();
        mViewPager.setAdapter(mAdapter);

        mTimer = new Timer();
        mTimerTask = new TimerTask() {
            @Override
            public void run() {
                MainActivity.this.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        loopViewpager();
                    }
                });
            }
        };
    }

    private void loopViewpager() {
        if (mAdapter.getCount() > 0) {
            int curPos = mViewPager.getCurrentItem();
            curPos = (++curPos) % mAdapter.getCount();
            mViewPager.setCurrentItem(curPos);
        }
    }

    private void stopLoopViewPager() {
        if (mTimer != null) {
            mTimer.cancel();
            mTimer.purge();
            mTimer = null;
        }
        if (mTimerTask != null) {
            mTimerTask.cancel();
            mTimerTask = null;
        }
    }

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

私たちはときにActivity破壊されたときに、それをすることが可能となるTimer実行待ち続けTimerTask、それは我々が活動はすぐに破壊されたときに、参照の活動は非常に、回復することはできません保持しているcancelからTimerTimerTaskメモリリークを避けるために。

コレクション内のオブジェクトがクリーンアップされず、メモリリークが発生します

オブジェクトが投入された場合、これはよりよく理解されArrayListHashMapそのようなコレクションとして、このコレクションは、オブジェクトを保持します。このオブジェクトが不要になったとき、コレクションから削除しませんでした。コレクションがまだ使用されている(そしてこのオブジェクトが役に立たない)限り、このオブジェクトはメモリリークを引き起こします。また、コレクションが静的に参照されている場合、コレクション内のこれらの役に立たないオブジェクトは、メモリリークを引き起こすことさえあります。したがって、コレクションを使用する場合は、メモリリークを回避するために、コレクションremoveまたはclearコレクションから未使用のオブジェクトを時間内に削除する必要があります。

リソースが閉じられたり解放されたりしないため、メモリリークが発生します

使用時にはIOFile流れやSqliteCursor迅速かつ他のリソースをシャットダウンします。これらのリソースは通常、読み取りおよび書き込み操作中にバッファーを使用します。時間内に閉じられない場合、これらのバッファーオブジェクトは常に占有され、解放されないため、メモリリークが発生します。したがって、それらを使用する必要がないときにそれらを閉じて、メモリリークを回避するためにバッファを時間内に解放できるようにします。

プロパティアニメーションはメモリリークを引き起こします

アニメーションも時間のかかる作業です。たとえばActivity、プロパティanimation(ObjectAnimatorはで開始されますが、破棄されたときにcancleメソッドは呼び出されません。アニメーションは表示されなくなりますが、アニメーションは引き続き再生されます。コントロールはActivity、それが配置されているコントロールによって参照されているためActivity正常に解放できません。したがって、メモリリークActivitycancel回避するために、プロパティアニメーションは、破棄されたときに削除する必要があります

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

WebViewはメモリリークを引き起こします

WebViewのメモリリークについては、Webページを読み込んだ後、WebViewが長時間メモリを占有して解放できないため、そのdestory()メソッドを呼び出して破棄し、アクティビティが破棄された後にメモリを解放する必要があります

さらにWebView、メモリリークに関する関連情報を調べたときに、この状況が発生しました。

Webview次のCallback保持Activity参照により、Webviewメモリが呼び出されWebview.destory()、他のメソッドで問題を解決できない場合でもメモリを解放できなくなります(Android 5.1以降)。

最終的な解決策は、親コンテナWebView破棄するWebView从に削除してから、破棄することWebViewです。詳細な分析プロセスについては、次の記事を参照してください:WebViewメモリリークソリューション

@Override
protected void onDestroy() {
    super.onDestroy();
    // 先从父控件中移除WebView
    mWebViewContainer.removeView(mWebView);
    mWebView.stopLoading();
    mWebView.getSettings().setJavaScriptEnabled(false);
    mWebView.clearHistory();
    mWebView.removeAllViews();
    mWebView.destroy();
}

総括する

Androidのメモリ最適化では、メモリリークがより重要な側面です。プログラムでメモリリークが発生すると、気付かない場合があります。コーディングプロセスでは、すべて良い習慣を身に付ける必要があります。要約すると、次の点を実行する限り、ほとんどの場合、メモリリークを回避できます。

  • シングルトンをActivity作成するときは、参照を使用しないようにしてください
  • アプリケーションオブジェクトのブランキングに注意するか、静的参照の場合は静的参照を少なくしてください。
  • 非静的内部クラスの代わりに静的内部クラス+ソフト参照を使用します。
  • 放送またはオブザーバーの登録を時間内にキャンセルします。
  • Activity時間のかかるタスクと属性アニメーション破棄されたときに記憶されcancelます。
  • ファイルストリームおよびCursorその他のリソースは時間内に閉じられます。
  • Activity破壊するときはWebView取り外して破壊します。

PS:私について(著者)


私は6年の開発経験を持つハンサムなAndroid包囲ライオンです。読んだ後はそれを気に入って、良い読書習慣を身に付けることを忘れないでください。WeChat検索「ProgrammingApe Development Center」は、乾物を書くのが好きなこのプログラマーに注目しています。

また、完全なテストサイトのPDFのためのAndroidの最初の行のインタビューはそれがために2年かかっている収集、整理して発表した。私の中で更新された情報[完全版] [GitHubのを]フレンズインタビューを必要とするが、それを参照することができます。それはあなたを助けている場合、することができますスターをクリック!

住所:[https://github.com/733gh/xiongfan]

 

おすすめ

転載: blog.csdn.net/qq_39477770/article/details/109387413