Cara malas memuat gambar di ListView di Android


1931

Saya menggunakan a ListViewuntuk menampilkan beberapa gambar dan keterangan yang terkait dengan gambar-gambar itu. Saya mendapatkan gambar dari Internet. Apakah ada cara untuk malas memuat gambar sehingga saat teks ditampilkan, UI tidak diblokir dan gambar ditampilkan saat diunduh?

Jumlah gambar tidak diperbaiki.


10
Anda dapat menggunakan AsyncImageView GreenDroid . Panggil saja setUrl.
Pascal Dimassimo

7
Saya sudah menggunakannya. Ini adalah implementasi yang luar biasa. Berita buruk bahwa AsyncImageView adalah bagian dari proyek GreenDroid besar, yang membuat aplikasi Anda lebih besar bahkan dalam kasus yang Anda butuhkan adalah AsyncImageView. Juga, sepertinya, proyek GreenDroid tidak diperbarui sejak 2011.
borisstr

5
Anda bahkan dapat mencoba pustaka ini: Android-http-image-manager yang menurut saya adalah yang terbaik untuk memuat gambar yang tidak sinkron.
Ritesh Kumar Dubey

34
Cukup gunakan Picasso, itu akan melakukan semuanya sendiri. 'Picasso.with (yourContext) .load (img src / path / drawable here). Ke (imageView yaitu target Anda);' Itu dia!
Anuj Sharma

6
coba gunakan: github.com/nostra13/Android-Universal-Image-Loader , perpustakaan ini sangat cepat dan efisien untuk pemuatan yang malas dan caching gambar
krunal patel

Jawaban:


1087

Inilah yang saya buat untuk menahan gambar yang sedang ditampilkan aplikasi saya. Harap dicatat bahwa objek "Log" yang digunakan di sini adalah pembungkus khusus saya di sekitar kelas Log terakhir di dalam Android.

package com.wilson.android.library;

/*
 Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License.  You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
*/
import java.io.IOException;

public class DrawableManager {
    private final Map<String, Drawable> drawableMap;

    public DrawableManager() {
        drawableMap = new HashMap<String, Drawable>();
    }

    public Drawable fetchDrawable(String urlString) {
        if (drawableMap.containsKey(urlString)) {
            return drawableMap.get(urlString);
        }

        Log.d(this.getClass().getSimpleName(), "image url:" + urlString);
        try {
            InputStream is = fetch(urlString);
            Drawable drawable = Drawable.createFromStream(is, "src");


            if (drawable != null) {
                drawableMap.put(urlString, drawable);
                Log.d(this.getClass().getSimpleName(), "got a thumbnail drawable: " + drawable.getBounds() + ", "
                        + drawable.getIntrinsicHeight() + "," + drawable.getIntrinsicWidth() + ", "
                        + drawable.getMinimumHeight() + "," + drawable.getMinimumWidth());
            } else {
              Log.w(this.getClass().getSimpleName(), "could not get thumbnail");
            }

            return drawable;
        } catch (MalformedURLException e) {
            Log.e(this.getClass().getSimpleName(), "fetchDrawable failed", e);
            return null;
        } catch (IOException e) {
            Log.e(this.getClass().getSimpleName(), "fetchDrawable failed", e);
            return null;
        }
    }

    public void fetchDrawableOnThread(final String urlString, final ImageView imageView) {
        if (drawableMap.containsKey(urlString)) {
            imageView.setImageDrawable(drawableMap.get(urlString));
        }

        final Handler handler = new Handler() {
            @Override
            public void handleMessage(Message message) {
                imageView.setImageDrawable((Drawable) message.obj);
            }
        };

        Thread thread = new Thread() {
            @Override
            public void run() {
                //TODO : set imageView to a "pending" image
                Drawable drawable = fetchDrawable(urlString);
                Message message = handler.obtainMessage(1, drawable);
                handler.sendMessage(message);
            }
        };
        thread.start();
    }

    private InputStream fetch(String urlString) throws MalformedURLException, IOException {
        DefaultHttpClient httpClient = new DefaultHttpClient();
        HttpGet request = new HttpGet(urlString);
        HttpResponse response = httpClient.execute(request);
        return response.getEntity().getContent();
    }
}

104
Saya pikir Anda harus menggunakan SoftReferences agar program Anda tidak akan pernah menyebabkan OutOfMemoryException. Karena GC dapat menghapus softreferences ketika heap size meningkat ... Anda dapat mengelola generasi Anda sendiri seperti setelah beberapa detik Anda dapat meletakkan gambar Anda ke daftar itu dan sebelum memuat Anda harus memeriksa bahwa jika gambar ada maka jangan unduh lagi alih-alih kumpulkan dari daftar itu dan juga memasukkannya kembali ke daftar softref Anda dan setelah beberapa saat Anda dapat membersihkan hardlist Anda :)
AZ_

36
Proyek Google Shelves adalah contoh luar biasa bagaimana mereka melakukan code.google.com/p/shelves
AZ_

12
Apakah Anda tidak melewatkan pengembalian ketika drawableMap berisi gambar ... tanpa memulai utas pengambilan?
Karussell

5
Kode ini memiliki beberapa masalah. Pertama, Anda harus men-cache Drawables, yang akan menyebabkan kebocoran memori: stackoverflow.com/questions/7648740/… . Kedua cache itu sendiri tidak pernah dihapus sehingga akan tumbuh selamanya, itu kebocoran memori lain.
satur9nine

10
belum ada yang mendengar tentang LRU Cache developer.android.com/training/displaying-bitmaps/…
Muhammad Babar

1025

Saya membuat demo sederhana daftar malas (terletak di GitHub) dengan gambar.

Penggunaan Dasar

ImageLoader imageLoader=new ImageLoader(context); ...
imageLoader.DisplayImage(url, imageView); 

Jangan lupa untuk menambahkan izin berikut ke AndroidManifest.xml Anda:

 <uses-permission android:name="android.permission.INTERNET"/>
 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> Please

buat hanya satu instance dari ImageLoader dan gunakan kembali di sekitar aplikasi Anda. Dengan cara ini caching gambar akan jauh lebih efisien.

