Android開発でよく見られる5大メモリ漏洩の問題と解決策

8437 ワード

Androidの開発では、メモリの漏洩が一般的な問題で、androidのプログラミング経験のある子供靴はすべて遭遇したことがあるはずですが、なぜメモリの漏洩が発生したのでしょうか.メモリの漏洩にはどのような影響がありますか?Androidプログラム開発では,1つのオブジェクトがもう使用する必要がなく,回収されるべきであった場合,もう1つの使用中のオブジェクトがその参照を持っていて回収できないため,回収されるべきオブジェクトが回収されずスタックメモリに留まり,メモリ漏れが発生する.メモリの漏洩にはどのような影響がありますか?これはアプリケーションOOMの主な原因の一つです.androidシステムは各アプリケーションに割り当てられるメモリが限られているため、1つのアプリケーションで発生するメモリの漏洩が比較的多い場合、アプリケーションに必要なメモリがこのシステムに割り当てられたメモリの限度額を超えてしまうことは避けられず、メモリオーバーフローによるアプリケーションCrashの原因となります.メモリ漏洩の原因と影響を理解した後、よくあるメモリ漏洩を把握し、今後のandroidプログラム開発では、できるだけ避ける必要があります.以下、android開発でよく見られるメモリ漏洩の問題と解決策を5つ探して、皆さんに共有してみましょう.
一、一例によるメモリリークAndroidの一例モデルは開発者に人気があるが、不適切な使用でもメモリリークを引き起こす.単一のインスタンスの静的特性により、単一のインスタンスのライフサイクルがアプリケーションのライフサイクルと同じように長くなるため、1つのオブジェクトが使用される必要がなくなり、単一のオブジェクトがオブジェクトの参照を持っている場合、このオブジェクトは正常に回収されず、メモリ漏れが発生することを示します.次のような例があります.
public class AppManager {
    private static AppManager instance;
    private Context context;
    private AppManager(Context context) {
        this.context = context;
    }
    public static AppManager getInstance(Context context) {
        if (instance != null) {
            instance = new AppManager(context);
        }
        return instance;
    }
}

これは普通の単例モードで、この単例を作成するとき、Contextを入力する必要があるため、このContextのライフサイクルの長さは重要です:1、アプリケーションのContextを入力します:これは何の問題もありません.単例のライフサイクルはアプリケーションと同じ長さです.2、入力されたのはActivityのContextである:このContextに対応するActivityが終了した場合、このContextはActivityのライフサイクルと同じ長さであるため(ActivityはContextに間接的に継承される)、現在のActivityが終了した場合、そのメモリは回収されない.このActivityの参照は、単一のオブジェクトが持っているためである.
正しい例は次のように変更する必要があります.
public class AppManager {
    private static AppManager instance;
    private Context context;
    private AppManager(Context context) {
        this.context = context.getApplicationContext();
    }
    public static AppManager getInstance(Context context) {
        if (instance != null) {
            instance = new AppManager(context);
        }
        return instance;
    }
}

これにより、どのContextが入力されても最終的にApplicationのContextが使用され、単一の例のライフサイクルがアプリケーションと同じ長さになるため、メモリの漏洩が防止されます.
二、非静的内部クラス静的インスタンスの作成によるメモリ漏洩
頻繁に起動するActivityで、同じデータリソースを繰り返し作成しないようにする場合があります.
public class MainActivity extends AppCompatActivity {
    private static TestResource mResource = null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if(mManager == null){
            mManager = new TestResource();
        }
        //...
    }
    class TestResource {
        //...
    }
}

これにより、Activity内部に非静的内部クラスの単一のインスタンスが作成され、Activityを起動するたびにこの単一のインスタンスのデータが使用されます.これにより、リソースの重複作成は回避されますが、非静的内部クラスはデフォルトで外部クラスの参照を持つため、この非静的内部クラスを使用して静的インスタンスが作成されます.このインスタンスのライフサイクルはアプリケーションと同じ長さです.これにより、静的インスタンスはActivityの参照を保持し続け、Activityのメモリリソースが正常に回収されない.正しい方法は、内部クラスを静的内部クラスに設定するか、内部クラスを抽出して単一の例にカプセル化します.Contextを使用する必要がある場合は、ApplicationContextを使用します.
三、Handlerによるメモリ漏れ
Handlerの使用によるメモリリークの問題は最も一般的であると言える.通常、ネットワークタスクの処理や要求コールバックのカプセル化などのapiはHandlerによって処理されるべきである.Handlerの使用コードの作成に規範がないと、メモリリークの可能性がある.以下の例である.
public class MainActivity extends AppCompatActivity {
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            //...
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        loadData();
    }
    private void loadData(){
        //...request
        Message message = Message.obtain();
        mHandler.sendMessage(message);
    }
}

