Banco de dados Android SQLite quando fechar

96

Estou trabalhando com um banco de dados SQLite no Android. Meu gerenciador de banco de dados é um singleton e agora abre uma conexão com o banco de dados quando ele é inicializado. É seguro deixar o banco de dados aberto o tempo todo para que quando alguém chamar minha turma para trabalhar com o banco de dados já esteja aberto? Ou devo abrir e fechar o banco de dados antes e depois de cada acesso ser necessário. Há algum mal em apenas deixá-lo aberto o tempo todo?

Obrigado!

w.donahue
fonte

Respostas:

60

Eu iria mantê-lo aberto o tempo todo e fechá-lo em algum método de ciclo de vida, como onStopou onDestroy. dessa forma, você pode verificar facilmente se o banco de dados já está em uso chamando isDbLockedByCurrentThreadou isDbLockedByOtherThreadsno único SQLiteDatabaseobjeto todas as vezes antes de usá-lo. isso evitará múltiplas manipulações no banco de dados e salvará seu aplicativo de um possível travamento

então, em seu singleton, você pode ter um método como este para obter seu único SQLiteOpenHelperobjeto:

private SQLiteDatabase db;
private MyDBOpenHelper mySingletonHelperField;
public MyDBOpenHelper getDbHelper() {
    db = mySingletonHelperField.getDatabase();//returns the already created database object in my MyDBOpenHelper class(which extends `SQLiteOpenHelper`)
    while(db.isDbLockedByCurrentThread() || db.isDbLockedByOtherThreads()) {
        //db is locked, keep looping
    }
    return mySingletonHelperField;
}

então, sempre que você quiser usar seu objeto auxiliar aberto, chame este método getter (certifique-se de que seja encadeado)

outro método em seu singleton pode ser (denominado CADA VEZ antes de tentar chamar o getter acima):

public void setDbHelper(MyDBOpenHelper mySingletonHelperField) {
    if(null == this.mySingletonHelperField) {
        this.mySingletonHelperField = mySingletonHelperField;
        this.mySingletonHelperField.setDb(this.mySingletonHelperField.getWritableDatabase());//creates and sets the database object in the MyDBOpenHelper class
    }
}

você pode querer fechar o banco de dados no singleton também:

public void finalize() throws Throwable {
    if(null != mySingletonHelperField)
        mySingletonHelperField.close();
    if(null != db)
        db.close();
    super.finalize();
}

se os usuários de seu aplicativo têm a capacidade de criar muitas interações de banco de dados muito rapidamente, você deve usar algo como demonstrei acima. mas se houver interações mínimas com o banco de dados, eu não me preocuparia com isso, apenas criar e fechar o banco de dados todas as vezes.

James
fonte
Eu poderia acelerar o acesso ao banco de dados por um fator de 2 com esta abordagem (manter o banco de dados aberto), obrigado
teh.fonsi
2
Girar é uma técnica muito ruim. Veja minha resposta abaixo.
mixel
1
@mixel bom ponto. Acredito que postei esta resposta antes que a API 16 estivesse disponível, mas posso estar errado
James,
1
@binnyb Acho melhor atualizar sua resposta para que não engane as pessoas.
mixel de
3
WARN isDbLockedByOtherThreads () está depreciado e retorna falso desde a API 16. Também verificar isDbLockedByCurrentThread () dará um loop infinito porque interrompe o thread atual e nada pode 'desbloquear o banco de dados' para que este método retorne verdadeiro.
matreshkin
20

A partir de agora, não há necessidade de verificar se o banco de dados está bloqueado por outro thread. Enquanto você usa o singleton SQLiteOpenHelper em cada thread, você está seguro. Da isDbLockedByCurrentThreaddocumentação:

O nome desse método vem de uma época em que ter uma conexão ativa com o banco de dados significava que o encadeamento estava segurando um bloqueio real no banco de dados. Hoje em dia, não existe mais um verdadeiro "bloqueio de banco de dados", embora os threads possam bloquear se não puderem adquirir uma conexão de banco de dados para realizar uma operação específica.

isDbLockedByOtherThreads está obsoleto desde o nível 16 da API.

