Puntos clave de la entrevista de Android: fugas de memoria comunes y soluciones de optimización

Si un objeto inútil (un objeto que ya no necesita ser usado) todavía es referenciado por otros objetos, el sistema no puede reciclar el objeto, por lo que la unidad de memoria ocupada por el objeto en el montón no puede liberarse, causando un desperdicio de espacio de memoria. La situación es una pérdida de memoria.

En el desarrollo de Android, algunos malos hábitos de programación pueden provocar pérdidas de memoria en nuestras aplicaciones desarrolladas. A continuación, se muestran algunos escenarios comunes de pérdida de memoria y soluciones de optimización en entrevistas de desarrollo de Android.

Singleton causa pérdida de memoria

El modo singleton se usa a menudo en el desarrollo de Android, pero si se usa incorrectamente, causará pérdidas de memoria. Debido a que la naturaleza estática del singleton hace que su ciclo de vida sea tan largo como el ciclo de vida de la aplicación, si un objeto ya no es útil, pero el singleton todavía tiene su referencia, entonces no se puede usar normalmente durante todo el ciclo de vida de la aplicación. Reciclaje, que conduce a pérdidas de memoria.

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;
    }
}

Al igual que el código anterior a este caso único, si llamamos al getInstance(Context context)método cuando los contextparámetros entrantes Activity, Servicecomo el contexto, conducirán a pérdidas de memoria.

Con Activity, por ejemplo, cuando comenzamos una Activity, y llamamos getInstance(Context context)al método para obtener AppSettingsun solo caso, pasar Activity.thiscomo contexttal AppSettingsclase Singleton sInstancepara mantener una Activityreferencia cuando dejamos Activitycuando el Activityno sirve de nada, sino porque sIntanceen una sola estática Por ejemplo, (existe en todo el ciclo de vida de la aplicación) seguirá teniendo esta Activityreferencia, lo Activityque provocará que el objeto no se pueda reciclar y liberar, lo que provoca una pérdida de memoria.

Para evitar pérdidas de memoria causadas por dicho singleton, podemos cambiar los contextparámetros al contexto global:

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

El contexto global Application Contextes el contexto de la aplicación, siempre que sea el ciclo de vida de un singleton, de modo que se eviten pérdidas de memoria.

El patrón singleton corresponde al ciclo de vida de la aplicación, por lo que intentamos evitar el Activitycontexto usado al construir el singleton , pero el Applicationcontexto usado .

Las variables estáticas provocan pérdidas de memoria

Las variables estáticas se almacenan en el área de métodos y su ciclo de vida comienza desde la carga de la clase hasta el final de todo el proceso. Una vez que se inicializa la variable estática, la referencia que contiene no se liberará hasta que finalice el proceso.

Por ejemplo, la siguiente situación, Activitypara evitar la creación repetida info, se sInfoutilizará como variable estática:

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) {
    }
}

InfoComo Activitymiembro estático, y tiene Activityuna referencia, pero sInfocomo variable estática, el ciclo de vida es definitivamente más largo que eso Activity. Entonces, después de Activitysalir, sInfotodavía se hace referencia a él Activityy Activityno se puede reciclar, lo que conduce a una pérdida de memoria.

En el desarrollo de Android, las existencias estáticas a menudo pueden provocar pérdidas de memoria debido al ciclo de vida inconsistente de su uso. Por lo tanto, al crear existencias estáticas, debemos considerar la relación de referencia entre cada miembro y hacer nuestro mejor esfuerzo Utilice las variables mantenidas estáticamente con moderación para evitar pérdidas de memoria. Por supuesto, también podemos restablecer el valor estático a nulo en el momento adecuado para que ya no contenga una referencia, lo que también puede evitar pérdidas de memoria.

Las clases internas no estáticas causan pérdidas de memoria

Las clases internas no estáticas (incluidas las clases internas anónimas) contendrán referencias a clases externas de forma predeterminada. Cuando el ciclo de vida de los objetos de clase interna no estática es más largo que el ciclo de vida de los objetos de clase externa, se producirán pérdidas de memoria.

Las pérdidas de memoria causadas por clases internas no estáticas se utilizan en un escenario típico en el desarrollo de Android Handler. Muchos desarrolladores Handlerescriben de esta manera:

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) {
                // 做相应逻辑
            }
        }
    };
}

