POST 多部分表单数据使用涵盖图像的 Revifit 2.0

我正在尝试使用 改造2.0版对服务器执行 HTTP POST

MediaType MEDIA_TYPE_TEXT = MediaType.parse("text/plain");
MediaType MEDIA_TYPE_IMAGE = MediaType.parse("image/*");


ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
imageBitmap.compress(Bitmap.CompressFormat.JPEG,90,byteArrayOutputStream);
profilePictureByte = byteArrayOutputStream.toByteArray();


Call<APIResults> call = ServiceAPI.updateProfile(
RequestBody.create(MEDIA_TYPE_TEXT, emailString),
RequestBody.create(MEDIA_TYPE_IMAGE, profilePictureByte));


call.enqueue();

服务器返回一个错误,说明该文件无效。

这很奇怪,因为我曾试图在 iOS 上用相同的格式上传相同的文件(使用其他库) ,但它上传成功了。

我想知道什么是正确的方式上传一个图像使用 改造2.0版

在上传之前,我应该先把它保存到磁盘上吗?

附注: 我已经为其他多部分请求,不包括图像和他们成功地完成了翻新。问题在于,当我试图在主体中包含一个字节时。

305857 次浏览

我在1.9和2.0中都强调了这个解决方案,因为它对某些人很有用

1.9中,我认为更好的解决方案是将文件保存到磁盘,并将其用作类型化文件,如:

改装1.9

(我不知道您的服务器端实现)有一个类似的 API 接口方法

@POST("/en/Api/Results/UploadFile")
void UploadFile(@Part("file") TypedFile file,
@Part("folder") String folder,
Callback<Response> callback);

然后把它当成

TypedFile file = new TypedFile("multipart/form-data",
new File(path));

使用以下方法

卢克菲特2.0(这是卢克菲特2中的 问题的一个解决方案,现在已经修复了,正确的方法请参阅 Jimmy 0251的回答)

接口:

public interface ApiInterface {


@Multipart
@POST("/api/Accounts/editaccount")
Call<User> editUser(@Header("Authorization") String authorization,
@Part("file\"; filename=\"pp.png\" ") RequestBody file,
@Part("FirstName") RequestBody fname,
@Part("Id") RequestBody id);
}

像这样使用:

File file = new File(imageUri.getPath());


RequestBody fbody = RequestBody.create(MediaType.parse("image/*"),
file);


RequestBody name = RequestBody.create(MediaType.parse("text/plain"),
firstNameField.getText()
.toString());


RequestBody id = RequestBody.create(MediaType.parse("text/plain"),
AZUtils.getUserId(this));


Call<User> call = client.editUser(AZUtils.getToken(this),
fbody,
name,
id);


call.enqueue(new Callback<User>() {


@Override
public void onResponse(retrofit.Response<User> response,
Retrofit retrofit) {


AZUtils.printObject(response.body());
}


@Override
public void onFailure(Throwable t) {


t.printStackTrace();
}
});

改造2.0中上传图像文件的更新代码

public interface ApiInterface {


@Multipart
@POST("user/signup")
Call<UserModelResponse> updateProfilePhotoProcess(@Part("email") RequestBody email,
@Part("password") RequestBody password,
@Part("profile_pic\"; filename=\"pp.png")
RequestBody file);
}

MediaType.parse("image/*")改为 MediaType.parse("image/jpeg")

RequestBody reqFile = RequestBody.create(MediaType.parse("image/jpeg"),
file);
RequestBody email = RequestBody.create(MediaType.parse("text/plain"),
"upload_test4@gmail.com");
RequestBody password = RequestBody.create(MediaType.parse("text/plain"),
"123456789");


Call<UserModelResponse> call = apiService.updateProfilePhotoProcess(email,
password,
reqFile);
call.enqueue(new Callback<UserModelResponse>() {


@Override
public void onResponse(Call<UserModelResponse> call,
Response<UserModelResponse> response) {


String
TAG =
response.body()
.toString();


UserModelResponse userModelResponse = response.body();
UserModel userModel = userModelResponse.getUserModel();


Log.d("MainActivity",
"user image = " + userModel.getProfilePic());


}


@Override
public void onFailure(Call<UserModelResponse> call,
Throwable t) {


Toast.makeText(MainActivity.this,
"" + TAG,
Toast.LENGTH_LONG)
.show();


}
});

有一个 正确的方式上传文件的名称与 翻新2,没有任何 黑客:

定义 API 接口:

@Multipart
@POST("uploadAttachment")
Call<MyResponse> uploadAttachment(@Part MultipartBody.Part filePart);
// You can add other parameters too