Mungkin bermanfaat bagi seseorang. Ini mengunduh gambar di utas latar belakang. Gambar sedang di-cache pada kartu SD dan di memori. Implementasi cache sangat sederhana dan hanya cukup untuk demo. Saya memecahkan kode gambar dengan inSampleSize untuk mengurangi konsumsi memori. Saya juga mencoba menangani tampilan daur ulang dengan benar.

Teks alternatif


7
Terima kasih, saya menggunakan versi kode Anda yang sedikit dimodifikasi di hampir semua proyek saya: code.google.com/p/vimeoid/source/browse/apk/src/com/fedorvlasov/…
shaman.sir

3
Adakah yang melihat kode oleh Gilles Debunne sebagai alternatif. Dua perbedaan besar yang saya lihat adalah 1) dalam caching memori vs kartu SD dan 2) penggunaan AsyncTasks alih-alih kumpulan thread. android-developers.blogspot.com/2010/07/...
Richard

4
Ada bug, terkadang dipecat: 10-13 09: 58: 46.738: ERROR / AndroidRuntime (24250): Handler tidak tertangkap: keluar utas utama karena pengecualian tanpa tertangkap 10-13 09: 58: 46.768: ERROR / AndroidRuntime (24250) : java.lang.ArrayIndexOutOfBoundsException: Array index di luar jangkauan: 0 10-13 09: 58: 46.768: ERROR / AndroidRuntime (24250): at java.util.Vector.elementAt (Vector.java:331) 10-13 09: 58: 46.768: ERROR / AndroidRuntime (24250): di java.util.Vector.get (Vector.java:445) 10-13 09: 58: 46.768: ERROR / AndroidRuntime (24250): at com.my.app.image .ImageLoader $ PhotosQueue.Clean (ImageLoader.java:91)
Mikooos

64
Saya ingin menambahkan untuk pemula seperti saya bahwa jika Anda ingin menambahkan kode ini ke proyek Anda sendiri, Anda harus menambahkan izin cache eksternal dalam manifes. Terima Kasih
Adam

2
@BruceHill Tidak ada photosQueue dalam kode sumber terbaru. Anda tampaknya menggunakan beberapa versi yang sangat lama.
Fedor

552

Saya merekomendasikan instrumen open source Universal Image Loader . Ini awalnya didasarkan pada proyek Fedor Vlasov LazyList dan telah jauh lebih baik sejak saat itu.

  • Memuat gambar multithread
  • Kemungkinan penyetelan lebar konfigurasi ImageLoader (pelaksana ulir, downlaoder, dekoder, memori dan cache disk, opsi tampilan gambar, dan lainnya)
  • Kemungkinan cache gambar dalam memori dan / atau pada sysytem file perangkat (atau kartu SD)
  • Kemungkinan untuk "mendengarkan" proses pemuatan
  • Kemungkinan untuk menyesuaikan setiap panggilan gambar tampilan dengan opsi yang terpisah
  • Dukungan widget
  • Dukungan Android 2.0+


16
jika Anda mencari kode "pemuatan gambar" yang luar biasa yang diperlakukan dan dipertahankan oleh penciptanya sebagai bayinya yang berharga (bukan hanya solusi satu kali), Anda baru saja menemukannya; big ups Nostra
AndroidGecko

Benar-benar pekerjaan yang hebat, tetapi sedikit keterlambatan dalam pandangan saya. Jika Anda bisa memberi tahu performa terbaik 'ImageLoader' build ... atau mungkin itu karena 'DisplayImageOptions'.
dinigo

Saya sangat menyukai gridview ini tetapi saya tidak dapat menggunakannya untuk tutorial saya. saya seorang pemula android. Saya membuat aplikasi tentang gridview tetapi aplikasi saya adalah targetSdkVersion 15 jadi saya harus menggunakan Asynctask untuk proses di latar belakang. Saya dulu menggunakan gridview ini tetapi tidak berhasil. bagaimana saya bisa menggunakannya di targetSdkVersion 15?
kongkea

Anda dapat membuat pertanyaan terpisah dengan tag [universal-image-loader]
nostra13

3
Setelah membaca dokumen resmi android dan mencoba melakukan hal ini sendiri, saya cukup yakin perpustakaan ini pada akhirnya semuanya memuat gambar. Tidak dapat cukup mengungguli.
Core

159

Multithreading For Performance , tutorial oleh Gilles Debunne.

Ini dari Blog Pengembang Android. Kode yang disarankan menggunakan:

  • AsyncTasks.
  • Ukuran yang keras, terbatas FIFO cache,.
  • garbage collectCache yang lembut dan mudah diunduh.
  • Tempat penampung Drawable saat Anda mengunduh.

masukkan deskripsi gambar di sini


10
Ini berfungsi dengan baik di 2.1 juga. Hanya saja, jangan gunakan AndroidHttpClient.
Thomas Ahle

2
@ thomas-ahle Terima kasih, saya melihat AndroidHttpClient memberikan kesalahan di 2.1, karena ini diterapkan dari 2.2, tetapi tidak benar-benar mencoba menemukan sesuatu yang lain untuk menggantikannya.
Adinia

4
@ Adina Kamu benar, aku lupa. Namun tidak ada dalam resep yang tidak bisa dilakukan dengan HttpClient normal.
Thomas Ahle

Saya telah mendengar di beberapa tempat, bahwa Google tidak merekomendasikan referensi lunak karena kernel android sangat bersemangat untuk mengumpulkan referensi ini dibandingkan dengan versi sistem sebelumnya.
Muhammad Ahmed AbuTalib

109

Pembaruan: Perhatikan bahwa jawaban ini sangat tidak efektif sekarang. Pengumpul Sampah bertindak agresif pada SoftReference dan WeakReference, jadi kode ini TIDAK cocok untuk aplikasi baru. (Alih-alih, coba pustaka seperti Universal Image Loader yang disarankan dalam jawaban lain.)

Terima kasih kepada James untuk kodenya, dan Bao-Long untuk saran penggunaan SoftReference. Saya menerapkan perubahan SoftReference pada kode James. Sayangnya SoftReferences menyebabkan gambar saya dikumpulkan terlalu cepat. Dalam kasus saya, itu baik-baik saja tanpa SoftReference, karena ukuran daftar saya terbatas dan gambar saya kecil.

