Se um objeto inútil (um objeto que não precisa mais ser usado) ainda for referenciado por outros objetos, o objeto não pode ser reciclado pelo sistema, de modo que a unidade de memória ocupada pelo objeto no heap não pode ser liberada, causando um desperdício de espaço de memória. A situação é um vazamento de memória.
No desenvolvimento do Android, alguns hábitos de programação ruins podem causar vazamentos de memória em nossos aplicativos desenvolvidos. Aqui estão alguns cenários comuns de vazamento de memória e soluções de otimização em entrevistas de desenvolvimento Android.
Singleton causa vazamento de memória
O modo singleton é frequentemente usado no desenvolvimento do Android, mas se usado incorretamente, ele causará vazamentos de memória. Como a natureza estática do singleton torna seu ciclo de vida tão longo quanto o ciclo de vida do aplicativo, se um objeto não é mais útil, mas o singleton ainda mantém uma referência a ele, então ele não pode ser usado normalmente durante todo o ciclo de vida do aplicativo. Reciclagem, o que leva a vazamentos de memória.
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;
}
}
Como o código acima, neste único caso, se chamarmos o getInstance(Context context)
método quando os context
parâmetros de entrada Activity
, Service
como o contexto, levarão a vazamentos de memória.
Com Activity
, por exemplo, quando iniciamos um Activity
, e chamamos getInstance(Context context)
o método para obter AppSettings
um único caso, passando Activity.this
como context
tal AppSettings
classe singleton sInstance
para manter uma Activity
referência quando encerramos Activity
quando o Activity
não há uso, mas porque sIntance
como um único estático Por exemplo (existe em todo o ciclo de vida do aplicativo) continuará a manter essa Activity
referência, resultando no Activity
objeto não pode ser reciclado e liberado, o que causa um vazamento de memória.
Para evitar vazamentos de memória causados por tal singleton, podemos alterar os context
parâmetros para o contexto global:
private AppSettings(Context context) {
this.mContext = context.getApplicationContext();
}
O contexto global Application Context
é o contexto da aplicação, desde que seja o ciclo de vida de um singleton, de forma que vazamentos de memória sejam evitados.
O padrão singleton corresponde ao ciclo de vida da aplicação, então tentamos evitar o Activity
contexto usado ao construir o singleton , mas o Application
contexto usado .
Variáveis estáticas causam vazamentos de memória
Variáveis estáticas são armazenadas na área do método, e seu ciclo de vida começa desde o carregamento da classe até o final de todo o processo. Depois que a variável estática é inicializada, a referência que ela contém não será liberada até que o processo termine.
Por exemplo, a seguinte situação, a Activity
fim de evitar a criação repetida info
, será sInfo
usada como uma variável 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) {
}
}
Info
Como Activity
um membro estático e mantém Activity
uma referência, mas sInfo
como uma variável estática, o ciclo de vida é definitivamente mais longo do que isso Activity
. Portanto, depois de Activity
sair, ele sInfo
ainda é referenciado Activity
e Activity
não pode ser reciclado, o que leva a um vazamento de memória.
No desenvolvimento do Android, os acervos estáticos podem muitas vezes levar a vazamentos de memória devido ao ciclo de vida inconsistente de seu uso. Portanto, ao criar acervos estáticos, precisamos considerar a relação de referência entre cada membro e tentar o nosso melhor Use variáveis mantidas estaticamente com moderação para evitar vazamentos de memória. Claro, também podemos redefinir o valor estático para nulo em um momento apropriado para que ele não mantenha mais uma referência, o que também pode evitar vazamentos de memória.
Classes internas não estáticas causam vazamentos de memória
Classes internas não estáticas (incluindo classes internas anônimas) manterão referências a classes externas por padrão. Quando o ciclo de vida de objetos de classe interna não estáticos for mais longo do que o de objetos de classe externa, ocorrerão vazamentos de memória.
Vazamentos de memória causados por classes internas não estáticas são usados em um cenário típico no desenvolvimento do Android Handler
. Muitos desenvolvedores Handler
escrevem desta forma:
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) {
// 做相应逻辑
}
}
};
}
Algumas pessoas podem dizer que, mHandler
se a Activity
referência não for mantida como uma variável estática , o ciclo de vida pode não ser Activity
mais longo e não deve necessariamente levar a vazamentos de memória. Obviamente, este não é o caso!
Familiar Handler
de mensagens todos sabem, mHandler
será salvo na mensagem enviada como uma variável de membro msg
, que msg
preensão de mHandler
referências, mas mHandler
é Activity
instância não-estático interior classe, que mHandler
preensão de Activity
referências, então podemos ser entendida como msg
uma indiretos participações Activity
referências . msg
Depois que a mensagem é transmitida para a primeira fila MessageQueue
e espera pelo Looper
processo de polling ( MessageQueue
e Looper
está associada a um thread, MessageQueue
é Looper
uma referência de variável de membro e Looper
é armazenada na ThreadLocal
). Então, depois de Activity
sair, ele msg
ainda pode estar MessageQueue
sem processamento ou sendo processado na fila de mensagens , o que levará Activity
ao Activity
vazamento de memória que não pode ser recuperado .
Geralmente, se você deseja usar classes internas no desenvolvimento do Android, mas deseja evitar vazamentos de memória, geralmente usa classes internas estáticas + referências fracas .
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
Ele é mantido por meio de referências fracas Activity
. Quando o GC executa a coleta de lixo, ele Activity
recupera e libera a unidade de memória ocupada quando a encontra . Dessa forma, não ocorrerão vazamentos de memória.
A abordagem acima evita o Activity
vazamento de memória resultante. A referência enviada msg
não é mais Activity
retirada, mas msg
ainda pode existir na fila de mensagens MessageQueue
, então é melhor mover o retorno de chamada e a mensagem enviada Activity
quando ela for destruída. mHandler
Livrar-se de.
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
Outra situação em que classes internas não estáticas causam vazamentos de memória é usar Thread
ou AsyncTask
.
Por exemplo, diretamente new
um thread filho em Activity 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();
}
}
Ou crie diretamente uma nova AsyncTask
tarefa assíncrona:
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();
}
}
Muitos iniciantes vão gostar deste novo thread acima e tarefas assíncronas, tal formulação não conhece muito hostil, desta forma, os novos threads filho Thread
e AsyncTask
objeto de classe interna anônima, o padrão externo implicitamente mantido em Activity
referências, levando à Activity
memória Ceder. Para evitar vazamentos de memória, você ainda precisa Handler
usar classes internas estáticas + aplicativos fracos como acima (o código não está listado, consulte a Hanlder
redação correta acima ).
Vazamento de memória devido a não registrado ou retorno de chamada
Por exemplo Activity
, se registrarmos a transmissão em, se Activity
não cancelarmos o registro após a destruição, a nova transmissão sempre existirá no sistema e manterá a Activity
referência como a classe interna não estática mencionada acima , causando vazamentos de memória. Portanto, o registro Activity
deve ser cancelado após a destruição da transmissão .
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);
}
}
Ao registrar o modo de observação, se não for cancelado a tempo, também causará vazamentos de memória. Por exemplo Retrofit+RxJava
, o retorno de chamada do observador usado para registrar a solicitação de rede também contém uma referência externa como uma classe interna anônima, portanto, você precisa se lembrar de cancelar o registro quando ele não for usado ou destruído.
Timer e TimerTask causam vazamentos de memória
Timer
E TimerTask
no Android geralmente são usados para fazer algumas tarefas de sincronização ou cíclicas, como a realização do carrossel 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();
}
}
Quando nós Activity
, quando destruído, é possível Timer
continuar à espera de ser executado TimerTask
, ele mantém Atividade de referência não podem ser recuperados, por isso, quando nós Atividade destruídos imediatamente cancel
para fora Timer
e TimerTask
para evitar uma fuga de memória.
Os objetos na coleção não são limpos causando um vazamento de memória
Isso é melhor compreendido, se um objeto for colocado ArrayList
, HashMap
como a coleção, essa coleção conterá o objeto. Quando não precisamos mais desse objeto, não o removemos da coleção, portanto, enquanto a coleção ainda estiver em uso (e esse objeto for inútil), esse objeto causa um vazamento de memória. E se a coleção for referenciada estaticamente, esses objetos inúteis na coleção até mesmo causarão vazamentos de memória. Portanto, ao usar uma coleção, você deve remover os objetos não utilizados da coleção remove
ou clear
coleção a tempo para evitar vazamentos de memória.
Os recursos não são fechados ou liberados, causando vazamentos de memória
Em uso IO
, File
fluxo ou Sqlite
, Cursor
para encerrar imediatamente e outros recursos. Esses recursos geralmente usam buffers durante as operações de leitura e gravação. Se não forem fechados a tempo, esses objetos de buffer permanecerão ocupados e não poderão ser liberados, resultando em vazamentos de memória. Portanto, nós os fechamos a tempo quando não precisamos mais usá-los, para que o buffer possa ser liberado a tempo de evitar vazamentos de memória.
A animação da propriedade causa vazamento de memória
A animação também é uma tarefa demorada. Por exemplo Activity
, a propriedade animation ( ObjectAnimator
) é iniciada em , mas nenhum cancle
método é chamado quando ela é destruída . Embora não possamos mais ver a animação, ela continuará a ser reproduzida. O controle é referenciado pelo controle Activity
no qual está localizado , o que torna Activity
impossível a liberação normalmente. Portanto , a animação da propriedade também deve ser descartada Activity
quando for destruída para cancel
evitar vazamentos de memória.
@Override
protected void onDestroy() {
super.onDestroy();
mAnimator.cancel();
}
WebView causa vazamento de memória
Em relação ao vazamento de memória do WebView, como o WebView ocupará a memória por muito tempo após carregar a página da web e não pode ser liberado, precisamos chamar seu destory()
método para destruí-lo para liberar a memória após a destruição da Activity .
Além disso WebView
, vi esta situação ao consultar informações relacionadas sobre vazamentos de memória:
Webview
As seguintes referências deCallback
retençãoActivity
fazem com que aWebview
memória não seja liberada, mesmo que oWebview.destory()
método seja chamado, o problema não pode ser resolvido (após o Android 5.1).
A solução final é remover o contêiner pai WebView
antes de destruí- lo WebView从
e, em seguida, destruí-lo WebView
. Para o processo de análise detalhado, consulte este artigo: Solução de vazamento de memória WebView .
@Override
protected void onDestroy() {
super.onDestroy();
// 先从父控件中移除WebView
mWebViewContainer.removeView(mWebView);
mWebView.stopLoading();
mWebView.getSettings().setJavaScriptEnabled(false);
mWebView.clearHistory();
mWebView.removeAllViews();
mWebView.destroy();
}
Resumindo
O vazamento de memória é um aspecto mais importante na otimização de memória do Android. Muitas vezes, quando ocorre um vazamento de memória em um programa, podemos não ser capazes de notar. Bons hábitos devem ser desenvolvidos durante o processo de codificação. Em resumo, contanto que você siga os seguintes pontos, pode evitar vazamentos de memória na maioria dos casos:
- Tente não usar
Activity
referências ao construir singletons ; - Preste atenção à supressão de objetos de aplicação ou use menos referências estáticas quando referências estáticas;
- Use classes internas estáticas + referências flexíveis em vez de classes internas não estáticas;
- Cancelar a transmissão ou o registro do observador a tempo;
- Tarefas que consomem
Activity
tempo e animações de atributos são lembradas quando são destruídascancel
; - Os fluxos de arquivos e
Cursor
outros recursos são encerrados a tempo; Activity
Ao destruirWebView
removido e destruído.
PS: Sobre mim (autor)
Eu sou um belo Android siege lion com 6 anos de experiência em desenvolvimento . Lembre-se de gostar depois de ler e desenvolver bons hábitos de leitura. A pesquisa do WeChat "Programming Ape Development Center" presta atenção a este programador que gosta de escrever produtos secos.
Além disso, levou dois anos para organizar e coletar o PDF do site de teste completo para entrevistas de primeira linha do Android. As informações [versão completa] foram atualizadas em meu [Github] . Amigos que precisam de entrevistas podem consultá-las. Se ajudar, você pode Clique na estrela!
Endereço: [https://github.com/733gh/xiongfan]