Android 的 HTTP 客户端:GET, POST, Download, Upload, Multipart Req…(Android HTTP Client: GET, POST, Download, Upload, Multipart Request)

手机/移动开发 William 297浏览 0评论
Often Android apps have to exchange information with a remote server. The easiest way is to use the
HTTP protocol as base to transfer information. There are several scenarios where the HTTP protocol is very useful like
downloading an image from a remote server or
uploading some binary data to the server. Android app performs GET or POST request to send data. In this post, we want to analyze how to use
HttpURLConnection to communicate with a remote server.

We will cover three main topics:

  • GET and POST requests
  • Download data from the server
  • Upload data to the server using MultipartRequest
As a server we will use three simple Servlet running inside Tomcat 7.0. We won’t cover how to create a Servlet using API 3.0 but the source code will be available soon.

Android应用通常都要和远程服务器进行交互。最简单的方式是使用基于HTTP协议来传输信息。在一些场景里HTTP协议是非常有用的,就像从远程服务器下载一个图像或者上传一些二进制数据到服务器。Android应用程序使用GET或POST请求来发送数据。在这篇文章,我们想要分析怎样用HttpURLConnection来和一个远程服务器通信。

我们包含这3个主要主题:

  • GET 和POST 请求
  • 从远程服务器下载数据
  • 使用MultipartRequest上传数据到服务器
我们会用运行在Tomcat7.0里的3个简单Servlet来作为一个简单的服务器。我们不会讲到怎样使用API3.0来创建Servlet但源代码现在就会提供。

GET and POST requests

GET and POST requests are the base blocks in HTTP protocol. To make this kind of requests we need first to open a connection toward the remove server:

HttpURLConnection con = (HttpURLConnection) ( new URL(url)).openConnection();
con.setRequestMethod("POST");
con.setDoInput(true);
con.setDoOutput(true);
con.connect();

In the first line we get the HttpURLConnection, while in the line 2 we set the method and at the end we connect to the server.

Once we have opened the connection we can write on it using the OutputStream.

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

As we already know parameters are written using key value pair.

The last step is reading the response, using the InputStream:

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

Everything is very simple by now, but we have to remember one thing: making an HTTP connection is a time consuming operation that could require long time sometime so we can’t run it in the main thread otherwise we could get a ANR problem. To solve it we can use an AsyncTask.

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);   
  }
}

Running the app we get:

android_httpclient_post_get_1 android_httpclient_post_get_2

As we can see we post a name to the server and it responds with the classic ‘Hello….’. On the server side we can check that the server received correctly our post parameter:

android_tomcat_post_log

GET 和POST 请求

GET和POST是HTTP协议里的基本模块。要使用这些类型的请求,我们首先需要打开一个连接到远程服务器连接:

HttpURLConnection con = (HttpURLConnection) ( new URL(url)).openConnection();
con.setRequestMethod("POST");
con.setDoInput(true);
con.setDoOutput(true);
con.connect();

我们在第一行得到HttpURLConnection,而在第二行我们设置了方法同时在最后连接到服务器。

一旦我们打开了连接,我们使用OutputStream在它上面写入东西。

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

我们已经知道参数是用键-值对来写入。

最后一步是使用InputStream读取返回。

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

现在所有东西都非常简单,但我们要记住一件事情:创建一个HTTP连接比较耗时的操作,有时候可能需要很长时间所以我们不能在主线程里运行它,否则我们会得到一个ANR
(Application Not Responding)问题。要解决这个问题,我们可以用一个AsyncTask。

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);   
  }
}

运行APP我们得到了:

android_httpclient_post_get_1 android_httpclient_post_get_2

我们可以看到传了一个name到服务器同时它回复了一个典型的“Hello…”。在服务器端我们可以检查服务器是否正确接收到我们传递的参数。

android_tomcat_post_log

Download data from server

One of the most common scenario is when an Android App has to
download some data from a remote sever. We can suppose that we want to download an image from the server. In this case we have always to use an
AsyncTask to complete our operation, the code is shown below:

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();
}

This method is called in this way:

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);
        
    }
   
}

Running the app we have:

android_httpclient_post_download

从服务器下载数据

最常见的场景之一是一个Android APp需要从一个远程服务器下载一些数据。我们假设我们需要从服务器下载一个图片。在这种情况下我们就总得使用
AsyncTask来完成我们的操作,代码如下: 

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();
}

这个方法用下面的方式来调用:

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);
        
    }
   
}

运行我们的App:

android_httpclient_post_download

Upload data to the server using MultipartRequest

This the most complex part in handling http connection. Natively HttpURLConnection doesn’t handle this type of request. It can happen that an Android App has to upload some binary data to the server. It can be that an app has to upload an image for example. In this case the request get more complex, because a “normal” request isn’t enough. We have to create a MultipartRequest.

A MultipartRequest is a request that is made by different parts like parameters and binary data. How can we handle this request?

Well the first step is opening a connection informing the server we wants to send some binary info:

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();
}

In the line 6 and 7 we specify the request content-type and another field called
boundary. This field is a char sequence used to separate different parts.

For each part we want to add we need to specify if it is text part like post parameter or it is a file (so binary data).

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());
    
}

where

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

To add a file part we can use:

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());
}

So in our app we have:

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);        
    }
   
}

Running it we have:

android_tomcat_post_upload_log android_httpclient_post_upload

Source code @
github.

使用MultipartRequest来上传数据到服务器

这是处理http连接里最复杂的部分。原生的HttpURLConnection不处理这类型的请求。它发生在一个Android App要上传一些二进制数据到服务器。它也可能是一个app上传一个图片。在这种情况下,请求会更加复杂,因为一个“普通”的请求并不足够。我们要创建一个MultipartRequest。

一个MultipartRequest是由各种不同部分组成的请求,就像参数和二进制数据。我们要怎样去处理这些请求呢?

第一部就是打开一个连接告诉服务器我们想传递一些二进制信息:

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行,我们指定了请求的content-type和另外一个叫boundary的字段。这个字段是一个字符串序列来分割不同部分的。

对于我们要加入的每个部分,我们需要指定它否是一个文本部分就像post参数或者它是一个而文件(二进制数据)。

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());
    
}

这里 

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

加入一个文件我们能够这样使用:

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());
}

因此在我们的App里就这样:

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.


via:oschina

转载请注明:AspxHtml学习分享网 » Android 的 HTTP 客户端:GET, POST, Download, Upload, Multipart Req…(Android HTTP Client: GET, POST, Download, Upload, Multipart Request)

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址