Ada diskusi dari tahun lalu mengenai SoftReferences di grup google: tautan ke utas . Sebagai solusi untuk pengumpulan sampah yang terlalu dini, mereka menyarankan kemungkinan pengaturan ukuran tumpukan VM secara manual menggunakan dalvik.system.VMRuntime.setMinimumHeapSize (), yang tidak terlalu menarik bagi saya.

public DrawableManager() {
    drawableMap = new HashMap<String, SoftReference<Drawable>>();
}

public Drawable fetchDrawable(String urlString) {
    SoftReference<Drawable> drawableRef = drawableMap.get(urlString);
    if (drawableRef != null) {
        Drawable drawable = drawableRef.get();
        if (drawable != null)
            return drawable;
        // Reference has expired so remove the key from drawableMap
        drawableMap.remove(urlString);
    }

    if (Constants.LOGGING) Log.d(this.getClass().getSimpleName(), "image url:" + urlString);
    try {
        InputStream is = fetch(urlString);
        Drawable drawable = Drawable.createFromStream(is, "src");
        drawableRef = new SoftReference<Drawable>(drawable);
        drawableMap.put(urlString, drawableRef);
        if (Constants.LOGGING) Log.d(this.getClass().getSimpleName(), "got a thumbnail drawable: " + drawable.getBounds() + ", "
                + drawable.getIntrinsicHeight() + "," + drawable.getIntrinsicWidth() + ", "
                + drawable.getMinimumHeight() + "," + drawable.getMinimumWidth());
        return drawableRef.get();
    } catch (MalformedURLException e) {
        if (Constants.LOGGING) Log.e(this.getClass().getSimpleName(), "fetchDrawable failed", e);
        return null;
    } catch (IOException e) {
        if (Constants.LOGGING) Log.e(this.getClass().getSimpleName(), "fetchDrawable failed", e);
        return null;
    }
}

public void fetchDrawableOnThread(final String urlString, final ImageView imageView) {
    SoftReference<Drawable> drawableRef = drawableMap.get(urlString);
    if (drawableRef != null) {
        Drawable drawable = drawableRef.get();
        if (drawable != null) {
            imageView.setImageDrawable(drawableRef.get());
            return;
        }
        // Reference has expired so remove the key from drawableMap
        drawableMap.remove(urlString);
    }

    final Handler handler = new Handler() {
        @Override
        public void handleMessage(Message message) {
            imageView.setImageDrawable((Drawable) message.obj);
        }
    };

    Thread thread = new Thread() {
        @Override
        public void run() {
            //TODO : set imageView to a "pending" image
            Drawable drawable = fetchDrawable(urlString);
            Message message = handler.obtainMessage(1, drawable);
            handler.sendMessage(message);
        }
    };
    thread.start();
}

3
Anda dapat membuat generasi seperti generasi keras dan generasi lunak. Anda dapat memperbaiki cache waktu yang jelas akan menghapus semua gambar yang tidak diakses dalam 3 detik .. Anda dapat melihat proyek Google
Shelves

developer.android.com/reference/java/lang/ref/… doc SoftReference memiliki catatan tentang caching, lihat bagian "Hindari Referensi Lembut untuk Caching". Sebagian besar aplikasi harus menggunakan android.util.LruCache alih-alih referensi lunak.
vokilam

Saya mengagumi kode Anda tetapi sekarang di Android O / S baru ada pengumpulan sampah 'agresif'. Memegang referensi yang lemah tidak masuk akal bagi saya.
j2emanue

@ j2emanue Anda benar, ketika saya mencoba menunjukkan di bagian atas jawaban saya, SoftReferences adalah sampah yang dikumpulkan terlalu cepat. Saya akan mencoba mengedit jawaban ini untuk membuatnya lebih jelas.
TalkLittle

97

Picasso

Gunakan Perpustakaan Picasso Jake Wharton. (Pustaka ImageLoading Sempurna dari pengembang ActionBarSherlock)

Pengunduhan dan caching library gambar yang tangguh untuk Android.

Gambar menambahkan konteks dan bakat visual yang sangat dibutuhkan untuk aplikasi Android. Picasso memungkinkan pemuatan gambar tanpa repot di aplikasi Anda — sering kali dalam satu baris kode!

Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);

Banyak perangkap umum pemuatan gambar di Android ditangani secara otomatis oleh Picasso:

Menangani daur ulang ImageView dan unduh pembatalan dalam adaptor. Transformasi gambar yang kompleks dengan penggunaan memori minimal. Memori dan cache disk otomatis.

Perpustakaan Picasso Jake Wharton

Meluncur

Glide adalah kerangka kerja manajemen media open source yang cepat dan efisien untuk Android yang membungkus decoding media, memori dan caching disk, dan pengumpulan sumber daya ke dalam antarmuka yang sederhana dan mudah digunakan.

Glide mendukung pengambilan, dekode, dan penayangan gambar video, gambar, dan GIF animasi. Glide termasuk api fleksibel yang memungkinkan pengembang untuk terhubung ke hampir semua tumpukan jaringan. Secara default Glide menggunakan stack berbasis HttpUrlConnection khusus, tetapi juga menyertakan pustaka utilitas yang dihubungkan dengan proyek Volley Google atau pustaka OkHttp Square.

Glide.with(this).load("http://goo.gl/h8qOq7").into(imageView);

Fokus utama Glide adalah membuat pengguliran daftar gambar semulus dan secepat mungkin, tetapi Glide juga efektif untuk hampir semua kasus di mana Anda perlu mengambil, mengubah ukuran, dan menampilkan gambar jarak jauh.

Pustaka Pemuatan Gambar Glide

Fresco oleh Facebook

Fresco adalah sistem yang kuat untuk menampilkan gambar dalam aplikasi Android.

Fresco menangani pemuatan dan tampilan gambar, jadi Anda tidak harus melakukannya. Ini akan memuat gambar dari jaringan, penyimpanan lokal, atau sumber daya lokal, dan menampilkan pengganti sampai gambar telah tiba. Ini memiliki dua tingkat cache; satu di memori dan satu lagi di penyimpanan internal.

