android - 下载图像并将其存储在 ContentProvider 中

标签 android bitmap inputstream android-contentprovider bitmapfactory

我有一个自己的 ContentProvider 实现,它支持通过重写 ContentProvider.openInputStream 和 openOutputStream 方法将图像保存/加载到每个记录。在本地一切正常。但现在我从 URL 下载图像,然后将它们保存到 ContentProvider 中。

最佳解决方案,但不起作用:为了避免在内存中创建巨大的位图,我想将传入的 HTTPS 流直接写入文件(选项 1)。但是当我加载位图时,BitmapFactory 会抛出错误。

有效,但没有最佳解决方案:如果我将传入 HTTPS 流(选项 2)中的位图加载到内存中,然后将其保存(压缩)到 ContentProvider - 然后稍后加载位图工作正常。

所以我想知道我做错了什么?

这里有一些要测试的 URL:

https://lh5.ggpht.com/goggZXKLiJst1uSWPmgzk9j2WqdNiPAQZyb59tddL1WIHQgb-cPV7uqGuqECdu7ChiW8vve_2UC-Ta16YfbLlA=s192 https://lh4.ggpht.com/EizCbwoyAndISCf1b2tjPkOSMEl-jJZoPJ386RtQ7Q4kJ-1tUDEhqweXrPP-jX7pbCAoCUYN7iw1beyiI9JTFAo=s160

示例代码(downloadDirect 导致错误的位图,downloadIndirect 有效):

private void downloadDirect(String url, int key, Context context)
        throws MalformedURLException, IOException {
    InputStream is = download(url);

    OutputStream os = openOutputStream(context, key);
    final byte[] buffer = new byte[BUFFER_SIZE];

    while (is.read(buffer) >= 0) {
        os.write(buffer);
    }

    os.close();
    is.close();
}

private void downloadIndirect(String url, int key, Context context)
        throws MalformedURLException, IOException {
    InputStream is = download(url);

    Bitmap bitmap = BitmapFactory.decodeStream(is);
    saveBitmap(context, key, bitmap);
}

private InputStream download(String url) throws MalformedURLException,
        IOException {

    URL newUrl = new URL(url);
    HttpURLConnection con = (HttpURLConnection) newUrl.openConnection();
    return con.getInputStream();
}

这些是 ContentProvider 的方法:

public static InputStream openInputStream(Context context, Uri contentUri,
        int key) throws FileNotFoundException {

    Uri uri = ContentUris.withAppendedId(contentUri, key);
    return context.getContentResolver().openInputStream(uri);
}

public static OutputStream openOutputStream(Context context,
        Uri contentUri, int key) throws FileNotFoundException {

    Uri uri = ContentUris.withAppendedId(contentUri, key);
    return context.getContentResolver().openOutputStream(uri);
}

protected static void saveBitmap(Context context, Uri contentUri,
        String basePath, int key, Bitmap value, boolean updateDatabase) {

    Uri uri = ContentUris.withAppendedId(contentUri, key);

    try {
        if (value == null) {
            deleteFile(uri, basePath, context, true);
            return;
        }

        OutputStream outStream;
        try {
            outStream = openOutputStream(context, contentUri, key);
            ImageUtils.saveToStream(value, outStream,
                    Bitmap.CompressFormat.PNG);
            outStream.close();

            Log.d(TAG,
                    "Image (" + value.getWidth() + "x" + value.getHeight()
                            + "pixels) saved to " + uri.toString());

        } catch (FileNotFoundException e) {
            e.printStackTrace();
            Log.e(TAG, "Could not save image to " + uri.toString());
        } catch (IOException e) {
            e.printStackTrace();
            Log.e(TAG, "Could not save image to " + uri.toString());
        }
    } finally {
        if (updateDatabase) {
            ContentValues values = new ContentValues();
            // modified column will be added automatically
            context.getContentResolver().update(uri, values, null, null);
        }
    }
}

ContentProvider 的 openFile 方法被重写如下:

public ParcelFileDescriptor openFile(Uri uri, String mode)
        throws FileNotFoundException {

    ContextWrapper cw = new ContextWrapper(getContext());

    // path to /data/data/yourapp/app_data/dir
    File directory = cw.getDir(basePath, Context.MODE_MULTI_PROCESS);
    directory.mkdirs();

    long id = ContentUris.parseId(uri);
    File path = new File(directory, String.valueOf(id));

    int imode = 0;
    if (mode.contains("w")) {
        imode |= ParcelFileDescriptor.MODE_WRITE_ONLY;
        if (!path.exists()) {
            try {
                path.createNewFile();
            } catch (IOException e) {
                Log.e(tag, "Could not create file: " + path.toString());
                e.printStackTrace();
            }
        }
    }
    if (mode.contains("r"))
        imode |= ParcelFileDescriptor.MODE_READ_ONLY;
    if (mode.contains("+"))
        imode |= ParcelFileDescriptor.MODE_APPEND;

    return ParcelFileDescriptor.open(path, imode);
}

最佳答案

我以错误的方式读取 HTTP 输入流。有两种正确的方法可以完美地工作:

a) 直接写入输出流

private void downloadDirect(String url, int key, Context context)
        throws MalformedURLException, IOException {

    InputStream is = download(url);
    OutputStream os = openOutputStream(context, key);


    final byte[] buffer = new byte[BUFFER_SIZE];

    int bytesRead = 0;
    while ((bytesRead = is.read(buffer, 0, buffer.length)) >= 0) {
        os.write(buffer, 0, bytesRead);
    }

    os.close();
    is.close();
}

b) 写入缓冲流,然后写入输出流

private void downloadDirect(String url, int key, Context context)
        throws MalformedURLException, IOException {

    InputStream is = download(url);
    OutputStream os = openOutputStream(context, key);

    BufferedInputStream bis = new BufferedInputStream(is);

    ByteArrayBuffer baf = new ByteArrayBuffer(50);
    int current = 0;
    while ((current = bis.read()) != -1) {
        baf.append((byte) current);
    }

    os.write(baf.toByteArray());

    os.close();
    is.close();
}

该解决方案的优点是,即使是大图像文件也可以下载并直接存储到文件中。稍后,您可以使用采样将这些(可能很大)图像加载到位图中。

我在互联网上找到的大多数文章都使用 HTTP 输入流来使用 BitmapFactory 来解码位图。这是一个非常糟糕的方法,因为如果图像太大,您可能会遇到 OutOfMemoryExceptions。

而且它也非常慢,因为首先您将传入流解码为位图,然后再次将位图编码为输出流。性能不佳。

关于android - 下载图像并将其存储在 ContentProvider 中,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24739392/

相关文章:

Android 位图 getPixel 取决于密度?

android - 另一个类中的错误 getResource()

java - 我什么时候会使用 BufferedInputStream?

c++ - 如何使用 C++ 中的流从文件末尾读取给定数量的行?

java - 如何使用 UTF-8 读取 InputStream?

android - 在 PreferenceScreen 中使用自定义字体

android - Android 的接近传感器如何工作?

android - com.android.dx.util.DexException : Multiple dex files define

android - PendingIntent.getBroadcast().send() 与 Context.sendBroadcast()

c# - 去除图像透明度