我们将使用 OkHttp 库来处理 Android 端的网络请求,因为它功能强大且易于使用,PHP 端将使用 $_FILES 全局变量来接收上传的文件。

目录
- 准备工作
- 服务器端 (PHP)
- 创建上传目录
- 编写
upload.php文件 - 安全性考虑
- 客户端 (Android Studio)
- 添加依赖
- 添加网络权限
- 编写文件选择器 UI
- 编写上传逻辑
- 完整代码示例
- 常见问题与解决方案
$_FILES['file']['error']错误码- 上传失败,返回
null - 上传后文件名乱码
- 上传大文件失败或超时
准备工作
- Android Studio: 确保你已经安装了最新版本的 Android Studio。
- PHP 环境: 你需要一台可以运行 PHP 的服务器(本地如 XAMPP/MAMP,或远程服务器)。
- OkHttp 库: 我们将使用 OkHttp 3 或 4 版本。
服务器端 (PHP)
我们在服务器上创建一个用于接收文件的 PHP 脚本。
a. 创建上传目录
在你的网站根目录下(htdocs 或 www),创建一个名为 uploads 的文件夹。确保这个文件夹有写入权限(在 Linux/Mac 上通常设置为 755,在 Windows 上确保 IIS 或 Apache 用户有写入权限)。
your_project_folder/
├── uploads/ <-- 文件将上传到这里
└── upload.php <-- 接收文件的PHP脚本
b. 编写 upload.php 文件
在 your_project_folder 下创建 upload.php 文件,内容如下:
<?php
// 允许任何来源的请求(开发时使用,生产环境应限制为你的域名)
header("Access-Control-Allow-Origin: *");
// 允许的请求方法
header("Access-Control-Allow-Methods: POST, OPTIONS");
// 允许的请求头
header("Access-Control-Allow-Headers: Content-Type");
// 如果是预检请求(OPTIONS),直接返回成功
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
http_response_code(200);
exit();
}
// 检查请求方法是否为 POST
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
// 检查文件是否存在
if (isset($_FILES['file'])) {
$file = $_FILES['file'];
$file_name = $file['name'];
$file_tmp_name = $file['tmp_name'];
$file_size = $file['size'];
$file_error = $file['error'];
// --- 安全性检查 ---
// 1. 检查上传是否成功
if ($file_error !== UPLOAD_ERR_OK) {
// 根据错误码返回不同的信息
$error_message = "上传失败: ";
switch ($file_error) {
case UPLOAD_ERR_INI_SIZE:
$error_message .= "文件大小超过 php.ini 中的 upload_max_filesize 指令。";
break;
case UPLOAD_ERR_FORM_SIZE:
$error_message .= "文件大小超过 HTML 表单中指定的 MAX_FILE_SIZE。";
break;
case UPLOAD_ERR_PARTIAL:
$error_message .= "文件只有部分被上传。";
break;
case UPLOAD_ERR_NO_FILE:
$error_message .= "没有文件被上传。";
break;
default:
$error_message .= "发生未知错误。";
break;
}
echo json_encode(['success' => false, 'message' => $error_message]);
exit;
}
// 2. 检查文件大小 ( 限制为 10MB)
$max_size = 10 * 1024 * 1024; // 10MB
if ($file_size > $max_size) {
echo json_encode(['success' => false, 'message' => '文件太大,最大允许 10MB。']);
exit;
}
// 3. 检查文件类型 (可选, 只允许图片)
$allowed_types = ['image/jpeg', 'image/png', 'image/gif'];
$file_type = mime_content_type($file_tmp_name); // 更准确获取MIME类型
if (!in_array($file_type, $allowed_types)) {
echo json_encode(['success' => false, 'message' => '不允许的文件类型。']);
exit;
}
// --- 安全性检查结束 ---
// 定义上传目录
$upload_dir = 'uploads/';
// 如果目录不存在,则创建
if (!is_dir($upload_dir)) {
mkdir($upload_dir, 0755, true);
}
// 生成唯一的文件名,避免文件名冲突
$file_extension = pathinfo($file_name, PATHINFO_EXTENSION);
$new_file_name = uniqid() . '.' . $file_extension;
$destination = $upload_dir . $new_file_name;
// 移动临时文件到最终目录
if (move_uploaded_file($file_tmp_name, $destination)) {
// 上传成功,返回成功信息和文件路径
echo json_encode([
'success' => true,
'message' => '文件上传成功!',
'file_path' => $destination,
'file_name' => $new_file_name
]);
} else {
// 上传失败
echo json_encode(['success' => false, 'message' => '文件移动失败。']);
}
} else {
// 没有文件被上传
echo json_encode(['success' => false, 'message' => '没有选择文件。']);
}
} else {
// 请求方法不是 POST
echo json_encode(['success' => false, 'message' => '无效的请求方法。']);
}
?>
PHP 代码解释:

header("Access-Control-Allow-Origin: *"): 允许任何来源的跨域请求。*在生产环境中,你应该将 `` 替换为你的 Android App 的域名**。isset($_FILES['file']): 检查是否有名为file的文件被上传。这个file必须与 Android 端MultipartBody.Part的 name 对应。$_FILES['file']['error']: PHP 提供的文件上传错误码,用于诊断上传失败的原因。move_uploaded_file(): 这是将上传的临时文件移动到最终存储位置的关键函数。json_encode(): 将 PHP 数组转换为 JSON 字符串,方便 Android 解析。
客户端 (Android Studio)
a. 添加依赖
在 app/build.gradle 文件的 dependencies 代码块中添加 OkHttp 依赖:
dependencies {
// ... 其他依赖
implementation("com.squareup.okhttp3:okhttp:4.12.0") // 使用最新版本
}
然后点击 "Sync Now"。
b. 添加网络权限
在 app/src/main/AndroidManifest.xml 文件中添加网络权限和 INTERNET 权限:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- 必须的互联网权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<application
...>
...
</application>
</manifest>
c. 编写文件选择器 UI
在 app/src/main/res/layout/activity_main.xml 中添加一个按钮和一个用于显示状态的 TextView:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp"
tools:context=".MainActivity">
<Button
android:id="@+id/btn_select_file"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="选择文件" />
<Button
android:id="@+id/btn_upload_file"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="上传文件" />
<TextView
android:id="@+id 