如何在Android上下载和保存图片

96

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

10个回答

230

2015年12月30日更新 - 图片下载的终极指南


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


TL;DR也就是说,别废话了,给我代码!!

跳到本文底部,复制 BasicImageDownloader (javadoc版本在这里)到你的项目中,实现 OnImageLoaderListener 接口,完成。

注意:虽然 BasicImageDownloader 处理可能出错的情况,防止你的应用崩溃,但它不会对下载的 Bitmaps 进行任何后处理(例如缩小大小)。


由于本文受到了相当多的关注,我已决定完全重写它,以防止人们使用已弃用的技术、糟糕的编程实践或做一些愚蠢的事情——比如寻找在主线程上运行网络或接受所有SSL证书的“黑科技”。

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

注意:我还没有为 SDK 23+(Marshmallow)调整权限管理,因此该项目针对 SDK 22(Lollipop)。

在本文结尾的结论中,我将分享关于我提到的每种图像下载方式的适当用例的意见。

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

注意:在大图像的情况下,你可能需要缩放它们

安卓 DownloadManager 是一种让系统帮你处理下载的方式,实际上可以下载任何类型的文件,而不仅仅是图像。您可以让下载在用户看不见的情况下静默进行,也可以在通知区域中显示下载进度。您还可以注册一个 BroadcastReceiver,以便在下载完成后收到通知。设置非常简单,请参阅链接的项目以获取示例代码。

如果您想要显示图像,使用 DownloadManager 通常不是一个好主意,因为您需要读取和解码已保存的文件,而不仅仅是将下载的 Bitmap 设置到 ImageView 中。此外, DownloadManager 不提供任何 API 来跟踪下载进度。

--

现在介绍伟大的东西 - 库。它们可以做更多的事情,不仅仅是下载和显示图像,包括:创建和管理内存/磁盘缓存,调整图像大小,转换它们等。

我将从 Volley 开始,这是由谷歌创建并由官方文档支持的强大库。尽管 Volley 是一个通用的网络库,不专门针对图像,但它具有相当强大的管理图像的 API。

您需要实现一个 Singleton 类来管理 Volley 请求,并准备就绪。

您可能想要将您的 ImageView 替换为 Volley 的 NetworkImageView,这样下载基本上就变成了一行代码:

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

如果您需要更多控制,使用Volley创建ImageRequest的代码如下:

     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非常合适。
--
Square的Picasso是一个著名的库,它可以为您完成所有图片加载工作。只需使用Picasso显示图像就像这样简单:
 Picasso.with(myContext)
       .load(url)
       .into(myImageView); 

默认情况下,Picasso会管理磁盘/内存缓存,因此您不需要担心这个问题。如果想要更多的控制权,可以实现Target接口并使用它将图像加载到其中-这将提供类似于Volley示例的回调函数。请查看演示项目以获取示例。

Picasso还允许您对下载的图像应用变换,并且甚至有一些扩展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会维护内存/磁盘缓存。

注意:作者已经在2015年11月27日之后停止维护该项目。但由于有许多贡献者,我们可以希望通用图像加载器将继续存在。

--

Facebook的Fresco是最新的、最先进的库,将图像管理提升到了一个新的水平:从在Lollipop之前将Bitmaps保存在java堆栈之外,到支持动画格式渐进式JPEG流

要了解Fresco背后的思想和技术,请参阅该帖子

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

Fresco使用Drawee来显示图像,您可以将它们视为ImageView

    <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和动画图像的示例。


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

请注意,以下文字反映了我的个人观点,不应被视为教条。

  • 如果您只需要下载/保存/显示一些图像,并且不打算在Recycler-/Grid-/ListView中使用它们,也不需要许多图片处于显示准备状态,则应使用BasicImageDownloader
  • 如果您的应用程序保存图像(或其他文件)作为用户或自动操作的结果,并且您不经常需要显示这些图像,请使用Android的DownloadManager
  • 如果您的应用程序进行大量网络传输/接收JSON数据、处理图像,但这些不是应用程序的主要目的,那么请使用Volley
  • 如果您的应用程序着重于图像/媒体,想要对图像应用一些转换,而不想烦恼于复杂的API,则使用Picasso(注意:不提供任何API来跟踪中间下载状态)或Universal Image Loader
  • 如果您的应用程序专注于图像,并且需要高级功能,例如显示动画格式,并且您准备阅读文档,请选择Fresco

如果您错过了,这是演示项目的Github链接


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

关于onPictureTaken()回调函数,它会将图片以byte[]的形式返回,是否可以直接从相机获取该图片的URL呢?或者在Android中,除了使用内置意图之外,是否有其他方法可以保存由相机拍摄的照片,例如基本的outputStream()?这似乎很奇怪,因为在onPictureTaken之后自然而然的事情是将其保存下来。难道没有特别的支持来完成这个任务吗? - Tombola
2
@Tombola 你好!这篇文章是关于从网络下载图片的。但是回答你的问题(就我所理解的):保存相机拍摄的照片的常见方法是从Cursor(在onActivityResult()方法中)获取其路径,然后使用该路径创建一个Bitmap。如果你想将此图像保存到SD卡中,那么你将无法避免使用FileOutputStreamByteArrayOutputStream - Droidman
请问您能否提供一个使用BasicImageDownloader的示例? - Jaydev
1
@JaydevKalivarapu 请在 GitHub 上检查演示应用程序(包含示例的源类)。 - Droidman
这就是Android上“basic”的样子。耶! - milosmns
显示剩余2条评论

35

