Статьи

HTTP-клиент Android: GET, POST, загрузка, выгрузка, многочастный запрос

Часто приложениям Android приходится обмениваться информацией с удаленным сервером. Самый простой способ — использовать протокол HTTP в качестве базы для передачи информации. Существует несколько сценариев, в которых протокол HTTP очень полезен, например загрузка изображения с удаленного сервера или загрузка некоторых двоичных данных на сервер. Android-приложение выполняет запрос GET или POST для отправки данных. В этом посте мы хотим проанализировать, как использовать HttpURLConnection для связи с удаленным сервером. Мы рассмотрим три основные темы:

  • GET и POST запросы
  • Скачать данные с сервера
  • Загрузить данные на сервер с помощью MultipartRequest

В качестве сервера мы будем использовать три простых сервлета, работающих внутри Tomcat 7.0. Мы не будем рассказывать, как создать сервлет с использованием API 3.0, но скоро будет доступен исходный код.

GET и POST запросы

GET и POST запросы являются базовыми блоками в протоколе HTTP. Чтобы сделать такого рода запросы, нам нужно сначала открыть соединение с удаленным сервером:

1
2
3
4
5
HttpURLConnection con = (HttpURLConnection) ( new URL(url)).openConnection();
con.setRequestMethod("POST");
con.setDoInput(true);
con.setDoOutput(true);
con.connect();

В первой строке мы получаем HttpURLConnection, в то время как в строке 2 мы устанавливаем метод и в конце подключаемся к серверу.

Как только мы открыли соединение, мы можем записать его, используя OutputStream.

1
con.getOutputStream().write( ("name=" + name).getBytes());

Как мы уже знаем, параметры записываются с использованием пары ключ-значение.

Последний шаг — чтение ответа с использованием InputStream:

1
2
3
4
5
InputStream is = con.getInputStream();
byte[] b = new byte[1024];
while ( is.read(b) != -1)
  buffer.append(new String(b));
con.disconnect();

Сейчас все очень просто, но мы должны помнить одну вещь: создание HTTP-соединения — это трудоемкая операция, которая может потребовать много времени, поэтому мы не можем запустить ее в главном потоке, иначе мы можем получить проблему ANR. Для ее решения мы можем использовать AsyncTask.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
private class SendHttpRequestTask extends AsyncTask<String, Void, String>{
 
  @Override
  protected String doInBackground(String... params) {
   String url = params[0];
   String name = params[1];
   String data = sendHttpRequest(url, name);
   return data;
  }
 
  @Override
  protected void onPostExecute(String result) {
   edtResp.setText(result);
   item.setActionView(null);  
  }
}

Запустив приложение мы получаем:

android_httpclient_post_get_1 android_httpclient_post_get_2

Как мы видим, мы публикуем имя на сервере, и он отвечает классическим «Hello….». На стороне сервера мы можем проверить, что сервер правильно получил наш параметр post:

Скачать данные с сервера

Один из наиболее распространенных сценариев — это когда приложение Android загружает некоторые данные с удаленного сервера. Мы можем предположить, что мы хотим загрузить изображение с сервера. В этом случае мы всегда должны использовать AsyncTask для завершения нашей операции, код показан ниже:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public byte[] downloadImage(String imgName) {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    try {
        System.out.println("URL ["+url+"] - Name ["+imgName+"]");
 
        HttpURLConnection con = (HttpURLConnection) ( new URL(url)).openConnection();
        con.setRequestMethod("POST");
        con.setDoInput(true);
        con.setDoOutput(true);
        con.connect();
        con.getOutputStream().write( ("name=" + imgName).getBytes());
 
        InputStream is = con.getInputStream();
        byte[] b = new byte[1024];
 
        while ( is.read(b) != -1)
            baos.write(b);
 
        con.disconnect();
    }
    catch(Throwable t) {
        t.printStackTrace();
    }
 
    return baos.toByteArray();
}

Этот метод вызывается так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
private class SendHttpRequestTask extends AsyncTask<String, Void, byte[]> {
 
    @Override
    protected byte[] doInBackground(String... params) {
        String url = params[0];
        String name = params[1];
 
        HttpClient client = new HttpClient(url);
        byte[] data = client.downloadImage(name);
 
        return data;
    }
 