Fresco Github

Di Android 4.x dan lebih rendah, Fresco menempatkan gambar di wilayah khusus memori Android. Ini memungkinkan aplikasi Anda berjalan lebih cepat - dan lebih jarang mengalami OutOfMemoryError yang ditakuti.

Dokumentasi Fresco


Picasso adalah perpustakaan yang dikembangkan oleh Square
LordRaydenMK

83

Loader berkinerja tinggi - setelah memeriksa metode yang disarankan di sini, saya menggunakan solusi Ben dengan beberapa perubahan -

  1. Saya menyadari bahwa bekerja dengan drawables lebih cepat daripada dengan bitmap jadi saya menggunakan drawables

  2. Menggunakan SoftReference bagus, tetapi itu membuat gambar yang di-cache terlalu sering dihapus, jadi saya menambahkan daftar Linked yang menyimpan referensi gambar, mencegah dari gambar yang akan dihapus, hingga mencapai ukuran yang telah ditentukan.

  3. Untuk membuka InputStream saya menggunakan java.net.URLConnection yang memungkinkan saya untuk menggunakan cache web (Anda perlu mengatur cache respons terlebih dahulu, tapi itu cerita lain)

Kode saya:

import java.util.Map; 
import java.util.HashMap; 
import java.util.LinkedList; 
import java.util.Collections; 
import java.util.WeakHashMap; 
import java.lang.ref.SoftReference; 
import java.util.concurrent.Executors; 
import java.util.concurrent.ExecutorService; 
import android.graphics.drawable.Drawable;
import android.widget.ImageView;
import android.os.Handler;
import android.os.Message;
import java.io.InputStream;
import java.net.MalformedURLException; 
import java.io.IOException; 
import java.net.URL;
import java.net.URLConnection;

public class DrawableBackgroundDownloader {    

private final Map<String, SoftReference<Drawable>> mCache = new HashMap<String, SoftReference<Drawable>>();   
private final LinkedList <Drawable> mChacheController = new LinkedList <Drawable> ();
private ExecutorService mThreadPool;  
private final Map<ImageView, String> mImageViews = Collections.synchronizedMap(new WeakHashMap<ImageView, String>());  

public static int MAX_CACHE_SIZE = 80; 
public int THREAD_POOL_SIZE = 3;

/**
 * Constructor
 */
public DrawableBackgroundDownloader() {  
    mThreadPool = Executors.newFixedThreadPool(THREAD_POOL_SIZE);  
}  


/**
 * Clears all instance data and stops running threads
 */
public void Reset() {
    ExecutorService oldThreadPool = mThreadPool;
    mThreadPool = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
    oldThreadPool.shutdownNow();

    mChacheController.clear();
    mCache.clear();
    mImageViews.clear();
}  

public void loadDrawable(final String url, final ImageView imageView,Drawable placeholder) {  
    mImageViews.put(imageView, url);  
    Drawable drawable = getDrawableFromCache(url);  

    // check in UI thread, so no concurrency issues  
    if (drawable != null) {  
        //Log.d(null, "Item loaded from mCache: " + url);  
        imageView.setImageDrawable(drawable);  
    } else {  
        imageView.setImageDrawable(placeholder);  
        queueJob(url, imageView, placeholder);  
    }  
} 


private Drawable getDrawableFromCache(String url) {  
    if (mCache.containsKey(url)) {  
        return mCache.get(url).get();  
    }  

    return null;  
}

private synchronized void putDrawableInCache(String url,Drawable drawable) {  
    int chacheControllerSize = mChacheController.size();
    if (chacheControllerSize > MAX_CACHE_SIZE) 
        mChacheController.subList(0, MAX_CACHE_SIZE/2).clear();

    mChacheController.addLast(drawable);
    mCache.put(url, new SoftReference<Drawable>(drawable));

}  

private void queueJob(final String url, final ImageView imageView,final Drawable placeholder) {  
    /* Create handler in UI thread. */  
    final Handler handler = new Handler() {  
        @Override  
        public void handleMessage(Message msg) {  
            String tag = mImageViews.get(imageView);  
            if (tag != null && tag.equals(url)) {
                if (imageView.isShown())
                    if (msg.obj != null) {
                        imageView.setImageDrawable((Drawable) msg.obj);  
                    } else {  
                        imageView.setImageDrawable(placeholder);  
                        //Log.d(null, "fail " + url);  
                    } 
            }  
        }  
    };  

    mThreadPool.submit(new Runnable() {  
        @Override  
        public void run() {  
            final Drawable bmp = downloadDrawable(url);
            // if the view is not visible anymore, the image will be ready for next time in cache
            if (imageView.isShown())
            {
                Message message = Message.obtain();  
                message.obj = bmp;
                //Log.d(null, "Item downloaded: " + url);  

                handler.sendMessage(message);
            }
        }  
    });  
}  



private Drawable downloadDrawable(String url) {  
    try {  
        InputStream is = getInputStream(url);

        Drawable drawable = Drawable.createFromStream(is, url);
        putDrawableInCache(url,drawable);  
        return drawable;  

    } catch (MalformedURLException e) {  
        e.printStackTrace();  
    } catch (IOException e) {  
        e.printStackTrace();  
    }  

    return null;  
}  


private InputStream getInputStream(String urlString) throws MalformedURLException, IOException {
    URL url = new URL(urlString);
    URLConnection connection;
    connection = url.openConnection();
    connection.setUseCaches(true); 
    connection.connect();
    InputStream response = connection.getInputStream();

    return response;
}
}

Bagus sekali! BTW ada kesalahan ketik pada nama kelas.
Mullins

5
Dalam hal ini menghemat waktu orang lain: import java.util.Map; import java.util.HashMap; import java.util.LinkedList; import java.util.Collections; import java.util.WeakHashMap; import java.lang.ref.SoftReference; import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; import android.graphics.drawable.Drawable; import android.widget.ImageView; import android.os.Handler; import android.os.Message; import java.io.InputStream; import java.net.MalformedURLException; import java.io.IOException; import java.net.URL; import java.net.URLConnection;
Michael Reed

