-
Android 端:
(图片来源网络,侵删)- 用户从相册或相机选择一张图片。
- 将图片文件转换成
byte[]或Base64字符串。 - 使用
HttpURLConnection或第三方库(如 OkHttp)构建一个POST请求。 - 将图片数据作为
multipart/form-data格式发送到 PHP 服务器。
-
PHP 服务器端:
- 接收来自 Android 的
POST请求。 - 检查请求中是否包含文件。
- 验证文件类型(是否为图片)、文件大小等。
- 为上传的文件生成一个唯一的文件名,以防止文件名冲突。
- 将文件从临时目录移动到你指定的服务器目标目录。
- 返回一个 JSON 响应,告知客户端上传成功或失败。
- 接收来自 Android 的
第一步:Android 客户端实现
我们将使用 HttpURLConnection 和现代的 java.nio.file API 来实现,这种方式不依赖第三方库,适合初学者理解原理。
添加网络权限
在 app/manifests/AndroidManifest.xml 文件中,必须添加网络访问权限。
<?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" />
<!-- 如果需要从相机拍照,还需要相机权限 -->
<uses-permission android:name="android.permission.CAMERA" />
<!-- 如果需要从相册选择,需要存储权限 (Android 13+ 需要 READ_MEDIA_IMAGES) -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<application ...>
...
</application>
</manifest>
创建上传工具类 UploadUtil.java
这个类将封装所有上传逻辑。

import android.util.Log;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class UploadUtil {
private static final String TAG = "UploadUtil";
private static final String LINE_END = "\r\n";
private static final String TWO_HYPHENS = "--";
private static final String BOUNDARY = "----WebKitFormBoundary7MA4YWxkTrZu0gW"; // 分隔符
public static void uploadFile(String filePath, String fileField, String serverUrl, final UploadCallback callback) {
File file = new File(filePath);
if (!file.exists()) {
if (callback != null) callback.onFailure("文件不存在");
return;
}
HttpURLConnection connection = null;
DataOutputStream outputStream = null;
InputStream inputStream = null;
try {
URL url = new URL(serverUrl);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setDoInput(true);
connection.setDoOutput(true);
connection.setUseCaches(false);
connection.setRequestProperty("Charset", "UTF-8");
connection.setRequestProperty("Connection", "Keep-Alive");
connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
// 创建输出流
outputStream = new DataOutputStream(connection.getOutputStream());
// 1. 添加文件头信息
StringBuilder sbHeader = new StringBuilder();
sbHeader.append(TWO_HYPHENS).append(BOUNDARY).append(LINE_END);
sbHeader.append("Content-Disposition: form-data; name=\"" + fileField + "\"; filename=\"" + file.getName() + "\"" + LINE_END);
sbHeader.append("Content-Type: application/octet-stream" + LINE_END); // 可以根据文件类型修改,如 image/jpeg
sbHeader.append(LINE_END);
outputStream.write(sbHeader.toString().getBytes("UTF-8"));
// 2. 添加文件内容
FileInputStream fileInputStream = new FileInputStream(file);
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = fileInputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
fileInputStream.close();
// 3. 混结束标记
outputStream.writeBytes(LINE_END);
outputStream.writeBytes(TWO_HYPHENS + BOUNDARY + TWO_HYPHENS + LINE_END);
// 获取服务器响应码
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
// 读取服务器返回的数据
inputStream = connection.getInputStream();
StringBuilder response = new StringBuilder();
byte[] buffer2 = new byte[1024];
int bytesRead2;
while ((bytesRead2 = inputStream.read(buffer2)) != -1) {
response.append(new String(buffer2, 0, bytesRead2));
}
if (callback != null) callback.onSuccess(response.toString());
} else {
String errorMsg = "上传失败,服务器响应码: " + responseCode;
Log.e(TAG, errorMsg);
if (callback != null) callback.onFailure(errorMsg);
}
} catch (IOException e) {
Log.e(TAG, "上传IO异常: " + e.getMessage());
if (callback != null) callback.onFailure("上传IO异常: " + e.getMessage());
} finally {
// 关闭流和连接
try {
if (outputStream != null) outputStream.close();
if (inputStream != null) inputStream.close();
if (connection != null) connection.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 上传回调接口
public interface UploadCallback {
void onSuccess(String response);
void onFailure(String error);
}
}
在 Activity 或 Fragment 中调用
假设你有一个按钮,点击后选择图片并上传。
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import java.util.HashMap;
import java.util.Map;
public class MainActivity extends AppCompatActivity {
private static final int PICK_IMAGE_REQUEST = 1;
private static final int REQUEST_PERMISSION_CODE = 1001;
private Button uploadButton;
private String selectedFilePath;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
uploadButton = findViewById(R.id.btn_upload);
uploadButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 检查权限
if (checkPermission()) {
chooseImage();
} else {
requestPermission();
}
}
});
}
private void chooseImage() {
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(Intent.createChooser(intent, "选择图片"), PICK_IMAGE_REQUEST);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == PICK_IMAGE_REQUEST && resultCode == RESULT_OK && data != null && data.getData() != null) {
Uri filePathUri = data.getData();
selectedFilePath = getRealPathFromURI(filePathUri); // 获取真实路径
if (selectedFilePath != null) {
Toast.makeText(this, "已选择: " + selectedFilePath, Toast.LENGTH_SHORT).show();
// 开始上传
startUpload();
}
}
}
private void startUpload() {
// 服务器地址
String serverUrl = "http://你的服务器地址/upload.php";
// 文件字段名,必须和PHP $_FILES['image'] 中的 'image' 对应
String fileField = "image";
Toast.makeText(this, "开始上传...", Toast.LENGTH_SHORT).show();
UploadUtil.uploadFile(selectedFilePath, fileField, serverUrl, new UploadUtil.UploadCallback() {
@Override
public void onSuccess(String response) {
runOnUiThread(() -> {
Log.d("Upload", "服务器响应: " + response);
Toast.makeText(MainActivity.this, "上传成功: " + response, Toast.LENGTH_LONG).show();
});
}
@Override
public void onFailure(String error) {
runOnUiThread(() -> {
Log.e("Upload", "上传失败: " + error);
Toast.makeText(MainActivity.this, "上传失败: " + error, Toast.LENGTH_LONG).show();
});
}
});
}
// 获取文件真实路径 (方法可能需要根据Android版本调整)
private String getRealPathFromURI(Uri contentUri) {
String[] proj = {MediaStore.Images.Media.DATA};
CursorLoader loader = new Cursor