上传文件如下:

File file = // initialize file here


MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", file.getName(), RequestBody.create(MediaType.parse("image/*"), file));


Call<MyResponse> call = api.uploadAttachment(filePart);

这只说明了文件上传,还可以用 @Part注释在同一方法中添加其他参数。

我为我的注册用户使用卢克菲特2.0,发送多部分/形式的文件图像和文本从注册帐户

在我的 RegisterActivity 中,使用 AsyncTask

//AsyncTask
private class Register extends AsyncTask<String, Void, String> {


@Override
protected void onPreExecute() {..}


@Override
protected String doInBackground(String... params) {
new com.tequilasoft.mesasderegalos.dbo.Register().register(txtNombres, selectedImagePath, txtEmail, txtPassword);
responseMensaje = StaticValues.mensaje ;
mensajeCodigo = StaticValues.mensajeCodigo;
return String.valueOf(StaticValues.code);
}


@Override
protected void onPostExecute(String codeResult) {..}

并且在我的 Register.java 类中,使用的是使用同步调用的嫩化

import android.util.Log;
import com.tequilasoft.mesasderegalos.interfaces.RegisterService;
import com.tequilasoft.mesasderegalos.utils.StaticValues;
import com.tequilasoft.mesasderegalos.utils.Utilities;
import java.io.File;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Response;
/**Created by sam on 2/09/16.*/
public class Register {


public void register(String nombres, String selectedImagePath, String email, String password){


try {
// create upload service client
RegisterService service = ServiceGenerator.createUser(RegisterService.class);


// add another part within the multipart request
RequestBody requestEmail =
RequestBody.create(
MediaType.parse("multipart/form-data"), email);
// add another part within the multipart request
RequestBody requestPassword =
RequestBody.create(
MediaType.parse("multipart/form-data"), password);
// add another part within the multipart request
RequestBody requestNombres =
RequestBody.create(
MediaType.parse("multipart/form-data"), nombres);


MultipartBody.Part imagenPerfil = null;
if(selectedImagePath!=null){
File file = new File(selectedImagePath);
Log.i("Register","Nombre del archivo "+file.getName());
// create RequestBody instance from file
RequestBody requestFile =
RequestBody.create(MediaType.parse("multipart/form-data"), file);
// MultipartBody.Part is used to send also the actual file name
imagenPerfil = MultipartBody.Part.createFormData("imagenPerfil", file.getName(), requestFile);
}


// finally, execute the request
Call<ResponseBody> call = service.registerUser(imagenPerfil, requestEmail,requestPassword,requestNombres);
Response<ResponseBody> bodyResponse = call.execute();
StaticValues.code  = bodyResponse.code();
StaticValues.mensaje  = bodyResponse.message();
ResponseBody errorBody = bodyResponse.errorBody();
StaticValues.mensajeCodigo  = errorBody==null
?null
:Utilities.mensajeCodigoDeLaRespuestaJSON(bodyResponse.errorBody().byteStream());
Log.i("Register","Code "+StaticValues.code);
Log.i("Register","mensaje "+StaticValues.mensaje);
Log.i("Register","mensajeCodigo "+StaticValues.mensaje);
}
catch (Exception e){
e.printStackTrace();
}
}
}

在 RegisterService 的接口中

public interface RegisterService {
@Multipart
@POST(StaticValues.REGISTER)
Call<ResponseBody> registerUser(@Part MultipartBody.Part image,
@Part("email") RequestBody email,
@Part("password") RequestBody password,
@Part("nombre") RequestBody nombre
);
}

用于实用程序解析 InputStream 响应

public class Utilities {
public static String mensajeCodigoDeLaRespuestaJSON(InputStream inputStream){
String mensajeCodigo = null;
try {
BufferedReader reader = new BufferedReader(
new InputStreamReader(
inputStream, "iso-8859-1"), 8);
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n");
}
inputStream.close();
mensajeCodigo = sb.toString();
} catch (Exception e) {
Log.e("Buffer Error", "Error converting result " + e.toString());
}
return mensajeCodigo;
}
}

您可以创建一个 Map来放置包括图像在内的 RequestBody的参数。

接口代码

public interface ApiInterface {
@Multipart
@POST("/api/Accounts/editaccount")
Call<User> editUser (@Header("Authorization") String authorization, @PartMap Map<String, RequestBody> map);
}

Java 类的代码

File file = new File(imageUri.getPath());
RequestBody fbody = RequestBody.create(MediaType.parse("image/*"), file);
RequestBody name = RequestBody.create(MediaType.parse("text/plain"), firstNameField.getText().toString());
RequestBody id = RequestBody.create(MediaType.parse("text/plain"), AZUtils.getUserId(this));


