Wie stellt man eine virtuelle Datei von einem Inhaltsanbieter am besten bereit?Android

Forum für diejenigen, die für Android programmieren
Guest
 Wie stellt man eine virtuelle Datei von einem Inhaltsanbieter am besten bereit?

Post by Guest »

Deshalb habe ich einen benutzerdefinierten ContentProvider geschrieben, der eine Datei bereitstellt, die bei jeder Anforderung mithilfe eines Inhalts-URI erstellt wird. Diese Datei existierte vor der Anfrage nicht und kombiniert mehrere andere Dateien in einem Zip-Archiv, ähnlich einem Exportmechanismus.
Ich könnte diese Datei auf die Festplatte schreiben und sie dann einfach übergeben Ergebnis von ParcelFileDescriptor.open(File) an die anfordernde App, aber dies hinterlässt ein Artefakt auf der Festplatte, bei dem es sich praktisch um eine redundante Kopie handelt, die schnell veraltet. Ich kenne auch keinen Mechanismus, mit dem ich diese Datei entfernen kann, sobald sie vollständig verbraucht ist. Daher müsste ich einen Mechanismus implementieren, der diese Dateien schließlich löscht.
Stattdessen möchte ich einen Puffer direkt übergeben, ohne zunächst auf die Festplatte schreiben zu müssen. Am Ende habe ich ParcelFileDescriptor.createPipe() verwendet, um den Inhalt mithilfe von Eingabe-/Ausgabeströmen und einem zusätzlichen Thread direkt zu streamen. Dies scheint in den meisten Fällen gut zu funktionieren, aber von allen Apps scheint Gmail den InputStream sofort zu schließen, was zu einer Ausnahme wegen unterbrochener Pipe führt und Gmail sich über eine leere Datei beschwert:
< pre class="lang-java Prettyprint-override">

Code: Select all

@Override
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
long id = Long.parseLong(Objects.requireNonNull(uri.getPathSegments().get(0)));
try {
ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
ParcelFileDescriptor output = pipe[1];
new Thread(() -> {
try (ParcelFileDescriptor.AutoCloseOutputStream out = new ParcelFileDescriptor.AutoCloseOutputStream(output)) {
serializeZipFile(out, id);
} catch (IOException e) {
Log.e(TAG, "Error during zip serialization.", e);
}
}).start();
return pipe[0];
} catch (IOException e) {
Log.e(TAG, "Error while trying to create pipe", e);
throw new FileNotFoundException("Failed to create pipe");
}
}
Nachdem ich einige Dokumentationen durchgesehen hatte, fand ich StorageManager#openProxyFileDescriptor(int, ProxyFileDescriptorCallback, Handler), das scheint ungefähr das zu tun, was ich will. Allerdings verwende ich mindestens SDK 24 und diese Methode wurde in SDK 26 eingeführt, also habe ich für den Fall SDK >= 26 diesen Code hinzugefügt, was Gmail gefiel:

Code: Select all

StorageManager storageManager = Objects.requireNonNull(getContext()).getSystemService(StorageManager.class);
ByteArrayOutputStream out = new ByteArrayOutputStream();
serializeZipFile(out, id);
return storageManager.openProxyFileDescriptor(ParcelFileDescriptor.MODE_READ_ONLY, new ProxyFileDescriptorCallback() {
private final byte[] bytes = out.toByteArray();

@Override
public long onGetSize() {
return bytes.length;
}

@Override
public int onRead(long offset, int size, byte[] data) {
if (offset >= bytes.length) {
return 0;
}
int intOffset = Math.toIntExact(offset);
int realSize = Math.min(Math.min(size, bytes.length - intOffset), data.length);
System.arraycopy(bytes, intOffset, data, 0, realSize);
return realSize;
}

@Override
public void onRelease() {}
}, new Handler(Looper.getMainLooper()));
Beide Ansätze erscheinen mir wirklich umständlich und unnötig kompliziert. Was das Gmail-Problem angeht, habe ich gerade Gmail von der Verarbeitung meiner Absicht für SDKs < 26 ausgeschlossen, aber es scheint, dass es einfach einen besseren Weg geben muss, dies zu tun?

Quick Reply

Change Text Case: 
   
  • Similar Topics
    Replies
    Views
    Last post