PicassoとGlide
- はじめに
- 一般的に見ると
- どのようなケースでうまくいかなかったのか?
- なんでこんな風な仕様にしたの?
- で、なにがうまくいかなかったの?
- Picasso => Glideを置き換えるとした場合の問題点
- Frescoとかはどうなのさ?
はじめに
この記事は Android その3 Advent Calendar 2016 - Qiita
の十日目の記事です。
- 昨日は rkowase - Qiitaさんの【Android】Google Play Developer Consoleの段階的公開機能のススメ - Qiitaのお話です
- 明日は ueno-yuhei - Qiitaさんの動画プレイヤーのシークバーの上にサムネイルを表示! on ExoPlayer - Qiitaのお話です
一般的に見ると
日本国内のアプリだとかなりPicasso押しの風潮ですが、
Picassoでうまくできなかった事例がGlideで実現できたケースが有ったので備忘録として書いてみます
どのようなケースでうまくいかなかったのか?
- 画像が読み込まれるまでは View.GONE
- 読み込まれた段階で View.VISIBLE に切り替える
なんでこんな風な仕様にしたの?
- GoogleMap系の周辺技術の整理メモ - exception think
- staticMap
- streetviewのサムネイル
等を表示したかったからです。*1
ちゃんとG様のApiKeyを取得すれば、確実に表示されることは保証されるのですが
- かなり凝ったアプリを作ったからといって収益に反映しない
- 企画/営業の方は今あるリソースでなんとか良くしようと知恵を絞る
みたいなところがあって、フリーの範囲でできるだけ頑張るみたいな形になってしまうのは仕方ないんでしょうね
で、なにがうまくいかなかったの?
下記に記載例を記述しますが、
- CallbackがSuccessなのに画面にImageViewの領域がうまく表示されない*2
- 表示されても、画像が白っぽく表示されたり正常に表示されない
現象が発生しました。
通常のレイアウトの場合
<!--中略--> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <!--横ピッタリ、縦は等比--> <ImageView android:visibility="View.GONE" android:scaleType="fitStart" android:adjustViewBounds="true" android:id="@+id/imageView" android:layout_width="match_parent" android:layout_height="wrap_content" /> <!--ボタン--> <Button android:layout_alignParentRight="true" android:layout_alignParentBottom="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="ボタン"/> </RelativeLayout> <!--中略-->
- hogeActivity
final ImageView imageView = (ImageView)findViewById(R.id.imageView); Picasso.with(context) .load(item.getUrl()) .into(imageView, new Callback() { @Override public void onSuccess() { //成功したときだけImageViewを表示モードにする imageView.setVisibility(View.VISIBLE); } @Override public void onError() { } });
InfoWindowの場合
- layout/info_window.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" > <data> <variable name="dto" type="hoge.fuga.itemDto" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <!--画像のタイトルとか--> <TextView android:gravity="center_horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@{dto.title}"/> <!--横ピッタリ、縦は等比--> <ImageView android:visibility="View.GONE" android:scaleType="fitStart" android:adjustViewBounds="true" android:id="@+id/imageView" android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout>
- MyInfoWindowAdapter.java
public class MyInfoWindowAdapter implements InfoWindowAdapter { private Context context; private InfoWindowBinding binding; private String lastMarkerId; public MyInfoWindowAdapter(Context context) { this.context = context; binding = InfoWindowBinding.inflate(LayoutInflater.from(context)); } @Override public View getInfoWindow(Marker marker) { return render(marker); } @Override public View getInfoContents(Marker marker) { if (marker != null && marker.isInfoWindowShown()) { marker.showInfoWindow(); } return null; } private boolean isRender = false; //マーカー連打対策 private View render(final Marker marker) { if(isRender)return; isRender=true; itemDto dto = (itemDto)marker.getTag(); //★ 配列やDBとかからもってくるならIdでも可 if (marker.getId().equals(lastMarkerId)){ binding.setDto(dto); binding.executePendingBindings();//即時反映をお願いする isRender=false; return; } lastMarkerId = marker.getId(); binding.setDto(dto); binding.executePendingBindings();//即時反映をお願いする //======PicassoでImageを読み込む処理 ===== final ImageView imageView = binding.imageView; Picasso.with(context) .load(item.getUrl()) .into(imageView, new Callback() { @Override public void onSuccess() { //成功したときだけImageViewを表示モードにしてInfoWindowを読み直す imageView.setVisibility(View.VISIBLE); getInfoContents(marker); } @Override public void onError() { } }); isRender=false; } }
で直してうまく動いた記述例
Glide.with(context)) .load(item.getUrl()) .asBitmap() .into(new SimpleTarget<Bitmap>(600, 400) { @Override public void onResourceReady(Bitmap bitmap, GlideAnimation anim) { imageView.setImageBitmap(bitmap); imageView.setVisibility(View.VISIBLE); getInfoContents(marker); } };
ただ下記だとうまく動かなかった
- サイズ指定がないケース
- 動くことは動くけど、何故かすごく遅い
Glide.with(context)) .load(item.getUrl()) .asBitmap() .into(new SimpleTarget<Bitmap>() { }
- ImageViewにサイズ任せなケース*3
- imageView.setVisibility(View.VISIBLE); してもうまく表示されない
Glide.with(context)
.load(item.getUrl())
.asBitmap()
.into(new BitmapImageViewTarget(imageView)) {
}
ImageViewじたいがView.GONE のためImageViewのサイズが正常に取れないからかもしれない。
Picasso => Glideを置き換えるとした場合の問題点
PicassoだとTransformationという機能が有るよね?
で出て来るお話ですが、一応Glideにも
wasabeef先生が作られたGlide Transformationsがあるようです。下記の記事を参考
ただAnimationみたいに自作ベースはかなり厳しい?かもとは感じました
PicasoだとOkhttpと親和性が高いよね?
Picassoサイド
- OkHttp3Downloaderと組み合わせ
- OkHttpClientの切り替え*4
が可能なのは強み
- ユーザーがアップロードした画像とかは https
- 一般的公開画像は http
な話もありましたが、Apple様次第でどうなるんでしょうね
全部httpsでアクセスみたいな話になるのでしょうか・・
- app/build.gradle
dependencies { compile 'com.squareup.picasso:picasso:2.5.2' compile 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0' }
OkHttpClient client = XXXX; Picasso picasso = new Picasso.Builder(context) .downloader(new OkHttp3Downloader(client)) .build()
Glideサイド
起動時にしか初期化ができないので切り替えができない*5
の話と合わせて書くと、下記みたいな記述になるイメージなんでしょうね。。*6
- app/build.gradle
dependencies { compile 'com.github.bumptech.glide:glide:3.7.0' compile 'com.github.bumptech.glide:okhttp3-integration:1.4.0@aar' }
public class LimitCacheSizeGlideModule implements GlideModule { // Modern device should have 8GB (=7.45GiB) or more! private static final int SMALL_INTERNAL_STORAGE_THRESHOLD_GIB = 6; private static final int DISK_CACHE_SIZE_FOR_SMALL_INTERNAL_STORAGE_MIB = 50; @Override public void applyOptions(Context context, GlideBuilder builder) { if (MyApplication.from(context).isTest()) return; // NOTE: StatFs will crash on robolectric. double totalGiB = getTotalBytesOfInternalStorage() / 1024.0 / 1024.0 / 1024.0; Log.i(String.format(Locale.US, "Internal Storage Size: %.1fGiB", totalGiB)); if (totalGiB < SMALL_INTERNAL_STORAGE_THRESHOLD_GIB) { Log.i("Limiting image cache size to " + DISK_CACHE_SIZE_FOR_SMALL_INTERNAL_STORAGE_MIB + "MiB"); builder.setDiskCache(new InternalCacheDiskCacheFactory(context, DISK_CACHE_SIZE_FOR_SMALL_INTERNAL_STORAGE_MIB)); } } @Override public void registerComponents(Context context, Glide glide) { OkHttpClient okHttpClient = XXXX; glide.register(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory(okHttpClient)); } private long getTotalBytesOfInternalStorage() { // http://stackoverflow.com/a/4595449/1474113 StatFs stat = new StatFs(Environment.getDataDirectory().getPath()); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { return getTotalBytesOfInternalStorageWithStatFs(stat); } else { return getTotalBytesOfInternalStorageWithStatFsPreJBMR2(stat); } } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) private long getTotalBytesOfInternalStorageWithStatFs(StatFs stat) { return stat.getTotalBytes(); } @SuppressWarnings("deprecation") private long getTotalBytesOfInternalStorageWithStatFsPreJBMR2(StatFs stat) { return (long) stat.getBlockSize() * stat.getBlockCount(); } }
- AndroidManifest.xml
<application> <meta-data android:name="hoge.fuga.application.LimitCacheSizeGlideModule" android:value="GlideModule" /> </application>
この場合 hoge.fugaがパッケージ名=applicationId
備忘的なメモ
Proguardはハマるので下記の記事はメモ
Frescoとかはどうなのさ?
- 特色としてWebP対応が有るけど、WebPのデータって作らないよな・・
- iOSでもlibwebpというLibraryを使えば表示できるようなのですが
- Glideの特色としてAnimationGif対応とか有りますけど使ってるの見たことないorz
- xmlでレイアウト定義で書く
- G様標準のAppCompatとかでないとlayoutPreviewでエラーor固まるのでかなりストレス
例)マテリアルっぽいインジケータの導入 とか・・
compile ‘com.hannesdorfmann.smoothprogressbar:library:1.0.0@aar’