|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +title: "Glide" |
| 4 | +date: 2019-06-17 |
| 5 | +categories: ["Biblioteki"] |
| 6 | +image: libraries/glide |
| 7 | +github: libraries/tree/master/glide |
| 8 | +description: "Biblioteki" |
| 9 | +version: Glide 4.9 |
| 10 | +keywords: "glide, image, imageview, bitmap, network, resources, memory, cache, disk, loader, imageloader, gif, transition, transformation, android, programowanie, programming" |
| 11 | +--- |
| 12 | + |
| 13 | +## Wstęp |
| 14 | +`Glide` jest wydajną biblioteką służącą do zarządzania multimediami, ich dekodowaniem i buforowaniem w pamięci oraz na dysku w celu ponownego szybkiego użycia. Skupia się przede wszystkim na płynnym i wydajnym wyświetlaniu, pobieraniu i modyfikowaniu obrazów. Ponadto obsługuje także obrazy animowane GIF oraz wideo. Zawiera elastyczne i prosty interfejs API, który umożliwia podłączanie niemal dowolnego klienta sieciowego (domyślnie jest to HttpUrlConnection). |
| 15 | + |
| 16 | +## Ładowanie |
| 17 | +Pobieranie i ładowanie obrazów jest proste i może ograniczyć się zaledwie do jednego ciągu instrukcji. Wyrażenie with zwraca obiekt typu `RequestBuilder` na którym należy wskazać źródło (`load`) i typ zasobu, miejsce docelowe (`into`) oraz opcjonalnie dokonać konfiguracji żądania. Możliwe jest także pobranie zasobu do wskazanego typu obietku z pominięciem ładowania do kontrolki widoku przez wskazanie jako miejsca docelowego obiektu typu `Target`. Glide automatycznie wylicza rozmiar obrazu, aby dopasować go do widoku w którym będzie wyświetlany, jednakże jawnie ustawiony rozmiar przyspiesza proces przetwarzania. W trakcie niszczenia komponentu powiązane z nim zasoby są poddawane recyklingowi, a te nieużywany zostają usuwane. |
| 18 | + |
| 19 | +{% highlight kotlin %} |
| 20 | +private fun loadImage() { |
| 21 | + //default load source as Drawable |
| 22 | + Glide.with(this) //pass Context - returns RequestBuilder |
| 23 | + .load(url) //pass url, file, drawable object, local resource etc |
| 24 | + //set some RequestListener if needed |
| 25 | + .into(imageView) //tell where to load image |
| 26 | +} |
| 27 | + |
| 28 | +private fun loadImageIntoBitmapTarget() { |
| 29 | + val target = object: CustomTarget<Bitmap>() { |
| 30 | + override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) { |
| 31 | + val bitmap = resource |
| 32 | + //do something with downloaded bitmap |
| 33 | + } |
| 34 | + override fun onLoadCleared(placeholder: Drawable?) { |
| 35 | + //action when loading canceled |
| 36 | + } |
| 37 | + //implement other callback methods like onLoadStarted, onLoadFailed |
| 38 | + } |
| 39 | + |
| 40 | + //could be: asDrawable, asBitmap, asFile, asGif |
| 41 | + Glide.with(this).asBitmap().load(url).into(target) |
| 42 | +} |
| 43 | + |
| 44 | +private fun clearData() { |
| 45 | + //manual loading data clear |
| 46 | + Glide.with(this).clear(imageView) //or pass target |
| 47 | +} |
| 48 | +{% endhighlight %} |
| 49 | + |
| 50 | +## Symbol zastępczy |
| 51 | +Symbol zastępczy (`placeholder`) jest graficznym wyświetlanym w trakcie pobierania i przetwarzania obiektu źródłowego. Gdy żądanie zostaje ukończone pozytywnie wówczas placeholder jest zastępowany przez źródło. Ładowanie odbywa się na wątku głównym, a transformacje są niedozwolone. Dodatkowo możliwe jest ustawienie obiektu placeholder dla żądania zakończonego kodem błędu (`error` i `fallback`), jednakże gdy nie zostanie on ustawiony wówczas pozostanie wyświetlany bieżący placeholder (jeśli został ustawiony). Ponadto możliwe jest ustawienie miniatury (`thumbnail`), która jest pobierana równolegle z głównym żądaniem co pozwala na zwiększenie doświadczeń użytkownika przez wyświetlenia obrazu niższej rozdzielczości (zamiast zastępnika) w trakcie oczekiwania na pobranie pełnego obrazu. |
| 52 | + |
| 53 | +{% highlight kotlin %} |
| 54 | +private fun loadWithPlaceholders() { |
| 55 | + Glide.with(this).load(url) |
| 56 | + .placeholder(R.drawable.placeholder) //main placeholder |
| 57 | + .error(R.drawable.placeholder_error) //placeholder when request permanently fails |
| 58 | + .fallback(R.drawable.placeholder_fallback) //placeholder when requested model is null |
| 59 | + .into(imageView) |
| 60 | +} |
| 61 | + |
| 62 | +private fun loadWithThumbnail() { |
| 63 | + Glide.with(this).load(url) |
| 64 | + .thumbnail(Glide.with(this).load(url_miniature)) |
| 65 | + .into(imageView) |
| 66 | +} |
| 67 | +{% endhighlight %} |
| 68 | + |
| 69 | +## Opcje |
| 70 | +Glide oferuje wiele opcji przetwarzania i ładowania zasobów takich jak m.in. transformacje, przejścia, czy buforowanie które można zastosować dla wybranych żądań bezpośrednio na obiekcie `RequestBuilder`. Opcje mogą być także współdzielone przez instancję `RequestOptions` (transformacje i strategie buforowania) oraz `TransitionOptions` (przejścia). Transformacje (`transitions`) zwracają zmodyfikowany zasób i są używane przede wszystkim do przycinania obrazu i stosowania filtrów. Zastosowanie metody dowolnej transformacji zastępuje poprzednią dlatego w celu zaaplikowania kilku transformacji należy przekazać je do metody transform. Przejścia (`transformations`) działa w kontekście pojedynczego żądania i pozwalają zdefiniować w jaki sposób Glide powinien przejść z obiektu zastępczego czy miniatury do załadowanego docelowego obrazu. Ponadto użycie przejść wpływa na wydajność w związku z czym należy rozważyć unikanie animacji przejść szczególnie w przypadku kolekcji. |
| 71 | + |
| 72 | +{% highlight kotlin %} |
| 73 | +private fun loadImageCustom() { |
| 74 | + //load image with some custom modifications |
| 75 | + Glide.with(this).load(url) |
| 76 | + .centerCrop() //apply some transforms |
| 77 | + .transition(withCrossFade()) |
| 78 | + .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC) |
| 79 | + .into(imageView) |
| 80 | +} |
| 81 | + |
| 82 | +private fun loadImageCustomOptions() { |
| 83 | + //TransitionOptions are tied with specific type like DrawableTransitionOptions |
| 84 | + val transitionOptions = DrawableTransitionOptions() |
| 85 | + .crossFade() |
| 86 | + |
| 87 | + //create RequestOptions and reuse it |
| 88 | + val requestOptions = RequestOptions() |
| 89 | + .centerCrop() |
| 90 | + .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC) |
| 91 | + //add more transforms |
| 92 | + |
| 93 | + Glide.with(this).load(url) |
| 94 | + .transition(transitionOptions) |
| 95 | + .apply(requestOptions).into(imageView) |
| 96 | +} |
| 97 | + |
| 98 | +private fun loadImageMultipleTransforms() { |
| 99 | + Glide.with(this).load(url) |
| 100 | + .transform(FitCenter(), Rotate(90)) |
| 101 | + .into(imageView) |
| 102 | +} |
| 103 | +{% endhighlight %} |
| 104 | + |
| 105 | +## Pamięć |
| 106 | +Zanim Glide rozpocznie pobieranie nowego zasobu dokonuje sprawdzenia warstw pamięci w celu jego odnalezienia i ponownego użycia co przebiega w następujących krokach: |
| 107 | + |
| 108 | +>**1. Aktywne zasoby** (obraz jest wyświetlany w innym widoku) |
| 109 | +>**2. Pamięc podręczna** (obraz został niedawno załadowany i pozostaje nadal w pamięci) |
| 110 | +>**3. Zasób** (obraz został wcześniej zdekodowany, przekształcony i zapisany w pamięci dysku) |
| 111 | +>**4. Dane** (dane z których uzyskano obraz zapisano wcześniej w pamięci podręcznej dysku) |
| 112 | +> |
| 113 | +
|
| 114 | +Jeśli zasób nie został odnaleziony w żadnej warstwie pamięci wówczas zostaje on pobrany z oryginalnego źródła. Weryfikacja istnienia zasobu w warstwach pamięci odbywa się na podstawie wyszukiwania klucza składającego się z modelu (`File`, `Uri`, `Url` itp.), opcjonalnej sygnatury dołączonej metodą `signature` oraz parametrów zasobów takich jak m.in. wielkość, transformacje, opcje czy typ (dla kroków 1-3). Glide pozwala na uwzględnienie (`onlyRetrieveFromCache`) lub pominięcie pamięci podręcznej (`skipMemoryCache`) oraz dostarcza kilka strategii dla pamięci dysku (`DiskCacheStrategy`), które umożliwiają wybór sposobu ładowania i zapisywania pobranych zasobów. |
| 115 | + |
| 116 | +{% highlight kotlin %} |
| 117 | +private fun loadImageWithCustomSignature() { |
| 118 | + Glide.with(this).load(url) |
| 119 | + .signature(ObjectKey("version")) //custom metadata if possible like last modified time |
| 120 | + .into(imageView) |
| 121 | +} |
| 122 | + |
| 123 | +private fun loadAndSaveImageBySomeStrategy() { |
| 124 | + Glide.with(this).load(url) |
| 125 | + .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC) //is default |
| 126 | + .into(imageView) |
| 127 | + |
| 128 | + /* use one of DiskStrategyCache: |
| 129 | + AUTOMATIC - optimal strategy based on data source |
| 130 | + DATA - stores original retrieved data in disk cache |
| 131 | + RESOURCE - stores data in disk cache after decoding |
| 132 | + ALL - remote data with both DATA and RESOURCE, local data only with RESOURCE |
| 133 | + NONE - saves no data to disk cache */ |
| 134 | +} |
| 135 | + |
| 136 | +private fun loadImageOnylIfExistsInCache() { |
| 137 | + Glide.with(this).load(url) |
| 138 | + .onlyRetrieveFromCache(true) |
| 139 | + .into(imageView) |
| 140 | + |
| 141 | + //if image doesn't exists in memory or disk cache then load fail |
| 142 | +} |
| 143 | + |
| 144 | +private fun loadImageSkippingCache() { |
| 145 | + Glide.with(this).load(url) |
| 146 | + .skipMemoryCache(true) |
| 147 | + .into(imageView) |
| 148 | + |
| 149 | + //skip memory cache |
| 150 | +} |
| 151 | +{% endhighlight %} |
| 152 | + |
| 153 | +Wielkość pamięci jest automatycznie ustalana i może być modyfikowana w oparciu o klasę `MemorySizeCalculator`. Tymczasowe zwiększenie pamięci podręcznej odbywa się za pomocą metody `setMemoryCategory`, a ręczne czyszczenie pamięci podręcznej i dysku przy użyciu `clearMemory` (na głównym wątku) oraz `clearDiskCache` (na wątku pobocznym). |
| 154 | + |
| 155 | +## Interfejs API |
| 156 | +Glide pozwala na rozszerzenie interfejsu API dzięki czemu możliwe jest ustawienie i nadpisanie domyślnych opcji globalnych dla żądań oraz dołączenie bibliotek integracyjnych. Aby wygenerować interfejs należy stworzyć klasę modułu `AppGlideModule` opatrzoną adnotacją `@GlideModule` oraz opcjonalnie klasę rozszerzeń `@GlideExtension` z metodami statycznymi oznaczonymi jako `@GlideOption` i `@GlideType`. |
| 157 | + |
| 158 | +{% highlight kotlin %} |
| 159 | +@GlideModule |
| 160 | +class CustomAppGlideModule : AppGlideModule() { |
| 161 | + |
| 162 | + //this will apply as global settings |
| 163 | + override fun applyOptions(context: Context, builder: GlideBuilder) { |
| 164 | + //create custom options |
| 165 | + val requestOptions = RequestOptions() |
| 166 | + .circleCrop() |
| 167 | + .diskCacheStrategy(DiskCacheStrategy.DATA) |
| 168 | + |
| 169 | + val transitionOptions = DrawableTransitionOptions() |
| 170 | + .crossFade() |
| 171 | + |
| 172 | + //set custom memory size |
| 173 | + val customMemoryCacheSize = (MemorySizeCalculator.Builder(context) |
| 174 | + .build().memoryCacheSize * 1.1).toLong() |
| 175 | + |
| 176 | + //apply some custom settings |
| 177 | + builder |
| 178 | + .setDefaultRequestOptions(requestOptions) |
| 179 | + .setDefaultTransitionOptions(Drawable::class.java, transitionOptions) |
| 180 | + .setLogLevel(Log.ERROR) |
| 181 | + .setMemoryCache(LruResourceCache(customMemoryCacheSize)) |
| 182 | + } |
| 183 | + |
| 184 | + //body can be also empty |
| 185 | +} |
| 186 | + |
| 187 | +@GlideExtension |
| 188 | +object CustomGlideExtension { |
| 189 | + |
| 190 | + //class with private constructor and static annotated methods |
| 191 | + |
| 192 | + @GlideOption |
| 193 | + @JvmStatic |
| 194 | + fun smallCircle(options: BaseRequestOptions<*>): BaseRequestOptions<*> { |
| 195 | + return options.circleCrop().override(100) |
| 196 | + } |
| 197 | + |
| 198 | + //more @GlideOption and @GlideType methods |
| 199 | +} |
| 200 | +{% endhighlight %} |
| 201 | + |
| 202 | +Odwołanie do stworzonego modułu odbywa się domyślnie na instancji `GlideApp` tworzonej przy budowaniu projektu. Nie wyklucza to jednak użycia w standardowy sposób za pomocą instancji `Glide`. |
| 203 | + |
| 204 | +{% highlight kotlin %} |
| 205 | +private fun loadImageByCustomModule() { |
| 206 | + //will draw circle crop 100px image with cross fade transition and custom log and cache settings |
| 207 | + GlideApp.with(this).load(url) |
| 208 | + .smallCircle() //new custom option |
| 209 | + .into(imageView) |
| 210 | +} |
| 211 | +{% endhighlight %} |
0 commit comments