Map<String, RequestBody> map = new HashMap<>();
map.put("file\"; filename=\"pp.png\" ", fbody);
map.put("FirstName", name);
map.put("Id", id);
Call<User> call = client.editUser(AZUtils.getToken(this), map);
call.enqueue(new Callback<User>() {
@Override
public void onResponse(retrofit.Response<User> response, Retrofit retrofit)
{
AZUtils.printObject(response.body());
}


@Override
public void onFailure(Throwable t) {
t.printStackTrace();
}
});

上传文件使用龟裂是相当简单的你需要建立你的 api 接口作为

public interface Api {


String BASE_URL = "http://192.168.43.124/ImageUploadApi/";




@Multipart
@POST("yourapipath")
Call<MyResponse> uploadImage(@Part("image\"; filename=\"myfile.jpg\" ") RequestBody file, @Part("desc") RequestBody desc);


}
在上面的代码中,< p > 是键名 形象,所以如果您使用的是 php,那么您将编写 $_ FILES [‘ image’][‘ tmp _ name’]来获得这个值。 Filename = “ myfile.jpg”是随请求一起发送的文件的名称。

现在上传文件,你需要一个方法,将给你的绝对路径从 Uri。

private String getRealPathFromURI(Uri contentUri) {
String[] proj = {MediaStore.Images.Media.DATA};
CursorLoader loader = new CursorLoader(this, contentUri, proj, null, null, null);
Cursor cursor = loader.loadInBackground();
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
cursor.moveToFirst();
String result = cursor.getString(column_index);
cursor.close();
return result;
}

现在您可以使用下面的代码来上传您的文件。

 private void uploadFile(Uri fileUri, String desc) {


//creating a file
File file = new File(getRealPathFromURI(fileUri));


//creating request body for file
RequestBody requestFile = RequestBody.create(MediaType.parse(getContentResolver().getType(fileUri)), file);
RequestBody descBody = RequestBody.create(MediaType.parse("text/plain"), desc);


//The gson builder
Gson gson = new GsonBuilder()
.setLenient()
.create();




//creating retrofit object
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Api.BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();


//creating our api
Api api = retrofit.create(Api.class);


//creating a call and calling the upload image method
Call<MyResponse> call = api.uploadImage(requestFile, descBody);


//finally performing the call
call.enqueue(new Callback<MyResponse>() {
@Override
public void onResponse(Call<MyResponse> call, Response<MyResponse> response) {
if (!response.body().error) {
Toast.makeText(getApplicationContext(), "File Uploaded Successfully...", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(getApplicationContext(), "Some error occurred...", Toast.LENGTH_LONG).show();
}
}


@Override
public void onFailure(Call<MyResponse> call, Throwable t) {
Toast.makeText(getApplicationContext(), t.getMessage(), Toast.LENGTH_LONG).show();
}
});
}

更详细的解释,你可以访问这个 翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳翻译: 奇芳

因此,这是完成任务的一个非常简单的方法。你需要遵循以下步骤:-

第一步

public interface APIService {
@Multipart
@POST("upload")
Call<ResponseBody> upload(
@Part("item") RequestBody description,
@Part("imageNumber") RequestBody description,
@Part MultipartBody.Part imageFile
);
}

你需要以 @Multipart request的身份打完整个电话。itemimage number只是包裹在 RequestBody中的字符串体。我们使用 MultipartBody.Part class,它允许我们在请求中发送二进制文件数据之外的实际文件名

2. 第二步

  File file = (File) params[0];
RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);


MultipartBody.Part body =MultipartBody.Part.createFormData("Image", file.getName(), requestBody);


RequestBody ItemId = RequestBody.create(okhttp3.MultipartBody.FORM, "22");
RequestBody ImageNumber = RequestBody.create(okhttp3.MultipartBody.FORM,"1");
final Call<UploadImageResponse> request = apiService.uploadItemImage(body, ItemId,ImageNumber);

现在您已经有了 image path并且需要转换成 file。现在使用方法 RequestBody.create(MediaType.parse("multipart/form-data"), file)file转换成 RequestBody。现在您需要使用方法 MultipartBody.Part.createFormData("Image", file.getName(), requestBody);RequestBody requestFile转换为 MultipartBody.Part

ImageNumberItemId是我的另一个数据,我需要发送到服务器,所以我也使两件事成为 RequestBody

了解更多信息

Kotlin 版本,更新了 RequestBody.create的退化:

改装界面

@Multipart
@POST("uploadPhoto")
fun uploadFile(@Part file: MultipartBody.Part): Call<FileResponse>