Algunas personas pueden decir que mHandlersi la Activityreferencia no se mantiene como una variable estática , el ciclo de vida puede no ser Activitymás largo y no necesariamente debe conducir a pérdidas de memoria. ¡Obviamente este no es el caso!

Los Handlermensajes familiares que todos conocen, mHandlerse guardarán en el mensaje enviado como una variable miembro msg, que msgcontiene mHandlerreferencias, pero mHandleres Activityuna instancia de clase interna no estática, que mHandlercontiene Activityreferencias, entonces podemos ser entendidos como referencias de msgexistencias indirectas. Activity. msgUna vez que el mensaje se transmite a la primera cola MessageQueuey espera Looperel proceso de sondeo ( MessageQueuey Looperestá asociado con un hilo, MessageQueuees Looperuna variable miembro que hace referencia y Looperse almacena en ThreadLocalel). Luego, después de Activitysalir, es msgposible que aún exista en la cola de mensajes MessageQueuesin procesar o en proceso, entonces esto conducirá Activitya Activityla pérdida de memoria que no se puede recuperar .

Generalmente, si desea usar clases internas en el desarrollo de Android, pero desea evitar pérdidas de memoria, generalmente usa clases internas estáticas + referencias débiles .

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) {
                    // 做相应逻辑
                }
            }
        }
    }
}

mHandlerSe mantiene por medio de referencias Activitydébiles.Cuando el GC realiza la recolección de basura, Activityrecuperará y liberará la unidad de memoria ocupada cuando la encuentre . De esta forma, no se producirán pérdidas de memoria.

El enfoque anterior evita la Activitypérdida de memoria resultante. La referencia enviada msgya no se Activityretiene, pero msgaún puede existir en la cola de mensajes MessageQueue, por lo que es mejor mover la devolución de llamada y el mensaje enviado Activitycuando se destruye. mHandlerDeshacerse de.

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

Otra situación en la que las clases internas no estáticas causan pérdidas de memoria es usar Threado AsyncTask.

Por ejemplo, directamente newun hilo secundario en la Actividad 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();
    }
}

O cree directamente una nueva AsyncTasktarea asincrónica:

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();
    }
}

A muchos principiantes les gustará este nuevo hilo anterior y las tareas asincrónicas, tal redacción no sabe muy poco amigable, de esta manera, los nuevos hilos secundarios Thready AsyncTaskel objeto de clase interna anónimo, el externo predeterminado implícitamente mantenido en Activityreferencias, lo que lleva a la Activitymemoria Ceda el paso. Para evitar pérdidas de memoria, aún debe Handlerusar clases internas estáticas + aplicaciones débiles como se indicó anteriormente (el código no está en la lista, consulte Hanlderla escritura correcta arriba ).

Pérdida de memoria debido a devolución de llamada o no registrada

Por ejemplo Activity, si nos registramos para transmitir en, si Activityno cancelamos el registro después de la destrucción, entonces esta nueva transmisión siempre existirá en el sistema y mantendrá Activityreferencias como la clase interna no estática mencionada anteriormente , causando pérdidas de memoria. Por lo tanto, el registro Activitydebe cancelarse después de que se destruya la transmisión .

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);
    }
}

Al registrar el modo de observación, si no se cancela a tiempo, también provocará pérdidas de memoria. Por ejemplo Retrofit+RxJava, la devolución de llamada del observador utilizada para registrar la solicitud de red también contiene una referencia externa como una clase interna anónima, por lo que debe recordar cancelar el registro cuando no se use o se destruya.

Timer y TimerTask causan pérdidas de memoria

TimerY TimerTasken Android se suelen utilizar para realizar algunas tareas de tiempo o cíclicas, como la realización de carrusel infinito 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();
    }
}

Cuando Activitycuando se destruye, es posible Timerseguir esperando a ser ejecutado TimerTask, lleva a cabo la actividad de referencia no se puede recuperar, así que cuando nos Actividad destruidos inmediatamente cancela cabo Timery TimerTaskcon el fin de evitar una pérdida de memoria.

Los objetos de la colección no se limpian, lo que provoca una pérdida de memoria.