我刚刚解决了这个问题,并且我想分享完整的代码,可以下载、保存到SD卡(并隐藏文件名),检索图像,最后检查图像是否已经存在。URL来自数据库,因此可以使用ID轻松地唯一命名文件。

首先,创建一个用于下载图像的类。

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_);
        }
    }
}

接下来,创建一个用于保存和检索文件的类。

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

那么,要访问图像,首先检查它是否已经存在;如果不存在,则下载。

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() ;
}

注意:尽管此示例通常可以工作,但它没有提供任何错误处理,并且缺乏对“AsyncTask”优势的基本理解(正确使用参数化、并行执行等)。有关详细信息,请参阅下面的示例。附言:对于那些可能认为我写这篇文章是为了出于某种原因推广自己的代码的人:不,我只是指出我在给定示例中看到的问题。 - Droidman
是的,Droidman,我同意你的观点。这段代码应该被视为一个模板,需要自己完成,包括错误处理。顺便说一下,你的代码也缺乏错误处理。如果发生IOException,你的连接和流会发生什么情况? - Androider
@Androider请仔细查看我的download方法,特别是我使用的任务的doInBackground方法。如果出现IOException,它将落入catch(Throwable e)块中,导致返回ImageError并触发onError()回调。 ImageError对象将包含原始堆栈跟踪和发生的Exception的原因。 - Droidman
1
是的,但您的连接不会断开,流也不会关闭。 - Androider
@Androider 哦,我明白你的意思了。虽然我在我的测试设备上从未注意到任何可疑的泄漏,但这种行为可能在其他设备上有所不同。感谢你的提示 - 我已经更新了代码。 - Droidman
无论如何,这些方法已经不再被广泛使用,可以使用Volley来简化图像下载过程。 - Nasz Njoka Sr.

13

这段代码可能会对你有所帮助。

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
我有一个简单的解决方案,完美地工作。这段代码不是我的,我在这个链接上找到的。以下是需要遵循的步骤: 1. 在下载图像之前,让我们编写一个方法将位图保存到Android内部存储器中的图像文件中。它需要一个上下文,最好使用通过getApplicationContext()传入的应用程序上下文。此方法可以放置在您的Activity类或其他util类中。
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. 现在我们有了一种将位图保存为Android中图像文件的方法,让我们编写一个通过URL下载图像的AsyncTask。这个私有类需要作为子类放置在您的Activity类中。在图像被下载后,在onPostExecute方法中,它调用上面定义的saveImage方法来保存图像。请注意,图像名称硬编码为“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,但我们需要执行它才能运行该AsyncTask。为此,在您的Activity类的onCreate方法中,或在按钮的onClick方法或其他适当位置中编写以下行。

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

图片应该保存在 /data/data/your.app.packagename/files/my_image.jpeg,查看此帖以从您的设备访问此目录。
我认为这解决了问题!如果您想要更进一步的步骤,比如加载图片,您可以遵循以下额外步骤:
4. 在图像下载完成后,我们需要一种从内部存储加载图像位图的方法,以便我们可以使用它。 让我们编写加载图像位图的方法。 此方法接受两个参数,一个上下文和一个图像文件名(不带完整路径),context.openFileInput(imageName)将在保存此文件名时在保存目录中查找文件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. 现在我们拥有了所有需要设置ImageView或者其他你想使用该图片的视图所需的东西。当我们保存图片时,我们将图片名称硬编码为“my_image.jpeg”,现在我们可以将这个图片名称传递给上面的loadImageBitmap方法来获取位图并将其设置到ImageView中。

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

获取图片全路径需要根据图片名称。
File file            = getApplicationContext().getFileStreamPath("my_image.jpeg");
String imageFullPath = file.getAbsolutePath();

7. 检查图像文件是否存在。

File file =

getApplicationContext().getFileStreamPath("my_image.jpeg");
if (file.exists()) Log.d("file", "my_image.jpeg exists!");
  1. 删除图像文件。

    File file = getApplicationContext().getFileStreamPath("my_image.jpeg"); if (file.delete()) Log.d("file", "my_image.jpeg 已删除!");


2

在Android Studio中下载图片的代码:

    DownloadManager downloadManager = 
         (DownloadManager)getSystemService(Context.DOWNLOAD_SERVICE);
    Uri uri = Uri.parse(url);
    DownloadManager.Request request = new DownloadManager.Request(uri);
    request.setNotificationVisibility
         (DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
    downloadManager.enqueue(request);
    FancyToast.makeText(getApplicationContext(), "Downloaded", 
       FancyToast.LENGTH_SHORT, FancyToast.SUCCESS, false).show();

0

试一下

 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();
            }
        }
    }
}

输出在此输入图像描述

确保您已经添加了内存写入权限

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

0

这段代码在我的项目中完美运行。

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
嗨,感谢您的贡献。请注意,将图像保存在应用程序的私有存储上述方法有两个缺点:1)该图像仅适用于您的应用程序,2)您应避免将图像保存到应用程序的私有存储中,因为它不适用于大容量内容。 - Droidman

0

@Droidman的帖子非常全面。Volley适用于几KB的小数据。当我尝试使用“BasicImageDownloader.java”时,Android Studio警告我AsyncTask类应该是静态的,否则可能会出现泄漏。我在另一个测试应用程序中使用了Volley,但由于泄漏而经常崩溃,因此我担心在图像下载器(图像可能为几百KB)中使用Volley。

我使用了Picasso,它运行良好,与上面发布的内容有一些小变化(可能是Picasso的更新)。以下代码对我有效:

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

-1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接