以下是一个 Android 中使用多线程进行文件批量上传并支持暂停、续传、删除功能且同步数据库记录的示例代码:
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.Environment;
import android.util.Log;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MultiThreadBatchUploader {
private static final String TAG = "MultiThreadBatchUploader";
private static final int BUFFER_SIZE = 8192;
private String uploadUrl;
private List<UploadTaskInfo> tasksInfo;
private ExecutorService executorService;
private UploadDatabaseHelper databaseHelper;
public MultiThreadBatchUploader(Context context, String uploadUrl) {
this.uploadUrl = uploadUrl;
tasksInfo = new ArrayList<>();
executorService = Executors.newFixedThreadPool(3); // 可以根据实际情况调整线程数量
databaseHelper = new UploadDatabaseHelper(context);
}
public void addFileForUpload(File file) {
tasksInfo.add(new UploadTaskInfo(file));
databaseHelper.insertUploadTask(file.getAbsolutePath(), 0);
}
public void startUpload() {
for (UploadTaskInfo taskInfo : tasksInfo) {
executorService.execute(new UploadTask(taskInfo));
}
}
public void pauseUpload(int taskIndex) {
if (taskIndex >= 0 && taskIndex < tasksInfo.size()) {
tasksInfo.get(taskIndex).pause();
updateDatabaseUploadProgress(taskIndex, tasksInfo.get(taskIndex).getUploadedBytes());
}
}
public void resumeUpload(int taskIndex) {
if (taskIndex >= 0 && taskIndex < tasksInfo.size()) {
tasksInfo.get(taskIndex).resume();
executorService.execute(new UploadTask(tasksInfo.get(taskIndex)));
updateDatabaseUploadProgress(taskIndex, tasksInfo.get(taskIndex).getUploadedBytes());
}
}
public void deleteUpload(int taskIndex) {
if (taskIndex >= 0 && taskIndex < tasksInfo.size()) {
tasksInfo.get(taskIndex).cancel();
File file = tasksInfo.get(taskIndex).getFile();
databaseHelper.deleteUploadTask(file.getAbsolutePath());
tasksInfo.remove(taskIndex);
}
}
private class UploadTaskInfo {
private File file;
private long uploadedBytes;
private boolean paused;
private boolean canceled;
public UploadTaskInfo(File file) {
this.file = file;
uploadedBytes = readUploadProgress();
paused = false;
canceled = false;
}
private long readUploadProgress() {
Cursor cursor = databaseHelper.getUploadTask(file.getAbsolutePath());
if (cursor.moveToFirst()) {
return cursor.getLong(cursor.getColumnIndex(UploadDatabaseHelper.COLUMN_UPLOADED_BYTES));
}
return 0;
}
private void saveUploadProgress(long bytesUploaded) {
databaseHelper.updateUploadTask(file.getAbsolutePath(), bytesUploaded);
}
public void pause() {
paused = true;
}
public void resume() {
paused = false;
}
public void cancel() {
canceled = true;
}
public boolean isPaused() {
return paused;
}
public boolean isCanceled() {
return canceled;
}
public long getUploadedBytes() {
return uploadedBytes;
}
public File getFile() {
return file;
}
}
private class UploadTask implements Runnable {
private UploadTaskInfo taskInfo;
public UploadTask(UploadTaskInfo taskInfo) {
this.taskInfo = taskInfo;
}
@Override
public void run() {
if (taskInfo.isCanceled()) {
return;
}
try {
URL url = new URL(uploadUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setDoOutput(true);
long fileLength = taskInfo.getFile().length();
connection.setRequestProperty("Content-Range", "bytes " + taskInfo.getUploadedBytes() + "-" + (fileLength - 1) + "/" + fileLength);
OutputStream outputStream = connection.getOutputStream();
FileInputStream inputStream = new FileInputStream(taskInfo.getFile());
inputStream.skip(taskInfo.getUploadedBytes());
byte[] buffer = new byte[BUFFER_SIZE];
int bytesRead;
while ((bytesRead = inputStream.read(buffer))!= -1) {
if (taskInfo.isPaused()) {
taskInfo.saveUploadProgress(taskInfo.getUploadedBytes());
return;
}
outputStream.write(buffer, 0, bytesRead);
taskInfo.uploadedBytes += bytesRead;
}
inputStream.close();
outputStream.close();
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
Log.d(TAG, "Upload completed for file: " + taskInfo.getFile().getName());
taskInfo.getProgressFile().delete();
databaseHelper.deleteUploadTask(taskInfo.getFile().getAbsolutePath());
} else {
Log.e(TAG, "Upload failed for file: " + taskInfo.getFile().getName() + " with response code: " + responseCode);
}
} catch (IOException e) {
Log.e(TAG, "Upload error for file: " + taskInfo.getFile().getName() + " " + e.getMessage());
}
}
}
private static long bytesToLong(byte[] bytes) {
return ((long) bytes[0] << 56) |
((long) (bytes[1] & 0xff) << 48) |
((long) (bytes[2] & 0xff) << 40) |
((long) (bytes[3] & 0xff) << 32) |
((long) (bytes[4] & 0xff) << 24) |
((long) (bytes[5] & 0xff) << 16) |
((long) (bytes[6] & 0xff) << 8) |
((long) (bytes[7] & 0xff));
}
private static byte[] longToBytes(long value) {
return new byte[]{
(byte) (value >> 56),
(byte) (value >> 48),
(byte) (value >> 40),
(byte) (value >> 32),
(byte) (value >> 24),
(byte) (value >> 16),
(byte) (value >> 8),
(byte) value
};
}
private void updateDatabaseUploadProgress(int taskIndex, long bytesUploaded) {
File file = tasksInfo.get(taskIndex).getFile();
databaseHelper.updateUploadTask(file.getAbsolutePath(), bytesUploaded);
}
private static class UploadDatabaseHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "upload_database";
private static final int DATABASE_VERSION = 1;
private static final String TABLE_NAME = "upload_tasks";
private static final String COLUMN_FILE_PATH = "file_path";
private static final String COLUMN_UPLOADED_BYTES = "uploaded_bytes";
public UploadDatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
String createTableQuery = "CREATE TABLE " + TABLE_NAME + "(" +
COLUMN_FILE_PATH + " TEXT PRIMARY KEY," +
COLUMN_UPLOADED_BYTES + " INTEGER" +
")";
db.execSQL(createTableQuery);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// 可以根据需要处理数据库升级
}
public void insertUploadTask(String filePath, long uploadedBytes) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(COLUMN_FILE_PATH, filePath);
values.put(COLUMN_UPLOADED_BYTES, uploadedBytes);
db.insert(TABLE_NAME, null, values);
db.close();
}
public Cursor getUploadTask(String filePath) {
SQLiteDatabase db = this.getReadableDatabase();
String selectQuery = "SELECT * FROM " + TABLE_NAME + " WHERE " + COLUMN_FILE_PATH + " =?";
return db.rawQuery(selectQuery, new String[]{filePath});
}
public void updateUploadTask(String filePath, long uploadedBytes) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(COLUMN_UPLOADED_BYTES, uploadedBytes);
db.update(TABLE_NAME, values, COLUMN_FILE_PATH + " =?", new String[]{filePath});
db.close();
}
public void deleteUploadTask(String filePath) {
SQLiteDatabase db = this.getWritableDatabase();
db.delete(TABLE_NAME, COLUMN_FILE_PATH + " =?", new String[]{filePath});
db.close();
}
}
public static void main(String[] args) {
// 在 Android 应用中,应该从合适的上下文获取 Context 对象传递给构造函数
Context context = null; // 这里只是示例,实际应用中应该传入正确的上下文
MultiThreadBatchUploader uploader = new MultiThreadBatchUploader(context, "http://your-server-url.com/upload");
File file1 = new File(Environment.getExternalStorageDirectory(), "file1.txt");
File file2 = new File(Environment.getExternalStorageDirectory(), "file2.txt");
uploader.addFileForUpload(file1);
uploader.addFileForUpload(file2);
uploader.startUpload();
}
}
请注意以下几点:
- 在实际的 Android 应用中,应该从合适的上下文(如 Activity 或 Service)获取
Context
对象传递给MultiThreadBatchUploader
的构造函数。 - 这个示例代码仅为演示目的,实际应用中还需要处理更多的错误情况和优化。
- 确保在合适的时候关闭数据库连接以避免资源泄漏。