Esto se comprende mejor, si se coloca un objeto ArrayList, HashMapcomo la colección, esta colección contendrá el objeto. Cuando ya no necesitamos este objeto, no lo eliminamos de la colección, así que mientras la colección esté todavía en uso (y este objeto sea inútil), este objeto causa una pérdida de memoria. Y si la colección tiene una referencia estática, esos objetos inútiles en la colección incluso causarán pérdidas de memoria. Por lo tanto, al utilizar una colección, debe eliminar los objetos no utilizados de la colección removeo clearcolección a tiempo para evitar pérdidas de memoria.

Los recursos no se cierran o liberan causando pérdidas de memoria

En uso IO, Fileflujo o Sqlite, Cursorpara apagar rápidamente y otros recursos. Estos recursos suelen utilizar búferes durante las operaciones de lectura y escritura. Si no se cierran a tiempo, estos objetos de búfer permanecerán ocupados y no podrán liberarse, lo que provocará pérdidas de memoria. Por lo tanto, los cerramos a tiempo cuando no necesitamos usarlos, para que el búfer pueda liberarse a tiempo para evitar pérdidas de memoria.

La animación de propiedad causa pérdida de memoria

La animación también es una tarea que requiere mucho tiempo. Por ejemplo Activity, la propiedad animation ( ObjectAnimator) se inicia en , pero no se llama a ningún canclemétodo cuando se destruye . Aunque ya no podemos ver la animación, la animación continuará reproduciéndose. El control está referenciado por el control Activityen el que se encuentra , lo que Activityimposibilita su liberación con normalidad. Por lo tanto , la animación de propiedad también debe descartarse Activitycuando se destruye para cancelevitar pérdidas de memoria.

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

WebView causa pérdida de memoria

Con respecto a la fuga de memoria de WebView, porque WebView ocupará la memoria durante mucho tiempo después de cargar la página web y no se puede liberar, por lo que debemos llamar a su destory()método para destruirlo y liberar la memoria después de que se destruye la actividad .

Además WebView, vi esta situación al consultar información relacionada sobre fugas de memoria:

WebviewLas siguientes referencias de Callbackretención Activityhacen que la Webviewmemoria no se pueda liberar, incluso si se llama Webview.destory()y otros métodos no pueden resolver el problema (después de Android 5.1).

La solución final es quitar el contenedor principal WebViewantes de destruirlo WebView从y luego destruirlo WebView. Para conocer el proceso de análisis detallado, consulte este artículo: Solución de pérdida de memoria de WebView .

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

para resumir

La pérdida de memoria es un aspecto más importante en la optimización de la memoria de Android. Muchas veces, cuando se produce una pérdida de memoria en un programa, es posible que no podamos notarlo. Se deben desarrollar buenos hábitos durante el proceso de codificación. En resumen, siempre que siga los siguientes puntos, puede evitar pérdidas de memoria en la mayoría de los casos:

  • Trate de no utilizar Activityreferencias al construir singletons ;
  • Preste atención al borrado de los objetos de la aplicación o utilice referencias menos estáticas cuando se trate de referencias estáticas;
  • Utilice clases internas estáticas + referencias suaves en lugar de clases internas no estáticas;
  • Cancelar la transmisión o el registro de observador a tiempo;
  • ActivityLas tareas que consumen mucho tiempo y las animaciones de atributos se recuerdan cuando se destruyen cancel;
  • Los flujos de archivos y Cursorotros recursos se cierran a tiempo;
  • ActivityAl destruir WebVieweliminado y destruido.

PD: Acerca de mí (autor)


Soy un guapo león de asedio de Android con 6 años de experiencia en desarrollo . Recuerda que te gustará después de leer y desarrollar buenos hábitos de lectura. WeChat search "Programming Ape Development Center" presta atención a este programador al que le gusta escribir productos secos.

Además, se necesitaron dos años para organizar y recopilar el PDF del sitio de prueba completo para entrevistas de primera línea de Android. La información [versión completa] se ha actualizado en mi [Github] . Los amigos que necesitan entrevistas pueden consultarla. Si te ayuda, puedes Haga clic en estrella!

Dirección: [https://github.com/733gh/xiongfan]

 

Supongo que te gusta

Origin blog.csdn.net/qq_39477770/article/details/109387413
Recomendado
Clasificación