Terima kasih banyak, ini implementasi yang bagus. Saya juga meletakkan placeholder yang berbeda untuk saat drawable sedang dimuat sehingga pengguna bisa mendapatkan umpan balik.
Juan Hernandez

Juga saya pikir lebih baik untuk menggunakan antrian LIFO di executorService (mThreadPool) daripada FIFO default sehingga gambar terakhir yang diminta (yang mungkin adalah yang terlihat) dimuat terlebih dahulu. Lihat stackoverflow.com/questions/4620061/how-to-create-lifo-executor
Juan Hernandez

9
@MichaelReed, jika Anda pengguna Eclipse, saya sarankan menggunakan Ctrl-Shift-O (itu huruf O, bukan angka 0). Ini mengotomatiskan proses penambahan impor dan mengaturnya untuk Anda. Jika Anda menggunakan Mac, gunakan Command-Shift-O sebagai gantinya.
SilithCrowe

78

Saya telah mengikuti Pelatihan Android ini dan saya pikir ini melakukan pekerjaan yang sangat baik dalam mengunduh gambar tanpa memblokir UI utama. Ini juga menangani caching dan berurusan dengan menggulir banyak gambar: Memuat Bitmap Besar Secara Efisien


Maaf, saya hanya menunjuk satu kelas untuk aplikasi Google IO (dan saya terlambat mengedit). Anda harus benar-benar mempelajari semua kelas utilitas pemuatan dan penyimpanan gambar yang dapat Anda temukan dalam paket yang sama dengan kelas cache .
mkuech

Adakah yang akan merekomendasikan mengambil file DiskLruCache, Image * .java dari folder util aplikasi iosched untuk membantu menangani pemuatan / caching gambar untuk tampilan daftar? Maksud saya, layak mengikuti panduan Pengembang online tentang masalah ini tetapi kelas-kelas ini (dari iosched) sedikit lebih jauh dengan polanya.
Gautam

65

1. Picasso memungkinkan pemuatan gambar tanpa repot di aplikasi Anda — sering kali dalam satu baris kode!

Gunakan Gradle:

implementation 'com.squareup.picasso:picasso:2.71828'

Hanya satu baris kode!

Picasso.get().load("http://i.imgur.com/DvpvklR.png").into(imageView);

2. Glide Perpustakaan pemuatan dan penyimpanan gambar untuk Android yang berfokus pada pengguliran yang lancar

Gunakan Gradle:

repositories {
  mavenCentral() 
  google()
}

dependencies {
   implementation 'com.github.bumptech.glide:glide:4.7.1'
   annotationProcessor 'com.github.bumptech.glide:compiler:4.7.1'
}

// Untuk tampilan sederhana:

  Glide.with(this).load("http://i.imgur.com/DvpvklR.png").into(imageView);

3. fresco adalah sistem yang kuat untuk menampilkan gambar di aplikasi Android.

Memulai dengan Fresco


Tutorial ini dapat membantu Anda lebih banyak untuk PICASOO: - androidtutorialshub.com/… dan GLIDE: - androidtutorialshub.com/…
lalit vasan

52

Saya telah menulis tutorial yang menjelaskan cara melakukan pemuatan gambar secara malas dalam tampilan daftar. Saya masuk ke beberapa detail tentang masalah daur ulang dan konkurensi. Saya juga menggunakan kumpulan thread tetap untuk mencegah pemijahan banyak thread.

Pemuatan gambar secara malas di Tutorial Listview


41

Cara saya melakukannya adalah dengan meluncurkan utas untuk mengunduh gambar di latar belakang dan memberikannya callback untuk setiap item daftar. Ketika sebuah gambar selesai diunduh, ia memanggil panggilan balik yang memperbarui tampilan untuk item daftar.

Metode ini tidak berfungsi dengan baik ketika Anda mendaur ulang tampilan.


menggunakan utas untuk setiap gambar adalah pendekatan yang saya gunakan juga. Jika Anda memisahkan model Anda dari tampilan Anda, Anda dapat bertahan model di luar Kegiatan (seperti dalam kelas 'aplikasi' Anda) untuk menyimpannya di-cache. Waspadalah terhadap kehabisan sumber daya jika Anda memiliki banyak gambar.
James A Wilson

bisakah Anda menjelaskannya? Saya baru mengenal pengembangan android. Terima kasih atas tipsnya
lostInTransit

14
Memulai utas baru untuk setiap gambar bukanlah solusi yang efektif. Anda dapat berakhir dengan banyak utas di memori dan pembekuan UI.
Fedor

Fedor, setuju, saya biasanya menggunakan antrian dan kumpulan utas, itulah cara terbaik untuk pergi.
jasonhudgins


29

Ini adalah masalah umum pada Android yang telah dipecahkan dalam banyak hal oleh banyak orang. Menurut pendapat saya solusi terbaik yang pernah saya lihat adalah perpustakaan yang relatif baru bernama Picasso . Berikut adalah hal-hal penting:

  • Open source, tetapi dipimpin oleh Jake Whartondari ActionBarSherlock ketenaran.
  • Muat secara sinkron gambar dari sumber daya jaringan atau aplikasi dengan satu baris kode
  • ListViewDeteksi otomatis
  • Disk dan memori cache otomatis
  • Dapat melakukan transformasi khusus
  • Banyak opsi yang bisa dikonfigurasi
  • API super sederhana
  • Sering diperbarui

29

Saya telah menggunakan NetworkImageView dari Android Volley Library yang baru com.android.volley.toolbox.NetworkImageView, dan sepertinya berfungsi cukup baik. Tampaknya, ini adalah tampilan yang sama yang digunakan di Google Play dan aplikasi Google baru lainnya. Layak untuk dicoba.


1
Saya pikir ini adalah solusi terbaik - jawaban lain sangat tua - voli benar-benar cepat dan dikombinasikan dengan jake warthons disklrucache itu solusi perfekt - saya mencoba banyak orang lain tetapi tidak ada yang stabil dan secepat voli
Alexander Sidikov Pfeif

26

Nah, waktu pemuatan gambar dari Internet memiliki banyak solusi. Anda juga dapat menggunakan perpustakaan Android-Query . Ini akan memberi Anda semua aktivitas yang diperlukan. Pastikan apa yang ingin Anda lakukan dan baca halaman wiki perpustakaan. Dan selesaikan pembatasan pemuatan gambar.

