Многие мобильные приложения оперируют изображениями и другими файлами, которые нужно отправлять на сервер. В системе Android это не является тривиальной задачей.
Общая теория и проблемы
Обычно для отправки файлов на сервер используется POST запрос протокола HTTP с типом содержимого multipart/form-data.
Сообщение такого типа состоит из нескольких частей, каждая из которых представляет собой содержимое некоторого элемента формы. Каждый элемент имеет заголовок, в котором указано имя элемента и тип контента. Передаваемый файл является одним их таких элементов.
Данный тип запроса используется и для передачи файлов с Android устройств. Однако Android не умеет создавать данные для передачи такого запроса в отличие от веб браузеров. Это приходится делать вручную.
В сети существуют библиотеки для формирования multipart запросов, однако они очень сложны в установке, что не стоит потраченного времени.
Реализация
Приведу сразу весь класс для загрузки изображений с комментариями. Класс представляет из себя AsyncTask, в котором выполняется POST запрос с типом содержимого multipart. Передаваемый файл записывается как один элемент multipart запроса.
/**
* Загружает файл на сервер
*/
public class FilesUploadingTask extends AsyncTask<Void, Void, String> {
// Конец строки
private String lineEnd = "\r\n";
// Два тире
private String twoHyphens = "--";
// Разделитель
private String boundary = "----WebKitFormBoundary9xFB2hiUhzqbBQ4M";
// Переменные для считывания файла в оперативную память
private int bytesRead, bytesAvailable, bufferSize;
private byte[] buffer;
private int maxBufferSize = 1*1024*1024;
// Путь к файлу в памяти устройства
private String filePath;
// Адрес метода api для загрузки файла на сервер
public static final String API_FILES_UPLOADING_PATH = "127.0.0.1/upload";
// Ключ, под которым файл передается на сервер
public static final String FORM_FILE_NAME = "file1";
public FilesUploadingTask(String filePath) {
this.filePath = filePath;
}
@Override
protected String doInBackground(Void... params) {
// Результат выполнения запроса, полученный от сервера
String result = null;
try {
// Создание ссылки для отправки файла
URL uploadUrl = new URL(API_FILES_UPLOADING_PATH);
// Создание соединения для отправки файла
HttpURLConnection connection = (HttpURLConnection) uploadUrl.openConnection();
// Разрешение ввода соединению
connection.setDoInput(true);
// Разрешение вывода соединению
connection.setDoOutput(true);
// Отключение кеширования
connection.setUseCaches(false);
// Задание запросу типа POST
connection.setRequestMethod("POST");
// Задание необходимых свойств запросу
connection.setRequestProperty("Connection", "Keep-Alive");
connection.setRequestProperty("Content-Type", "multipart/form-data;boundary="+boundary);
// Создание потока для записи в соединение
DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream());
// Формирование multipart контента
// Начало контента
outputStream.writeBytes(twoHyphens + boundary + lineEnd);
// Заголовок элемента формы
outputStream.writeBytes("Content-Disposition: form-data; name=\"" +
FORM_FILE_NAME + "\"; filename=\"" + filePath + "\"" + lineEnd);
// Тип данных элемента формы
outputStream.writeBytes("Content-Type: image/jpeg" + lineEnd);
// Конец заголовка
outputStream.writeBytes(lineEnd);
// Поток для считывания файла в оперативную память
FileInputStream fileInputStream = new FileInputStream(new File(filePath));
bytesAvailable = fileInputStream.available();
bufferSize = Math.min(bytesAvailable, maxBufferSize);
buffer = new byte[bufferSize];
// Считывание файла в оперативную память и запись его в соединение
bytesRead = fileInputStream.read(buffer, 0, bufferSize);
while (bytesRead > 0) {
outputStream.write(buffer, 0, bufferSize);
bytesAvailable = fileInputStream.available();
bufferSize = Math.min(bytesAvailable, maxBufferSize);
bytesRead = fileInputStream.read(buffer, 0, bufferSize);
}
// Конец элемента формы
outputStream.writeBytes(lineEnd);
outputStream.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);
// Получение ответа от сервера
int serverResponseCode = connection.getResponseCode();
// Закрытие соединений и потоков
fileInputStream.close();
outputStream.flush();
outputStream.close();
// Считка ответа от сервера в зависимости от успеха
if(serverResponseCode == 200) {
result = readStream(connection.getInputStream());
} else {
result = readStream(connection.getErrorStream());
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (ProtocolException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
// Считка потока в строку
public static String readStream(InputStream inputStream) throws IOException {
StringBuffer buffer = new StringBuffer();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String line;
while ((line = reader.readLine()) != null) {
buffer.append(line);
}
return buffer.toString();
}
}
Использование
Чтобы загрузить файл на сервер, запустите выполнение AsyncTask:
new FilesUploadingTask(filePath).execute();
Классный пример!!! Обыскала весь интернет. Из всего интернета только он один рабочий!
ОтветитьУдалитьСпасибо огромное!!!
Если еще подскажите, как в нем еще передать дополнительный параметр (String), так вообще цены не будет!
Добавить после DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream());
УдалитьBufferedWriter outputStream2 = new BufferedWriter(new OutputStreamWriter(outputStream, "UTF-8"));
addFormField(outputStream2, "key", "value");
И объявить функцию
public void addFormField(BufferedWriter dos, String parameter, String value){
try {
dos.write(twoHyphens + boundary + lineEnd);
dos.write("Content-Disposition: form-data; name=\""+parameter+"\"" + lineEnd);
dos.write("Content-Type: text/plain; charset=UTF-8" + lineEnd);
dos.write(lineEnd);
dos.write(value + lineEnd);
dos.flush();
}
catch(Exception e){
}
}
Огромное спасибо за этот класс. Наконец-то я разобрался с файловыми потоками. Долго не мог в этом разобраться. Взял Ваш класс в свою библиотеку.
ОтветитьУдалитьЯ рад помочь) но это очень старая реализация. Попробуйте воспользоваться библиотекой Retrofit для общения с сервером. В ней реализована и отправка файлов.
УдалитьДа, очень прошу покажите подробно как реализовать загрузку изображений на сервер с помощью Retrofit? Очень желательно и с серверной частью, а то нигде таких примеров нет.
УдалитьОпишите более подробно как его использовать, новичок может не сообразить
ОтветитьУдалитьДобрый день!
ОтветитьУдалитьКаким образом можно получить результат выполнения класса? Передача прошла удачно или нет.
Ураааааа!!! Работает!!! О-о-о-о, мудрейший из всех мудрецов, добрейший из всех добрых - спасибо огромное! Три вечера убил, пробуя разные коды из интернета, и только Ваш код, уважаемый автор, работает. Низкий поклон и с наилучшими пожеланиями. Всего хорошего Вам и Вашим близким, уважаемый)))
ОтветитьУдалитьДумаю, что BufferedReader надо бы явно закрыть.
ОтветитьУдалить