Coil é uma biblioteca para carregar imagens no Android, escrita em Kotlin e construída sobre corrotinas. De acordo com a documentação oficial, a biblioteca suporta Memory Cache, Disk Cache e transformações com aceleração de hardware. Coil se destaca pelo tamanho mínimo do APK (cerca de 150 KB) e total compatibilidade com Jetpack Compose.
Principais pontos
Coil (Coroutine Image Loader) é uma biblioteca para carregar imagens no Android, totalmente escrita em Kotlin e que utiliza corrotinas para operações assíncronas. Ela fornece uma API unificada para carregar imagens bitmap da rede, recursos, sistema de arquivos e Content Provider, com cache automático em vários níveis.
Ao contrário do Glide e Picasso, o Coil usa Kotlin Coroutines em vez de cadeias de callbacks, tornando o código mais linear e previsível. Todas as operações de carregamento e decodificação são executadas em threads de fundo através do dispatcher Dispatchers.IO, com resultados entregues na thread principal sem alternância explícita.
O Coil suporta transformações (Round, Blur, Grayscale), animações de transição, SVG e GIF, bem como Targets personalizados para exibição não padrão. De acordo com o Google I/O 2023, o Coil é recomendado em tutoriais oficiais do Jetpack Compose junto com o Glide.
ImageLoader é o componente principal do Coil, responsável por executar requisições de carregamento e gerenciar o cache. Cada instância contém referências ao MemoryCache, DiskCache, BitmapPool e um pool de corrotinas. Por padrão, é usado um singleton criado através de Coil.imageLoader(context).
ImageRequest é um objeto que descreve uma única requisição de carregamento de imagem: fonte de dados (URL, URI, recurso Int), ImageView ou Target de destino, transformações, configurações de cache e placeholder. O ImageRequest é construído através de um builder, garantindo flexibilidade e legibilidade.
val request = ImageRequest.Builder(context)
.data("https://example.com/image.jpg")
.crossfade(true)
.size(512, 512)
.transformations(listOf(RoundedCornersTransformation(12f)))
.memoryCachePolicy(CachePolicy.ENABLED)
.diskCachePolicy(CachePolicy.ENABLED)
.target(imageView)
.build()
Após a construção, o ImageRequest é passado para o ImageLoader através de enqueue ou execute. O método enqueue inicia uma corrotina e retorna um Disposable, permitindo cancelar o carregamento ao sair da tela. O método execute é uma função suspend que retorna um Result diretamente.
O ImageLoader verifica sequencialmente MemoryCache, DiskCache e apenas em caso de falha de ambos executa uma requisição de rede através de HttpEngine. Após o carregamento, os bytes são decodificados em um Bitmap considerando o tamanho alvo, transformações são aplicadas, o resultado é armazenado em ambos os caches e passado para o Target.
O Coil é construído sobre uma arquitetura de componentes com capacidade de substituir qualquer parte através de Injeção de Dependência. Todos os componentes são registrados no ImageLoaderFactory e passados ao construtor do ImageLoader através do builder.
ImageLoader é o ponto de entrada para todas as operações de carregamento. Cada instância contém um pool de corrotinas, BitmapPool, MemoryCache, DiskCache e uma lista de interceptadores. Por padrão, uma instância global é criada, mas para testes unitários, instâncias separadas com caches isolados podem ser criadas.
MemoryCache é um cache em memória baseado em LRU (Least Recently Used) que armazena objetos Bitmap decodificados. O tamanho máximo padrão é 25% da memória disponível do aplicativo, mas não menos que 32 MB. A chave do cache é formada por URL + tamanho + transformações, evitando a recuperação de imagens desatualizadas.
DiskCache é um cache baseado em arquivos para dados brutos (JPEG, PNG, WebP) e metadados decodificados. Ele está localizado no diretório de cache do aplicativo e suporta limpeza automática quando o limite é excedido. As operações de disco são realizadas através de DiskCache.Builder com configuração de diretório e tamanho máximo.
Coil implementa uma estratégia de cache em vários níveis que minimiza requisições de rede e acelera a exibição de imagens. Cada nível tem seu próprio propósito e tempo de vida dos dados.
| Nível | Tipo de armazenamento | Tempo de vida | Tamanho padrão |
|---|---|---|---|
| Memory Cache | Bitmap na RAM | Até despejo LRU | 25% do heap, a partir de 32 MB |
| Disk Cache | Arquivos JPEG/WebP | Até exceder o limite | 250 MB |
| Http Cache | Respostas OkHttp | Conforme cabeçalhos Cache-Control | Depende do cliente HTTP |
Memory Cache fornece acesso instantâneo a Bitmaps já decodificados. Disk Cache garante que o aplicativo funcione sem rede (offline-first) após o primeiro carregamento. Http Cache no nível OkHttp lida com requisições condicionais com ETag e If-Modified-Since.
Políticas de cache são configuradas por requisição através de CachePolicy com três valores: ENABLED, READ_ONLY, WRITE_ONLY, DISABLED. Por exemplo, para avatares de usuários, pode-se definir READ_ONLY para Memory Cache e ENABLED para Disk Cache.
O Coil fornece vários métodos de integração dependendo da arquitetura do aplicativo. Vamos ver três cenários principais com exemplos de código funcionais.
load é uma função de extensão para ImageView, a maneira mais simples de carregar uma imagem em uma linha. A função aceita URL, URI, recurso Int ou File, juntamente com todos os parâmetros opcionais através de um configurador lambda.
imageView.load("https://example.com/photo.jpg") {
crossfade(true)
placeholder(R.drawable.placeholder)
error(R.drawable.error)
size(300, 300)
transformations(CircleCropTransformation())
}
O método load retorna um Disposable, que pode ser cancelado no onDestroy ou ao reutilizar a View. Isso evita vazamentos de memória e requisições de rede desnecessárias durante a rolagem rápida de listas.
AsyncImage é uma função composable para carregar imagens em UI declarativa. Aceita qualquer fonte de dados e três parâmetros opcionais para estados: placeholder, error e success.
@Composable
fun NetworkImage(url: String) {
AsyncImage(
model = url,
contentDescription = "imagem de rede",
placeholder = ColorPainter(Color.Gray),
error = ColorPainter(Color.Red)
)
}
SubcomposeAsyncImage é uma versão mais flexível que permite personalizar a exibição durante o carregamento através de um slot de conteúdo. Isso é útil para esqueletos (shimmer) e barras de progresso.
Se ImageView ou AsyncImage não forem adequados, você pode implementar um Target com um único método onSuccess que aceita um Bitmap. Isso é usado para carregar em Notification, RemoteViews ou texturas OpenGL.
val target = object : BitmapTarget() {
override fun onSuccess(result: Bitmap) {
notificationRemoteView.setImageViewBitmap(R.id.icon, result)
}
}
imageLoader.enqueue(
ImageRequest.Builder(context)
.data(url)
.target(target)
.build()
)
A escolha da biblioteca de carregamento de imagens depende dos requisitos do projeto. Coil compete com Glide e Picasso, cada um com seus pontos fortes. Uma comparação das principais características é apresentada na tabela.
| Característica | Coil | Glide | Picasso |
|---|---|---|---|
| Idioma | Kotlin (100%) | Java + Kotlin | Java |
| Tamanho do APK | ~150 KB | ~500 KB | ~120 KB |
| Corrotinas | Integradas | Não (callbacks) | Não (callbacks) |
| Jetpack Compose | Suporte nativo | Via accompanist | De terceiros |
| GIF/WebP | Sim (integrado) | Sim (integrado) | Não |
| Recomendação Google | Sim (I/O 2023) | Sim | Não |
Para novos projetos em Kotlin e Jetpack Compose, o Coil se torna a escolha natural graças a zero dependências adicionais de corrotinas e tamanho mínimo. O Glide continua preferível para cenários complexos com animações e previews de vídeo. O Picasso fica atrás de ambos em funcionalidade, mas ganha em simplicidade.
A adição do Coil a um projeto Android é feita através de uma dependência Gradle. Após a adição, a biblioteca registra automaticamente um ImageLoader via ContentProvider, portanto, a inicialização manual em Application não é necessária. Se for necessária personalização, um ImageLoader personalizado é criado através do builder.
// build.gradle.kts (app module)
dependencies {
implementation("io.coil-kt:coil:2.6.0")
// Para Jetpack Compose adicionalmente:
implementation("io.coil-kt:coil-compose:2.6.0")
// Para suporte SVG:
implementation("io.coil-kt:coil-svg:2.6.0")
// Para suporte GIF:
implementation("io.coil-kt:coil-gif:2.6.0")
}
Para personalizar o ImageLoader, é usado ImageLoaderFactory — um singleton criado em Application.onCreate. Na fábrica, você pode configurar limites de cache, cliente HTTP, decodificadores personalizados e log. Por padrão, o Coil usa OkHttp com um pool de conexões pronto.
class App : Application(), ImageLoaderFactory {
override fun newImageLoader(): ImageLoader {
return ImageLoader.Builder(this)
.memoryCache {
MemoryCache.Builder()
.maxSizePercent(0.25)
.build()
}
.diskCache {
DiskCache.Builder()
.directory(cacheDir.resolve("coil_cache"))
.maxSizeBytes(512 * 1024 * 1024)
.build()
}
.build()
}
}
Perguntas frequentes
Coil é uma biblioteca para carregar imagens no Android, escrita em Kotlin usando corrotinas. É usada para carregamento assíncrono, cache e exibição de imagens bitmap da rede, recursos ou sistema de arquivos.
O Coil é escrito 100% em Kotlin e usa corrotinas em vez do mecanismo de callbacks do Glide. O Coil tem um tamanho de APK menor (~150 KB vs ~500 KB) e suporte nativo ao Jetpack Compose através do AsyncImage.
Adicione a dependência io.coil-kt:coil:2.6.0 ao build.gradle.kts. Para Jetpack Compose, adicione também io.coil-kt:coil-compose:2.6.0. A biblioteca registra automaticamente um ImageLoader via ContentProvider.
Coil suporta JPEG, PNG, WebP, BMP, SVG (através do módulo coil-svg) e GIF (através do módulo coil-gif). Os formatos AVIF e HEIF são suportados através de um decodificador personalizado em dispositivos com Android 10+.
O cache é configurado através de ImageLoader.Builder: memoryCache especificando a porcentagem do heap, diskCache com caminho e limite em bytes. As políticas de cache (ENABLED, DISABLED, READ_ONLY) são configuradas por requisição através do CachePolicy.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também