2016-10-07 8 views
4

verwenden ich dieses Tutorial das Herunterladen von Dateien in meiner Anwendung zu implementieren: https://www.learn2crack.com/2016/05/downloading-file-using-retrofit.htmlPause und Fortsetzen Downloads mit Retrofit

Das Problem, das ist, wenn Internet-Geschwindigkeit ist langsam oder wenn Netzwerk einmal für eine Sekunde schwankt, wird der Download beendet permanent. Gibt es eine Möglichkeit, wie die App erkennen kann, dass das Internet nicht aktiv ist (verbunden, aber eigentlich funktioniert das Netz nicht), dann pausiert der Download und wird fortgesetzt, wenn das Internet in Ordnung ist.

Oder eine Alternative, damit der Benutzer nicht frustriert wird?

+0

So haben Sie die Lösung? Wenn ja, bitte posten Sie es als Antwort. –

+0

Nein, wie Sie sehen können, scheint keiner bei SO zu wissen –

+0

Das ist ein bisschen schwer zu glauben, aber wenn ich die Lösung bekomme, werde ich definitiv hier posten. –

Antwort

2

Ich war auch heute mit diesem Problem konfrontiert und habe keine guten Lösungen gefunden, die Download-Lebensläufe, Fortschrittsbenachrichtigungen und BufferedSink-Nutzung für schnelle nio-Vorgänge sofort implementiert haben.

So könnte es mit Retrofit2 und RxJava2 gemacht werden. Code ist witten in Kotlin für Android, aber es kann leicht zu reinen JVM portiert werden: nur von AndroidSchedulers loszuwerden

Code Fehler enthalten kann, wie es von Grund auf in kurzer Zeit und wurde kaum geprüft geschrieben wurde.

import com.google.gson.GsonBuilder 
import io.reactivex.Observable 
import io.reactivex.ObservableEmitter 
import io.reactivex.ObservableOnSubscribe 
import io.reactivex.android.schedulers.AndroidSchedulers 
import io.reactivex.functions.Consumer 
import io.reactivex.functions.Function 
import io.reactivex.schedulers.Schedulers 
import okhttp3.OkHttpClient 
import okhttp3.ResponseBody 
import okio.Buffer 
import okio.BufferedSink 
import okio.ForwardingSource 
import okio.Okio 
import org.slf4j.LoggerFactory 
import retrofit2.Response 
import retrofit2.Retrofit 
import retrofit2.converter.gson.GsonConverterFactory 
import retrofit2.http.GET 
import retrofit2.http.Header 
import retrofit2.http.Streaming 
import retrofit2.http.Url 
import java.io.File 
import java.io.IOException 
import java.util.concurrent.ConcurrentHashMap 
import java.util.regex.Pattern 

class FileDownloader(val baseUrl: String) { 

    private val log = LoggerFactory.getLogger(FileDownloader::class.java) 

    private val expectedFileLength = ConcurrentHashMap<String, Long>() 
    private val eTag = ConcurrentHashMap<String, String>() 

    private val apiChecker: FileDownloaderAPI 

    init { 
     apiChecker = Retrofit.Builder() 
       .baseUrl(baseUrl) 
       .client(OkHttpClient()) 
       .addConverterFactory(GsonConverterFactory.create(GsonBuilder().setLenient().create())) 
       .build() 
       .create(FileDownloaderAPI::class.java) 

    } 


    /** 
    * 
    * @return File Observable 
    */ 
    fun download(
      urlPath: String, 
      file: File, 
      dlProgressConsumer: Consumer<Int>): Observable<File> { 
     return Observable.create(ObservableOnSubscribe<File> { 
      val downloadObservable: Observable<Int> 

      if (file.exists() && 
        file.length() > 0L && 
        file.length() != expectedFileLength[file.name] 
        ) { 
       /** 
       * Try to get rest of the file according to: 
       * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html 
       */ 
       downloadObservable = apiChecker.downloadFile(
         urlPath, 
         "bytes=${file.length()}-", 
         eTag[file.name] ?: "0" 
       ).flatMap(
         DownloadFunction(file, it) 
       ) 
      } else { 
       /** 
       * Last time file was fully downloaded or not present at all 
       */ 
       if (!file.exists()) 
        eTag[file.name] = "" 

       downloadObservable = apiChecker.downloadFile(
         urlPath, 
         eTag[file.name] ?: "0" 
       ).flatMap(
         DownloadFunction(file, it) 
       ) 

      } 

      downloadObservable 
        .observeOn(AndroidSchedulers.mainThread()) 
        .subscribe(dlProgressConsumer) 

     }).subscribeOn(Schedulers.io()) 
       .observeOn(AndroidSchedulers.mainThread()) 
    } 

