Android
メモリリーク
サーチ…
共通のメモリリークとその修正方法
1.あなたの状況を修正する:
たとえば、Toastは1つではなく多くのアクティビティで見ることができるので、トーストのためにgetApplicationContext()
を使用し、アクティビティが終了してもサービスを実行し続けることができるため、次のようなサービスを開始できます。
Intent myService = new Intent(getApplicationContext(), MyService.class);
どのようなコンテキストが適切であるかの簡単なガイドとして、この表を使用してください。
ここでの文脈に関するオリジナル記事 。
2. Contextへの静的参照
深刻なメモリリークの間違いは、 View
への静的参照を保持することです。各View
は、 Context
への内部参照があります。つまり、ビュー階層全体の古いアクティビティは、アプリが終了するまでガベージコレクションされません。画面を回転させると、あなたのアプリは2度メモリに保存されます。
ビュー、コンテキスト、またはその子孫の静的参照が絶対にないことを確認してください。
3.実際にサービスを終了していることを確認します。
たとえば、GoogleロケーションサービスAPIを使用するintentServiceがあります。そして、私はgoogleApiClient.disconnect();
を呼び出すことを忘れていましたgoogleApiClient.disconnect();
:
//Disconnect from API onDestroy()
if (googleApiClient.isConnected()) {
LocationServices.FusedLocationApi.removeLocationUpdates(googleApiClient, GoogleLocationService.this);
googleApiClient.disconnect();
}
4.画像とビットマップの使用状況を確認する:
SquareのライブラリPicassoを使用している場合、 .fit()
を使用しないでメモリがリークしていたことがわかりました。これは、メモリフットプリントを平均50MBから19MB未満に大幅に削減しました。
Picasso.with(ActivityExample.this) //Activity context
.load(object.getImageUrl())
.fit() //This avoided the OutOfMemoryError
.centerCrop() //makes image to not stretch
.into(imageView);
5.ブロードキャストレシーバを使用している場合は、登録を解除します。
6. java.util.Observer
(オブザーバー・パターン)を使用している場合は、次のようにします。
deleteObserver(observer);
を必ず使用してくださいdeleteObserver(observer);
AsyncTaskでアクティビティを漏らさないようにする
注意の言葉 :AsyncTaskが持っている多くの落とし穴年代を離れてここで説明するメモリリークから。したがって、このAPIを十分に理解していない場合は、このAPIに注意してください。多くの選択肢(Thread、EventBus、RxAndroidなど)があります。
AsyncTask
よくある間違いの1つは、ホストActivity
(またはFragment
)への強い参照を取得することです。
class MyActivity extends Activity {
private AsyncTask<Void, Void, Void> myTask = new AsyncTask<Void, Void, Void>() {
// Don't do this! Inner classes implicitly keep a pointer to their
// parent, which in this case is the Activity!
}
}
これは、 AsyncTask
が親Activity
よりもすぐにAsyncTask
ため、タスクが実行されている間に構成の変更が発生した場合など、問題になります。
これを行う正しい方法は、タスクを親をキャプチャしないstatic
クラスにし、ホストの弱い参照を保持することです。 Activity
:
class MyActivity extends Activity {
static class MyTask extends AsyncTask<Void, Void, Void> {
// Weak references will still allow the Activity to be garbage-collected
private final WeakReference<MyActivity> weakActivity;
MyTask(MyActivity myActivity) {
this.weakActivity = new WeakReference<>(myActivity);
}
@Override
public Void doInBackground(Void... params) {
// do async stuff here
}
@Override
public void onPostExecute(Void result) {
// Re-acquire a strong reference to the activity, and verify
// that it still exists and is active.
MyActivity activity = weakActivity.get();
if (activity == null
|| activity.isFinishing()
|| activity.isDestroyed()) {
// activity is no longer valid, don't do anything!
return;
}
// The activity is still valid, do main-thread stuff here
}
}
}
アクティビティの匿名コールバック
匿名クラスを作成するたびに、その親クラスへの暗黙の参照が保持されます。だからあなたが書くとき:
public class LeakyActivity extends Activity
{
...
foo.registerCallback(new BarCallback()
{
@Override
public void onBar()
{
// do something
}
});
}
実際にはLeakyActivityインスタンスへの参照をfooに送信しています。ユーザーがLeakyActivityから移動すると、この参照によってLeakyActivityインスタンスがガベージコレクションされなくなります。これは深刻なリークです。アクティビティはビュー階層全体への参照を保持するため、メモリ内の大きなオブジェクトです。
この漏れを避ける方法:
もちろん、匿名のコールバックをアクティビティ全体で使用することは避けることができます。アクティビティライフサイクルに関して、すべてのコールバックの登録を解除することもできます。そのようです:
public class NonLeakyActivity extends Activity
{
private final BarCallback mBarCallback = new BarCallback()
{
@Override
public void onBar()
{
// do something
}
});
@Override
protected void onResume()
{
super.onResume();
foo.registerCallback(mBarCallback);
}
@Override
protected void onPause()
{
super.onPause();
foo.unregisterCallback(mBarCallback);
}
}
静的クラスのアクティビティコンテキスト
多くの場合、Androidクラスのいくつかを使いやすいユーティリティクラスにラップしたいと思うでしょう。これらのユーティリティクラスでは、Android OSやアプリケーションのリソースにアクセスするためのコンテキストが必要になることがよくあります。これの一般的な例は、SharedPreferencesクラスのラッパーです。 Androidsの共有設定にアクセスするには、次のように記述する必要があります。
context.getSharedPreferences(prefsName, mode);
それで、次のクラスを作成したくなるかもしれません:
public class LeakySharedPrefsWrapper
{
private static Context sContext;
public static void init(Context context)
{
sContext = context;
}
public int getInt(String name,int defValue)
{
return sContext.getSharedPreferences("a name", Context.MODE_PRIVATE).getInt(name,defValue);
}
}
現在、アクティビティコンテキストでinit()
を呼び出すと、LeakySharedPrefsWrapperはアクティビティへの参照を保持し、ガベージコレクションされないようにします。
避ける方法:
静的ヘルパー関数を呼び出すときは、 context.getApplicationContext();
を使用してアプリケーションコンテキストを送信できますcontext.getApplicationContext();
静的ヘルパー関数を作成するときは、指定されたコンテキストからアプリケーションコンテキストを抽出できます(アプリケーションコンテキストでgetApplicationContext()を呼び出すとアプリケーションコンテキストが返されます)。ラッパーへの修正は簡単です:
public static void init(Context context)
{
sContext = context.getApplicationContext();
}
アプリケーションのコンテキストがユースケースに適していない場合、各ユーティリティ関数にContextパラメータを含めることができます。これらのコンテキストパラメータへの参照を避けるべきです。この場合、ソリューションは次のようになります。
public int getInt(Context context,String name,int defValue)
{
// do not keep a reference of context to avoid potential leaks.
return context.getSharedPreferences("a name", Context.MODE_PRIVATE).getInt(name,defValue);
}
LeakCanaryライブラリでメモリリークを検出する
LeakCanaryは、オープンソースのJavaライブラリで、デバッグビルドのメモリリークを検出します。
build.gradle
依存関係を追加するだけです:
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
}
次に、 Application
クラスで:
public class ExampleApplication extends Application {
@Override public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
}
}
LeakCanaryは、 デバッグビルドでアクティビティのメモリリークが検出されたときに、自動的に通知を表示します。
注:リリースコードには、 leakcanary-android-no-op
依存関係に存在する2つの空のクラス以外のLeakCanaryへの参照は含まれません。
リスナーによるアクティビティの漏洩を避ける
アクティビティでリスナーを実装または作成する場合は、リスナーが登録されているオブジェクトのライフサイクルに常に注意してください。
ユーザーがログインまたはログアウトしたときに関心を持つさまざまな活動/断片があるアプリケーションを考えてみましょう。これを行う1つの方法は、ユーザーの状態が変化したときに通知を受け取るために購読できるUserController
シングルトンインスタンスを持つUserController
です。
public class UserController {
private static UserController instance;
private List<StateListener> listeners;
public static synchronized UserController getInstance() {
if (instance == null) {
instance = new UserController();
}
return instance;
}
private UserController() {
// Init
}
public void registerUserStateChangeListener(StateListener listener) {
listeners.add(listener);
}
public void logout() {
for (StateListener listener : listeners) {
listener.userLoggedOut();
}
}
public void login() {
for (StateListener listener : listeners) {
listener.userLoggedIn();
}
}
public interface StateListener {
void userLoggedIn();
void userLoggedOut();
}
}
次に、 SignInActivity
2つのアクティビティがあります。
public class SignInActivity extends Activity implements UserController.StateListener{
UserController userController;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.userController = UserController.getInstance();
this.userController.registerUserStateChangeListener(this);
}
@Override
public void userLoggedIn() {
startMainActivity();
}
@Override
public void userLoggedOut() {
showLoginForm();
}
...
public void onLoginClicked(View v) {
userController.login();
}
}
そしてMainActivity
:
public class MainActivity extends Activity implements UserController.StateListener{
UserController userController;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.userController = UserController.getInstance();
this.userController.registerUserStateChangeListener(this);
}
@Override
public void userLoggedIn() {
showUserAccount();
}
@Override
public void userLoggedOut() {
finish();
}
...
public void onLogoutClicked(View v) {
userController.logout();
}
}
この例では、ユーザがログインしてから再びログアウトするMainActivity
に、 MainActivity
インスタンスがリークされるということがMainActivity
ます。リークは、 UserController#listeners
アクティビティへの参照があるために発生しUserController#listeners
。
注:匿名の内部クラスをリスナーとして使用しても、アクティビティはまだリークします。
...
this.userController.registerUserStateChangeListener(new UserController.StateListener() {
@Override
public void userLoggedIn() {
showUserAccount();
}
@Override
public void userLoggedOut() {
finish();
}
});
...
匿名の内部クラスには外部クラス(この場合はアクティビティ)への暗黙の参照があるため、アクティビティはまだリークします。このため、外部クラスのインスタンスメソッドを内部クラスから呼び出すことができます。実際、外部クラスへの参照を持たない内部クラスの唯一の型は静的内部クラスです。
つまり、非静的な内部クラスのすべてのインスタンスは、それらを作成した外部クラスのインスタンスへの暗黙の参照を保持します。
これを解決するには、 UserController#listeners
からリスナーを削除するメソッドを追加するか、またはWeakReference
を使用してリスナーの参照を保持する方法のいずれかを使用して、これを解決する方法が2つあります。
代替1:リスナーを削除する
removeUserStateChangeListener(StateListener listener)
という新しいメソッドを作成しremoveUserStateChangeListener(StateListener listener)
。
public class UserController {
...
public void registerUserStateChangeListener(StateListener listener) {
listeners.add(listener);
}
public void removeUserStateChangeListener(StateListener listener) {
listeners.remove(listener);
}
...
}
次に、アクティビティのonDestroy
メソッドでこのメソッドを呼び出します。
public class MainActivity extends Activity implements UserController.StateListener{
...
@Override
protected void onDestroy() {
super.onDestroy();
userController.removeUserStateChangeListener(this);
}
}
この変更により、ユーザーのログインとログアウト時にMainActivity
のインスタンスがMainActivity
はなくなりました。ただし、ドキュメントが明確でない場合は、 UserController
を使用している次の開発者が、アクティビティが破棄されたときにリスナーの登録を解除する必要があることを忘れてしまう可能性があります。
別の方法2:弱参照の使用
まず、弱い参照が何であるかを説明して始めましょう。弱い参照は、その名前が示すように、オブジェクトへの弱い参照を保持します。強い参照である通常のインスタンスフィールドと比較して、弱参照はガベージコレクタGCがオブジェクトを削除するのを止めません。上記の例では、 UserController
がWeakReference
をリスナーを参照するために使用した場合、 MainActivity
が破棄された後でMainActivity
をガベージMainActivity
することができます。
要約すると、弱い参照は、誰にもこのオブジェクトへの強い参照がない場合、先に進み、それを削除することをGCに伝えます。
WeakReference
リストを使用してリスナーを追跡するようにUserController
を変更しましょう:
public class UserController {
...
private List<WeakReference<StateListener>> listeners;
...
public void registerUserStateChangeListener(StateListener listener) {
listeners.add(new WeakReference<>(listener));
}
public void removeUserStateChangeListener(StateListener listenerToRemove) {
WeakReference referencesToRemove = null;
for (WeakReference<StateListener> listenerRef : listeners) {
StateListener listener = listenerRef.get();
if (listener != null && listener == listenerToRemove) {
referencesToRemove = listenerRef;
break;
}
}
listeners.remove(referencesToRemove);
}
public void logout() {
List referencesToRemove = new LinkedList();
for (WeakReference<StateListener> listenerRef : listeners) {
StateListener listener = listenerRef.get();
if (listener != null) {
listener.userLoggedOut();
} else {
referencesToRemove.add(listenerRef);
}
}
}
public void login() {
List referencesToRemove = new LinkedList();
for (WeakReference<StateListener> listenerRef : listeners) {
StateListener listener = listenerRef.get();
if (listener != null) {
listener.userLoggedIn();
} else {
referencesToRemove.add(listenerRef);
}
}
}
...
}
この変更では、Listenerが削除されているかどうかは関係ありません。なぜなら、 UserController
はいずれのリスナーに対しても強い参照を保持していないからです。しかし、毎回この定型文を書くのは面倒です。したがって、 WeakCollection
という汎用クラスを作成しましょう。
public class WeakCollection<T> {
private LinkedList<WeakReference<T>> list;
public WeakCollection() {
this.list = new LinkedList<>();
}
public void put(T item){
//Make sure that we don't re add an item if we already have the reference.
List<T> currentList = get();
for(T oldItem : currentList){
if(item == oldItem){
return;
}
}
list.add(new WeakReference<T>(item));
}
public List<T> get() {
List<T> ret = new ArrayList<>(list.size());
List<WeakReference<T>> itemsToRemove = new LinkedList<>();
for (WeakReference<T> ref : list) {
T item = ref.get();
if (item == null) {
itemsToRemove.add(ref);
} else {
ret.add(item);
}
}
for (WeakReference ref : itemsToRemove) {
this.list.remove(ref);
}
return ret;
}
public void remove(T listener) {
WeakReference<T> refToRemove = null;
for (WeakReference<T> ref : list) {
T item = ref.get();
if (item == listener) {
refToRemove = ref;
}
}
if(refToRemove != null){
list.remove(refToRemove);
}
}
}
WeakCollection<T>
使う代わりにUserController
を書き直してみましょう。
public class UserController {
...
private WeakCollection<StateListener> listenerRefs;
...
public void registerUserStateChangeListener(StateListener listener) {
listenerRefs.put(listener);
}
public void removeUserStateChangeListener(StateListener listenerToRemove) {
listenerRefs.remove(listenerToRemove);
}
public void logout() {
for (StateListener listener : listenerRefs.get()) {
listener.userLoggedOut();
}
}
public void login() {
for (StateListener listener : listenerRefs.get()) {
listener.userLoggedIn();
}
}
...
}
上記のコード例に示すように、 WeakCollection<T>
は、通常のリストの代わりにWeakReference
を使用するのに必要な定型コードをすべて削除します。これをすべて無効にするには: UserController#removeUserStateChangeListener(StateListener)
への呼び出しがUserController#removeUserStateChangeListener(StateListener)
た場合、リスナーとそれが参照しているすべてのオブジェクトはリークしません。
匿名クラス、ハンドラ、タイマータスク、スレッドでメモリリークを避ける
アンドロイドでは、すべての開発者がプロジェクトで少なくとも1回Anonymous Class
(Runnable)を使用します。 Anonymous Class
は親(アクティビティ)への参照があります。長期実行タスクを実行すると、親アクティビティーはタスクが終了するまで破棄されません。
Exampleは、ハンドラとAnonymous Runnable
クラスを使用します。 Runnable
が終了する前にアクティビティを終了すると、メモリがリークします。
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
// do abc long 5s or so
}
}, 10000); // run "do abc" after 10s. It same as timer, thread...
どうやって解決するの?
-
Anonymous Class
長時間操作しないでください。または、Static class
が必要で、WeakReference
をアクティビティやビューなどに渡す必要があります。Thread
はAnonymous Class
と同じです。 - アクティビティが破棄されたときに
Handler
、Timer
キャンセルします。