Sytuacja kobiet w IT w 2024 roku
3.07.20205 min
Siva Ganesh Kantamani

Siva Ganesh KantamaniLead Android DeveloperBoomer Marketing

Łatwe ładowanie obrazków na Androidzie w Kotlinie dzięki Coil

Dowiedz się, jak działa Coil, czyli biblioteka Kotlina do ładowania obrazków oraz zobacz, jak wypada przy Glide i Picasso.

Łatwe ładowanie obrazków na Androidzie w Kotlinie dzięki Coil

Dowiesz się tutaj, jak używać Coil — nowego image loadera Kotlina od Instacart. Poza omówieniem ogólnego zastosowania przyjrzymy się również transformacjom, cofaniu żądań i funkcji samplowania obrazków. Porównamy też jego działanie z Glide oraz Picasso.

Coil to nowa biblioteka do ładowania obrazków od Instacart, który używa wielu zaawansowanych funkcji, takich jak współprogramy, OkHttp oraz androidX.lifecycle. Coil dodaje około 1.500 funkcji do Twojego APK, co jest porównywalne do Picasso, ale jest to trochę mniej od Glide, czy Fresco. Coil również zawiera takie funkcje jak samplowanie, efektywne użycie pamięci i automatyczne cofanie lub zatrzymywanie żądań. 

Coil jest domyślnie w pełni kompatybilny z technikami optymalizacji R8, więc programiści nie muszą dodawać żadnych reguł ProGuard związanych z tym image loaderem.

Integracja

Coil wymaga kodu bajtowego Javy 8, więc należy dodać następujący kod to pliku build.gradle, aby wszystko działało.

android {
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
    kotlinOptions {
        jvmTarget = "1.8"
    }
}

Nasz image loader posiada 4 artefakty, a każdy z nich ma swoje własne przeznaczenie. Głównie używamy jednak dwóch poniższych:

  1. io.coil-kt: Coil: domyślny artefakt, który zawiera singleton Coil. Jeśli nie używasz wstrzykiwania zależności lub nie zarządzasz samodzielnie instancjami Coil w swojej apce, to lepiej użyć takiego artefaktu. 
  2. io.coil-kt:coil-base: artefakt bazowy, który nie zawiera singletona Coil. Ten artefakt jest lepszy, jeśli używasz wstrzykiwania zależności, aby dodać instancję (lub kilka instancji) Coil. 

//Singleton artifact
implementation "io.coil-kt:coil:0.9.5"
//Base artifact without singleton
implementation "io.coil-kt:coil-base:0.9.5"

Funkcje

Teraz gdy już zintegrowaliśmy naszą bibliotekę, czas zacząć działać z Coil. Spróbujmy załadować prosty obraz: 

imageView.load("https://www.example.com/image.jpg")


Ponieważ Coil jest pierwszym programem ładującym obrazy w Kotlinie, wykorzystano wiele funkcji tego języka, a jedną z nich są rozszerzenia. Load jest właśnie funkcją rozszerzającą Coila, dzięki której można naprawdę prosto załadować zdalny obrazek.

Dzięki rozszerzeniu load Coil uchronił nas od przekazywania kontekstu i tworzenia instancji imageholder za każdym razem. Spójrzmy na rozszerzenie od Coil:

inline fun ImageView.load(
    uri: String?,
    imageLoader: ImageLoader = Coil.loader(),
    builder: LoadRequestBuilder.() -> Unit = {}
): RequestDisposable {
    return imageLoader.load(context, uri) {
        target(this@load)
        builder()
    }
}

Musieliśmy wykorzystać kontekst z imageView oraz tworzenie loadera z Coil. To dlatego preferujemy biblioteki natywne.  

Placeholder i obrazek błędu 

Prawie wszystkie biblioteki (np. Glide, Picasso i Fresco) mają taką funkcję — pokazujemy sztuczny obraz (placeholder), dopóki nie zostanie załadowany właściwy obrazek lub pokazujemy obrazek błędu, jeśli żądanie się nie powiedzie.

Jest to funkcja wbudowana w bibliotekę i najbardziej ekscytująca jest tutaj składnia: wykorzystując te funkcje widzimy, jak fajnie korzysta ona ze składni Kotlina (np. funkcje let oraz apply w Kotlinie). Spójrzmy: 

fun loadWithCoil(imageView: ImageView, imageUrl: String){
    imageView.load(imageUrl){
        placeholder(R.drawable.placeholder)
        error(R.drawable.error)
    }
}

Wstępne ładowanie obrazów

Coil używa współprogramów do lepszego pobierania obrazów. Jesteśmy w stanie pobrać zdalny obraz przy użyciu zawieszalnej funkcji get (zdefiniowanej z suspend). Spójrzmy:

val image = Coil.get(imageUrl)


