среда, 1 июля 2015 г.

Android. Upload file to server.

Многие мобильные приложения оперируют изображениями и другими файлами, которые нужно отправлять на сервер. В системе 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();






9 комментариев:

  1. Классный пример!!! Обыскала весь интернет. Из всего интернета только он один рабочий!
    Спасибо огромное!!!

    Если еще подскажите, как в нем еще передать дополнительный параметр (String), так вообще цены не будет!

    ОтветитьУдалить
    Ответы
    1. Добавить после 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){

      }
      }

      Удалить
  2. Огромное спасибо за этот класс. Наконец-то я разобрался с файловыми потоками. Долго не мог в этом разобраться. Взял Ваш класс в свою библиотеку.

    ОтветитьУдалить
    Ответы
    1. Я рад помочь) но это очень старая реализация. Попробуйте воспользоваться библиотекой Retrofit для общения с сервером. В ней реализована и отправка файлов.

      Удалить
    2. Да, очень прошу покажите подробно как реализовать загрузку изображений на сервер с помощью Retrofit? Очень желательно и с серверной частью, а то нигде таких примеров нет.

      Удалить
  3. Опишите более подробно как его использовать, новичок может не сообразить

    ОтветитьУдалить
  4. Добрый день!

    Каким образом можно получить результат выполнения класса? Передача прошла удачно или нет.

    ОтветитьУдалить
  5. Ураааааа!!! Работает!!! О-о-о-о, мудрейший из всех мудрецов, добрейший из всех добрых - спасибо огромное! Три вечера убил, пробуя разные коды из интернета, и только Ваш код, уважаемый автор, работает. Низкий поклон и с наилучшими пожеланиями. Всего хорошего Вам и Вашим близким, уважаемый)))

    ОтветитьУдалить
  6. Думаю, что BufferedReader надо бы явно закрыть.

    ОтветитьУдалить