Jak pobrać i zapisać obraz w systemie Android


Odpowiedzi:


224

Edytuj od 30.12.2015 - Kompletny przewodnik po pobieraniu obrazów


Ostatnia ważna aktualizacja: 31 marca 2016 r


TL; DR aka przestań mówić, po prostu daj mi kod !!

Przejdź do końca tego postu, skopiuj BasicImageDownloader(wersję javadoc tutaj ) do swojego projektu, zaimplementuj OnImageLoaderListenerinterfejs i gotowe.

Uwaga : chociaż BasicImageDownloaderobsługuje możliwe błędy i zapobiega awariom aplikacji w przypadku, gdy coś pójdzie nie tak, nie wykona ona żadnego przetwarzania końcowego (np. Zmniejszenia rozmiaru) pobranego pliku Bitmaps.


Ponieważ ten post spotkał się z dużym zainteresowaniem, zdecydowałem się całkowicie go przerobić, aby ludzie nie używali przestarzałych technologii, złych praktyk programowania lub po prostu robienia głupich rzeczy - takich jak szukanie „hacków” do uruchomienia sieci w głównym wątku lub akceptuj wszystkie certyfikaty SSL.

Stworzyłem projekt demonstracyjny o nazwie „Image Downloader”, który pokazuje, jak pobrać (i zapisać) obraz przy użyciu mojej własnej implementacji programu do pobierania, wbudowanego systemu Android, DownloadManagera także niektórych popularnych bibliotek open source. Możesz wyświetlić pełny kod źródłowy lub pobrać projekt na GitHub .

Uwaga : nie dostosowałem jeszcze zarządzania uprawnieniami dla SDK 23+ (Marshmallow), dlatego projekt jest ukierunkowany na SDK 22 (Lollipop).

W podsumowaniu na końcu tego posta podzielę się moją skromną opinią na temat właściwego przypadku użycia dla każdego określonego sposobu pobierania obrazu, o którym wspomniałem.

Zacznijmy od własnej implementacji (kod znajdziesz na końcu postu). Przede wszystkim jest to Basic ImageDownloader i to wszystko. Wszystko, co robi, to łączy się z podanym adresem URL, odczytuje dane i próbuje je zdekodować jako a Bitmap, wywołując w razie potrzeby OnImageLoaderListenerwywołania zwrotne interfejsu. Zaleta tego podejścia - jest proste i masz jasny przegląd tego, co się dzieje. Dobry sposób, jeśli wszystko, czego potrzebujesz, to pobieranie / wyświetlanie i zapisywanie niektórych obrazów, podczas gdy nie zależy Ci na utrzymywaniu pamięci podręcznej / pamięci podręcznej dysku.

Uwaga: w przypadku dużych obrazów może być konieczne ich zmniejszenie .

-

Android DownloadManager to sposób, aby system zajął się pobieraniem za Ciebie. W rzeczywistości jest w stanie pobierać wszelkiego rodzaju pliki, nie tylko obrazy. Możesz pozwolić, aby pobieranie odbywało się cicho i niewidoczne dla użytkownika, lub możesz umożliwić użytkownikowi wyświetlanie pobierania w obszarze powiadomień. Możesz także zarejestrować, BroadcastReceiveraby otrzymywać powiadomienia po zakończeniu pobierania. Konfiguracja jest dość prosta, zapoznaj się z połączonym projektem, aby uzyskać przykładowy kod.

Korzystanie z DownloadManagerpliku nie jest ogólnie dobrym pomysłem, jeśli chcesz również wyświetlić obraz, ponieważ musiałbyś odczytać i zdekodować zapisany plik zamiast po prostu ustawić pobrany plik Bitmapna plik ImageView. DownloadManagerRównież nie zawiera żadnych API dla ciebie app śledzić postęp pobierania.

-

Teraz wprowadzenie wspaniałego materiału - bibliotek. Mogą zrobić znacznie więcej niż tylko pobieranie i wyświetlanie obrazów, w tym: tworzenie i zarządzanie pamięcią podręczną / dyskową pamięcią podręczną, zmianę rozmiaru obrazów, przekształcanie ich i nie tylko.

Zacznę od Volley , potężnej biblioteki stworzonej przez Google i objętej oficjalną dokumentacją. Będąc biblioteką sieciową ogólnego przeznaczenia, która nie specjalizuje się w obrazach, Volley oferuje dość potężne API do zarządzania obrazami.

Będziesz musiał zaimplementować klasę Singleton do zarządzania żądaniami Volley i gotowe.

Możesz zamienić swój plik na ImageViewVolley NetworkImageView, więc pobieranie w zasadzie staje się jednowierszowe:

((NetworkImageView) findViewById(R.id.myNIV)).setImageUrl(url, MySingleton.getInstance(this).getImageLoader());

Jeśli potrzebujesz większej kontroli, tak wygląda tworzenie za ImageRequestpomocą Volley:

     ImageRequest imgRequest = new ImageRequest(url, new Response.Listener<Bitmap>() {
             @Override
             public void onResponse(Bitmap response) {
                    //do stuff
                }
            }, 0, 0, ImageView.ScaleType.CENTER_CROP, Bitmap.Config.ARGB_8888, 
             new Response.ErrorListener() {
             @Override
             public void onErrorResponse(VolleyError error) {
                   //do stuff
                }
            });