    private inner class DownloadFunction(
      val file: File, 
      val fileEmitter: ObservableEmitter<File> 
    ) : Function<Response<ResponseBody>, Observable<Int>> { 

     var contentLength = 0L 

     var startingByte = 0L 
     var endingByte = 0L 
     var totalBytes = 0L 


     var contentRangePattern = "bytes ([0-9]*)-([0-9]*)/([0-9]*)" 
     fun parseContentRange(contentRange: String) { 
      val matcher = Pattern.compile(contentRangePattern).matcher(contentRange) 
      if (matcher.find()) { 
       startingByte = matcher.group(1).toLong() 
       endingByte = matcher.group(2).toLong() 
       totalBytes = matcher.group(3).toLong() 
      } 
     } 

     var totalRead = 0L 

     var lastPercentage = 0 

     override fun apply(response: Response<ResponseBody>): Observable<Int> { 
      return Observable.create { subscriber -> 
       try { 
        if (!response.isSuccessful) { 
         /** 
         * Including response 304 Not Modified 
         */ 
         fileEmitter.onError(IllegalStateException("Code: ${response.code()}, ${response.message()}; Response $response")) 
         [email protected] 
        } 

        contentLength = response.body().contentLength() 


        log.info("{}", response) 
        /** 
        * Receiving partial content, which in general means that download is resumed 
        */ 
        if (response.code() == 206) { 
         parseContentRange(response.headers().get("Content-Range")) 
         log.debug("Getting range from {} to {} of {} bytes", startingByte, endingByte, totalBytes) 
        } else { 
         endingByte = contentLength 
         totalBytes = contentLength 
         if (file.exists()) 
          file.delete() 
        } 

        log.info("Starting byte: {}, ending byte {}", startingByte, endingByte) 

        totalRead = startingByte 

        eTag.put(file.name, response.headers().get("ETag")) 
        expectedFileLength.put(file.name, totalBytes) 


        val sink: BufferedSink 
        if (startingByte > 0) { 
         sink = Okio.buffer(Okio.appendingSink(file)) 
        } else { 
         sink = Okio.buffer(Okio.sink(file)) 
        } 

        sink.use { 
         it.writeAll(object : ForwardingSource(response.body().source()) { 

          override fun read(sink: Buffer, byteCount: Long): Long { 
           val bytesRead = super.read(sink, byteCount) 

           totalRead += bytesRead 

           /** 
           * May not wok good if we get some shit from the middle of the file, 
           * though that's not the case of this function, as we plan only to 
           * resume downloads 
           */ 
           val currentPercentage = (totalRead * 100/totalBytes).toInt() 
           if (currentPercentage > lastPercentage) { 
            val progress = "$currentPercentage%" 
            lastPercentage = currentPercentage 
            subscriber.onNext(currentPercentage) 
            log.debug("Downloading {} progress: {}", file.name, progress) 
           } 
           return bytesRead 
          } 
         }) 
        } 

        subscriber.onComplete() 
        fileEmitter.onNext(file) 
        fileEmitter.onComplete() 
       } catch (e: IOException) { 
        log.error("Last percentage: {}, Bytes read: {}", lastPercentage, totalRead) 
        fileEmitter.onError(e) 
       } 
      } 
     } 

    } 

    interface FileDownloaderAPI { 


     @Streaming @GET 
     fun downloadFile(
       @Url fileUrl: String, 
       @Header("If-None-Match") eTag: String 
     ): Observable<Response<ResponseBody>> 

     @Streaming @GET 
     fun downloadFile(
       @Url fileUrl: String, 

       // https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35 
       @Header("Range") bytesRange: String, 

       // https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.27 
       @Header("If-Range") eTag: String 
     ): Observable<Response<ResponseBody>> 
    } 
} 

und dann verwenden, wo Sie

wollen
val fileDownloader = FileDownloader("http://wwww.example.com") 
    fileDownloader.download(
      "/huge-video.mkv", 
      File("file-where-I-will-save-this-video.mkv"), 
      Consumer { progress -> 
       updateProgressNotificatuin() 
      } 
    ).subscribe({ 
     log.info("File saved at path {}", it.absolutePath) 
    },{ 
     log.error("Download error {}", it.message, it) 
    },{ 
     log.info("Download completed") 
    }) 

Abhängigkeiten verwendet in diesem Beispiel:

dependencies { 
    compile "org.jetbrains.kotlin:kotlin-stdlib:1.1.1" 
    compile 'io.reactivex.rxjava2:rxandroid:2.0.1' 

    compile 'com.squareup.retrofit2:retrofit:2.2.0' 
    compile 'com.squareup.retrofit2:converter-gson:2.2.0' 
    compile 'com.squareup.retrofit2:adapter-rxjava2:2.2.0' 
    compile 'com.google.code.gson:gson:2.7' 


    compile 'org.slf4j:slf4j-api:1.7.25' 
} 
Verwandte Themen