Android大文件上传秒传之实战篇

2023-11-04


源码传送门


在上一篇文章我们介绍了获取大文件的一个唯一的特征值MD5,通过MD5我们可以唯一的标识一个文件,并可以实现秒传效果,今天的这篇文章主要介绍大文件的上传操作,当然谈到上传文件,网络是必不可少的,现在也有很多较为流行的网络框架,如volley,OkHttp,Retrofit。而今天的这篇文章是采用最原始的上传文件的方法,通过HttpClient上传文件的方式。

HttpClient API

在API 23(6.0系统)之前,HttpClient类是Android API中本身自带的方法,但是在23及以后的版本中谷歌放弃了HttpClient,如果想要使用需要在gradle文件中加上下面代码

android {
    useLibrary 'org.apache.http.legacy'
    }

加入上面的代码后,我们build一下就可以API23及以后版本中可以继续使用HttpClient,在使用HttpClient上传文件时可以使用MultipartEntity,FileBody,要使用这个类对象的话,我们需要导入相关jar包,在此我使用的是httpmine-4.1.3.jar。可能有些人说了,为何废弃了,还要用,不要问为什么,因为我也不知道,哈哈,其实是懒,主要是公司老项目用的是这个,还没准备大动,所以就在这基础上做的。当然后期肯定要使用最新最流行的的技术,暂时未考虑(写文章的时候正在学习Retrofit+RxJava,也学的已经差不多了,入了门道,准备开刀)。

Demo运行图

这里写图片描述

文件上传分析

