Рабочий многоточечный запрос POST с волейболом и без HttpEntity
На самом деле это не вопрос, однако я хотел бы поделиться с вами некоторым моим рабочим кодом для справки, когда вам это нужно.
Как мы знаем, HttpEntity
устарела с API22 и полностью удалена с API23. В настоящее время мы больше не можем получить доступ к HttpEntity Reference на Android Developer (404). Итак, вот мой рабочий пример кода для POST Multipart Request с Volley и без HttpEntity. Это работает, протестировано с Asp.Net Web API
. Конечно, код, возможно, является просто базовым примером, в котором публикуются два существующих отрисовываемых файла, а также не является лучшим решением для всех случаев и не является хорошей настройкой.
MultipartActivity.java:
package com.example.multipartvolley;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v4.content.ContextCompat;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
import com.android.volley.NetworkResponse;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
public class MultipartActivity extends Activity {
private final Context context = this;
private final String twoHyphens = "--";
private final String lineEnd = "\r\n";
private final String boundary = "apiclient-" + System.currentTimeMillis();
private final String mimeType = "multipart/form-data;boundary=" + boundary;
private byte[] multipartBody;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_multipart);
byte[] fileData1 = getFileDataFromDrawable(context, R.drawable.ic_action_android);
byte[] fileData2 = getFileDataFromDrawable(context, R.drawable.ic_action_book);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
try {
// the first file
buildPart(dos, fileData1, "ic_action_android.png");
// the second file
buildPart(dos, fileData2, "ic_action_book.png");
// send multipart form data necesssary after file data
dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);
// pass to multipart body
multipartBody = bos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
String url = "http://192.168.1.100/api/postfile";
MultipartRequest multipartRequest = new MultipartRequest(url, null, mimeType, multipartBody, new Response.Listener<NetworkResponse>() {
@Override
public void onResponse(NetworkResponse response) {
Toast.makeText(context, "Upload successfully!", Toast.LENGTH_SHORT).show();
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Toast.makeText(context, "Upload failed!\r\n" + error.toString(), Toast.LENGTH_SHORT).show();
}
});
VolleySingleton.getInstance(context).addToRequestQueue(multipartRequest);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_multipart, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
private void buildPart(DataOutputStream dataOutputStream, byte[] fileData, String fileName) throws IOException {
dataOutputStream.writeBytes(twoHyphens + boundary + lineEnd);
dataOutputStream.writeBytes("Content-Disposition: form-data; name=\"uploaded_file\"; filename=\""
+ fileName + "\"" + lineEnd);
dataOutputStream.writeBytes(lineEnd);
ByteArrayInputStream fileInputStream = new ByteArrayInputStream(fileData);
int bytesAvailable = fileInputStream.available();
int maxBufferSize = 1024 * 1024;
int bufferSize = Math.min(bytesAvailable, maxBufferSize);
byte[] buffer = new byte[bufferSize];
// read file and write it into form...
int bytesRead = fileInputStream.read(buffer, 0, bufferSize);
while (bytesRead > 0) {
dataOutputStream.write(buffer, 0, bufferSize);
bytesAvailable = fileInputStream.available();
bufferSize = Math.min(bytesAvailable, maxBufferSize);
bytesRead = fileInputStream.read(buffer, 0, bufferSize);
}
dataOutputStream.writeBytes(lineEnd);
}
private byte[] getFileDataFromDrawable(Context context, int id) {
Drawable drawable = ContextCompat.getDrawable(context, id);
Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 0, byteArrayOutputStream);
return byteArrayOutputStream.toByteArray();
}
}
MultipartRequest.java:
package com.example.multipartvolley;
import com.android.volley.AuthFailureError;
import com.android.volley.NetworkResponse;
import com.android.volley.ParseError;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.HttpHeaderParser;
import java.util.Map;
class MultipartRequest extends Request<NetworkResponse> {
private final Response.Listener<NetworkResponse> mListener;
private final Response.ErrorListener mErrorListener;
private final Map<String, String> mHeaders;
private final String mMimeType;
private final byte[] mMultipartBody;
public MultipartRequest(String url, Map<String, String> headers, String mimeType, byte[] multipartBody, Response.Listener<NetworkResponse> listener, Response.ErrorListener errorListener) {
super(Method.POST, url, errorListener);
this.mListener = listener;
this.mErrorListener = errorListener;
this.mHeaders = headers;
this.mMimeType = mimeType;
this.mMultipartBody = multipartBody;
}
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
return (mHeaders != null) ? mHeaders : super.getHeaders();
}
@Override
public String getBodyContentType() {
return mMimeType;
}
@Override
public byte[] getBody() throws AuthFailureError {
return mMultipartBody;
}
@Override
protected Response<NetworkResponse> parseNetworkResponse(NetworkResponse response) {
try {
return Response.success(
response,
HttpHeaderParser.parseCacheHeaders(response));
} catch (Exception e) {
return Response.error(new ParseError(e));
}
}
@Override
protected void deliverResponse(NetworkResponse response) {
mListener.onResponse(response);
}
@Override
public void deliverError(VolleyError error) {
mErrorListener.onErrorResponse(error);
}
}
ОБНОВИТЬ:
Для текстовой части, пожалуйста, обратитесь к ответу @Oscar ниже.
Ответы
Ответ 1
Я переписываю ваш код @RacZo и @BNK более модульным и простым в использовании, как
VolleyMultipartRequest multipartRequest = new VolleyMultipartRequest(Request.Method.POST, url, new Response.Listener<NetworkResponse>() {
@Override
public void onResponse(NetworkResponse response) {
String resultResponse = new String(response.data);
// parse success output
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
error.printStackTrace();
}
}) {
@Override
protected Map<String, String> getParams() {
Map<String, String> params = new HashMap<>();
params.put("api_token", "gh659gjhvdyudo973823tt9gvjf7i6ric75r76");
params.put("name", "Angga");
params.put("location", "Indonesia");
params.put("about", "UI/UX Designer");
params.put("contact", "[email protected]");
return params;
}
@Override
protected Map<String, DataPart> getByteData() {
Map<String, DataPart> params = new HashMap<>();
// file name could found file base or direct access from real path
// for now just get bitmap data from ImageView
params.put("avatar", new DataPart("file_avatar.jpg", AppHelper.getFileDataFromDrawable(getBaseContext(), mAvatarImage.getDrawable()), "image/jpeg"));
params.put("cover", new DataPart("file_cover.jpg", AppHelper.getFileDataFromDrawable(getBaseContext(), mCoverImage.getDrawable()), "image/jpeg"));
return params;
}
};
VolleySingleton.getInstance(getBaseContext()).addToRequestQueue(multipartRequest);
Проверьте полный код VolleyMultipartRequest
в моей сути.
Ответ 2
Просто хочу добавить в ответ. Я пытался понять, как добавить текстовые поля к телу, и создал для этого следующую функцию:
private void buildTextPart(DataOutputStream dataOutputStream, String parameterName, String parameterValue) throws IOException {
dataOutputStream.writeBytes(twoHyphens + boundary + lineEnd);
dataOutputStream.writeBytes("Content-Disposition: form-data; name=\"" + parameterName + "\"" + lineEnd);
dataOutputStream.writeBytes("Content-Type: text/plain; charset=UTF-8" + lineEnd);
dataOutputStream.writeBytes(lineEnd);
dataOutputStream.writeBytes(parameterValue + lineEnd);
}
Это работает довольно хорошо.
Ответ 3
Для тех, кто изо всех сил пытается отправить параметры utf-8 и все еще не везет, проблема, с которой я столкнулся, была в dataOutputStream, и измените код @RacZo на код ниже:
private void buildTextPart(DataOutputStream dataOutputStream, String parameterName, String parameterValue) throws IOException {
dataOutputStream.writeBytes(twoHyphens + boundary + lineEnd);
dataOutputStream.writeBytes("Content-Disposition: form-data; name=\"");
dataOutputStream.write(parameterName.getBytes("UTF-8"));
dataOutputStream.writeBytes(lineEnd);
dataOutputStream.writeBytes("Content-Type: text/plain; charset=UTF-8" + lineEnd);
dataOutputStream.writeBytes(lineEnd);
dataOutputStream.write(parameterValue.getBytes("UTF-8"));
dataOutputStream.writeBytes(lineEnd);
}
Ответ 4
Вот версия Kotlin класса, разрешающего многочастный запрос с Volley 1.1.1.
В основном он основан на решении @BNK, но немного упрощен. Я не заметил какой-либо конкретной проблемы с производительностью. Я загрузил 5-мегабайтную картинку примерно за 3 секунды.
class MultipartWebservice(context: Context) {
private var queue: RequestQueue? = null
private val boundary = "apiclient-" + System.currentTimeMillis()
private val mimeType = "multipart/form-data;boundary=$boundary"
init {
queue = Volley.newRequestQueue(context)
}
fun sendMultipartRequest(
method: Int,
url: String,
fileData: ByteArray,
fileName: String,
listener: Response.Listener<NetworkResponse>,
errorListener: Response.ErrorListener
) {
// Create multi part byte array
val bos = ByteArrayOutputStream()
val dos = DataOutputStream(bos)
buildMultipartContent(dos, fileData, fileName)
val multipartBody = bos.toByteArray()
// Request header, if needed
val headers = HashMap<String, String>()
headers["API-TOKEN"] = "458e126682d577c97d225bbd73a75b5989f65e977b6d8d4b2267537019ad9d20"
val request = MultipartRequest(
method,
url,
errorListener,
listener,
headers,
mimeType,
multipartBody
)
queue?.add(request)
}
@Throws(IOException::class)
private fun buildMultipartContent(dos: DataOutputStream, fileData: ByteArray, fileName: String) {
val twoHyphens = "--"
val lineEnd = "\r\n"
dos.writeBytes(twoHyphens + boundary + lineEnd)
dos.writeBytes("Content-Disposition: form-data; name=\"file\"; filename=\"$fileName\"$lineEnd")
dos.writeBytes(lineEnd)
dos.write(fileData)
dos.writeBytes(lineEnd)
dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd)
}
class MultipartRequest(
method: Int,
url: String,
errorListener: Response.ErrorListener?,
private var listener: Response.Listener<NetworkResponse>,
private var headers: MutableMap<String, String>,
private var mimeType: String,
private var multipartBody: ByteArray
) : Request<NetworkResponse>(method, url, errorListener) {
override fun getHeaders(): MutableMap<String, String> {
return if (headers.isEmpty()) super.getHeaders() else headers
}
override fun getBodyContentType(): String {
return mimeType
}
override fun getBody(): ByteArray {
return multipartBody
}
override fun parseNetworkResponse(response: NetworkResponse?): Response<NetworkResponse> {
return try {
Response.success(response, HttpHeaderParser.parseCacheHeaders(response))
} catch (e: Exception) {
Response.error(ParseError(e))
}
}
override fun deliverResponse(response: NetworkResponse?) {
listener.onResponse(response)
}
}
}
Ответ 5
Я нашел обертку из исходной библиотеки залпов, которую легче интегрировать для запросов из нескольких частей. Он также поддерживает загрузку данных из нескольких частей вместе с другими параметрами запроса. Поэтому я делюсь своим кодом для будущих разработчиков, которые могут столкнуться с проблемой, с которой я столкнулся (т.е. загружать данные из нескольких частей, используя залп вместе с некоторыми другими параметрами).
Добавьте следующую библиотеку в файл build.gradle
.
dependencies {
compile 'dev.dworks.libs:volleyplus:+'
}
Обратите внимание, что я удалил исходную библиотеку залпов из своего build.gradle
и вместо этого использовал вышеуказанную библиотеку, которая может обрабатывать как многочастные, так и обычные запросы, имеющие сходную технику интеграции.
Тогда мне просто нужно было написать следующий класс, который обрабатывает операцию запроса POST.
public class POSTMediasTask {
public void uploadMedia(final Context context, String filePath) {
String url = getUrlForPOSTMedia(); // This is a dummy function which returns the POST url for you
SimpleMultiPartRequest multiPartRequestWithParams = new SimpleMultiPartRequest(Request.Method.POST, url,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
Log.d("Response", response);
// TODO: Do something on success
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// TODO: Handle your error here
}
});
// Add the file here
multiPartRequestWithParams.addFile("file", filePath);
// Add the params here
multiPartRequestWithParams.addStringParam("param1", "SomeParamValue1");
multiPartRequestWithParams.addStringParam("param2", "SomeParamValue2");
RequestQueue queue = Volley.newRequestQueue(context);
queue.add(multiPartRequestWithParams);
}
}
Теперь выполните задачу следующим образом.
new POSTMediasTask().uploadMedia(context, mediaPath);
Вы можете загрузить один файл за один раз, используя эту библиотеку. Однако мне удалось загрузить несколько файлов, просто инициировав несколько задач.
Надеюсь, это поможет!
Ответ 6
изменил @Angga Ari Wijaya помощник, вместо того, чтобы использовать растровое изображение ImageView, использовать необработанное растровое изображение, на которое пользователь загрузил:
VolleyMultiPartЗапрос от Ангга ты
Хелпер класс
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.support.v4.content.ContextCompat;
import java.io.ByteArrayOutputStream;
/**
* Sketch Project Studio
* Created by Angga on 12/04/2016 14.27.
*/
public class AppHelper {
/**
* Turn drawable resource into byte array.
*
* @param context parent context
* @param id drawable resource id
* @return byte array
*/
public static byte[] getFileDataFromDrawable(Context context, Bitmap bitmap) {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 0, byteArrayOutputStream);
return byteArrayOutputStream.toByteArray();
}
/**
* Turn drawable into byte array.
*
* @param drawable data
* @return byte array
*/
public static byte[] getFileDataFromDrawable(Context context, Drawable drawable) {
Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, byteArrayOutputStream);
return byteArrayOutputStream.toByteArray();
}
}
Деятельность
private void initImageUpload() {
Intent it = new Intent();
it.setType("image/*");
String[] mimeTypes = {"image/jpeg", "image/png"};
it.setAction(Intent.ACTION_GET_CONTENT);
it.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
startActivityForResult(it, GALLERY_REQUEST_CODE);
}
@Override
public void onActivityResult(int requestCode,int resultCode,Intent data){
// Result code is RESULT_OK only if the user selects an Image
if (resultCode == Activity.RESULT_OK)
switch (requestCode){
case GALLERY_REQUEST_CODE:
//data.getData returns the content URI for the selected Image
filePath = data.getData();
// Set the Image in ImageView
try {
bitmap = null;
profilePicture.setImageBitmap(null);
bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), filePath);
profilePicture.setImageBitmap(bitmap);
} catch (IOException e) {
e.printStackTrace();
}
//profilePicture.setImageURI(selectedImage);
//Picasso.get().load(filePath).fit().into(profilePicture);
// upload image
uploadImage(filePath);
break;
}
}
private void uploadImage(Uri filePath) {
// getPath
String path = getPath(filePath);
// get the acess token
final String accessToken = logOutSingleton.getLoginToken();
VolleyMultipartRequest multipartRequest = new VolleyMultipartRequest(Request.Method.PATCH, baseUrl + "api/v0.6/user-profile/edit-image/", new Response.Listener<NetworkResponse>() {
@Override
public void onResponse(NetworkResponse response) {
String resultResponse = new String(response.data);
Log.d("response", resultResponse);
try {
JSONObject result = new JSONObject(resultResponse);
String url = result.getString("local_image");
logOutSingleton.setImgUrl(url);
} catch (JSONException e) {
e.printStackTrace();
}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError volleyError) {
String message = "";
if (volleyError instanceof NetworkError) {
message = "Cannot connect to Internet...Please check your connection!";
} else if (volleyError instanceof ServerError) {
message = "The server could not be found. Please try again after some time!!";
} else if (volleyError instanceof AuthFailureError) {
message = "Cannot connect to Internet...Please check your connection!";
} else if (volleyError instanceof ParseError) {
message = "Parsing error! Please try again after some time!!";
} else if (volleyError instanceof NoConnectionError) {
message = "Cannot connect to Internet...Please check your connection!";
} else if (volleyError instanceof TimeoutError) {
message = "Connection TimeOut! Please check your internet connection.";
}
Log.d("volleyError", message);
}
}) {
@Override
public Map<String, String> getHeaders() {
String bearer = "Bearer " + accessToken;
Map<String, String> params = new HashMap<>();
params.put("Authorization", bearer);
return params;
}
@Override
protected Map<String, DataPart> getByteData() {
Map<String, DataPart> params = new HashMap<>();
// file name could found file base or direct access from real path
// for now just get bitmap data from ImageView
params.put("local_image", new DataPart("local_image.png", AppHelper.getFileDataFromDrawable(getBaseContext(), bitmap), "image/png"));
return params;
}
};
MySingleton.getInstance(getApplicationContext()).addToRequestQueue(multipartRequest);
}
protected String getPath(Uri uri){
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
cursor.moveToFirst();
String document_id = cursor.getString(0);
document_id = document_id.substring(document_id.lastIndexOf(":")+1);
cursor.close();
cursor = getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
null, MediaStore.Images.Media._ID + " = ? ", new String[]{document_id}, null
);
cursor.moveToFirst();
String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
cursor.close();
return path;
}
Какая разница?
-
в реальном мире мы просим пользователя загрузить изображение, поэтому, если мы получим растровое изображение, оно может потерять качество или форму.
-
Я тестировал изменение изображения, и иногда растровое изображение, отправленное на сервер, было повреждено в некоторых пикселях из-за этого.
-
решение простое, так как хранение необработанного растрового изображения наActivityResult
@Angga Ari Wijaya, ваш шаблон MultipartRequest был идеальным, надеюсь, вы увидите это как дополнение к реальному окружению.
Рад был помочь
PS: в этом проекте я использую однонаправленный тип аутентификации, поэтому я переопределяю заголовки.