mixel
fonte
Não faz sentido usar uma instância por thread. SQLiteOpenHelper é seguro para threads. Isso também é muito ineficiente em termos de memória. Em vez disso, um aplicativo deve manter uma única instância de SQLiteOpenHelper por banco de dados. Para melhor simultaneidade, é recomendado usar WriteAheadLogging, que fornece pooling de conexões para até 4 conexões.
ejboy
@ejboy eu quis dizer isso. "Use singleton (= um) SQLiteOpenHelper em cada thread", não "por thread".
mixel de
15

Em relação às perguntas:

Meu gerenciador de banco de dados é um singleton e agora abre uma conexão com o banco de dados quando ele é inicializado.

Devemos dividir 'abrindo DB', 'abrindo uma conexão'. SQLiteOpenHelper.getWritableDatabase () fornece um banco de dados aberto. Mas não precisamos controlar as conexões, pois isso é feito internamente.

É seguro deixar o banco de dados aberto o tempo todo para que quando alguém chamar minha turma para trabalhar com o banco de dados já esteja aberto?

Sim, ele é. As conexões não param se as transações forem fechadas corretamente. Observe que seu banco de dados também será fechado automaticamente se o GC o finalizar.

Ou devo abrir e fechar o banco de dados antes e depois de cada acesso ser necessário.

Fechar a instância SQLiteDatabase não dá nada extraordinário, exceto fechar conexões, mas isso é ruim para o desenvolvedor se houver algumas conexões no momento. Além disso, após SQLiteDatabase.close (), SQLiteOpenHelper.getWritableDatabase () retornará uma nova instância.

Há algum mal em apenas deixá-lo aberto o tempo todo?

Não, não há. Observe também que fechar o banco de dados em um momento e thread não relacionados, por exemplo, em Activity.onStop () pode fechar conexões ativas e deixar os dados em estado inconsistente.

matreshkin
fonte
Obrigado, depois de ler suas palavras "após SQLiteDatabase.close (), SQLiteOpenHelper.getWritableDatabase () retornará uma nova instância" Percebi que finalmente tenho uma resposta para um antigo problema do meu aplicativo. Para um problema que se tornou crítico após a migração para o Android 9 (consulte stackoverflow.com/a/54224922/297710 )
yvolk
1

Do ponto de vista do desempenho, a maneira ideal é manter uma única instância de SQLiteOpenHelper no nível do aplicativo. Abrir o banco de dados pode ser caro e é uma operação de bloqueio, portanto não deve ser feito no thread principal e / ou nos métodos de ciclo de vida da atividade.

O método setIdleConnectionTimeout () (introduzido no Android 8.1) pode ser usado para liberar RAM quando o banco de dados não é usado. Se o tempo limite de inatividade for definido, as conexões do banco de dados serão fechadas após um período de inatividade, ou seja, quando o banco de dados não foi acessado. As conexões serão reabertas de forma transparente para o aplicativo, quando uma nova consulta for executada.

Além disso, um aplicativo pode chamar releaseMemory () quando entra em segundo plano ou detecta pressão de memória, por exemplo, em onTrimMemory ()

ejboy
fonte
0

Você também pode usar ContentProvider. Ele fará isso por você.

Gregory Buiko
fonte
-9

Crie seu próprio contexto de aplicativo e, em seguida, abra e feche o banco de dados a partir dele. Esse objeto também possui um método OnTerminate () que você pode usar para fechar a conexão. Ainda não tentei, mas parece uma abordagem melhor.

@binnyb: Não gosto de usar finalize () para fechar a conexão. Pode funcionar, mas pelo que entendi, escrever código em um método Java finalize () é uma má ideia.

Leander
fonte
Você não quer depender da finalização porque nunca sabe quando ela será chamada. Um objeto pode ficar no limbo até que o GC decida limpá-lo, e só então finalize () será chamado. É por isso que é inútil. A maneira correta de fazer isso é ter um método que é chamado quando o objeto não é mais usado (independentemente de ser ou não coletado como lixo), e é exatamente isso que onTerminate () é.
erwan
5
Os documentos dizem muito claramente que onTerminatenão será chamado em um ambiente de produção - developer.android.com/reference/android/app/…
Eliezer