Warto wspomnieć, że Volley posiada doskonały mechanizm obsługi błędów, zapewniając VolleyErrorklasę, która pomaga określić dokładną przyczynę błędu. Jeśli Twoja aplikacja dużo pracuje w sieci, a zarządzanie obrazami nie jest jej głównym celem, to Volley to idealne rozwiązanie dla Ciebie.

-

Square's Picasso to dobrze znana biblioteka, która zajmie się ładowaniem wszystkich obrazów. Samo wyświetlenie obrazu za pomocą Picassa jest tak proste, jak:

 Picasso.with(myContext)
       .load(url)
       .into(myImageView); 

Domyślnie Picasso zarządza pamięcią podręczną dysku / pamięci, więc nie musisz się tym martwić. Aby uzyskać większą kontrolę, możesz zaimplementować Targetinterfejs i użyć go do załadowania obrazu - zapewni to wywołania zwrotne podobne do przykładu Volley. Sprawdź przykładowy projekt.

Picasso pozwala również zastosować transformacje do pobranego obrazu, a istnieją nawet inne biblioteki, które rozszerzają ten interfejs API. Działa również bardzo dobrze w RecyclerView/ ListView/ GridView.

-

Universal Image Loader to kolejna bardzo popularna biblioteka służąca do zarządzania obrazami. Używa swojej własnej, ImageLoaderktóra (po zainicjowaniu) ma globalną instancję, której można użyć do pobrania obrazów w jednej linii kodu:

  ImageLoader.getInstance().displayImage(url, myImageView);

Jeśli chcesz śledzić postęp pobierania lub uzyskać dostęp do pobranych Bitmap:

 ImageLoader.getInstance().displayImage(url, myImageView, opts, 
 new ImageLoadingListener() {
     @Override
     public void onLoadingStarted(String imageUri, View view) {
                     //do stuff
                }

      @Override
      public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
                   //do stuff
                }

      @Override
      public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
                   //do stuff
                }

      @Override
      public void onLoadingCancelled(String imageUri, View view) {
                   //do stuff
                }
            }, new ImageLoadingProgressListener() {
      @Override
      public void onProgressUpdate(String imageUri, View view, int current, int total) {
                   //do stuff
                }
            });

optsArgumentem w tym przykładzie jestDisplayImageOptions obiekt. Zapoznaj się z projektem demonstracyjnym, aby dowiedzieć się więcej.

Podobnie jak Volley, UIL zapewnia FailReason klasę, która umożliwia sprawdzenie, co poszło nie tak w przypadku niepowodzenia pobierania. Domyślnie UIL utrzymuje pamięć / podręczną pamięć dyskową, jeśli wyraźnie nie powiesz mu, aby tego nie robił.

Uwaga : autor wspomniał, że od 27 listopada 2015 r. Nie zajmuje się już projektem. Ale ponieważ jest wielu współpracowników, możemy mieć nadzieję, że Universal Image Loader będzie nadal działać.

-

Facebook Fresco to najnowsza i (IMO) najbardziej zaawansowana biblioteka, która przenosi zarządzanie obrazami na nowy poziom: od trzymania Bitmapssię stosu java (przed Lollipopem) do obsługi animowanych formatów i progresywnego przesyłania strumieniowego JPEG .

Aby dowiedzieć się więcej o pomysłach i technikach stojących za Fresco, przeczytaj ten post .

Podstawowe użycie jest dość proste. Pamiętaj, że wystarczy zadzwonić Fresco.initialize(Context);tylko raz, najlepiej wApplication zajęciach. Inicjowanie Fresco więcej niż raz może prowadzić do nieprzewidywalnego zachowania i błędów OOM.

Fresco używa Drawees do wyświetlania obrazów, możesz o nich myśleć jako o ImageViews:

    <com.facebook.drawee.view.SimpleDraweeView
    android:id="@+id/drawee"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    fresco:fadeDuration="500"
    fresco:actualImageScaleType="centerCrop"
    fresco:placeholderImage="@drawable/placeholder_grey"
    fresco:failureImage="@drawable/error_orange"
    fresco:placeholderImageScaleType="fitCenter"
    fresco:failureImageScaleType="centerInside"
    fresco:retryImageScaleType="centerCrop"
    fresco:progressBarImageScaleType="centerInside"
    fresco:progressBarAutoRotateInterval="1000"
    fresco:roundAsCircle="false" />

Jak widać, wiele rzeczy (w tym opcje transformacji) jest już zdefiniowanych w XML, więc wszystko, co musisz zrobić, aby wyświetlić obraz, to jednolinijkowy:

 mDrawee.setImageURI(Uri.parse(url));

Fresco zapewnia rozszerzone API do dostosowywania, które w pewnych okolicznościach może być dość skomplikowane i wymaga od użytkownika uważnego przeczytania dokumentacji (tak, czasami potrzebujesz RTFM).

Do przykładowego projektu dołączyłem przykłady progresywnych plików JPEG i animowanych obrazów.


Wniosek - „Dowiedziałem się o wspaniałych rzeczach, czego powinienem teraz użyć?”