在分析文件分块上传之前我们先来介绍如何直接上传单个文件。在Android中的apache包中有一个HttpClient的默认实现类DefaultHttpClient,在上传的时候我们需要指定上传方式如是GET,POST等请求方式,而在apache包中提供了了对应的HttpPost,HttpGet.在这里我们使用POST请求。如下代码

        MultipartEntity mpEntity=new MultipartEntity();
        try {
            mpEntity.addPart("md5", new StringBody(chunkInfo.getMd5()));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        FileBody fileBody = new FileBody(new File(chunkInfo.getFilePath()));
        mpEntity.addPart("file", fileBody);
        HttpPost post = new HttpPost(actionUrl);
        // 发送请求体
        post.setEntity(mpEntity);
        DefaultHttpClient dhc = new DefaultHttpClient();
        try {
            dhc.getParams().setParameter(
                    CoreConnectionPNames.CONNECTION_TIMEOUT, 10000);
            HttpResponse response = dhc.execute(post);
            int res = response.getStatusLine().getStatusCode();
            Log.e("图片上传返回响应码", res + ",");
            switch (res) {
                case 200:
                    //流形式获得
                    StringBuilder builder = new StringBuilder();
                    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
                    for (String s = bufferedReader.readLine(); s != null; s = bufferedReader.readLine()) {
                        builder.append(s);
                    }
                    retMsg = builder.toString();
                    break;
                case 404:
                    retMsg = "-1";
                    break;
                default:
                    retMsg = "500";
            }

        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

很简单,通过MultipartEntity,FileBody就可以实现文件上传了。上面的代码很简单,当然如果想展示上传进度的话,我们只需要写个类继承FilterOutputStream,就可以自己写个监听回调展示进度,然后再发个广播更新UI,详细代码不贴了,可点击一键直达查看。

在上传整个文件的时候我们看到主要用到的是FileBody,那么我们就可以从这个地方入手,实现文件分块上传。通过源码写文件主要是通过writeTo()方法实现的

    /** @deprecated */
    @Deprecated
    public void writeTo(OutputStream out, int mode) throws IOException {
        this.writeTo(out);
    }

    public void writeTo(OutputStream out) throws IOException {
        if(out == null) {
            throw new IllegalArgumentException("Output stream may not be null");
        } else {
            FileInputStream in = new FileInputStream(this.file);

            try {
                byte[] tmp = new byte[4096];

                int l;
                while((l = in.read(tmp)) != -1) {
                    out.write(tmp, 0, l);
                }

                out.flush();
            } finally {
                in.close();
            }
        }
    }

看到writeTo方法的具体实现后你就知道了,通过while((l = in.read(tmp)) != -1)判断并循环读取文件到输出流。那么既然我们是讲文件分块上传,我们可以读取文件的一部分就可以了这样就可以实现分块上传了。

文件分块分析

对于文件的从指定位置读取指定大小数据,我用了RandomAccessFile对文件随机读取,通过seek()方法指定读取的起始位置
假如我们我们的文件是长度大小fileLength,我们将分块大小是chunkLength.那么我们分块数量计算为

int chunks=(int)(fileLength/chunkLength+(fileLength%chunkLength>0?1:0));

这样我们就计算了分块总数,则我们可以计算我们每一次上传的块的起始位置如下

offset=chunk*chunkLength;//我们服务器将第一块为0块,如果你的服务接口设的是从1开始,那就是offset就为(chunk-1)*chunkLength;

计算出了offset,我们上传每一块只需要执行代码randomAccessFile.seek(chunk*chunkLength);即可,然后读取chunkLength长度的数据。
好了,代码来了

自定义FileBody

/**
 * Created by xiehui on 2016/10/13.
 */
public class CustomFileBody extends AbstractContentBody {
    private File file = null;
    private int chunk = 0;  //第几个分片
    private int chunks = 1;  //总分片数
    private int chunkLength = 1024 * 1024 * 1; //分片大小1MB
    public CustomFileBody(File file) {
        this(file, "application/octet-stream");
    }
    public CustomFileBody(ChunkInfo chunkInfo) {
        this(new File(chunkInfo.getFilePath()), "application/octet-stream");
        this.chunk = chunkInfo.getChunk();
        this.chunks = chunkInfo.getChunks();
        this.file = new File(chunkInfo.getFilePath());
        if (this.chunk == this.chunks) {
            //先不判断,固定1M
            //this.chunkLength=this.file.length()-(this)
        }
    }
    public CustomFileBody(File file, String mimeType) {
        super(mimeType);
        if (file == null) {
            throw new IllegalArgumentException("File may not be null");
        } else {
            this.file = file;
        }
    }
    @Override
    public String getFilename() {
        return this.file.getName();
    }

    @Override
    public String getCharset() {
        return null;
    }

    public InputStream getInputStream() throws IOException {
        return new FileInputStream(this.file);
    }

    @Override
    public String getTransferEncoding() {
        return "binary";
    }

    @Override
    public long getContentLength() {
        return chunkLength;
    }

    @Override
    public void writeTo(OutputStream out) throws IOException {
        if (out == null) {
            throw new IllegalArgumentException("Output stream may not be null");
        } else {
            //不使用FileInputStream
            RandomAccessFile randomAccessFile = new RandomAccessFile(this.file, "r");
            try {
                //int size = 1024 * 1;//1KB缓冲区读取数据
                byte[] tmp = new byte[1024];
                //randomAccessFile.seek(chunk * chunkLength);
                if (chunk+1<chunks){//中间分片
                    randomAccessFile.seek(chunk*chunkLength);
                    int n = 0;
                    long readLength = 0;//记录已读字节数
                    while (readLength <= chunkLength - 1024) {
                        n = randomAccessFile.read(tmp, 0, 1024);
                        readLength += 1024;
                        out.write(tmp, 0, n);
                    }
                    if (readLength <= chunkLength) {
                        n = randomAccessFile.read(tmp, 0, (int)(chunkLength - readLength));
                        out.write(tmp, 0, n);
                    }
                }else{
                    randomAccessFile.seek(chunk*chunkLength);
                    int n = 0;
                    while ((n = randomAccessFile.read(tmp, 0, 1024)) != -1) {
                        out.write(tmp, 0, n);
                    }
                }
                out.flush();
            } finally {
                randomAccessFile.close();
            }
        }
    }

    public File getFile() {
        return this.file;
    }
}

文件分块上传模型类ChunkInfo

 * Created by xiehui on 2016/10/21.
 */
public class ChunkInfo  extends FileInfo implements Serializable{
    /**
     * 文件的当前分片值
     */
    private int chunk=1;
    /**
     * 文件总分片值
     */
    private int chunks=1;
    /**
     * 下载进度值
     */
    private int progress=1;

    public int getChunks() {
        return chunks;
    }

    public void setChunks(int chunks) {
        this.chunks = chunks;
    }

    public int getChunk() {
        return chunk;
    }

    public void setChunk(int chunk) {
        this.chunk = chunk;
    }

    public int getProgress() {
        return progress;
    }

    public void setProgress(int progress) {
        this.progress = progress;
    }

    @Override
    public String toString() {
        return "ChunkInfo{" +
                "chunk=" + chunk +
                ", chunks=" + chunks +
                ", progress=" + progress +
                '}';
    }
}

具体上传实现

 public String uploadFile() {
        String retMsg = "1";
        CustomMultipartEntity mpEntity = new CustomMultipartEntity(
                new CustomMultipartEntity.ProgressListener() {
                    @Override
                    public void transferred(long num) {
                        Intent intent2 = new Intent();
                        ChunkInfo chunkIntent = new ChunkInfo();
                        chunkIntent.setChunks(chunkInfo.getChunks());
                        chunkIntent.setChunk(chunkInfo.getChunk());
                        chunkIntent.setProgress((int) num);
                        intent2.putExtra("chunkIntent", chunkIntent);
                        intent2.setAction("ACTION_UPDATE");
                        context.sendBroadcast(intent2);
                    }
                });
        try {
            mpEntity.addPart("chunk", new StringBody(chunkInfo.getChunk() + ""));
            mpEntity.addPart("chunks", new StringBody(chunkInfo.getChunks() + ""));
             mpEntity.addPart("fileLength", new StringBody(chunkInfo.getFileLength()));
            mpEntity.addPart("md5", new StringBody(chunkInfo.getMd5()));

        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        CustomFileBody customFileBody = new CustomFileBody(chunkInfo);
        mpEntity.addPart("file", customFileBody);
        HttpPost post = new HttpPost(actionUrl);
        // 发送请求体
        post.setEntity(mpEntity);
        DefaultHttpClient dhc = new DefaultHttpClient();
        try {
            dhc.getParams().setParameter(
                    CoreConnectionPNames.CONNECTION_TIMEOUT, 10000);
            HttpResponse response = dhc.execute(post);
            int res = response.getStatusLine().getStatusCode();
            switch (res) {
                case 200:
                    //流形式获得
                    StringBuilder builder = new StringBuilder();
                    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
                    for (String s = bufferedReader.readLine(); s != null; s = bufferedReader.readLine()) {
                        builder.append(s);
                    }
                    retMsg = builder.toString();
                    break;
                case 404:
                    retMsg = "-1";
                    break;
                default:
                    retMsg = "500";
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return retMsg;

    }

到此文件分块上传已基本完毕。那么此时你可能会问秒传的实现在哪了呢?别激动,在前面的分析中我们上传的参数有一个是md5,我们上传文件后将此值保存在数据库,以及图片的url链接,那么当我们上传文件之前先通过这个调用一个接口并上传参数md5,服务接口查询数据库是否有此md5的文件,如果有的话,直接将图片url返回即可,此时就提示用户文件上传成功,如果数据库没有此md5文件,则上传文件。

接口延伸

由于客户端上传的是文件块,当最后一块上传完成后,如果接口是每一分块保存了一个临时文件,则需要对分块的文件进行合并及删除。这个服务器FileChannel进行进行读写,当然也可以使用RandomAccessFile,因为我们上传了文件的总大小,则接口接收到分块文件时直接创建一个文件并调用randomAccessFile.setLength();方法设置长度,之后通过上传的seek方法在指定位置写入数据到文件即可。

到此,本篇文章真的结束了,若文章有不足或者错误的地方,欢迎指正,以防止给其他读者错误引导

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android大文件上传秒传之实战篇 的相关文章

  • Firebase 管理 SDK Android

    在 Android 中初始化 Firebase Admin SDK 的代码 Override protected void onCreate Bundle savedInstanceState super onCreate savedIns
  • 检测到设备正在振动?

    我使用下面的代码来振动设备 public void vibrator try Vibrator vibrator Vibrator getSystemService Context VIBRATOR SERVICE vibrator vib
  • 从 Throwable 获取错误代码 - Android

    我怎样才能从错误代码可投掷 https developer android com reference java lang Throwable html public void onFailure Throwable exception 我
  • Android第一次动画不流畅

    我正在尝试一个动画将 imageView 从屏幕底部滑动到屏幕中心 但是当我第一次执行此动画时 它不平滑 但当第二次执行动画时 它是正常且平滑的 我几乎尝试了所有方法 但无法解决我的问题 这是我的动画文件
  • Firebase Analytics 禁用受众国家/地区跟踪

    我正在开发一个严格不允许位置跟踪的应用程序 我想使用 Firebase Analytic 的其他功能 例如 PageTransitions 和 Crashalitics 但如果我无法禁用受众位置跟踪 我就无法使用其中任何功能 这是我在 An
  • Delphi XE7 Android 全屏(隐藏软键)

    如何在XE7中全屏显示 隐藏顶部 标题 和底部 软键 工具栏 在 XE6 中 我可以通过在应用程序部分写入来调整 AndroidManifest 以使我的应用程序全屏显示并且没有操作栏 android theme android style
  • 无法在自定义 AOSP 上安装 Google Play 中的某些应用程序:项目不可用。理由:9

    我在尝试从 Google Play 安装某些应用程序时收到以下错误 LibraryUtils isAvailable not available restriction 9 DocUtils getAvailabilityRestricti
  • 已经使用 AsyncTask doInBackground 但新数据未显示

    我使用 AsyncTask 创建一个聊天室来接收消息 因此它总是检查即将到来的消息并将其显示给客户端 但代码似乎无法按我希望的方式工作 在客户端只显示所有旧数据 新数据不显示 因为当我尝试从服务器发送消息时 新数据没有显示在客户端中 我对这
  • TextView 之间有分隔线

    我正在尝试在 android studio 中创建以下布局 因为我对 android 东西还很陌生 所以我第一次尝试使用 LinearLayout 并认为这可能无法实现 现在我正在尝试使用RelativeLayout 我已经用颜色创建了这个
  • 更新到材质 1.2.0 后,材质按钮上缺少圆角半径属性

    这是我的材质按钮代码
  • 带有自定义阵列适配器的微调器不允许选择项目

    我使用自定义阵列适配器作为微调器 但是 当在下拉列表中选择一个项目时 下拉列表保留在那里 并且微调器不会更新 这是错误行为 与使用带有字符串的通用数组适配器相比 这是自定义类 我错过了什么吗 谢谢 public class Calendar
  • ROOM迁移过程中如何处理索引信息

    CODE Entity tableName UserRepo indices Index value id unique true public class GitHubRepo PrimaryKey autoGenerate true p
  • 使用 Matrix.setPolyToPoly 选择位图上具有 4 个点的区域

    我正在 Android 上使用位图 在使用 4 个点选择位图上的区域时遇到问题 并非所有 4 点组都适合我 在某些情况下 结果只是一个空白位图 而不是裁剪后的位图 如图所示 并且 logcat 中没有任何错误 甚至是内存错误 这是我用来进行
  • 如何在不更改手机语言的情况下更改Android应用程序语言?

    我希望用户在应用程序内选择一种语言 选择语言后 我希望字符串使用特定语言 如果我更改手机语言 那么我的应用程序将以设置的语言运行 我无法找到任何在不更改手机语言的情况下设置语言的方法 此外 一旦设置了语言 更改就应该反映出来 有人可以建议一
  • Android Webview 图像未加载

    我制作了一个简单的应用程序WebView 但有些图片无法加载 正确 在我的电脑上 错误 在模拟器中 Correct 错误 没有横幅 于是我用Chrome debug进行调试 发现我的代码被改变了 我不添加像noscript or style
  • Android 设备上的静默安装

    我已经接受了一段时间了 在 Android 上静默安装应用程序是不可能的 也就是说 让程序安装捆绑为 APK 的应用程序 而不提供标准操作系统安装提示并完成应用程序安装程序活动 但现在我已经拿到了 Appbrain 快速网络安装程序的副本
  • Android中webview的截图方法

    我在 webview 中的 html5 canvas 上画了一些线 并尝试使用下面的代码截取 webview 的屏幕截图 WebView webView WebView findViewById R id webview webView s
  • SharedFlow 和 StateFlow 的主要区别

    两者有什么区别共享流 and 状态流 以及如何使用这些MVI建筑学 使用简单更好吗Flow或者这些作为状态和事件 Flow 是冷的 意味着它仅在收集数据时才发出数据 另外Flow不能保存数据 可以把它看成是水在里面流动的管道 Flow中的数
  • Android 如何聚焦当前位置

    您好 我有一个 Android 应用程序 可以在谷歌地图上找到您的位置 但是当我启动该应用程序时 它从非洲开始 而不是在我当前的城市 国家 位置等 我已经在developer android com上检查了信息与位置问题有关 但问题仍然存在
  • 在 Android 中,如何将字符串从 Activity 传递到 Service?

    任何人都可以告诉如何将字符串或整数从活动传递到服务 我试图传递一个整数 setpossition 4 但它不需要 启动时总是需要 0 Service 我不知道为什么我不能通过使用 Service 实例从 Activity 进行操作 publ

随机推荐

  • Matlab快速入门——逻辑与流程控制

    1 if else end A rand 1 10 limit 0 5 B A gt limit if any B fprintf Indices of values gt 4 2f n limit disp find B else dis
  • 视频分辨率无损放大软件 Topaz Video Enhance AI 2.3.0

    视频分辨率无损放大软件 Topaz Video Enhance AI 2 3 0 Topaz Video Enhance AI是一款非常好用的视频分辨率放大软件 用户可以通过这款软件将视频的分辨率进行自定义调节 最高能够将其放大至8K分辨率
  • python导入标准库的关键字是,导入整个Python标准库

    我需要一种方法将整个Python标准库导入我的程序 虽然这看起来似乎是一个坏主意 但我想这样做是因为py2exe会将整个标准库与我的程序打包在一起 所以我的用户可以在我给它们的shell中从中导入它 是否有捷径可寻 加分点 我希望这个动作不
  • NSX-T 问题一(亚信无代理杀毒部署期间,主机传输节点无法安装问题)

    项目场景 某金融客户部署亚信无代理杀毒期间出现了NSX T部署问题 之前由于未查询兼容性列表 使用了NSX T 3 2版本 与亚信厂商沟通后 更改为使用NSX T 3 1 3 5版本 问题描述 部署期间在安装主机传输节点处出现NSX 安装失
  • 【华为OD机试python】称砝码【 2023 Q1 A卷

    题目描述 现有n种砝码 重量互不相等 分别为 m1 m2 m3 mn 每种砝码对应的数量为 x1 x2 x3 xn 现在要用这些砝码去称物体的重量 放在同一侧 问能称出多少种不同的重量 注 称重重量包括 0 数据范围 每组输入数据满足 1
  • Java多线程通信-Semaphore(信号量)

    一 semaphone 信号量 Semaphone 信号量 是一个同步工具类 用来控制同时访问某个资源的线程数量 还可以用来实现某些资源池 或者给容器添加边界 Semaphone管理着一组 虚拟 的许可 permit 许可的初始数量可通过构
  • C++中的多态——理解虚函数表及多态实现原理

    多态及其实现原理 一 多态的概念 概念 构成条件 二 虚函数的重写 重写的定义 重写的特殊情况 override和final关键字 区分重写 重载 重定义 抽象类的概念 三 多态的实现原理 父类对象模型 补充 生成默认构造方法的场景 子类对
  • Kubenetes 集群Master与Node节点

    Master节点 Kubernetes里的Master指的是集群控制节点 在每个Kubernetes集群里都需要有一个Master来负责整个集群的管理和控制 基本上Kubernetes的所有控制命令都发给它 它负责具体的执行过程 我们后面执
  • Python所有方向的学习路线图,让Python初学者少走弯路

    举个例子 如果你要学习爬虫 那么你就去学Python爬虫学习路线图上面的知识点 这样学下来之后 你的知识体系是比较全面的 比起在网上找到什么就学什么 容易造成重复学 有时候也会学到一些用处不大的东西 还有一点就是 有了学习路线图 你就能够明
  • Vue 并排放置两个div的写法

    Vue的开发文档 Layout布局模块 https element eleme cn zh CN component select 样例 使用
  • 华为OD机试 - 等和子数组最小和(Java)

    题目描述 给定一个数组nums 将元素分为若干个组 使得每组和相等 求出满足条件的所有分组中 组内元素和的最小值 输入描述 第一行输入 m 接着输入m个数 表示此数组nums 数据范围 1 lt m lt 50 1 lt nums i lt
  • MySQL高级学习笔记

    目录 1 MySQL数据库逻辑架构 1 网络连接层 2 服务层 MySQL Server 1 连接池 Connection Pool 2 系统管理和控制工具 Management Services Utilities 3 SQL接口 SQL
  • 信息系统、课设、毕设

    信息系统 课设 毕设 可使用技术 后端 PHP Node js等 前端 jQuery vue等 UI layui elementui等 数据库 SQLserver MySQL等 APP uniapp等 适合 进销存ERP系统 商城 网站 A
  • mac卸载idea

    idea只有30天的试用期 比较穷 买不起正版 又不想用破解 是因为发现破解的版本写代码的时候反应好迟钝 一个报红的问题修复后半天还不好 到期了只能完全卸载重新装一遍 这样也还有个好处 能用最新版 一直在追求最新版的路上 给像我一样的强迫症
  • C语言:格式化输入输出函数

    C语言 格式化输入输出函数 1 格式化输出函数 printf 2 格式化输入函数 scanf C语言提供的格式化输入输出函数的原型在头文件stdio h中声明 在使用时应在程序头部包含该文件 include
  • 安卓Unity3D Camera图像和音频采集推送代码

    安卓Unity3d 可以使用ReadPixels从当前Render Target读取图像 音频可以从AudioClip读取 具体调用GetData接口 读取到的可能是float类型 有些音频编码器可能需要sint16格式 这需要做一个转换
  • 数据处理方法:归一化与标准化处理

    在数据挖掘中 在建模前需要对数据进行预处理 预处理方法包括归一化与标准化 对数据进行缩放 1 归一化 Normalization 将数据缩放到0 1之间 线性 常用 归一化 最大最小值归一化 y x
  • mysql jdbc配置重连_Spring Boot 配置MySQL数据库重连的操作方法

    使用jdbc连接MySQL 如果连接失效 可能会报类似的错误 com mysql jdbc exceptions jdbc4 CommunicationsException The last packet successfully rece
  • 预训练模型--GPT

    why预训练 finetune 目前在nlp领域 比较流行的一种方式就是 pretrain finetune 为什么是这种模式呢 在nlp领域大量数据是无标签的 只有小量数据是有标签的 而大量数据可以帮助模型获得更好的效果 泛化能力 所以在
  • Android大文件上传秒传之实战篇

    源码传送门 在上一篇文章我们介绍了获取大文件的一个唯一的特征值MD5 通过MD5我们可以唯一的标识一个文件 并可以实现秒传效果 今天的这篇文章主要介绍大文件的上传操作 当然谈到上传文件 网络是必不可少的 现在也有很多较为流行的网络框架 如v