Kotlin下载器初版实现

361 阅读1分钟

实现目标

  • 实现对大文件进行分片下载,分片下载完成后进行数据合并,成为一个完整的文件;
  • 后续目标“协程”

成果展示

image.png image.png

maven配置文件

<dependencies>
    <dependency>
        <groupId>org.jetbrains.kotlin</groupId>
        <artifactId>kotlin-test-junit5</artifactId>
        <version>1.7.10</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-engine</artifactId>
        <version>5.8.2</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.jetbrains.kotlin</groupId>
        <artifactId>kotlin-stdlib-jdk8</artifactId>
        <version>1.7.10</version>
    </dependency>
    <dependency>
        <groupId>com.squareup.retrofit2</groupId>
        <artifactId>retrofit</artifactId>
        <version>2.9.0</version>
    </dependency>
    <dependency>
        <groupId>com.squareup.retrofit2</groupId>
        <artifactId>converter-gson</artifactId>
        <version>2.9.0</version>
    </dependency>
    <dependency>
        <groupId>com.squareup.okhttp3</groupId>
        <artifactId>logging-interceptor</artifactId>
        <version>4.10.0</version>
    </dependency>
    <dependency>
        <groupId>org.jetbrains.kotlinx</groupId>
        <artifactId>kotlinx-coroutines-core</artifactId>
        <version>1.6.4</version>
    </dependency>
</dependencies>

主程序

package com.jack.demo

import com.google.gson.Gson
import com.jack.demo.thread.DownLoadTask
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import java.io.File
import java.util.concurrent.Future
import java.util.concurrent.LinkedBlockingDeque
import java.util.concurrent.ThreadPoolExecutor
import java.util.concurrent.TimeUnit

var gson: Gson = Gson()

//切片大小
var slicesSize = 10 * 1024 * 1024
var basePath = "D:\www\test\"

//线程池
val pool = ThreadPoolExecutor(
    2, 200,
    120L, TimeUnit.SECONDS,
    LinkedBlockingDeque(),//无界,FIFO队列
    ThreadPoolExecutor.AbortPolicy()
);

fun main(args: Array<String>) {
    //资源地址
    var url = "http://127.0.0.1:8099/file/v.mp4"
    //构造请求
    var request: Request = Request.Builder().url(url).get().build()
    //获取资源长度
    val client = OkHttpClient()
    var response: Response = client.newCall(request).execute()
    var dataLength: Int? = response.body()?.contentLength()?.toInt()
    println("要下载的文件长度:$dataLength  ${response.headers().get("Content-Type")}")
    //进行分片
    println("切片阈值:$slicesSize")
    var list = calculationSlices(dataLength!!)
    println("切片数量:${list?.size} 切片详情:" + gson.toJson(list))
    //提交分片下载任务
    var futures = ArrayList<Future<String>>()
    for ((index, value) in list!!.withIndex()) {
        futures.add(pool.submit(DownLoadTask(value.startIndex, value.endIndex, basePath + index)))
    }
    //获取分片下载结果
    for (future in futures){
        println("子任务下载结果:"+future.get(10,TimeUnit.SECONDS))
    }
    //合并成完整视频
    var filePath = dataLength?.let { combineVideo(list.size, it) }
    println("完整视频路径 $filePath")
    System.exit(0)
}

/**
 * 合并为完整视频并返回视频地址
 */
fun combineVideo(size: Int, dataLength: Int): String {
    var bts = ByteArray(0)
    for (index in 0 until size) {
        val myFile = File(basePath + index)
        var bytes = myFile.inputStream().readBytes()
        bts = bts.plus(bytes)
    }
    var filePath = basePath + "aa.mp4";
    var file = File(filePath)
    //保存至文件中
    bts?.let { file.writeBytes(it) }
    return filePath
}

/**
 * 分片对象
 */
class Slices(var startIndex: Int, var endIndex: Int)

/**
 * 计算分片信息
 */
fun calculationSlices(dataLength: Int): ArrayList<Slices> {
    var result = ArrayList<Slices>()

    var parks: Int = dataLength / slicesSize
    var over: Int = dataLength % slicesSize
    if (over >= 0) parks += 1

    var startIndex = 0

    for (index in 0 until parks) {
        var endIndex = startIndex + slicesSize
        endIndex = if (endIndex > dataLength) dataLength else endIndex
        var slices = Slices(startIndex, endIndex)
        result.add(slices)
        startIndex = endIndex + 1
    }
    return result
}

分片下载任务

package com.jack.demo.thread

import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import java.io.File
import java.util.concurrent.Callable

class DownLoadTask(var startIndex: Int, var endIndex: Int, var filePath: String) : Callable<String> {
    override fun call(): String {
        var url = "http://127.0.0.1:8099/file/v.mp4"
        //构造请求
        var request: Request = Request.Builder().url(url).header("Range", "bytes=$startIndex-$endIndex").get().build()
        //获取资源长度
        val client = OkHttpClient()
        var response: Response = client.newCall(request).execute()
        var dataBytes = response.body()?.bytes()
        var file = File(filePath)
        //保存至文件中
        dataBytes?.let { file.writeBytes(it) }
        println("线程ID:${Thread.currentThread().id} 下载完成 切片[$startIndex,$endIndex] 文件保存成功:$filePath")
        return "success"
    }
}