役に立たないオブジェクト(もう使用する必要のないオブジェクト)が他のオブジェクトによって参照されている場合、そのオブジェクトはシステムによって再利用できないため、ヒープ内のオブジェクトによって占有されているメモリユニットを解放できず、メモリスペースの浪費が発生します。状況はメモリリークです。
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
のパラメータActivity
、Service
例えばコンテキストとしては、メモリリークにつながります。
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) {
}
}
Info
Activity
静的メンバー、およびホールドActivity
参照しますが、sInfo
静的変数として、ライフサイクルは間違いなくよりも長いですActivity
。だから、後にActivity
出て行く、それはsInfo
まだ参照されているActivity
とActivity
メモリリークにつながる、再利用することはできません。
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
未処理または処理されるメッセージキューにつながる、Activity
とActivity
メモリリーク再利用することができません。
一般に、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
もはやActivity
unheldが、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();
}
}
多くの初心者は、上記のこの新しいスレッドと非同期タスクを気に入るはずです。このような言い回しはあまりわかりにくいため、新しい子スレッドThread
とAsyncTask
匿名の内部クラスオブジェクト、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
からTimer
とTimerTask
メモリリークを避けるために。
コレクション内のオブジェクトがクリーンアップされず、メモリリークが発生します
オブジェクトが投入された場合、これはよりよく理解されArrayList
、HashMap
そのようなコレクションとして、このコレクションは、オブジェクトを保持します。このオブジェクトが不要になったとき、コレクションから削除しませんでした。コレクションがまだ使用されている(そしてこのオブジェクトが役に立たない)限り、このオブジェクトはメモリリークを引き起こします。また、コレクションが静的に参照されている場合、コレクション内のこれらの役に立たないオブジェクトは、メモリリークを引き起こすことさえあります。したがって、コレクションを使用する場合は、メモリリークを回避するために、コレクションremove
またはclear
コレクションから未使用のオブジェクトを時間内に削除する必要があります。
リソースが閉じられたり解放されたりしないため、メモリリークが発生します
使用時にはIO
、File
流れやSqlite
、Cursor
迅速かつ他のリソースをシャットダウンします。これらのリソースは通常、読み取りおよび書き込み操作中にバッファーを使用します。時間内に閉じられない場合、これらのバッファーオブジェクトは常に占有され、解放されないため、メモリリークが発生します。したがって、それらを使用する必要がないときにそれらを閉じて、メモリリークを回避するためにバッファを時間内に解放できるようにします。
プロパティアニメーションはメモリリークを引き起こします
アニメーションも時間のかかる作業です。たとえばActivity
、プロパティanimation(ObjectAnimator
)はで開始されますが、破棄されたときにcancle
メソッドは呼び出されません。アニメーションは表示されなくなりますが、アニメーションは引き続き再生されます。コントロールはActivity
、それが配置されているコントロールによって参照されているため、Activity
正常に解放できません。したがって、メモリリークActivity
をcancel
回避するために、プロパティアニメーションは、破棄されたときにも削除する必要があります。
@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のを]。フレンズインタビューを必要とするが、それを参照することができます。それはあなたを助けている場合、することができますスターをクリック!