Android开发:如何将裁剪后图片路径转可存字符串并上传至Spring Boot服务器
解决Android裁剪图片后上传至Spring Boot服务器(字符串字段存储)
你当前拿到的profileImageUri.getPath()是Content Uri对应的本地临时路径,这个路径仅App临时有权限访问,服务器无法直接读取,且App卸载或系统清理后路径会失效,完全不适合长期存储。结合后端字符串字段的要求,提供两种可行方案:
方案一:将图片转为Base64字符串上传
把裁剪后的图片编码成Base64字符串,直接作为字符串字段传给后端存储,后续获取时再解码为图片。
步骤1:从Uri获取图片并转为Base64
修改你的onActivityResult方法,添加Base64转换逻辑:
@Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE && resultCode == RESULT_OK && data != null) { CropImage.ActivityResult result = CropImage.getActivityResult(data); Uri profileImageUri = result.getUri(); profilePicture.setImageURI(profileImageUri); String base64Image = null; try { InputStream inputStream = getContentResolver().openInputStream(profileImageUri); byte[] bytes = convertStreamToByteArray(inputStream); base64Image = Base64.encodeToString(bytes, Base64.DEFAULT); inputStream.close(); Log.d("Main", "Base64预览: " + base64Image.substring(0, 50) + "..."); } catch (IOException e) { e.printStackTrace(); Toast.makeText(this, "图片编码失败", Toast.LENGTH_SHORT).show(); } if (base64Image != null) { uploadUserInfoWithAvatar(base64Image); } } else { Toast.makeText(PatientRegistrationActivity.this, "操作失败,请重试", Toast.LENGTH_SHORT).show(); } } // 自定义流转字节数组方法(无需依赖第三方库) private byte[] convertStreamToByteArray(InputStream is) throws IOException { ByteArrayOutputStream buffer = new ByteArrayOutputStream(); int nRead; byte[] data = new byte[1024]; while ((nRead = is.read(data, 0, data.length)) != -1) { buffer.write(data, 0, nRead); } buffer.flush(); return buffer.toByteArray(); }
步骤2:Retrofit接口与实体类定义
// Retrofit接口 public interface UserApi { @POST("users/register") Call<UserResponse> registerUser(@Body UserRequest request); } // 用户请求实体类 public class UserRequest { private String username; private String password; private String avatar; // 对应后端字符串字段,存储Base64 // 构造方法、getter、setter }
步骤3:Spring Boot后端接收处理
@PostMapping("/users/register") public ResponseEntity<UserResponse> registerUser(@RequestBody UserRequest request) { // 直接将Base64字符串存入数据库 User user = new User(); user.setUsername(request.getUsername()); user.setPassword(request.getPassword()); user.setAvatar(request.getAvatar()); userRepository.save(user); return ResponseEntity.ok(new UserResponse(user)); }
注意:Base64字符串体积比原图片大30%左右,大尺寸图片建议用方案二。
方案二:上传图片文件,服务器返回存储路径字符串
先上传图片到服务器,服务器保存后返回可访问的路径字符串(如/uploads/avatar/xxx.jpg),再将该路径与其他用户信息一起保存。
步骤1:构建Multipart请求上传图片
修改onActivityResult并添加路径适配方法:
@Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE && resultCode == RESULT_OK && data != null) { CropImage.ActivityResult result = CropImage.getActivityResult(data); Uri profileImageUri = result.getUri(); profilePicture.setImageURI(profileImageUri); String realPath = getRealPathFromUri(profileImageUri); if (realPath == null) { Toast.makeText(this, "无法获取图片路径", Toast.LENGTH_SHORT).show(); return; } File imageFile = new File(realPath); RequestBody requestFile = RequestBody.create(imageFile, MediaType.parse("image/jpeg")); MultipartBody.Part body = MultipartBody.Part.createFormData("avatar", imageFile.getName(), requestFile); uploadAvatar(body); } else { Toast.makeText(PatientRegistrationActivity.this, "操作失败,请重试", Toast.LENGTH_SHORT).show(); } } // 适配不同Android版本的真实路径获取方法 private String getRealPathFromUri(Uri uri) { String path = null; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { Cursor cursor = getContentResolver().query(uri, new String[]{MediaStore.Images.Media.DISPLAY_NAME}, null, null, null); if (cursor != null) { if (cursor.moveToFirst()) { String fileName = cursor.getString(0); path = getExternalFilesDir(Environment.DIRECTORY_PICTURES) + "/" + fileName; // 将Content Uri文件复制到App私有目录 try { InputStream inputStream = getContentResolver().openInputStream(uri); FileOutputStream outputStream = new FileOutputStream(path); byte[] buffer = new byte[1024]; int length; while ((length = inputStream.read(buffer)) > 0) { outputStream.write(buffer, 0, length); } inputStream.close(); outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } cursor.close(); } } else { if (DocumentsContract.isDocumentUri(this, uri)) { String docId = DocumentsContract.getDocumentId(uri); if ("com.android.providers.media.documents".equals(uri.getAuthority())) { String id = docId.split(":")[1]; String selection = MediaStore.Images.Media._ID + "=" + id; path = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection); } else if ("com.android.providers.downloads.documents".equals(uri.getAuthority())) { Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.parseLong(docId)); path = getImagePath(contentUri, null); } } else if ("content".equalsIgnoreCase(uri.getScheme())) { path = getImagePath(uri, null); } else if ("file".equalsIgnoreCase(uri.getScheme())) { path = uri.getPath(); } } return path; } private String getImagePath(Uri uri, String selection) { String path = null; Cursor cursor = getContentResolver().query(uri, null, selection, null, null); if (cursor != null) { if (cursor.moveToFirst()) { path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA)); } cursor.close(); } return path; }
步骤2:Retrofit接口与实体类定义
public interface UserApi { @Multipart @POST("uploads/avatar") Call<AvatarUploadResponse> uploadAvatar(@Part MultipartBody.Part avatar); @POST("users/register") Call<UserResponse> registerUser(@Body UserRequest request); } // 图片上传响应实体类 public class AvatarUploadResponse { private String avatarPath; // 服务器返回的图片路径 // getter、setter } // 用户请求实体类 public class UserRequest { private String username; private String password; private String avatar; // 存储服务器返回的路径字符串 // 构造方法、getter、setter }
步骤3:上传图片后保存用户信息
private void uploadAvatar(MultipartBody.Part body) { UserApi api = RetrofitClient.getInstance().create(UserApi.class); Call<AvatarUploadResponse> call = api.uploadAvatar(body); call.enqueue(new Callback<AvatarUploadResponse>() { @Override public void onResponse(Call<AvatarUploadResponse> call, Response<AvatarUploadResponse> response) { if (response.isSuccessful() && response.body() != null) { String avatarPath = response.body().getAvatarPath(); UserRequest request = new UserRequest("testUser", "123456", avatarPath); registerUser(request); } else { Toast.makeText(PatientRegistrationActivity.this, "图片上传失败", Toast.LENGTH_SHORT).show(); } } @Override public void onFailure(Call<AvatarUploadResponse> call, Throwable t) { t.printStackTrace(); Toast.makeText(PatientRegistrationActivity.this, "网络请求失败", Toast.LENGTH_SHORT).show(); } }); } private void registerUser(UserRequest request) { UserApi api = RetrofitClient.getInstance().create(UserApi.class); Call<UserResponse> call = api.registerUser(request); call.enqueue(new Callback<UserResponse>() { @Override public void onResponse(Call<UserResponse> call, Response<UserResponse> response) { if (response.isSuccessful()) { Toast.makeText(PatientRegistrationActivity.this, "注册成功", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(PatientRegistrationActivity.this, "注册失败", Toast.LENGTH_SHORT).show(); } } @Override public void onFailure(Call<UserResponse> call, Throwable t) { t.printStackTrace(); Toast.makeText(PatientRegistrationActivity.this, "网络请求失败", Toast.LENGTH_SHORT).show(); } }); }
步骤4:Spring Boot后端处理上传与静态资源映射
@RestController @RequestMapping("/uploads") public class UploadController { private final String UPLOAD_DIR = System.getProperty("user.dir") + "/uploads/avatar/"; @PostMapping("/avatar") public ResponseEntity<AvatarUploadResponse> uploadAvatar(@RequestParam("avatar") MultipartFile file) { if (file.isEmpty()) { return ResponseEntity.badRequest().body(new AvatarUploadResponse("文件为空")); } File dir = new File(UPLOAD_DIR); if (!dir.exists()) { dir.mkdirs(); } String fileName = UUID.randomUUID().toString() + "." + file.getOriginalFilename().split("\\.")[1]; File dest = new File(UPLOAD_DIR + fileName); try { file.transferTo(dest); String avatarPath = "/uploads/avatar/" + fileName; return ResponseEntity.ok(new AvatarUploadResponse(avatarPath)); } catch (IOException e) { e.printStackTrace(); return ResponseEntity.internalServerError().body(new AvatarUploadResponse("文件保存失败")); } } } // 配置静态资源映射,允许客户端直接访问图片 @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/uploads/avatar/**") .addResourceLocations("file:" + System.getProperty("user.dir") + "/uploads/avatar/"); } }
注意事项
- 权限处理:在AndroidManifest.xml中添加权限,并动态申请:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <!-- Android 13+ -->
- 图片压缩:大尺寸图片建议压缩后再上传,避免请求超时或体积过大:
private Bitmap compressImage(Uri uri) throws IOException { InputStream is = getContentResolver().openInputStream(uri); BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeStream(is, null, options); is.close(); int reqWidth = 1080; int reqHeight = 1080; int inSampleSize = 1; if (options.outHeight > reqHeight || options.outWidth > reqWidth) { int halfHeight = options.outHeight / 2; int halfWidth = options.outWidth / 2; while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) { inSampleSize *= 2; } } options.inJustDecodeBounds = false; options.inSampleSize = inSampleSize; is = getContentResolver().openInputStream(uri); Bitmap bitmap = BitmapFactory.decodeStream(is, null, options); is.close(); return bitmap; }
内容的提问来源于stack exchange,提问作者Ukeme Elijah
相关产品推荐
相关产品推荐