及上载

fun uploadFile(fileUrl: String){
val file = File(fileUrl)
val fileUploadService = RetrofitClientInstance.retrofitInstance.create(FileUploadService::class.java)
val requestBody = file.asRequestBody(file.extension.toMediaTypeOrNull())
val filePart = MultipartBody.Part.createFormData(
"blob",file.name,requestBody
)
val call = fileUploadService.uploadFile(filePart)


call.enqueue(object: Callback<FileResponse>{
override fun onFailure(call: Call<FileResponse>, t: Throwable) {
Log.d(TAG,"Fckd")
}


override fun onResponse(call: Call<FileResponse>, response: Response<FileResponse>) {
Log.d(TAG,"success"+response.toString()+" "+response.body().toString()+"  "+response.body()?.status)
}


})
}

感谢@jimmy0251

不要使用 函数名中的多个参数 只要遵循简单的几个参数约定,就可以提高代码的可读性,你可以这样做-

// MultipartBody.Part.createFormData("partName", data)
Call<SomReponse> methodName(@Part MultiPartBody.Part part);
// RequestBody.create(MediaType.get("text/plain"), data)
Call<SomReponse> methodName(@Part(value = "partName") RequestBody part);
/* for single use or you can use by Part name with Request body */


// add multiple list of part as abstraction |ease of readability|
Call<SomReponse> methodName(@Part List<MultiPartBody.Part> parts);
Call<SomReponse> methodName(@PartMap Map<String, RequestBody> parts);
// this way you will save the abstraction of multiple parts.

可以有 您可能会遇到多个异常,所有这些异常都记录为代码浏览 retrofit2/RequestFactory.java。你可以在 parseParameterAnnotationparseMethodAnnotation两个函数中异常抛出,请通过这个,它会比 谷歌/堆栈溢出节省你大量的时间

在 kotlin 中很容易,使用 < em > toMediaType < em > asRequestBody < em > toRequestBody 的扩展方法,这里有一个例子:

在这里,我发布了一对夫妇的正常领域连同一个 pdf 文件和图像文件使用多部分

这是使用改装的 API 声明:

    @Multipart
@POST("api/Lesson/AddNewLesson")
fun createLesson(
@Part("userId") userId: RequestBody,
@Part("LessonTitle") lessonTitle: RequestBody,
@Part pdf: MultipartBody.Part,
@Part imageFile: MultipartBody.Part
): Maybe<BaseResponse<String>>

以下是它的实际名称:

api.createLesson(
userId.toRequestBody("text/plain".toMediaType()),
lessonTitle.toRequestBody("text/plain".toMediaType()),
startFromRegister.toString().toRequestBody("text/plain".toMediaType()),
MultipartBody.Part.createFormData(
"jpeg",
imageFile.name,
imageFile.asRequestBody("image/*".toMediaType())
),
MultipartBody.Part.createFormData(
"pdf",
pdfFile.name,
pdfFile.asRequestBody("application/pdf".toMediaType())
)
* Return MultipartBody from file path


public static MultipartBody.Part generateFileBody(String imagePath)
{
File file = new File(imagePath);
RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
return MultipartBody.Part.createFormData("mediaProfilePic", file.getName(), requestFile);
}

RequestBody 可用于上载

  val body: RequestBody = Builder().setType(MultipartBody.FORM)
.addFormDataPart(
"file", "<image name you wish to give>",
RequestBody.create(
MediaType.parse("application/octet-stream"),
File(path)
)
)
.build()
uploadProfilePhoto(body)

然后这样打电话:

   @POST("/**")
suspend fun uploadProfilePhoto(
@Body body: RequestBody,
): ResponseBody
}

在我的例子中,我需要发送一个 PDF 文件(application/pdf)和 JSON 信息(application/json)。谢天谢地,逆转录2使这一切变得超级简单。

我的界面如下:

interface MyApi {
@Multipart
@POST("upload")
fun uploadPDF(
@Part file: MultipartBody.Part,
@Part(value = "jsoninfo") jsoninfo: MyJsonObject
): Call<MyResponse>
}

其中,jsoninfo是我的 JSON 数据的名称,MyJsonObject是我的数据类,当然,MyResponse是我期望的响应。

然后,我调用我的 API 方法,如下所示:

val myJsonObject = MyJsonObject(...)


// "file" is of type byte[] already
val requestBody = RequestBody.create(file, MediaType.parse("application/pdf"))
val filePart = MultipartBody.Part.createFormData("file", "myfile.pdf", requestBody)


api.uploadPDF(filePart, myJsonObject).enqueue(...)