domingo, 31 de janeiro de 2016

Acessando o SQLite de forma assíncrona

Olá povo,

Não importa o quanto eu estude, sempre tem algo novo sobre Android que eu não sabia. Conversando com os meus amigos Diego Nascimento e André [Mion], descobri a classe AsyncQueryHandler que nos permite realizar as transações de banco de dados em uma Thread separada.
Acho que a maioria de vocês deve saber que quando o usuário está interagindo com o aplicativo, ele está utilizando o que chamamos de Main Thread ou UI Thread. Para que não tenhamos problemas de desempenho na renderização da UI, ela deve executar a 60 fps (frames por segundo). Se fizermos um cálculo rápido, podemos constatar que nenhuma operação que é feita na UI Thread deve demorar que 16 milissegundos. Caso contrário, perderemos frames a aplicação e o usuário começará a notar a aplicação "travando" ou demorando a responder.
Aqui no blog já falei sobre várias técnicas para não utilizar a Main Thread, tais como: AsyncTask, AsyncTaskLoader e LoaderManager. Essa última, em particular resolver parte do problema do título desse post, pois com o CursorLoader + LoaderManager podemos fazer a consulta em Content Provider em background. Perfeito! Mas... E as operações de inclusão, exclusão e atualização?
Para esses casos temos a classe AsyncQueryHandler. Essa classe facilita a realização dessas operações em background. Embora, o SQLite seja muito rápido, caso você esteja fazendo uma transação que utilize várias tabelas, ou mesmo que trabalhe com muitos registros, esse processamento poderá demorar bem mais que 16ms.
A utilização dessa classe é bem simples.
public void inserir() {
    ContentValues values = new ContentValues();
    values.put("titulo", "Lembrete");
    values.put("descricao", "Escrever post");

    Uri uri = Uri.parse("content://seu.content.provider/mensagens");

    // Começa o processo assíncrono
    new MeuAsync(getActivity()).startInsert(0, null, uri, values);
}

class MeuAsync extends  AsyncQueryHandler {
    // Usando WeakReference para evitar leak da Activity
    private WeakReference<Context> mContext;

    public MeuAsync(Context ctx) {
        super(ctx.getContentResolver());
        mContext = new WeakReference<>(ctx);
    }

    @Override
    protected void onInsertComplete(int token, Object cookie, Uri uri) {
        super.onInsertComplete(token, cookie, uri);
        if (mContext.get() != null) {
            Toast.makeText(mContext.get(),
                    "Registro inserido com sucesso!",
                    Toast.LENGTH_SHORT).show();
        }
    }
}
Perceba que estamos utilizando o método startInsert() passando como parâmetro: um identificador para essa transação; um objeto qualquer que podemos utilizar posteriormente no método de callback (aqui passamos null); a URI para o seu Content Provider; e os valores que serão inseridos.
De forma semelhando, poderíamos utilizar os métodos: startUpdate(), startDelete() e startQuery().

Na classe MeuAsync, ficam os métodos que serão chamados após a realização da operação em background. No nosso exemplo utilizamos apenas o o onInsertComplete. Mas nos casos de atualização, exclusão e busca de registros, utilizaríamos os métodos onUpdateComplete(), onDeleteComplete e onQueryComplete().

4br4ç05,
nglauber

6 comentários:

Alessandro Barreto disse...

Interessante essa classe. Mas lá vai algumas duvidas, 1- Se a minha aplicação não tiver um Content Provide como usar essa classe? 2- Quando eu for criar um banco de dados para minha aplicação tenho que criar um Content Provide é aconselhável ?

Nelson Glauber disse...

Oi Alessandro,

Content Provider é um dos quatro principais componentes do Android (junto com Activity, Service e Broadcast Receiver).
Seu uso é altamente recomendado e facilita bastante a integração com outros componentes do sistema. Sem contar que temos uma forma padrão de abstrair o acesso a dados do aplicativo.

4br4ç05,
nglauber

Hugaleno Bezerra disse...

Comecei a pouco tempo no android, comprei o seu livro pra me ajudar e tá ajudando bastante mas o que eu queria saber era se seguir o livro é o suficiente? Eu vou tentando reproduzir os códigos mas na grande maioria das vezes tenho que olhar o seu código pra conseguir seguir adiante, você teria alguma dica? Qual seria a melhor forma de realmente aprender a programar para android?
email:hugaleno@gmail.com

Agradeço a atenção desde já,

Hugaleno Bezerra, mero mortal! :)

Nelson Glauber disse...

Oi Hugaleno,

Primeiramente obrigado por adquirir o livro, tomara que goste e aprenda muito com ele.
Em relação a sua pergunta, posso dizer sem sombra de dúvidas que APENAS o livro NÃO é suficiente. Digo isso, porque até hoje acho coisas que não sabia sobre Android a cada dia que trabalho com a plataforma. Esse post é um exemplo disso (e outros como o RecyclerView).
É importante participar de eventos, ver vídeos, e achar outras fontes de consulta para analisar os pontos fortes e fracos de cada uma.
Então visite sempre:
http://developer.android.com (site oficial dos desenvolvedores Android com documentação, treinamentos, etc.)
http://android-developers.blogspot.com.br/ (blog oficial sobre Android que trás as novidades sobre Android)
https://www.udacity.com/courses/android (site com cursos gratuitos sobre Android patrocinado pelo Google)

Ah! E para aprender "realmente", tem que ter alguns aplicativos desenvolvidos :)

4br4ç05,
nglauber

CTN Cardoso disse...

Como roda em uma Thread separada, não pode ocorrer erro do banco já ter uma conexão aberta? Neste caso retornar que o banco está bloqueado?

Nelson Glauber disse...

Oi CNT Cardoso,

O fato de ser em uma thread separada não quer dizer que você terá várias threads acessando o banco. E mesmo assim o SQLite suporta operações de insert, delete e update em múltiplas threads desde que seja com a mesma conexão (instância de SQLiteOpenHelper).
A boa prática é sempre fechar o banco ao terminar uma operação e tudo ficará bem :)

4br4ç05,
nglauber