Скачать и сохранить изображение в ContentProvider
Я получил собственную реализацию 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);
}
}
}
Метод openFile ContentProvider переопределяется следующим образом:
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);
}
1 ответ
Я неправильно читал поток ввода HTTP. Есть два правильных способа, которые работают отлично:
а) запись непосредственно в выходной поток
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();
}
б) записать в буферный поток, а затем в выходной поток
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, если изображение слишком велико.
И это тоже очень медленно, потому что сначала вы декодируете входящий поток в растровое изображение, а затем снова кодируете растровое изображение в выходной поток. Плохая производительность.