Ini kode saya:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    View v = convertView;
    if (v == null) {
        LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        v = vi.inflate(R.layout.row, null);
    }

    ImageView imageview = (ImageView) v.findViewById(R.id.icon);
    AQuery aq = new AQuery(convertView);

    String imageUrl = "http://www.vikispot.com/z/images/vikispot/android-w.png";

    aq.id(imageview).progress(this).image(imageUrl, true, true, 0, 0, new BitmapAjaxCallback() {
        @Override
        public void callback(String url, ImageView iv, Bitmap bm, AjaxStatus status) {
            iv.setImageBitmap(bm);
        }
    ));

    return v;
}

Ini harus menyelesaikan masalah pemuatan malas Anda.


Nice Work to me, But Need a Jar file untuk dimasukkan dalam proyek Anda. Anda dapat mengunduh file JAR dari sini AQuery androidAQuery = AQuery baru (ini); tautan adalah: code.google.com/archive/p/android-query/downloads
Selim Raza

25

Saya pikir masalah ini sangat populer di kalangan pengembang Android, dan ada banyak perpustakaan seperti itu yang mengklaim untuk menyelesaikan masalah ini, tetapi hanya beberapa dari mereka yang tampaknya sudah siap. AQuery adalah salah satu perpustakaan seperti itu, tetapi lebih baik daripada kebanyakan dari mereka dalam semua aspek dan layak untuk dicoba.


22

Anda harus mencoba Universal Loader ini. Saya menggunakan ini setelah melakukan banyak RnD pada pemuatan malas.

Pemuat Gambar Universal

fitur

  • Memuat gambar multithread (async atau sinkronisasi)
  • Kustomisasi luas konfigurasi ImageLoader (pelaksana ulir, pengunduh, dekoder, memori dan cache disk, tampilkan opsi gambar, dll.)
  • Banyak opsi penyesuaian untuk setiap panggilan gambar tampilan (gambar rintisan, sakelar caching, opsi decoding, pemrosesan dan tampilan Bitmap, dll.)
  • Caching gambar dalam memori dan / atau pada disk (sistem file perangkat atau kartu SD)
  • Mendengarkan proses pemuatan (termasuk proses pengunduhan)

Dukungan Android 2.0+

masukkan deskripsi gambar di sini


20

Lihat Shutterbug , portWebImage ringan (perpustakaan yang bagus di iOS) dari Applidium ke Android. Ini mendukung caching asinkron, menyimpan URL yang gagal, menangani konkurensi dengan baik, dan subkelas yang membantu disertakan.

Permintaan penarikan (dan laporan bug) juga diterima!


16

DroidParts memiliki ImageFetcher yang membutuhkan nol konfigurasi untuk memulai.

  • Menggunakan disk & di dalam memori Least Recent Used (LRU) cache.
  • Secara efisien menerjemahkan gambar.
  • Mendukung memodifikasi bitmap di utas latar belakang.
  • Memiliki cross-fade sederhana.
  • Memiliki pemanggilan ulang progres pemuatan gambar.

Klon DroidPartsGram untuk contoh:

Masukkan deskripsi gambar di sini


Hai, Saya sudah melihat contoh kode tapi saya mengalami masalah menggunakan ImageFetcher dengan ArrayAdapter, maukah Anda melihat pertanyaan saya? stackoverflow.com/questions/21089147/... Terima kasih =]
masha

16

Hanya tip cepat untuk seseorang yang ragu tentang perpustakaan apa yang digunakan untuk memuat gambar yang malas:

Ada empat cara dasar.

  1. DIY => Bukan solusi terbaik tetapi untuk beberapa gambar dan jika Anda ingin pergi tanpa repot menggunakan perpustakaan lain

  2. Volley's Lazy Memuat library => Dari guys di android. Ini bagus dan semuanya tetapi tidak didokumentasikan dengan baik dan karenanya merupakan masalah untuk digunakan.

  3. Picasso: Solusi sederhana yang hanya berfungsi, Anda bahkan dapat menentukan ukuran gambar persis yang ingin Anda bawa. Ini sangat sederhana untuk digunakan tetapi mungkin tidak terlalu "performan" untuk aplikasi yang harus berurusan dengan jumlah gambar yang sangat banyak.

  4. UIL: Cara terbaik untuk malas memuat gambar. Anda dapat men-cache gambar (tentu saja Anda memerlukan izin), menginisialisasi loader sekali, kemudian menyelesaikan pekerjaan Anda. Pustaka pemuatan gambar asinkron yang paling matang yang pernah saya lihat sejauh ini.


15

Novoda juga memiliki perpustakaan pemuatan gambar yang malas dan banyak aplikasi seperti Songkick, Podio, SecretDJ, dan ImageSearch menggunakan perpustakaan mereka.

Perpustakaan mereka dihosting di sini di Github dan mereka memiliki pelacak isu yang cukup aktif juga. Proyek mereka tampaknya cukup aktif juga, dengan lebih dari 300 komitmen pada saat menulis balasan ini.


1
Sebenarnya Novoda adalah perpustakaan yang hebat tapi ... kadang-kadang Anda tidak perlu perpustakaan besar dan hanya pendekatan sederhana dari solusinya. Itulah sebabnya LazyList di Github sangat bagus, jika aplikasi Anda hanya menampilkan gambar di listView dan bukan fitur utama aplikasi Anda, hanya kegiatan lain yang saya lebih suka menggunakan sesuatu yang lebih ringan. Kalau tidak, jika Anda tahu bahwa Anda harus sering menggunakannya dan merupakan bagian dari intinya, cobalah Novoda.
Nicolas Jafelle

13

Periksa garpu LazyList saya . Pada dasarnya, saya meningkatkan LazyList dengan menunda panggilan ImageView dan membuat dua metode:

  1. Saat Anda perlu meletakkan sesuatu seperti "Memuat gambar ..."
  2. Ketika Anda perlu menunjukkan gambar yang diunduh.

Saya juga meningkatkan ImageLoader dengan menerapkan singleton pada objek ini.


12

