凌峰创科服务平台

android 上传文件 服务器

核心概念

  1. HTTP 协议: 文件上传本质上是使用 HTTP 协议的 POST 请求,有两种主要方式:

    android 上传文件 服务器-图1
    (图片来源网络,侵删)
    • multipart/form-data: 这是最常用、最标准的方式,它允许你在一个请求中发送多种类型的数据(比如文本字段和文件),文件内容会被编码后放在请求体中。
    • application/octet-stream: 这种方式相对简单,直接将文件二进制流作为请求体发送,但它通常只能上传一个文件,并且不能同时发送额外的文本参数。
  2. 权限: Android 应用需要声明权限才能访问文件系统。

    • android.permission.READ_EXTERNAL_STORAGE: 读取外部存储(如手机相册、下载文件夹)中的文件。
    • android.permission.WRITE_EXTERNAL_STORAGE: 写入外部存储,如果需要将文件保存到外部存储,则需要此权限。
    • 注意: 从 Android 6.0 (API 23) 开始,READ_EXTERNAL_STORAGE运行时权限,必须在代码中动态请求,从 Android 11 (API 30) 开始,管理文件变得更加严格,推荐使用 MediaStore API 来访问媒体文件。

使用 HttpURLConnection (原生 Java API)

这是 Android SDK 自带的方式,无需第三方库,适合简单的上传需求。

Android 客户端实现

步骤:

  1. 添加网络权限 (AndroidManifest.xml):

    android 上传文件 服务器-图2
    (图片来源网络,侵删)
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
  2. 处理运行时权限 (在 Activity 或 Fragment 中):

    // 在需要上传文件的地方,例如按钮点击事件中
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
            != PackageManager.PERMISSION_GRANTED) {
        // 请求权限
        ActivityCompat.requestPermissions(this,
                new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
                REQUEST_CODE_READ_STORAGE);
    } else {
        // 权限已授予,开始上传
        uploadFile();
    }
  3. 编写上传方法:

    public void uploadFile() {
        // 1. 选择文件 (这里以一个示例文件路径为例)
        // 实际应用中,你应该通过 Intent 调用系统文件选择器
        String filePath = Environment.getExternalStorageDirectory().getPath() + "/my_image.jpg";
        File file = new File(filePath);
        if (!file.exists()) {
            Toast.makeText(this, "文件不存在", Toast.LENGTH_SHORT).show();
            return;
        }
        // 2. 创建请求体
        RequestBody requestBody = new MultipartBody.Builder()
                .setType(MultipartBody.FORM)
                .addFormDataPart("file", file.getName(), // "file" 是服务器端接收的字段名
                        RequestBody.create(MediaType.parse("image/jpeg"), file))
                .addFormDataPart("description", "This is a test upload") // 可以添加其他文本参数
                .build();
        // 3. 创建请求
        Request request = new Request.Builder()
                .url("https://your.server.com/upload") // 替换为你的服务器地址
                .post(requestBody)
                .build();
        // 4. 发起异步请求
        OkHttpClient client = new OkHttpClient();
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                // 请求失败
                runOnUiThread(() -> Toast.makeText(MainActivity.this, "上传失败: " + e.getMessage(), Toast.LENGTH_SHORT).show());
            }
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                // 请求成功
                String responseData = response.body().string();
                runOnUiThread(() -> {
                    Toast.makeText(MainActivity.this, "上传成功: " + responseData, Toast.LENGTH_SHORT).show();
                });
            }
        });
    }

服务器端接收 (以 Java Spring Boot 为例)

// Spring Boot Controller
@RestController
@RequestMapping("/api")
public class FileUploadController {
    @PostMapping("/upload")
    public ResponseEntity<String> handleFileUpload(
            @RequestParam("file") MultipartFile file,
            @RequestParam("description") String description) {
        if (file.isEmpty()) {
            return ResponseEntity.badRequest().body("请选择一个文件");
        }
        try {
            // 保存文件到服务器指定目录
            String serverPath = "/var/www/uploads/" + file.getOriginalFilename();
            Files.copy(file.getInputStream(), Paths.get(serverPath), StandardCopyOption.REPLACE_EXISTING);
            // 返回成功响应
            return ResponseEntity.ok("文件上传成功: " + file.getOriginalFilename() + ", 描述: " + description);
        } catch (IOException e) {
            e.printStackTrace();
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("文件上传失败");
        }
    }
}

使用 OkHttp + Retrofit (强烈推荐)

对于生产环境,使用 Retrofit 是更好的选择,它将网络请求接口化,代码更简洁、易维护。

添加依赖 (app/build.gradle)

// OkHttp 和 Retrofit
implementation 'com.squareup.okhttp3:okhttp:4.9.3'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0' // 用于解析 JSON 响应
// 图片加载库 (可选,但强烈推荐)
implementation 'com.github.bumptech.glide:glide:4.12.0'

定义 API 接口

import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import retrofit2.Call;
import retrofit2.http.Multipart;
import retrofit2.http.POST;
import retrofit2.Part;
public interface ApiService {
    // @Multipart 表示这是一个 multipart/form-data 请求
    // @POST 指定请求方法为 POST
    // @Part 用于标记要上传的每个部分
    @Multipart
    @POST("upload") // 你的 API 路径
    Call<UploadResponse> uploadFile(
            @Part("file") RequestBody file, // "file" 是服务器字段名
            @Part("description") RequestBody description // 其他文本参数
    );
}