このようにHandlerを作成するとメモリが漏洩し、mHandlerはHandlerの非静的匿名内部クラスのインスタンスであるため、外部クラスActivityの参照を保持し、メッセージキューがLooperスレッドで処理メッセージをポーリングし続けていることを知っています.このActivityが終了すると、メッセージキューには未処理のメッセージまたは処理中のメッセージがあり、メッセージキューのMessageはmHandlerインスタンスの参照を保持します.mHandlerはActivityの参照も持っているため、このActivityのメモリリソースをタイムリーに回収できず、メモリの漏洩を引き起こすため、もう一つの方法は:
public class MainActivity extends AppCompatActivity {
    private MyHandler mHandler = new MyHandler(this);
    private TextView mTextView ;
    private static class MyHandler extends Handler {
        private WeakReference reference;
        public MyHandler(Context context) {
            reference = new WeakReference<>(context);
        }
        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = (MainActivity) reference.get();
            if(activity != null){
                activity.mTextView.setText("");
            }
        }
    }
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = (TextView)findViewById(R.id.textview);
        loadData();
    }
 
    private void loadData() {
        //...request
        Message message = Message.obtain();
        mHandler.sendMessage(message);
    }
}

静的Handler内部クラスを作成し、Handlerが所有するオブジェクトに弱い参照を使用します.これにより、Activityの漏洩は回避されますが、Looperスレッドのメッセージキューには処理するメッセージがある可能性があります.ActivityのDestroyまたはStopでは、メッセージキューからメッセージを削除する必要があります.より正確な方法は、次のとおりです.
public class MainActivity extends AppCompatActivity {
   private MyHandler mHandler = new MyHandler(this);
   private TextView mTextView ;
   private static class MyHandler extends Handler {
       private WeakReference reference;
       public MyHandler(Context context) {
           reference = new WeakReference<>(context);
       }
       @Override
       public void handleMessage(Message msg) {
           MainActivity activity = (MainActivity) reference.get();
           if(activity != null){
               activity.mTextView.setText("");
           }
       }
   }

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
       mTextView = (TextView)findViewById(R.id.textview);
       loadData();
   }

   private void loadData() {
       //...request
       Message message = Message.obtain();
       mHandler.sendMessage(message);
   }

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

mHandlerを使用します.removeCallbacksAndMessages(null);は、メッセージキュー内のすべてのメッセージとすべてのRunnableを削除します.もちろんmHandlerも使用できる.removeCallbacks();またはmHandler.removeMessages();で指定したRunnableとMessageを削除します.
四、スレッドによるメモリ漏れ
スレッドによるメモリの漏洩も、通常は一般的ですが、次の2つの例では、誰もがこのように書いています.
//——————test1
       new AsyncTask() {
           @Override
           protected Void doInBackground(Void... params) {
               SystemClock.sleep(10000);
               return null;
           }
       }.execute();
//——————test2
       new Thread(new Runnable() {
           @Override
           public void run() {
               SystemClock.sleep(10000);
           }
       }).start();

上記の非同期タスクとRunnableは匿名の内部クラスであるため、現在のActivityには暗黙的な参照があります.Activityが破棄される前にタスクが完了していない場合、Activityのメモリリソースが回収されず、メモリが漏洩します.正しい方法は、静的内部クラスを使用する方法です.次のようにします.
   static class MyAsyncTask extends AsyncTask {
       private WeakReference weakReference;

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

       @Override
       protected Void doInBackground(Void... params) {
           SystemClock.sleep(10000);
           return null;
       }

       @Override
       protected void onPostExecute(Void aVoid) {
           super.onPostExecute(aVoid);
           MainActivity activity = (MainActivity) weakReference.get();
           if (activity != null) {
               //...
           }
       }
   }
   static class MyRunnable implements Runnable{
       @Override
       public void run() {
           SystemClock.sleep(10000);
       }
   }
//——————
   new Thread(new MyRunnable()).start();
   new MyAsyncTask(this).execute();

これにより、Activityのメモリリソースの漏洩が回避されます.もちろん、Activityの破棄時にも、対応するタスクAsyncTask::cancel()をキャンセルし、バックグラウンドでリソースの浪費を回避する必要があります.
五、資源が閉鎖されていないことによるメモリ漏れ
BraodcastReceiver、ContentObserver、File、Cursor、Stream、Bitmapなどのリソースを使用している場合は、Activityの破棄時に直ちにクローズまたはログアウトする必要があります.そうしないと、これらのリソースは回収されず、メモリが漏洩します.