我想通过以下方式提供保存在“外部存储”上的图像文件ContentProvider
.
这些图像文件被“破坏”——前 50 个字节与某个任意值进行异或。我想在里面做“demangle”ContentProvider
以便其他应用程序不需要做特殊处理。
我使用的是 Mininum SDK 版本 14。
这是我的第一次尝试 - 使用管道ParcelFileDescriptor
:
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
// basic uri/mode check here
return openPipeHelper(uri, getType(uri), null, new FileInputStream(getImageFile(uri)), new PipeDataWriter<InputStream>() {
@Override
public void writeDataToPipe(ParcelFileDescriptor output, Uri uri, final String mimeType, Bundle opts, InputStream input) {
InputStream fin = new FilterInputStream(input) {
private int cnt = 0;
private byte mask;
@Override
public int read() throws IOException {
byte[] buffer = new byte[1];
return read(buffer) == -1 ? -1 : (buffer[0] & 0xff);
}
@Override
public int read(@NonNull byte[] buffer) throws IOException {
return read(buffer, 0, buffer.length);
}
@Override
public int read(@NonNull byte[] buffer, int byteOffset, int byteCount) throws IOException {
int ret = super.read(buffer, byteOffset, byteCount);
if (ret <= 0) return ret;
if (cnt == 0) {
switch (mimeType) {
case "image/png":
mask = (byte) (buffer[byteOffset] ^ 137);
break;
case "image/webp":
mask = (byte) (buffer[byteOffset] ^ 'R');
break;
}
}
for (int i = byteOffset; i < byteOffset + ret && cnt < 50; i++, cnt++) {
buffer[i] ^= mask;
}
return ret;
}
};
OutputStream fout = new ParcelFileDescriptor.AutoCloseOutputStream(output);
byte[] buf = new byte[1024 * 1024];
try {
while (true) {
int n = fin.read(buf);
if (n == -1) break;
Log.i(TAG, "openFile get n=" + n);
fout.write(buf, 0, n);
fout.flush();
}
} catch (IOException ex) {
// EPIPE likely means pipe closed on other end; treat it as WAI.
if (!ex.getMessage().contains("EPIPE")) {
Log.w(TAG, "openFile failed", ex);
}
} finally {
try {
fin.close();
} catch (IOException ex) {
Log.w(TAG, "openFile failed closing input", ex);
}
try {
fout.close();
} catch (IOException ex) {
Log.w(TAG, "openFile failed closing output", ex);
}
}
}
});
}
Result:
- 与以下产品配合良好
ImageView.setImageURI()
.
- 不适用于 Android 默认图库(
Intent.ACTION_VIEW
with setDataAndType()
)
- 与 ES 图像查看器配合良好
看来画廊不喜欢“管道流”。
这是第二次尝试 - 读取整个文件,分解并用作ParcelFileDescriptor.fromData()
:
File file = getImageFile(uri);
byte[] buffer = readFully(file);
String mimeType = getType(uri);
byte mask;
switch (mimeType) {
case "image/png":
mask = (byte) (buffer[0] ^ 137);
break;
case "image/webp":
mask = (byte) (buffer[0] ^ 'R');
break;
default:
mask = 0;
break;
}
for (int i = 0; i < 50; i++) buffer[i] ^= mask;
return (ParcelFileDescriptor) ParcelFileDescriptor.class.getMethod("fromData", byte[].class, String.class).invoke(null, buffer, getImageFile(uri).getName());
Result:
- 不能很好地与
ImageView.setImageURI()
.
- 与 Android 默认图库配合良好
- 与 ES 图像查看器配合良好
似乎时不时地,MemoryFile
在制作ParcelFileDescriptor.fromData()
之前已关闭并处置ImageView.setImageURI()
获取数据。
这是第三次尝试 - 将分解的图像写入临时文件:
// buffer contains readFully and demangled image binary
try {
File tmpFile = File.createTempFile("image", getImageExtension(uri));
OutputStream os = new FileOutputStream(tmpFile);
try {
os.write(buffer);
} finally {
try {
os.close();
} catch (IOException ex2) {
Log.w(TAG, "openFile(): closing failed", ex2);
}
}
return ParcelFileDescriptor.open(tmpFile, ParcelFileDescriptor.MODE_READ_ONLY);
} catch (IOException ex2) {
Log.e(TAG, "openFile(): writing failed", ex2);
return null;
}
Result:
- 与以下产品配合良好
ImageView.setImageURI()
.
- 与 Android 默认图库配合良好
- 与 ES 图像查看器配合良好
但是,我不喜欢这个解决方案,因为很难确定何时可以删除临时文件。
这三个解决方案都有其缺陷,我找不到完美的解决方案。做这些事情的“正确”方法是什么?