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
相关产品推荐
相关产品推荐

