2016-06-02 2 views
0

In meiner App implementiere ich einen Mechanismus zum Herunterladen von Dateien über den DownloadManager. Wenn Benutzer einen Download starten, wird neben der Schaltfläche die Schaltfläche Abbrechen angezeigt, über die der Benutzer den Download abbrechen kann.Android DownloadManager, Backup-Dateien und abgebrochene Downloads

Diese Datendateien werden regelmäßig auf dem Server aktualisiert, sodass die Benutzer die gleiche Datei von Zeit zu Zeit erneut herunterladen möchten. Dateinamen bleiben bei Aktualisierungen stabil.

Da der Benutzer während des Downloads jederzeit auf Abbrechen klicken kann, möchte ich die alte Version beibehalten, bis der Download erfolgreich abgeschlossen wurde. Zu diesem Zweck benenne ich die vorhandene Datei um und starte erst dann den Download. Wenn der Benutzer den Download abbricht (auch wenn der Download aus irgendeinem Grund fehlschlägt), möchte ich die Backup-Datei an ihrem ursprünglichen Speicherort wiederherstellen.

Für den Fall, Abbrechen, habe ich ursprünglich den folgenden Code ausgeführt werden, wenn die Schaltfläche geklickt wird Abbrechen:

if (downloadManager.remove(reference) > 0) { 
    if (destFile.exists()) 
     destFile.delete(); 
    backupFile.renameTo(destFile); 
} 

Wenn ich eine Datei zu aktualisieren, die alte Datei, bevor der Download beginnt umbenannt wird. Nachdem ich den Download abgebrochen habe, sind sowohl die Teildatei als auch das Backup verschwunden.

Da ich bereits eine FileObserver verwenden, um den Download-Fortschritt zu überwachen, habe ich es erweitert, um auch nach Dateilöschung zu sehen und eine Protokollmeldung zu generieren. Im Logcat sehe ich zwei Löschungsereignisse für dieselbe Datei, die anzeigt, dass die teilweise heruntergeladene Datei gelöscht wird, die Sicherung umbenannt wird und dann die umbenannte Sicherung gelöscht wird.

Fair genug, dachte ich, anscheinend kümmert sich DownloadManager um das Löschen im Hintergrund, also muss ich darauf achten, dass das passiert. Also habe ich den obigen Event-Handler modifiziert, um den Dateipfad nur in einer Liste zu speichern und noch keine Dateioperationen durchzuführen. Ich habe dann meine FileObserver geändert, um alle gelöschten Dateien mit der Liste zu vergleichen: Wenn es übereinstimmt, benennen Sie die Sicherungsdatei um. Außerdem habe ich für jede Operation eine Protokollausgabe hinzugefügt.

Die Reihenfolge der Ereignisse ist jedoch immer noch die gleiche: jetzt wird die teilweise heruntergeladene Datei vom Download-Manager gelöscht, wodurch FileObserver ausgelöst wird, was wiederum die Sicherungsdatei umbenennen wird. Danach wird die Backup-Datei gelöscht.

Es sieht so aus, als ob der Download-Manager übereifrig ist: Wenn ein Download abgebrochen wird, löscht er die heruntergeladene Datei, prüft dann, ob sie wirklich verschwunden ist und versucht den Löschvorgang erneut, wenn sie noch eine Datei in diesem Pfad findet.

Wie kann ich das umgehen und verhindern, dass der Download-Manager Dateien löscht, die er nicht heruntergeladen hat?

Antwort

0

Ich habe am Ende mit dem Problem des mehrfachen Löschens gearbeitet, indem ich die Tatsache ausnutzte, dass der Android Download Manager niemals eine existierende Datei überschreibt und Download-Ziele selbst auf einen Namen umbenennt, der noch verfügbar ist.

Wenn eine Datei erneut heruntergeladen wird, stört es mich nicht, die alte Datei aus dem Weg zu räumen. Der Download-Manager erkennt, dass bereits eine Datei vorhanden ist, und wählt einen anderen Namen für den Download aus. Wenn der Download erfolgreich abgeschlossen ist, lösche ich die alte Datei und benenne die neue Datei um.

Die einzige Herausforderung bestand darin, den Dateinamen zu bestimmen, den der Download-Manager gewählt hat, da Android offensichtlich keine explizite Benachrichtigung dafür zu haben scheint. Keine Absicht wird ausgelöst, wenn der Download startet, daher musste ich erneut auf die FileObserver zurückgreifen.

Beobachten für FileObserver.CREATE schien wie der einfachste Weg. Wenn ich jedoch zu diesem Zeitpunkt die Liste der Downloads abfrage, gibt die Abfrage einen Nullwert für den lokalen Pfad zurück.

Ich griff deshalb auf FileObserver.MODIFY zurück, der auf jeder Änderung an einer Datei feuert. Ich verwende es bereits, um den Download-Fortschritt anzuzeigen, und an dieser Stelle muss eine lokale Datei vorhanden sein. Wenn dieses Ereignis zum ersten Mal für eine Datei ausgelöst wird, die vom Download-Manager umbenannt wurde, erhalte ich einen Dateinamen, der noch nicht in meiner Liste enthalten ist. Ich laufe dann den folgenden Code:

 // File file: the file being downloaded 
     // DownloadInfo info: information about a download in progress 
     /* First progress report for a renamed file */ 
     DownloadManager.Query query = new DownloadManager.Query(); 
     query.setFilterByStatus(~(DownloadManager.STATUS_FAILED | DownloadManager.STATUS_SUCCESSFUL)); 
     Cursor cursor = downloadManager.query(query); 
     if (!cursor.moveToFirst()) { 
      cursor.close(); 
      return; 
     } 
     do { 
      Long reference = cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_ID)); 
      String path = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME)); 
      if (file.equals(new File(path))) { 
       info = downloadsByReference.get(reference); 
       if (info != null) { 
        info.downloadFile = file; 
        downloadsByFile.put(info.downloadFile, info); 
       } 
      } 
     } while (cursor.moveToNext()); 
     cursor.close(); 

Jeder Download läuft durch ein DownloadInfo Beispiel beschrieben, die unter anderem einer Referenz beiden Dateinamen hat. Ich halte sie in drei Map s:

  • downloadsByReference verwendet die IDs der Download-Manager als Schlüssel
  • downloadsByFile verwendet die lokale Datei als Schlüssel
  • downloadsByUri verwendet die URI als Schlüssel

Wenn ein Download beendet ist, suche ich seine ID in downloadsByReference, um die zwei Dateinamen zu erhalten. Wenn sie sich unterscheiden, lösche ich die alte Datei und benenne sie dann um.