Zwróć uwagę, że poniższy tekst odzwierciedla moją osobistą opinię i nie powinien być traktowany jako postulat.

  • Jeśli potrzebujesz tylko pobrać / zapisać / wyświetlić niektóre obrazy, nie planuj ich używać w a Recycler-/Grid-/ListViewi nie potrzebujesz całej grupy obrazów, aby były gotowe do wyświetlenia, BasicImageDownloader powinien odpowiadać Twoim potrzebom.
  • Jeśli Twoja aplikacja zapisuje obrazy (lub inne pliki) w wyniku działania użytkownika lub zautomatyzowanej akcji i nie chcesz, aby obrazy były często wyświetlane, użyj narzędzia Android DownloadManager .
  • Jeśli Twoja aplikacja dużo korzysta z sieci, przesyła / odbiera JSONdane, współpracuje z obrazami, ale to nie jest głównym celem aplikacji, przejdź do Volley .
  • Twoja aplikacja jest skoncentrowana na obrazach / mediach, chcesz zastosować pewne przekształcenia do obrazów i nie chcesz zawracać sobie głowy złożonym interfejsem API: użyj Picassa (Uwaga: nie zapewnia żadnego interfejsu API do śledzenia pośredniego stanu pobierania) lub Universal Image Ładowarka
  • Jeśli Twoja aplikacja dotyczy wyłącznie obrazów, potrzebujesz zaawansowanych funkcji, takich jak wyświetlanie animowanych formatów, i jesteś gotowy do czytania dokumentów, przejdź do Fresco .

Jeśli to przegapiłeś, link Github do projektu demonstracyjnego.


A oto plik BasicImageDownloader.java

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.support.annotation.NonNull;
import android.util.Log;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashSet;
import java.util.Set;

public class BasicImageDownloader {

    private OnImageLoaderListener mImageLoaderListener;
    private Set<String> mUrlsInProgress = new HashSet<>();
    private final String TAG = this.getClass().getSimpleName();

    public BasicImageDownloader(@NonNull OnImageLoaderListener listener) {
        this.mImageLoaderListener = listener;
    }

    public interface OnImageLoaderListener {
        void onError(ImageError error);      
        void onProgressChange(int percent);
        void onComplete(Bitmap result);
    }


