You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android文件上传延迟:如何用HttpURLConnection实现即时多线程上传

Android文件上传优化方案(HttpURLConnection+多线程+本地缓存)

完全可以通过HttpURLConnection结合多线程解决你的问题,核心是把上传逻辑从UI线程剥离,避免阻塞导致的延迟,同时添加超时控制,失败时自动缓存到SQLite。

核心实现思路

  • 选中文件后立即启动子线程执行上传,绝不阻塞UI线程
  • 给HttpURLConnection设置10秒的连接超时与读取超时
  • 上传失败(超时/服务器不可达)时,将文件路径、上传状态等信息存入SQLite
  • 用线程池管理多文件上传,避免创建过多线程导致性能问题

代码示例

1. 上传管理类(UploadManager)

负责多线程上传、超时控制、失败缓存逻辑

import android.content.Context;
import java.io.File;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class UploadManager {
    private static final ExecutorService UPLOAD_POOL = Executors.newFixedThreadPool(3); // 限制并发线程数
    private static final int TIMEOUT = 10 * 1000; // 10秒超时
    private static final String UPLOAD_URL = "http://your-server-upload-api"; // 替换为你的服务器接口

    private final Context mContext;
    private final DbHelper mDbHelper;

    public UploadManager(Context context) {
        mContext = context.getApplicationContext();
        mDbHelper = new DbHelper(mContext);
    }

    // 单文件上传入口
    public void uploadFile(File file) {
        UPLOAD_POOL.execute(() -> {
            boolean uploadSuccess = doUpload(file);
            if (!uploadSuccess) {
                // 上传失败,存入SQLite
                mDbHelper.insertPendingFile(file.getAbsolutePath());
            }
        });
    }

    // 多文件上传入口
    public void uploadFiles(File[] files) {
        for (File file : files) {
            uploadFile(file);
        }
    }

    // 实际上传逻辑
    private boolean doUpload(File file) {
        HttpURLConnection conn = null;
        FileInputStream fis = null;
        OutputStream os = null;

        try {
            URL url = new URL(UPLOAD_URL);
            conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("POST");
            conn.setConnectTimeout(TIMEOUT);
            conn.setReadTimeout(TIMEOUT);
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setUseCaches(false);

            // 设置请求头(根据服务器要求调整)
            conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW");

            String boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW";
            os = conn.getOutputStream();

            // 构造表单数据(文件字段)
            StringBuilder sb = new StringBuilder();
            sb.append("--").append(boundary).append("\r\n");
            sb.append("Content-Disposition: form-data; name=\"file\"; filename=\"").append(file.getName()).append("\"\r\n");
            sb.append("Content-Type: application/octet-stream\r\n\r\n");
            os.write(sb.toString().getBytes());

            // 写入文件数据
            fis = new FileInputStream(file);
            byte[] buffer = new byte[1024 * 8];
            int len;
            while ((len = fis.read(buffer)) != -1) {
                os.write(buffer, 0, len);
            }
            os.write(("\r\n--" + boundary + "--\r\n").getBytes());
            os.flush();

            // 检查响应码
            int responseCode = conn.getResponseCode();
            return responseCode == HttpURLConnection.HTTP_OK;

        } catch (Exception e) {
            e.printStackTrace();
            return false; // 超时、网络异常等情况均返回失败
        } finally {
            try {
                if (fis != null) fis.close();
                if (os != null) os.close();
                if (conn != null) conn.disconnect();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

2. SQLite数据库辅助类(DbHelper)

负责存储失败的待上传文件信息

import android.content.ContentValues;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class DbHelper extends SQLiteOpenHelper {
    private static final String DB_NAME = "upload_cache.db";
    private static final int DB_VERSION = 1;
    private static final String TABLE_PENDING = "pending_uploads";
    private static final String COLUMN_PATH = "file_path";

    public DbHelper(Context context) {
        super(context, DB_NAME, null, DB_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        String createTableSql = "CREATE TABLE " + TABLE_PENDING + " (" +
                "id INTEGER PRIMARY KEY AUTOINCREMENT, " +
                COLUMN_PATH + " TEXT UNIQUE NOT NULL)";
        db.execSQL(createTableSql);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("DROP TABLE IF EXISTS " + TABLE_PENDING);
        onCreate(db);
    }

    // 插入待上传文件
    public void insertPendingFile(String filePath) {
        SQLiteDatabase db = getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put(COLUMN_PATH, filePath);
        db.insertWithOnConflict(TABLE_PENDING, null, values, SQLiteDatabase.CONFLICT_IGNORE);
        db.close();
    }

    // 可扩展:查询所有待上传文件,用于后续重试
    // public List<String> getPendingFiles() { ... }
}

3. 调用示例(在Activity/Fragment中)

选中文件后立即调用上传,无UI阻塞

// 假设在文件选择回调中
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == FILE_SELECT_REQUEST && resultCode == RESULT_OK) {
        // 获取选中的文件(单文件示例,多文件可同理处理)
        Uri uri = data.getData();
        File file = new File(getRealPathFromUri(uri)); // 需实现Uri转真实路径的方法

        // 立即启动上传
        UploadManager uploadManager = new UploadManager(this);
        uploadManager.uploadFile(file);
    }
}

// 辅助方法:Uri转文件真实路径(适配不同Android版本)
private String getRealPathFromUri(Uri uri) {
    // 实现逻辑略,可根据Android版本和文件来源(媒体库/文件管理器)处理
    // 示例代码可参考常规的Uri转路径实现
    return "";
}

关键注意事项

  • 权限:需在Manifest中添加网络权限<uses-permission android:name="android.permission.INTERNET"/>,以及文件读取权限(根据Android版本动态申请)
  • 线程池:使用固定大小线程池避免多文件上传时创建大量线程,推荐设置3-5个并发线程
  • 超时设置:必须同时设置setConnectTimeout和setReadTimeout,确保10秒内无响应则判定为失败
  • 文件重复处理:SQLite中使用UNIQUE约束避免重复插入同一文件
  • 重试逻辑:可扩展DbHelper添加查询待上传文件的方法,在App启动或网络恢复时自动重试上传

内容的提问来源于stack exchange,提问作者chris oojer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 09:35:22