    @Override
    protected void onPostExecute(byte[] result) {
        Bitmap img = BitmapFactory.decodeByteArray(result, 0, result.length);
        imgView.setImageBitmap(img);
        item.setActionView(null);
 
    }
 
}

Запустив приложение мы имеем:

Загрузить данные на сервер с помощью MultipartRequest

Это самая сложная часть в обработке http-соединения. Собственно HttpURLConnection не обрабатывает этот тип запроса. Может случиться, что приложение Android должно загрузить некоторые двоичные данные на сервер. Может быть, приложение должно загрузить изображение, например. В этом случае запрос становится более сложным, потому что «нормального» запроса недостаточно. Мы должны создать MultipartRequest.

MultipartRequest — это запрос, который выполняется различными частями, такими как параметры и двоичные данные. Как мы можем обработать этот запрос?

Итак, первый шаг — это открыть соединение, информирующее сервер, что мы хотим отправить некоторую двоичную информацию:

01
02
03
04
05
06
07
08
09
10
public void connectForMultipart() throws Exception {
    con = (HttpURLConnection) ( new URL(url)).openConnection();
    con.setRequestMethod("POST");
    con.setDoInput(true);
    con.setDoOutput(true);
    con.setRequestProperty("Connection", "Keep-Alive");
    con.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
    con.connect();
    os = con.getOutputStream();
}

В строках 6 и 7 мы указываем тип контента запроса и другое поле, называемое границей . Это поле представляет собой последовательность символов, используемую для разделения различных частей.

Для каждой части, которую мы хотим добавить, нам нужно указать, является ли она текстовой частью, такой как параметр post, или это файл (то есть двоичные данные).

01
02
03
04
05
06
07
08
09
10
11
public void addFormPart(String paramName, String value) throws Exception {
  writeParamData(paramName, value);
}
 
private void writeParamData(String paramName, String value) throws Exception {
    os.write( (delimiter + boundary + "\r\n").getBytes());
    os.write( "Content-Type: text/plain\r\n".getBytes());
    os.write( ("Content-Disposition: form-data; name=\"" + paramName + "\"\r\n").getBytes());;
    os.write( ("\r\n" + value + "\r\n").getBytes());
 
}

где

1
2
private String delimiter = "--";
private String boundary =  "SwA"+Long.toString(System.currentTimeMillis())+"SwA";

Чтобы добавить часть файла, мы можем использовать:

01
02
03
04
05
06
07
08
09
10
11
public void addFilePart(String paramName, String fileName, byte[] data) throws Exception {
    os.write( (delimiter + boundary + "\r\n").getBytes());
    os.write( ("Content-Disposition: form-data; name=\"" + paramName +  "\"; filename=\"" + fileName + "\"\r\n"  ).getBytes());
    os.write( ("Content-Type: application/octet-stream\r\n"  ).getBytes());
    os.write( ("Content-Transfer-Encoding: binary\r\n"  ).getBytes());
    os.write("\r\n".getBytes());
 
    os.write(data);
 
    os.write("\r\n".getBytes());
}

Итак, в нашем приложении мы имеем:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
private class SendHttpRequestTask extends AsyncTask<String, Void, String> {
 
    @Override
    protected String doInBackground(String... params) {
        String url = params[0];
        String param1 = params[1];
        String param2 = params[2];
        Bitmap b = BitmapFactory.decodeResource(UploadActivity.this.getResources(), R.drawable.logo);
 
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        b.compress(CompressFormat.PNG, 0, baos);
 
        try {
            HttpClient client = new HttpClient(url);
            client.connectForMultipart();
            client.addFormPart("param1", param1);
            client.addFormPart("param2", param2);
            client.addFilePart("file", "logo.png", baos.toByteArray());
            client.finishMultipart();
            String data = client.getResponse();
        }
        catch(Throwable t) {
            t.printStackTrace();
        }
 
        return null;
    }
 
    @Override
    protected void onPostExecute(String data) {           
        item.setActionView(null);
 
    }
 
}

Запустив его мы имеем:

android_tomcat_post_upload_log android_httpclient_post_upload

Исходный код @ github .

Ссылка: Android HTTP-клиент: GET, POST, Download, Upload, Multipart Request от нашего партнера JCG Франческо Аццолы в блоге Surviving с Android .