android多线程上传大文件,android大文件上传

一、实现方式选型

需求:1. 支持多文件上传。2. 大文件上传

目前主流的做法有四种:

线程池

后台Service

前台Service

WorkManager

方案

执行速度

生命周期

特点

线程池

马上执行

很短

用来执行耗时操作,这是比较普遍的做法,但是它比较容易在后台被系统杀死。

后台Service

马上执行

后台服务实际上也要用到线程,只是它是一个独立的组件比较容易管理,但是同样也容易被高版本的系统杀死。

前台Service

马上执行

在通知栏通知用户在做什么操作,和应用生命周期绑定,比后台Service优先级高,但是不适合后台任务的场景。

WorkManager

延迟执行

很长

优势在于即使应用退出后依然可以执行后台任务,但任务可能会延迟执行。

WorkManager 的特性其实比较诱人,即使杀了应用依然能把后台任务进行到底,但对于可能上传很大文件的应用来说有些流氓了。。而且可能会延迟执行,不适合我们的场景。

前台Service不适合我们后台上传的场景。

线程池生命周期太短,对于这种持续执行I/O操作的场景还是需要更稳定的方式。

后台Service相对来说是比较好的方案,但同样有缺点,就是在8.0以上的系统很容易被杀死,而且系统内存不足的情况下也会优先被杀死。所以需要做好重启Service,有必要的话还可以重启被中断的任务。

选完核心的组件后需要确定如何实现大文件上传,这方面其实并不需要纠结,基本就是切片上传或长连接上传,这里采取的是切片上传的方案。

最后对于多文件上传的处理:

并发上传。

串行上传。

这里选择了串行上传,一个文件上传完后再上传下一个文件。不选择并发上传的原因是即使多线程上传,线程大部分时间还是在处理I/O问题,每个文件分到的带宽会减少,和单一的按顺序上传文件应该相差不大。而且多文件可能也会给服务端带来压力。最后多线程并发也会增加上传完成后回调处理的复杂度。

最后确定的方案是 后台Service+切片上传+串行上传。

二、串行任务队列

由于需要串行上传,因此需要实现串行队列,其实 java 库已经有类似的类 LinkedBlockingQueue,不过我依然需要一些定制的 api 来实现一些功能,所以直接自己简单实现了一个任务队列,先明确下这个队列的特点:

当一个任务在执行时,下一个任务在队列中等待。

当队列中没任务时,子线程一直在等待,直到队列内添加了新任务。

不能重复添加同一个任务。

实现以上功能需要用到锁相关的知识,这里用的是 ReentrantLock 可重入锁:

private val thread: Thread

private var queue = CopyOnWriteArrayList()

private

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android上传文件时,我们可以使用流式上传的方式,即通过分块上传的方式将大文件分为多个小块进行上传。具体实现步骤如下: 1. 添加 OkHttp 库依赖:在 app 的 build.gradle 文件中添加以下代码: ```gradle dependencies { implementation 'com.squareup.okhttp3:okhttp:4.9.0' } ``` 2. 创建一个 RequestBody 对象,该对象包含文件内容。 ```java File file = new File(filePath); RequestBody requestBody = RequestBody.create(MediaType.parse("application/octet-stream"), file); ``` 3. 创建一个 Request 对象,设置上传文件的 URL 和其他参数。注意,这里不需要设置 Content-Length 头部,因为我们将文件分块上传。 ```java Request request = new Request.Builder() .url(uploadUrl) .post(requestBody) .build(); ``` 4. 创建一个 OkHttpClient 对象,并使用它发送请求。在发送请求之前,我们需要通过设置 Interceptor 来实现分块上传。具体来说,我们需要在请求头部中添加 Content-Type 和 Content-Range 头部,以及在请求体中添加文件的小块内容。 ```java OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { Request originalRequest = chain.request(); Response originalResponse = chain.proceed(originalRequest); if (originalResponse.code() != 308) { return originalResponse; } // 获取已上传的字节数 long uploadedBytes = originalResponse.body().contentLength(); while (true) { // 读取下一块文件内容 byte[] buffer = new byte[1024 * 1024]; int bytesRead = inputStream.read(buffer); if (bytesRead == -1) { break; } // 计算本次上传的字节数和总字节数 long totalBytes = file.length(); long numBytes = bytesRead; long nextUploadedBytes = uploadedBytes + numBytes; // 构造本次上传的请求 Request request = originalRequest.newBuilder() .header("Content-Type", "application/octet-stream") .header("Content-Range", "bytes " + uploadedBytes + "-" + (nextUploadedBytes - 1) + "/" + totalBytes) .method("POST", new RequestBody() { @Override public MediaType contentType() { return MediaType.parse("application/octet-stream"); } @Override public long contentLength() { return numBytes; } @Override public void writeTo(BufferedSink sink) throws IOException { sink.write(buffer, 0, bytesRead); } }) .build(); // 发送本次上传的请求 Response response = chain.proceed(request); if (response.code() != 308) { return response; } // 更新已上传的字节数 uploadedBytes = nextUploadedBytes; } // 所有块上传完成后,返回最终的响应 return originalResponse.newBuilder() .body(ResponseBody.create(null, new byte[0])) .build(); } }) .build(); Response response = client.newCall(request).execute(); ``` 在上面的代码中,我们首先发送一个空请求,得到服务器返回的 308 状态码和已上传的字节数。然后,我们通过循环读取文件内容,并构造包含文件小块内容的请求来分块上传文件。每个请求包含 Content-Type 和 Content-Range 头部,Content-Range 头部指示本次上传的字节范围。最后,我们将所有块上传完成后,发送一个空请求来告诉服务器上传已完成。 注意,上面的代码中使用了一个 inputStream 对象来读取文件内容,这个对象需要在外部进行初始化。具体来说,我们可以在一个线程中读取文件内容,并将 inputStream 对象传递给分块上传的代码块。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值