Jika Anda ingin menampilkan tata letak Shimmer seperti Facebook, ada perpustakaan facebook resmi untuk itu. FaceBook Shimmer Android

Ini menangani semuanya, Anda hanya perlu meletakkan kode desain yang Anda inginkan dengan cara bersarang di bingkai shimmer. Berikut ini contoh kode.

<com.facebook.shimmer.ShimmerFrameLayout
     android:id=“@+id/shimmer_view_container”
     android:layout_width=“wrap_content”
     android:layout_height="wrap_content"
     shimmer:duration="1000">

 <here will be your content to display />

</com.facebook.shimmer.ShimmerFrameLayout>

Dan di sini adalah kode java untuk itu.

ShimmerFrameLayout shimmerContainer = (ShimmerFrameLayout) findViewById(R.id.shimmer_view_container);
shimmerContainer.startShimmerAnimation();

Tambahkan ketergantungan ini di file gradle Anda.

implementation 'com.facebook.shimmer:shimmer:0.1.0@aar'

Berikut ini tampilannya.Tangkapan layar Shimmer Android


11

Semua kode di atas memiliki nilai mereka sendiri tetapi dengan pengalaman pribadi saya coba saja dengan Picasso.

Picasso adalah pustaka khusus untuk tujuan ini, bahkan ia akan mengelola cache dan semua operasi jaringan lainnya secara otomatis. Anda harus menambahkan pustaka dalam proyek Anda dan hanya menulis satu baris kode untuk memuat gambar dari URL jarak jauh.

Silakan kunjungi di sini: http://code.tutsplus.com/tutorials/android-sdk-working-with-picasso--cms-22149


9

Gunakan perpustakaan glide. Ini bekerja untuk saya dan akan bekerja untuk kode Anda juga. Ini bekerja untuk gambar maupun gif juga.

ImageView imageView = (ImageView) findViewById(R.id.test_image); 
    GlideDrawableImageViewTarget imagePreview = new GlideDrawableImageViewTarget(imageView);
    Glide
            .with(this)
            .load(url)
            .listener(new RequestListener<String, GlideDrawable>() {
                @Override
                public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) {                       
                    return false;
                }

                @Override
                public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
                    return false;
                }
            })
            .into(imagePreview);
}

7

Saya dapat merekomendasikan cara lain yang berfungsi seperti pesona: Android Query.

Anda dapat mengunduh file JAR dari sini

AQuery androidAQuery = new AQuery(this);

Sebagai contoh:

androidAQuery.id(YOUR IMAGEVIEW).image(YOUR IMAGE TO LOAD, true, true, getDeviceWidth(), ANY DEFAULT IMAGE YOU WANT TO SHOW);

Ini sangat cepat dan akurat, dan menggunakan ini Anda dapat menemukan lebih banyak fitur seperti animasi saat memuat, mendapatkan bitmap (jika perlu), dll.


7

Berikan Aquery mencoba. Ini memiliki metode luar biasa sederhana untuk memuat dan menyimpan gambar secara asinkron.



4
public class ImageDownloader {

Map<String, Bitmap> imageCache;

public ImageDownloader() {
    imageCache = new HashMap<String, Bitmap>();

}

// download function
public void download(String url, ImageView imageView) {
    if (cancelPotentialDownload(url, imageView)) {

        // Caching code right here
        String filename = String.valueOf(url.hashCode());
        File f = new File(getCacheDirectory(imageView.getContext()),
                filename);

        // Is the bitmap in our memory cache?
        Bitmap bitmap = null;

        bitmap = (Bitmap) imageCache.get(f.getPath());

        if (bitmap == null) {

            bitmap = BitmapFactory.decodeFile(f.getPath());

            if (bitmap != null) {
                imageCache.put(f.getPath(), bitmap);
            }

        }
        // No? download it
        if (bitmap == null) {
            try {
                BitmapDownloaderTask task = new BitmapDownloaderTask(
                        imageView);
                DownloadedDrawable downloadedDrawable = new DownloadedDrawable(
                        task);
                imageView.setImageDrawable(downloadedDrawable);
                task.execute(url);
            } catch (Exception e) {
                Log.e("Error==>", e.toString());
            }

        } else {
            // Yes? set the image
            imageView.setImageBitmap(bitmap);
        }
    }
}

// cancel a download (internal only)
private static boolean cancelPotentialDownload(String url,
        ImageView imageView) {
    BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);

    if (bitmapDownloaderTask != null) {
        String bitmapUrl = bitmapDownloaderTask.url;
        if ((bitmapUrl == null) || (!bitmapUrl.equals(url))) {
            bitmapDownloaderTask.cancel(true);
        } else {
            // The same URL is already being downloaded.
            return false;
        }
    }
    return true;
}

// gets an existing download if one exists for the imageview
private static BitmapDownloaderTask getBitmapDownloaderTask(
        ImageView imageView) {
    if (imageView != null) {
        Drawable drawable = imageView.getDrawable();
        if (drawable instanceof DownloadedDrawable) {
            DownloadedDrawable downloadedDrawable = (DownloadedDrawable) drawable;
            return downloadedDrawable.getBitmapDownloaderTask();
        }
    }
    return null;
}

// our caching functions
// Find the dir to save cached images
private static File getCacheDirectory(Context context) {
    String sdState = android.os.Environment.getExternalStorageState();
    File cacheDir;

    if (sdState.equals(android.os.Environment.MEDIA_MOUNTED)) {
        File sdDir = android.os.Environment.getExternalStorageDirectory();

        // TODO : Change your diretcory here
        cacheDir = new File(sdDir, "data/ToDo/images");
    } else
        cacheDir = context.getCacheDir();

    if (!cacheDir.exists())
        cacheDir.mkdirs();
    return cacheDir;
}

private void writeFile(Bitmap bmp, File f) {
    FileOutputStream out = null;

    try {
        out = new FileOutputStream(f);
        bmp.compress(Bitmap.CompressFormat.PNG, 80, out);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            if (out != null)
                out.close();
        } catch (Exception ex) {
        }
    }
}

// download asynctask
public class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> {
    private String url;
    private final WeakReference<ImageView> imageViewReference;

