android之如何在Android中下载和保存图像

Terrylee 阅读:203 2025-06-02 22:19:02 评论:0

如何从 Android 中的给定 url 下载和保存图像?

请您参考如下方法:

2015 年 12 月 30 日编辑 - 图像下载终极指南

最后一次重大更新:2016 年 3 月 31 日

TL;DR a.k.a. 停止说话,给我代码!

Skip to the bottom of this post, copy the BasicImageDownloader (javadoc version here) into your project, implement the OnImageLoaderListener interface and you're done.

Note: though the BasicImageDownloader handles possible errors and will prevent your app from crashing in case anything goes wrong, it will not perform any post-processing (e.g. downsizing) on the downloaded Bitmaps.



由于这篇文章受到了相当多的关注,我决定对其进行彻底修改,以防止人们使用过时的技术、糟糕的编程实践或只是做一些愚蠢的事情——比如寻找“黑客”来在主线程上运行网络或接受所有 SSL 证书。

我创建了一个名为“Image Downloader”的演示项目,它演示了如何使用我自己的下载器实现下载(和保存)图像,Android 的内置 DownloadManager以及一些流行的开源库。您可以查看完整的源代码或下载项目 on GitHub .

Note: I have not adjusted the permission management for SDK 23+ (Marshmallow) yet, thus the project is targeting SDK 22 (Lollipop).



在我的 结论 在这篇文章的最后,我将分享我对我提到的每种特定图像下载方式的正确用例的拙见。

让我们从一个自己的实现开始(你可以在文章末尾找到代码)。首先,这是一个 基础版 ImageDownloader 就是这样。它所做的只是连接到给定的 url,读取数据并尝试将其解码为 Bitmap ,触发 OnImageLoaderListener适当时的接口(interface)回调。
这种方法的优点 - 它很简单,您可以清楚地了解正在发生的事情。如果您只需要下载/显示和保存一些图像,而您不关心维护内存/磁盘缓存,那么这是一个很好的方法。

Note: in case of large images, you might need to scale them down.



——

安卓 DownloadManager是一种让系统为您处理下载的方法。它实际上能够下载任何类型的文件,而不仅仅是图像。您可以让您的下载静默进行并且对用户不可见,或者您可以让用户在通知区域中看到下载。您也可以注册 BroadcastReceiver下载完成后收到通知。设置非常简单,请参阅示例代码的链接项目。

使用 DownloadManager如果您还想显示图像,通常不是一个好主意,因为您需要读取和解码保存的文件,而不仅仅是设置下载的 BitmapImageView . DownloadManager也不为您的应用程序提供任何 API 来跟踪下载进度。

——

现在介绍很棒的东西 - 图书馆。他们可以做的不仅仅是下载和显示图像,包括:创建和管理内存/磁盘缓存、调整图像大小、转换它们等等。

我将从 Volley 开始,一个由 Google 创建并被官方文档覆盖的强大库。虽然是一个不专门处理图像的通用网络库,但 Volley 具有非常强大的用于管理图像的 API。

您将需要实现 Singleton用于管理 Volley 请求的类,您可以开始使用了。

您可能想更换您的 ImageView与 Volley 的 NetworkImageView ,所以下载基本变成单行了:
((NetworkImageView) findViewById(R.id.myNIV)).setImageUrl(url, MySingleton.getInstance(this).getImageLoader()); 

如果您需要更多控制,这就是创建 ImageRequest 的样子。与 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 
                } 
            }); 

值得一提的是,Volley 通过提供 VolleyError 具有出色的错误处理机制。帮助您确定错误的确切原因的类。如果您的应用程序执行大量网络操作并且管理图像不是其主要目的,那么 Volley 它非常适合您。

——

方正 Picasso是一个众所周知的库,它将为您完成所有图像加载工作。使用 Picasso 显示图像非常简单:
 Picasso.with(myContext) 
       .load(url) 
       .into(myImageView);  

默认情况下,Picasso 管理磁盘/内存缓存,因此您无需担心。如需更多控制,您可以实现 Target界面并使用它来加载您的图像 - 这将提供类似于 Volley 示例的回调。检查演示项目以获取示例。

Picasso 还允许您对下载的图像应用转换,甚至还有 other libraries围绕扩展那些API。在 RecyclerView 中也能很好地工作/ ListView/ GridView .

——

Universal Image Loader是另一个非常流行的库,用于图像管理。它使用自己的 ImageLoader它(一旦初始化)有一个全局实例,可用于在一行代码中下载图像:
  ImageLoader.getInstance().displayImage(url, myImageView); 

如果您想跟踪下载进度或访问下载的 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 
                } 
            }); 
opts本例中的参数是 DisplayImageOptions对象。请参阅演示项目以了解更多信息。

与 Volley 类似,UIL 提供了 FailReason类,使您能够检查下载失败时出了什么问题。默认情况下,如果您没有明确告诉 UIL 不这样做,UIL 会维护内存/磁盘缓存。

备注 :作者已经提到,他在 2015 年 11 月 27 日不再维护该项目。但是由于有很多贡献者,我们希望通用图像加载器能够继续存在。

——

Facebook Fresco是最新的和 (IMO) 最先进的库,它将图像管理提升到一个新的水平:从保持 Bitmaps关闭 Java 堆(在 Lollipop 之前)以支持 animated formatsprogressive JPEG streaming .

要了解有关 Fresco 背后的想法和技术的更多信息,请参阅 this post .

基本用法很简单。请注意,您需要调用 Fresco.initialize(Context);只有一次,最好在 Application类。多次初始化 Fresco 可能会导致不可预测的行为和 OOM 错误。

壁画用途 Drawee s 来显示图像,你可以把它们想象成 ImageView s:
    <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" /> 

正如你所看到的,很多东西(包括转换选项)已经在 XML 中定义了,所以你需要做的就是显示一个单行:
 mDrawee.setImageURI(Uri.parse(url)); 

Fresco 提供了一个扩展的自定义 API,在某些情况下,它可能非常复杂,并且需要用户仔细阅读文档(是的,有时您需要 RTFM)。

我在示例项目中包含了渐进式 JPEG 和动画图像的示例。

结论 - “我已经了解了很棒的东西,我现在应该使用什么?”

Note that the following text reflects my personal opinion and should not be taken as a postulate.


  • 如果你只需要下载/保存/显示一些图像,不要打算在 Recycler-/Grid-/ListView 中使用它们。并且不需要一大堆图像即可显示, BasicImageDownloader 应该适合您的需求。
  • 如果您的应用程序保存图像(或其他文件)作为用户或自动操作的结果,并且您不需要经常显示图像,请使用 Android 下载管理器 .
  • 如果您的应用程序进行大量网络连接,则发送/接收 JSON数据,适用于图像,但这些不是应用程序的主要目的,请使用 Volley 抽射 .
  • 您的应用以图像/媒体为中心,您想对图像应用一些转换并且不想打扰复杂的 API:使用 毕加索 (注:不提供任何API来跟踪中间下载状态)或通用图像加载器
  • 如果您的应用都是关于图像的,您需要高级功能,例如显示动画格式,并且您已准备好阅读文档,请使用 壁画 .

  • 如果您错过了, Github link对于演示项目。

    这是 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; 
            } 
          } 
       } 
    


    标签:Android
    声明

    1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

    关注我们

    一个IT知识分享的公众号