创建 Retrofit 实例

public class RetrofitClient {
    private static final String BASE_URL = "https://your.server.com/api/"; // 基础 URL
    private static Retrofit retrofit = null;
    public static Retrofit getClient() {
        if (retrofit == null) {
            OkHttpClient okHttpClient = new OkHttpClient.Builder()
                    .connectTimeout(60, TimeUnit.SECONDS)
                    .writeTimeout(60, TimeUnit.SECONDS)
                    .readTimeout(60, TimeUnit.SECONDS)
                    .build();
            retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .client(okHttpClient)
                    .addConverterFactory(GsonConverterFactory.create()) // 添加 JSON 转换器
                    .build();
        }
        return retrofit;
    }
}

在 Activity/ViewModel 中调用

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button uploadButton = findViewById(R.id.upload_button);
        uploadButton.setOnClickListener(v -> startUpload());
    }
    private void startUpload() {
        // 1. 获取文件
        String filePath = Environment.getExternalStorageDirectory().getPath() + "/my_image.jpg";
        File file = new File(filePath);
        // 2. 创建 RequestBody
        RequestBody fileBody = RequestBody.create(MediaType.parse("image/*"), file);
        RequestBody descriptionBody = RequestBody.create(MediaType.parse("text/plain"), "This is a test");
        // 3. 创建 API Service 实例
        ApiService apiService = RetrofitClient.getClient().create(ApiService.class);
        // 4. 发起请求
        Call<UploadResponse> call = apiService.uploadFile(fileBody, descriptionBody);
        call.enqueue(new Callback<UploadResponse>() {
            @Override
            public void onResponse(Call<UploadResponse> call, Response<UploadResponse> response) {
                if (response.isSuccessful()) {
                    UploadResponse uploadResponse = response.body();
                    Log.d("Upload", "Success: " + uploadResponse.getMessage());
                    Toast.makeText(MainActivity.this, "上传成功", Toast.LENGTH_SHORT).show();
                } else {
                    Log.e("Upload", "Error: " + response.code());
                    Toast.makeText(MainActivity.this, "上传失败: " + response.code(), Toast.LENGTH_SHORT).show();
                }
            }
            @Override
            public void onFailure(Call<UploadResponse> call, Throwable t) {
                Log.e("Upload", "Failure: " + t.getMessage());
                Toast.makeText(MainActivity.this, "上传失败: " + t.getMessage(), Toast.LENGTH_SHORT).show();
            }
        });
    }
}

最佳实践与注意事项

  1. 后台任务: 文件上传可能很耗时,绝对不能在主线程(UI线程)中执行,必须使用异步任务、线程池或协程,上面的例子中,Retrofit 的 enqueue 方法已经帮我们处理了这一点。

  2. 进度显示: 良好的用户体验需要显示上传进度,OkHttp 3.0+ 提供了 ProgressResponseBody 来实现这个功能。

    • 你需要创建一个继承自 ResponseBody 的类,在其中拦截 source() 方法,计算已读取的字节数。
    • 然后创建一个 OkHttpClient,并设置一个 Interceptor 来包装你的原始 ResponseProgressResponseBody
    • 在回调中,你可以通过一个接口(如 UploadCallbacks)将进度更新到 UI。
  3. 文件选择: 不要硬编码文件路径,使用 Intent 调用系统文件选择器或图片选择器,让用户自己选择要上传的文件。

    Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
    intent.setType("image/*");
    startActivityForResult(intent, PICK_IMAGE_REQUEST);
  4. 安全性:

    • HTTPS: 必须使用 HTTPS 协议,防止数据在传输过程中被窃听或篡改。
    • 认证: 服务器需要对上传请求进行身份验证,例如使用 Token 或 OAuth2。
    • 文件类型/大小限制: 在服务器端对上传的文件类型(通过扩展名或 Content-Type)、大小进行严格校验,防止恶意用户上传超大文件或病毒文件。
  5. 错误处理: 处理各种可能的错误,如网络中断、服务器返回 500 错误、文件读取失败等,并向用户友好的提示。

  6. 取消上传: 提供一个取消按钮,允许用户中断上传,Retrofit 的 Call 对象有一个 cancel() 方法,可以取消正在进行的请求。

  7. Android 10+ 作用域存储: 对于 Android 10 (API 29) 及更高版本,推荐使用 MediaStore API 或 Storage Access Framework (SAF) 来访问和共享文件,而不是直接访问 Environment.getExternalStorageDirectory(),这是未来的趋势,可以更好地保护用户隐私。


特性 HttpURLConnection Retrofit + OkHttp
依赖 无需第三方库 需要 OkHttp 和 Retrofit
易用性 较低,代码繁琐 ,接口化,代码简洁
功能 基础功能 强大,支持拦截器、适配器、进度等
维护性 ,易于扩展和测试
适用场景 简单 demo、小型项目 所有生产环境项目

强烈建议在新的 Android 项目中使用 Retrofit + OkHttp 的方案,它能让你更专注于业务逻辑,而不是网络通信的底层细节。

分享:
扫描分享到社交APP
上一篇
下一篇