Dzięki zawieszalnej (a więc zdefiniowanej z suspend) funkcji get możemy pobierać zdalne obrazy jako drawable.

Wywołania zwrotne

Zdarzą się przypadki, w których będziesz potrzebować wywołania zwrotnego po pobraniu obrazu. Coil zajmuje się tym przy pomocy funkcji targets:

fun coilWithCallbacks(){
    Coil.load(context, "https://www.example.com/image.jpg") {
        target { drawable ->
            // Handle the successful result.
        }
    }
}


Istnieją trzy typy target: Target, ViewTarget oraz possibleViewTarget.

  • Targetużywamy, kiedy żądanie nie jest połączone z żadnym widokiem
  • ViewTargetużywamy, kiedy żądanie jest połączone z imageview (np. pokazywanie placeholdera, dopóki żądanie nie zostanie ukończone).
  • possibleViewTargetużywamy, kiedy zachodzi potrzeba bitmap pooling’u. 

Transformacje

Teraz kiedy techniki ładowania mamy z głowy, czas na transformację. Transformacje są niezwykle przydatne i powinny być zawarte w każdej bibiotece do ładowania obrazków. W Coil mamy 4 transformacje: blur, circle crop, grayscale oraz rounded corners. Poniższy kod pokazuje, jak ich używać:

fun loadWithCoil(imageView: ImageView, imageUrl: String){
    imageView.load(imageUrl){
        placeholder(R.drawable.gradient_place_details)
        error(R.drawable.gradient_place_details)
        transformations(CircleCropTransformation())
    }
}

Cofanie żądań

Efektywne wykorzystanie zasobów jest kluczem do wydajności aplikacji. Coil domyślnie anuluje żądanie, gdy imageview zostanie odłączony lub kontekst zostanie zniszczony.

Coil domyślnie zajmuje się wszystkimi przypadkami wycieków pamięci, ale zdarzają się sytuacje, w których chcemy anulować całą operację. W tym przypadku nasz image loader zwraca RequestDisposable dla każdego żądania load, za pomocą którego można anulować żądanie. Spójrzmy:

val disposable = imageView.load("https://www.example.com/image.jpg")

// Cancel the request.
disposable.dispose()

Samplowanie obrazów

Samplowanie obrazów jest najbardziej zaawansowaną techniką używaną do ładowania obrazów o jakości opartej na rozmiarze imageview. Załóżmy, że na dysku znajduje się obraz o rozmiarze 500 x 500. Coil początkowo ładuje 100 x 100 i używa tego obrazu jako placeholdera aż do załadowania obrazu o pełnej jakości. Działa on jak progresywny JPEG. Możemy to włączyć podczas ładowania dowolnego obrazu za pomocą funkcji crossfade. Spójrzmy:

imageView.load(imageUrl){
    crossfade(true)
}

Porównanie z Glide/Picasso

Teraz porównajmy kod z Glide i Picasso.


Podstawowe użycie:

// Glide
Glide.with(context)
    .load(url)
    .into(imageView)

// Picasso
Picasso.get()
    .load(url)
    .into(imageView)

// Coil
imageView.load(url)


Żądania wywołania zwrotnego

// Glide (has optional callbacks for start and error)
Glide.with(context)
    .load(url)
    .into(object : CustomTarget<Drawable>() {
        override fun onResourceReady(resource: Drawable, transition: Transition<Drawable>) {
            // Handle the successful result.
        }

        override fun onLoadCleared(placeholder: Drawable) {
            // Remove the drawable provided in onResourceReady from any Views and ensure no references to it remain.
        }
    })

// Picasso
Picasso.get()
    .load(url)
    .into(object : BitmapTarget {
        override fun onBitmapLoaded(bitmap: Bitmap, from: Picasso.LoadedFrom) {
            // Handle the successful result.
        }

        override fun onBitmapFailed(e: Exception, errorDrawable: Drawable?) {
            // Handle the error drawable.
        }

        override fun onPrepareLoad(placeHolderDrawable: Drawable?) {
            // Handle the placeholder drawable.
        }
    })

// Coil (has optional callbacks for start and error)
Coil.load(context, url) {
    target { drawable ->
        // Handle the successful result.
    }
}


Niestandardowe żądania

imageView.scaleType = ImageView.ScaleType.FIT_CENTER

// Glide
Glide.with(context)
    .load(url)
    .placeholder(placeholder)
    .fitCenter()
    .into(imageView)

// Picasso
Picasso.get()
    .load(url)
    .placeholder(placeholder)
    .fit()
    .into(imageView)

// Coil (autodetects the scale type)
imageView.load(url) {
    placeholder(placeholder)
}


Oryginał tekstu w języku angielskim możesz przeczytać tutaj.

<p>Loading...</p>