Pontos principais da entrevista do Android: vazamentos de memória comuns e soluções de otimização

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 contextparâmetros de entrada Activity, Servicecomo 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 AppSettingsum único caso, passando Activity.thiscomo contexttal AppSettingsclasse singleton sInstancepara manter uma Activityreferência quando encerramos Activityquando o Activitynão há uso, mas porque sIntancecomo um único estático Por exemplo (existe em todo o ciclo de vida do aplicativo) continuará a manter essa Activityreferência, resultando no Activityobjeto 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 contextparâ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 Activitycontexto usado ao construir o singleton , mas o Applicationcontexto 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 Activityfim de evitar a criação repetida info, será sInfousada 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) {
    }
}

InfoComo Activityum membro estático e mantém Activityuma referência, mas sInfocomo uma variável estática, o ciclo de vida é definitivamente mais longo do que isso Activity. Portanto, depois de Activitysair, ele sInfoainda é referenciado Activitye Activitynã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 Handlerescrevem 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, mHandlerse a Activityreferência não for mantida como uma variável estática , o ciclo de vida pode não ser Activitymais longo e não deve necessariamente levar a vazamentos de memória. Obviamente, este não é o caso!

Familiar Handlerde mensagens todos sabem, mHandlerserá salvo na mensagem enviada como uma variável de membro msg, que msgpreensão de mHandlerreferências, mas mHandleré Activityinstância não-estático interior classe, que mHandlerpreensão de Activityreferências, então podemos ser entendida como msguma indiretos participações Activityreferências . msgDepois que a mensagem é transmitida para a primeira fila MessageQueuee espera pelo Looperprocesso de polling ( MessageQueuee Looperestá associada a um thread, MessageQueueé Looperuma referência de variável de membro e Looperé armazenada na ThreadLocal). Então, depois de Activitysair, ele msgainda pode estar MessageQueuesem processamento ou sendo processado na fila de mensagens , o que levará Activityao Activityvazamento 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) {
                    // 做相应逻辑
                }
            }
        }
    }
}

mHandlerEle é mantido por meio de referências fracas Activity. Quando o GC executa a coleta de lixo, ele Activityrecupera 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 Activityvazamento de memória resultante. A referência enviada msgnão é mais Activityretirada, mas msgainda pode existir na fila de mensagens MessageQueue, então é melhor mover o retorno de chamada e a mensagem enviada Activityquando ela for destruída. mHandlerLivrar-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 Threadou AsyncTask.

Por exemplo, diretamente newum 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 AsyncTasktarefa 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 Threade AsyncTaskobjeto de classe interna anônima, o padrão externo implicitamente mantido em Activityreferências, levando à Activitymemória Ceder. Para evitar vazamentos de memória, você ainda precisa Handlerusar classes internas estáticas + aplicativos fracos como acima (o código não está listado, consulte a Hanlderredaçã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 Activitynão cancelarmos o registro após a destruição, a nova transmissão sempre existirá no sistema e manterá a Activityreferência como a classe interna não estática mencionada acima , causando vazamentos de memória. Portanto, o registro Activitydeve 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

TimerE TimerTaskno 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 Timercontinuar à 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 cancelpara fora Timere TimerTaskpara 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, HashMapcomo 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 removeou clearcoleçã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, Filefluxo ou Sqlite, Cursorpara 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 canclemé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 Activityno qual está localizado , o que torna Activityimpossível a liberação normalmente. Portanto , a animação da propriedade também deve ser descartada Activityquando for destruída para cancelevitar 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:

WebviewAs seguintes referências de Callbackretenção Activityfazem com que a Webviewmemória não seja liberada, mesmo que o Webview.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 WebViewantes 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 Activityreferê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 Activitytempo e animações de atributos são lembradas quando são destruídas cancel;
  • Os fluxos de arquivos e Cursoroutros recursos são encerrados a tempo;
  • ActivityAo destruir WebViewremovido 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]

 

Acho que você gosta

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