    public BitmapDownloaderTask(ImageView imageView) {
        imageViewReference = new WeakReference<ImageView>(imageView);
    }

    @Override
    // Actual download method, run in the task thread
    protected Bitmap doInBackground(String... params) {
        // params comes from the execute() call: params[0] is the url.
        url = (String) params[0];
        return downloadBitmap(params[0]);
    }

    @Override
    // Once the image is downloaded, associates it to the imageView
    protected void onPostExecute(Bitmap bitmap) {
        if (isCancelled()) {
            bitmap = null;
        }

        if (imageViewReference != null) {
            ImageView imageView = imageViewReference.get();
            BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
            // Change bitmap only if this process is still associated with
            // it
            if (this == bitmapDownloaderTask) {
                imageView.setImageBitmap(bitmap);

                // cache the image

                String filename = String.valueOf(url.hashCode());
                File f = new File(
                        getCacheDirectory(imageView.getContext()), filename);

                imageCache.put(f.getPath(), bitmap);

                writeFile(bitmap, f);
            }
        }
    }

}

static class DownloadedDrawable extends ColorDrawable {
    private final WeakReference<BitmapDownloaderTask> bitmapDownloaderTaskReference;

    public DownloadedDrawable(BitmapDownloaderTask bitmapDownloaderTask) {
        super(Color.WHITE);
        bitmapDownloaderTaskReference = new WeakReference<BitmapDownloaderTask>(
                bitmapDownloaderTask);
    }

    public BitmapDownloaderTask getBitmapDownloaderTask() {
        return bitmapDownloaderTaskReference.get();
    }
}

// the actual download code
static Bitmap downloadBitmap(String url) {
    HttpParams params = new BasicHttpParams();
    params.setParameter(CoreProtocolPNames.PROTOCOL_VERSION,
            HttpVersion.HTTP_1_1);
    HttpClient client = new DefaultHttpClient(params);
    final HttpGet getRequest = new HttpGet(url);

    try {
        HttpResponse response = client.execute(getRequest);
        final int statusCode = response.getStatusLine().getStatusCode();
        if (statusCode != HttpStatus.SC_OK) {
            Log.w("ImageDownloader", "Error " + statusCode
                    + " while retrieving bitmap from " + url);
            return null;
        }

        final HttpEntity entity = response.getEntity();
        if (entity != null) {
            InputStream inputStream = null;
            try {
                inputStream = entity.getContent();
                final Bitmap bitmap = BitmapFactory
                        .decodeStream(inputStream);
                return bitmap;
            } finally {
                if (inputStream != null) {
                    inputStream.close();
                }
                entity.consumeContent();
            }
        }
    } catch (Exception e) {
        // Could provide a more explicit error message for IOException or
        // IllegalStateException
        getRequest.abort();
        Log.w("ImageDownloader", "Error while retrieving bitmap from "
                + url + e.toString());
    } finally {
        if (client != null) {
            // client.close();
        }
    }
    return null;
 }
}

4

Saya mengalami masalah ini dan menerapkan lruCache. Saya percaya Anda membutuhkan API 12 dan di atasnya atau menggunakan pustaka compatiblity v4. lurCache adalah memori cepat, tetapi juga memiliki anggaran, jadi jika Anda khawatir bahwa Anda dapat menggunakan diskcache ... Semuanya dijelaskan dalam Caching Bitmaps .

Sekarang saya akan memberikan implementasi saya yang merupakan panggilan tunggal dari mana saja seperti ini:

//Where the first is a string and the other is a imageview to load.

DownloadImageTask.getInstance().loadBitmap(avatarURL, iv_avatar);

Berikut adalah kode yang ideal untuk melakukan cache dan kemudian memanggil di atas dalam getView dari adaptor ketika mengambil gambar web:

public class DownloadImageTask {

    private LruCache<String, Bitmap> mMemoryCache;

    /* Create a singleton class to call this from multiple classes */

    private static DownloadImageTask instance = null;

    public static DownloadImageTask getInstance() {
        if (instance == null) {
            instance = new DownloadImageTask();
        }
        return instance;
    }

    //Lock the constructor from public instances
    private DownloadImageTask() {

        // Get max available VM memory, exceeding this amount will throw an
        // OutOfMemory exception. Stored in kilobytes as LruCache takes an
        // int in its constructor.
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

        // Use 1/8th of the available memory for this memory cache.
        final int cacheSize = maxMemory / 8;

        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                // The cache size will be measured in kilobytes rather than
                // number of items.
                return bitmap.getByteCount() / 1024;
            }
        };
    }

    public void loadBitmap(String avatarURL, ImageView imageView) {
        final String imageKey = String.valueOf(avatarURL);

        final Bitmap bitmap = getBitmapFromMemCache(imageKey);
        if (bitmap != null) {
            imageView.setImageBitmap(bitmap);
        } else {
            imageView.setImageResource(R.drawable.ic_launcher);

            new DownloadImageTaskViaWeb(imageView).execute(avatarURL);
        }
    }

    private void addBitmapToMemoryCache(String key, Bitmap bitmap) {
        if (getBitmapFromMemCache(key) == null) {
            mMemoryCache.put(key, bitmap);
        }
    }

    private Bitmap getBitmapFromMemCache(String key) {
        return mMemoryCache.get(key);
    }

    /* A background process that opens a http stream and decodes a web image. */

    class DownloadImageTaskViaWeb extends AsyncTask<String, Void, Bitmap> {
        ImageView bmImage;

        public DownloadImageTaskViaWeb(ImageView bmImage) {
            this.bmImage = bmImage;
        }

        protected Bitmap doInBackground(String... urls) {

            String urldisplay = urls[0];
            Bitmap mIcon = null;
            try {
                InputStream in = new java.net.URL(urldisplay).openStream();
                mIcon = BitmapFactory.decodeStream(in);

            } 
            catch (Exception e) {
                Log.e("Error", e.getMessage());
                e.printStackTrace();
            }

            addBitmapToMemoryCache(String.valueOf(urldisplay), mIcon);

            return mIcon;
        }

        /* After decoding we update the view on the main UI. */
        protected void onPostExecute(Bitmap result) {
            bmImage.setImageBitmap(result);
        }
    }
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.