    public void download(@NonNull final String imageUrl, final boolean displayProgress) {
        if (mUrlsInProgress.contains(imageUrl)) {
            Log.w(TAG, "a download for this url is already running, " +
                    "no further download will be started");
            return;
        }

        new AsyncTask<Void, Integer, Bitmap>() {

            private ImageError error;

            @Override
            protected void onPreExecute() {
                mUrlsInProgress.add(imageUrl);
                Log.d(TAG, "starting download");
            }

            @Override
            protected void onCancelled() {
                mUrlsInProgress.remove(imageUrl);
                mImageLoaderListener.onError(error);
            }

            @Override
            protected void onProgressUpdate(Integer... values) {
                mImageLoaderListener.onProgressChange(values[0]);
            }

        @Override
        protected Bitmap doInBackground(Void... params) {
            Bitmap bitmap = null;
            HttpURLConnection connection = null;
            InputStream is = null;
            ByteArrayOutputStream out = null;
            try {
                connection = (HttpURLConnection) new URL(imageUrl).openConnection();
                if (displayProgress) {
                    connection.connect();
                    final int length = connection.getContentLength();
                    if (length <= 0) {
                        error = new ImageError("Invalid content length. The URL is probably not pointing to a file")
                                .setErrorCode(ImageError.ERROR_INVALID_FILE);
                        this.cancel(true);
                    }
                    is = new BufferedInputStream(connection.getInputStream(), 8192);
                    out = new ByteArrayOutputStream();
                    byte bytes[] = new byte[8192];
                    int count;
                    long read = 0;
                    while ((count = is.read(bytes)) != -1) {
                        read += count;
                        out.write(bytes, 0, count);
                        publishProgress((int) ((read * 100) / length));
                    }
                    bitmap = BitmapFactory.decodeByteArray(out.toByteArray(), 0, out.size());
                } else {
                    is = connection.getInputStream();
                    bitmap = BitmapFactory.decodeStream(is);
                }
            } catch (Throwable e) {
                if (!this.isCancelled()) {
                    error = new ImageError(e).setErrorCode(ImageError.ERROR_GENERAL_EXCEPTION);
                    this.cancel(true);
                }
            } finally {
                try {
                    if (connection != null)
                        connection.disconnect();
                    if (out != null) {
                        out.flush();
                        out.close();
                    }
                    if (is != null)
                        is.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return bitmap;
        }

            @Override
            protected void onPostExecute(Bitmap result) {
                if (result == null) {
                    Log.e(TAG, "factory returned a null result");
                    mImageLoaderListener.onError(new ImageError("downloaded file could not be decoded as bitmap")
                            .setErrorCode(ImageError.ERROR_DECODE_FAILED));
                } else {
                    Log.d(TAG, "download complete, " + result.getByteCount() +
                            " bytes transferred");
                    mImageLoaderListener.onComplete(result);
                }
                mUrlsInProgress.remove(imageUrl);
                System.gc();
            }
        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
    }

    public interface OnBitmapSaveListener {
        void onBitmapSaved();
        void onBitmapSaveError(ImageError error);
    }


    public static void writeToDisk(@NonNull final File imageFile, @NonNull final Bitmap image,
                               @NonNull final OnBitmapSaveListener listener,
                               @NonNull final Bitmap.CompressFormat format, boolean shouldOverwrite) {

    if (imageFile.isDirectory()) {
        listener.onBitmapSaveError(new ImageError("the specified path points to a directory, " +
                "should be a file").setErrorCode(ImageError.ERROR_IS_DIRECTORY));
        return;
    }

    if (imageFile.exists()) {
        if (!shouldOverwrite) {
            listener.onBitmapSaveError(new ImageError("file already exists, " +
                    "write operation cancelled").setErrorCode(ImageError.ERROR_FILE_EXISTS));
            return;
        } else if (!imageFile.delete()) {
            listener.onBitmapSaveError(new ImageError("could not delete existing file, " +
                    "most likely the write permission was denied")
                    .setErrorCode(ImageError.ERROR_PERMISSION_DENIED));
            return;
        }
    }

    File parent = imageFile.getParentFile();
    if (!parent.exists() && !parent.mkdirs()) {
        listener.onBitmapSaveError(new ImageError("could not create parent directory")
                .setErrorCode(ImageError.ERROR_PERMISSION_DENIED));
        return;
    }

    try {
        if (!imageFile.createNewFile()) {
            listener.onBitmapSaveError(new ImageError("could not create file")
                    .setErrorCode(ImageError.ERROR_PERMISSION_DENIED));
            return;
        }
    } catch (IOException e) {
        listener.onBitmapSaveError(new ImageError(e).setErrorCode(ImageError.ERROR_GENERAL_EXCEPTION));
        return;
    }

    new AsyncTask<Void, Void, Void>() {

        private ImageError error;

        @Override
        protected Void doInBackground(Void... params) {
            FileOutputStream fos = null;
            try {
                fos = new FileOutputStream(imageFile);
                image.compress(format, 100, fos);
            } catch (IOException e) {
                error = new ImageError(e).setErrorCode(ImageError.ERROR_GENERAL_EXCEPTION);
                this.cancel(true);
            } finally {
                if (fos != null) {
                    try {
                        fos.flush();
                        fos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            return null;
        }

        @Override
        protected void onCancelled() {
            listener.onBitmapSaveError(error);
        }

        @Override
        protected void onPostExecute(Void result) {
            listener.onBitmapSaved();
        }
    }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
  }

public static Bitmap readFromDisk(@NonNull File imageFile) {
    if (!imageFile.exists() || imageFile.isDirectory()) return null;
    return BitmapFactory.decodeFile(imageFile.getAbsolutePath());
}

public interface OnImageReadListener {
    void onImageRead(Bitmap bitmap);
    void onReadFailed();
}

public static void readFromDiskAsync(@NonNull File imageFile, @NonNull final OnImageReadListener listener) {
    new AsyncTask<String, Void, Bitmap>() {
        @Override
        protected Bitmap doInBackground(String... params) {
            return BitmapFactory.decodeFile(params[0]);
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            if (bitmap != null)
                listener.onImageRead(bitmap);
            else
                listener.onReadFailed();
        }
    }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, imageFile.getAbsolutePath());
}

    public static final class ImageError extends Throwable {

        private int errorCode;
        public static final int ERROR_GENERAL_EXCEPTION = -1;
        public static final int ERROR_INVALID_FILE = 0;
        public static final int ERROR_DECODE_FAILED = 1;
        public static final int ERROR_FILE_EXISTS = 2;
        public static final int ERROR_PERMISSION_DENIED = 3;
        public static final int ERROR_IS_DIRECTORY = 4;


        public ImageError(@NonNull String message) {
            super(message);
        }

        public ImageError(@NonNull Throwable error) {
            super(error.getMessage(), error.getCause());
            this.setStackTrace(error.getStackTrace());
        }

        public ImageError setErrorCode(int code) {
            this.errorCode = code;
            return this;
        }

        public int getErrorCode() {
            return errorCode;
        }
      }
   }

A co z wywołaniem zwrotnym onPictureTaken (), które podaje obraz jako bajt [], czy można uzyskać adres URL do tego obrazu prosto z kamery? A może zwykły stary outputStream () jest jedynym sposobem w Androidzie, aby zapisać zdjęcie zrobione aparatem bez użycia wbudowanej intencji? Wydaje się to dziwne, ponieważ naturalną rzeczą do zrobienia po onPictureTaken jest oczywiście zapisanie go. Czy nie ma dla tego szczególnego wsparcia?
Tombola

2
@Tombola Hi! Ten post dotyczy pobierania zdjęcia z sieci. Ale odpowiadając na twoje pytanie (o ile je zrozumiałem): powszechnym sposobem zapisywania obrazu z kamery jest pobranie jego ścieżki z Cursor(w onActivityResult()metodzie), a następnie utworzenie Bitmapprzy użyciu tej ścieżki. I tak, nie unikniesz używania a FileOutputStreami a, ByteArrayOutputStreamjeśli chcesz zapisać ten obraz na SD.
Droidman

@BartBurg to pytanie dotyczy pobierania i zapisywania obrazu. Ale w pewnym momencie masz rację, ponieważ istnieje metoda zapisu, ze względu na kompletność powinna również istnieć metoda odczytu. Wkrótce dodam to w kolejnej aktualizacji tego postu.
Droidman

Czy możesz podać przykład za pomocą tego BasicImageDownloader?
Jaydev

1
@JaydevKalivarapu sprawdź aplikację demonstracyjną na GitHub ( klasa źródłowa zawierająca przykład )
Droidman

35

Właśnie przyszedłem z rozwiązania tego problemu i chciałbym udostępnić cały kod, który można pobrać, zapisać na karcie SD (i ukryć nazwę pliku) i pobrać obrazy, a na koniec sprawdza, czy obraz już tam jest. Adres URL pochodzi z bazy danych, więc nazwę pliku można łatwo określić za pomocą id.

najpierw pobierz obrazy

   private class GetImages extends AsyncTask<Object, Object, Object> {
    private String requestUrl, imagename_;
    private ImageView view;
    private Bitmap bitmap ; 
      private FileOutputStream fos;
    private GetImages(String requestUrl, ImageView view, String _imagename_) {
        this.requestUrl = requestUrl;
        this.view = view;
        this.imagename_ = _imagename_ ;
    }

    @Override
    protected Object doInBackground(Object... objects) {
        try {
            URL url = new URL(requestUrl);
            URLConnection conn = url.openConnection();
            bitmap = BitmapFactory.decodeStream(conn.getInputStream());
        } catch (Exception ex) {
        }
        return null;
    }

    @Override
    protected void onPostExecute(Object o) { 
        if(!ImageStorage.checkifImageExists(imagename_))
        { 
                view.setImageBitmap(bitmap);
                ImageStorage.saveToSdCard(bitmap, imagename_); 
            }  
        }  
   }

Następnie utwórz klasę do zapisywania i pobierania plików

  public class ImageStorage {


public static String saveToSdCard(Bitmap bitmap, String filename) {

    String stored = null;

    File sdcard = Environment.getExternalStorageDirectory() ; 

    File folder = new File(sdcard.getAbsoluteFile(), ".your_specific_directory");//the dot makes this directory hidden to the user
    folder.mkdir(); 
    File file = new File(folder.getAbsoluteFile(), filename + ".jpg") ;
    if (file.exists())
        return stored ;

    try {
        FileOutputStream out = new FileOutputStream(file);
        bitmap.compress(Bitmap.CompressFormat.JPEG, 90, out);
        out.flush();
        out.close();
        stored = "success";
    } catch (Exception e) {
        e.printStackTrace();
    }
     return stored;
   }

public static File getImage(String imagename) {

        File mediaImage = null;
        try {
            String root = Environment.getExternalStorageDirectory().toString();
            File myDir = new File(root);
            if (!myDir.exists())
                return null;

            mediaImage = new File(myDir.getPath() + "/.your_specific_directory/"+imagename);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return mediaImage;
    }
public static boolean checkifImageExists(String imagename)
{
    Bitmap b = null ;
    File file = ImageStorage.getImage("/"+imagename+".jpg");
    String path = file.getAbsolutePath();

    if (path != null)
        b = BitmapFactory.decodeFile(path); 

    if(b == null ||  b.equals(""))
    {
        return false ;
    }
    return true ;
}
  }

Następnie Aby uzyskać dostęp do obrazów, najpierw sprawdź, czy już tam jest, a jeśli nie, pobierz

          if(ImageStorage.checkifImageExists(imagename))
            {
                File file = ImageStorage.getImage("/"+imagename+".jpg"); 
                String path = file.getAbsolutePath(); 
                if (path != null){
                    b = BitmapFactory.decodeFile(path);
                    imageView.setImageBitmap(b);
                }  
            } else { 
                new GetImages(imgurl, imageView, imagename).execute() ; 
            }

Uwaga : chociaż ten przykład mógłby ogólnie działać, nie zapewnia żadnej obsługi błędów, a także brakuje podstawowego zrozumienia AsyncTaskjego zalet (właściwe użycie parametryzacji, wykonywanie równoległe ...). Aby uzyskać szczegółowe informacje, zapoznaj się z poniższymi przykładami. PS . dla tych, którzy mogą pomyśleć, że napisałem to, aby promować swój własny kod z dowolnego powodu: nie, po prostu wskazuję problemy, które widzę w podanym przykładzie.
Droidman

Tak Droidman, zgadzam się z tobą. Ten fragment kodu należy traktować jako próbnik i trzeba go samodzielnie wypełnić, łącznie z obsługą błędów. Nawiasem mówiąc, w Twoim kodzie brakuje również obsługi błędów. Co się stanie z Twoim połączeniem i strumieniami w przypadku IOException?
Androider

@Androider Proszę przyjrzeć się bliżej mojej downloadmetodzie, w szczególności doInBackgroundmetodzie zadania, z którego korzystam. An IOExceptionwylądowałby w catch (Throwable e)bloku, co spowodowałoby ImageErrorzwrócenie i onError()wywołanie wywołania zwrotnego. ImageErrorObiekt będzie zawierać ślady pierwotnego stosu i przyczynę nastąpiłoException
Droidman

1
Tak, ale Twoje połączenie nie zostanie rozłączone, a Twoje transmisje nie zostaną zamknięte
Androider

@Androider ah, rozumiem twój punkt widzenia. Chociaż nigdy nie zauważyłem żadnych podejrzanych wycieków na moich urządzeniach testowych, to zachowanie może być inne na innych urządzeniach. Dzięki za podpowiedź - zaktualizowałem kod
Droidman

33

Dlaczego naprawdę potrzebujesz własnego kodu, aby go pobrać? Co powiesz na przekazanie swojego identyfikatora URI do menedżera pobierania?

public void downloadFile(String uRl) {
    File direct = new File(Environment.getExternalStorageDirectory()
            + "/AnhsirkDasarp");

    if (!direct.exists()) {
        direct.mkdirs();
    }

    DownloadManager mgr = (DownloadManager) getActivity().getSystemService(Context.DOWNLOAD_SERVICE);

    Uri downloadUri = Uri.parse(uRl);
    DownloadManager.Request request = new DownloadManager.Request(
            downloadUri);

    request.setAllowedNetworkTypes(
            DownloadManager.Request.NETWORK_WIFI
                    | DownloadManager.Request.NETWORK_MOBILE)
            .setAllowedOverRoaming(false).setTitle("Demo")
            .setDescription("Something useful. No, really.")
            .setDestinationInExternalPublicDir("/AnhsirkDasarp", "fileName.jpg");

    mgr.enqueue(request);

}

1
Idealny przykład dla api> 8.
Jeeten Parmar

Twój kod działa idealnie dla jednego obrazu! co mogę zrobić, jeśli chcę pobrać więcej (100 zdjęć) z pliku zdjęć na moim serwerze?
Kostantinos Ibra

Otrzymuję komunikat, że plik jest uszkodzony!
Anup

2
Może .setDestinationInExternalPublicDir ("/ AnhsirkDasarp", "nazwa_pliku.jpg");
Ekspert chce być

2
Skąd wiemy, czy zostało pobrane pomyślnie, czy nie
Prabs

12

to może ci pomóc ...

Button download_image = (Button)bigimagedialog.findViewById(R.id.btn_downloadimage);
                    download_image.setOnClickListener(new View.OnClickListener()
                    {
                        public void onClick(View v)
                        {
                            boolean success = (new File("/sdcard/dirname")).mkdir(); 
                            if (!success)
                            {
                                Log.w("directory not created", "directory not created");
                            }

                            try
                            {
                                URL url = new URL("YOUR_URL");
                                HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                                connection.setDoInput(true);
                                connection.connect();
                                InputStream input = connection.getInputStream();
                                Bitmap myBitmap = BitmapFactory.decodeStream(input);

                                String data1 = String.valueOf(String.format("/sdcard/dirname/%d.jpg",System.currentTimeMillis()));

                                FileOutputStream stream = new FileOutputStream(data1);

                                ByteArrayOutputStream outstream = new ByteArrayOutputStream();
                                myBitmap.compress(Bitmap.CompressFormat.JPEG, 85, outstream);
                                byte[] byteArray = outstream.toByteArray();

                                stream.write(byteArray);
                                stream.close();

                                Toast.makeText(getApplicationContext(), "Downloading Completed", Toast.LENGTH_SHORT).show();
                            }
                            catch (Exception e)
                            {
                                e.printStackTrace();
                            }
                        }
                    });

6

Mam proste rozwiązanie, które działa doskonale. Kod nie jest mój, znalazłem go pod tym linkiem . Oto kroki, które należy wykonać:

1. Przed pobraniem obrazu napiszmy metodę zapisywania bitmapy do pliku obrazu w pamięci wewnętrznej Androida. Potrzebuje kontekstu, lepiej użyć przepustki w kontekście aplikacji przez getApplicationContext (). Tę metodę można wrzucić do klasy Activity lub innych klas użytkowych.

public void saveImage(Context context, Bitmap b, String imageName) 
{
    FileOutputStream foStream;
    try 
    {
        foStream = context.openFileOutput(imageName, Context.MODE_PRIVATE);
        b.compress(Bitmap.CompressFormat.PNG, 100, foStream);
        foStream.close();
    } 
    catch (Exception e) 
    {
        Log.d("saveImage", "Exception 2, Something went wrong!");
        e.printStackTrace();
    }
}

2. Teraz mamy metodę zapisywania bitmapy do pliku obrazu w andorid, napiszmy AsyncTask do pobierania obrazów z adresu URL. Ta prywatna klasa musi być umieszczona w Twojej klasie Activity jako podklasa. Po pobraniu obrazu w metodzie onPostExecute wywołuje zdefiniowaną powyżej metodę saveImage, aby zapisać obraz. Uwaga, nazwa obrazu jest zakodowana na stałe jako „my_image.png”.

private class DownloadImage extends AsyncTask<String, Void, Bitmap> {
    private String TAG = "DownloadImage";
    private Bitmap downloadImageBitmap(String sUrl) {
        Bitmap bitmap = null;
        try {
            InputStream inputStream = new URL(sUrl).openStream();   // Download Image from URL
            bitmap = BitmapFactory.decodeStream(inputStream);       // Decode Bitmap
            inputStream.close();
        } catch (Exception e) {
            Log.d(TAG, "Exception 1, Something went wrong!");
            e.printStackTrace();
        }
        return bitmap;
    }

    @Override
    protected Bitmap doInBackground(String... params) {
        return downloadImageBitmap(params[0]);
    }

    protected void onPostExecute(Bitmap result) {
        saveImage(getApplicationContext(), result, "my_image.png");
    }
}

3. AsyncTask do pobrania obrazu jest zdefiniowany, ale musimy go wykonać, aby uruchomić ten AsyncTask. Aby to zrobić, napisz ten wiersz w swojej metodzie onCreate w swojej klasie Activity lub w metodzie onClick przycisku lub w innym miejscu, które uważasz za stosowne.

new DownloadImage().execute("http://developer.android.com/images/activity_lifecycle.png");

Obraz powinien być zapisany w /data/data/your.app.packagename/files/my_image.jpeg, sprawdź ten post, aby uzyskać dostęp do tego katalogu z twojego urządzenia.

IMO to rozwiązuje problem! Jeśli chcesz wykonać dalsze kroki, takie jak załadowanie obrazu, możesz wykonać następujące dodatkowe kroki:

4. Po pobraniu obrazu potrzebujemy sposobu załadowania mapy bitowej obrazu z pamięci wewnętrznej, abyśmy mogli z niej skorzystać. Napiszmy metodę ładowania bitmapy obrazu. Ta metoda przyjmuje dwa parametry, kontekst i nazwę pliku obrazu, bez pełnej ścieżki, context.openFileInput (nazwa_obrazu) wyszuka plik w katalogu zapisu, gdy ta nazwa pliku została zapisana w powyższej metodzie saveImage.

public Bitmap loadImageBitmap(Context context, String imageName) {
    Bitmap bitmap = null;
    FileInputStream fiStream;
    try {
        fiStream    = context.openFileInput(imageName);
        bitmap      = BitmapFactory.decodeStream(fiStream);
        fiStream.close();
    } catch (Exception e) {
        Log.d("saveImage", "Exception 3, Something went wrong!");
        e.printStackTrace();
    }
    return bitmap;
}

5. Teraz mamy wszystko, czego potrzebowaliśmy do ustawienia obrazu ImageView lub innych widoków, w których chcesz używać tego obrazu. Kiedy zapisujemy obraz, zakodowaliśmy na stałe nazwę obrazu jako „my_image.jpeg”, teraz możemy przekazać tę nazwę obrazu do powyższej metody loadImageBitmap, aby pobrać mapę bitową i ustawić ją na ImageView.

someImageView.setImageBitmap(loadImageBitmap(getApplicationContext(), "my_image.jpeg"));

6. Aby uzyskać pełną ścieżkę obrazu według nazwy obrazu.

File file            = getApplicationContext().getFileStreamPath("my_image.jpeg");
String imageFullPath = file.getAbsolutePath();

7. Sprawdź, czy plik obrazu istnieje.

Plik plik =

getApplicationContext().getFileStreamPath("my_image.jpeg");
if (file.exists()) Log.d("file", "my_image.jpeg exists!");
  1. Aby usunąć plik obrazu.

    File file = getApplicationContext (). GetFileStreamPath ("my_image.jpeg"); if (file.delete ()) Log.d ("plik", "mój_image.jpeg usunięty!");


0

ten kod doskonale działa w moim projekcie

downloadImagesToSdCard(imagepath,imagepath);

private void downloadImagesToSdCard(String downloadUrl,String imageName)
        {
            try
            {
                URL url = new URL("www.xxx.com"+downloadUrl); 
                /* making a directory in sdcard */
            //  String sdCard=Environment.getExternalStorageDirectory().toString();
                ContextWrapper cw = new ContextWrapper(getActivity());
                 // path to /data/data/yourapp/app_data/imageDir
                File directory = cw.getDir("files", Context.MODE_PRIVATE);

                File myDir = new File(directory,"folder");

                /*  if specified not exist create new */
                if(!myDir.exists())
                {
                    myDir.mkdir();
                    Log.v("", "inside mkdir");
                }

                /* checks the file and if it already exist delete */
                String fname = imageName;
                File file = new File (myDir, fname);
                Log.d("file===========path", ""+file);
                if (file.exists ()) 
                    file.delete (); 

                /* Open a connection */
                URLConnection ucon = url.openConnection();
                InputStream inputStream = null;
                HttpURLConnection httpConn = (HttpURLConnection)ucon;
                httpConn.setRequestMethod("GET");
                httpConn.connect();
                inputStream = httpConn.getInputStream();
                /*if (httpConn.getResponseCode() == HttpURLConnection.HTTP_OK) 
                {
                    inputStream = httpConn.getInputStream();
                }*/

                FileOutputStream fos = new FileOutputStream(file);  
                int totalSize = httpConn.getContentLength();
                int downloadedSize = 0;   
                byte[] buffer = new byte[1024];
                int bufferLength = 0;
                while ( (bufferLength = inputStream.read(buffer)) >0 ) 
                {                 
                    fos.write(buffer, 0, bufferLength);                  
                    downloadedSize += bufferLength;                 
                    Log.i("Progress:","downloadedSize:"+downloadedSize+"totalSize:"+ totalSize) ;
                }   

                fos.close();
                Log.d("test", "Image Saved in sdcard..");  
                viewimage();
            }
            catch(IOException io)
            {                  
                io.printStackTrace();
            }
            catch(Exception e)
            {                     
                e.printStackTrace();
            }


        } 

        public void viewimage()
        {
              String path = serialnumber+".png";
               ContextWrapper cw = new ContextWrapper(getActivity());

                //path to /data/data/yourapp/app_data/dirName
                File directory = cw.getDir("files", Context.MODE_PRIVATE);

                File mypath=new File(directory,"folder/"+path);

                Bitmap b;
                try {
                    b = BitmapFactory.decodeStream(new FileInputStream(mypath));
                //  b.compress(format, quality, stream)
                    profile_image.setImageBitmap(Bitmap.createScaledBitmap(b, 120, 120, false));
                } catch (FileNotFoundException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
        }

1
cześć dzięki za wkład. Zwróć uwagę, że powyższe podejście do zapisywania obrazu w prywatnej pamięci aplikacji ma 2 wady: 1) obraz będzie dostępny tylko dla Twojej aplikacji i 2) powinieneś unikać zapisywania obrazów w prywatnej pamięci aplikacji, ponieważ nie jest on przeznaczony dla dużych treści.
Droidman

0

Spróbuj tego

 try
                {
                    Bitmap bmp = null;
                    URL url = new URL("Your_URL");
                    URLConnection conn = url.openConnection();
                    bmp = BitmapFactory.decodeStream(conn.getInputStream());
                    File f = new File(Environment.getExternalStorageDirectory(),System.currentTimeMillis() + ".jpg");
                    if(f.exists())
                        f.delete();
                    f.createNewFile();
                    Bitmap bitmap = bmp;
                    ByteArrayOutputStream bos = new ByteArrayOutputStream();
                    bitmap.compress(Bitmap.CompressFormat.PNG, 0 /*ignored for PNG*/, bos);
                    byte[] bitmapdata = bos.toByteArray();
                    FileOutputStream fos = new FileOutputStream(f);
                    fos.write(bitmapdata);
                    fos.flush();
                    fos.close();
                    Log.e(TAG, "imagepath: "+f );
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                }

0
public class testCrop extends AppCompatActivity {
    ImageView iv;
    String imagePath = "https://style.pk/wp-content/uploads/2015/07/omer-Shahzad-performed-umrah-600x548.jpg";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.testcrpop);
        iv = (ImageView) findViewById(R.id.testCrop);
        imageDownload image = new imageDownload(testCrop.this, iv);
        image.execute(imagePath);
    }
    class imageDownload extends AsyncTask<String, Integer, Bitmap> {
        Context context;
        ImageView imageView;
        Bitmap bitmap;
        InputStream in = null;
        int responseCode = -1;
//constructor.
        public imageDownload(Context context, ImageView imageView) {
            this.context = context;
            this.imageView = imageView;
        }
        @Override
        protected void onPreExecute() {
        }
        @Override
        protected Bitmap doInBackground(String... params) {
            try {
                URL url = new URL(params[0]);
                HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
                httpURLConnection.setDoOutput(true);
                httpURLConnection.connect();
                responseCode = httpURLConnection.getResponseCode();
                if (responseCode == HttpURLConnection.HTTP_OK) {
                    in = httpURLConnection.getInputStream();
                    bitmap = BitmapFactory.decodeStream(in);
                    in.close();
                }
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return bitmap;
        }
        @Override
        protected void onPostExecute(Bitmap data) {
            imageView.setImageBitmap(data);
            saveImage(data);
        }

        private void saveImage(Bitmap data) {
            File createFolder = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),"test");
            createFolder.mkdir();
            File saveImage = new File(createFolder,"downloadimage.jpg");
            try {
                OutputStream outputStream = new FileOutputStream(saveImage);
                data.compress(Bitmap.CompressFormat.JPEG,100,outputStream);
                outputStream.flush();
                outputStream.close();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

WYNIKwprowadź opis obrazu tutaj

Upewnij się, że dodałeś uprawnienia do zapisywania danych w pamięci

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

0

Post @Droidman jest dość obszerny. Volley działa dobrze z małymi danymi o wielkości kilku kilobajtów. Kiedy próbowałem użyć „BasicImageDownloader.java”, Android Studio ostrzegło mnie, że klasa AsyncTask powinna być statyczna lub mogą występować przecieki. Użyłem Volley w innej aplikacji testowej, która ciągle się zawieszała z powodu wycieków, więc martwię się, czy użyję Volley do pobierania obrazów (obrazy mogą mieć kilka 100 kB).

Użyłem Picassa i działało dobrze, jest mała zmiana (prawdopodobnie aktualizacja Picassa) od tego co napisałem powyżej. Poniższy kod zadziałał dla mnie:

    public static void imageDownload(Context ctx, String url){
    Picasso.get().load(yourURL)
            .into(getTarget(url));
}
private static Target getTarget(final String url){

    Target target2 = new Target() {
        @Override
        public void onBitmapLoaded(final Bitmap bitmap, Picasso.LoadedFrom from) {
            new Thread(new Runnable() {

                @Override
                public void run() {

                    File file = new File(localPath + "/"+"YourImageFile.jpg");
                    try {
                        file.createNewFile();
                        FileOutputStream ostream = new FileOutputStream(file);
                        bitmap.compress(Bitmap.CompressFormat.JPEG, 80, ostream);
                        ostream.flush();
                        ostream.close();
                    } catch (IOException e) {
                        Log.e("IOException", e.getLocalizedMessage());
                    }
                }
            }).start();
        }

        @Override
        public void onBitmapFailed(Exception e, Drawable errorDrawable) {
        }

        @Override
        public void onPrepareLoad(Drawable placeHolderDrawable) {
        }
    };
    return target;
}

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.