Android:HTTP协议编程(一)

  大多数的涉及到网络交互的Android App都是采用HTTP协议发送和接收数据。Android引入了多种HTTP协议编程方式,如Apache HTTP Client,HttpURLConnection,开源框架xUtils,Google官方的Volley等等。日前,笔者开发中的项目Frutiger v1.0进展到构建网络交互的阶段,不想再做伸手党套用xUtils框架,于是抽点时间来研究HttpURLConnection,Basic-HTTP-Client,Volley三种方式。

  笔者选择了登录问题作为研究客户端与服务器交互的场景。首先客户端向服务器提交用户名(userName)和密码(password)两个参数,然后由服务器对提交的数据进行检查,产生“用户不存在!”,“密码错误!”,“登录成功!”三种可能的结果返回给客户端。

  下面给出服务器端的Servlet示例代码:

 1 public class SignInServlet extends HttpServlet {
 2
 3     /**
 4      * 响应客户端的GET请求;
 5      */
 6     protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
 7         PrintWriter writer = response.getWriter();
 8         writer.write("登录页面!");
 9     }
10
11     /**
12      * 响应客户端的POST请求;
13      */
14     protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
15         String responseResult = "";
16         /**
17          * 获取客户端提交的数据;
18          */
19         System.out.println(request.getRemoteAddr());
20         String userName = request.getParameter("userName");
21         String password = request.getParameter("password");
22         System.out.println("userName:" + userName + " password:" + password + "!");
23         responseResult = handleSignInData(userName, password);
24
25         response.setCharacterEncoding("UTF-8");
26         PrintWriter writer = response.getWriter();
27         writer.write(responseResult);
28     }
29
30     private String handleSignInData(String userName, String password){
31         String responseResult = "";
32         if(!userName.equals("test")){
33             responseResult = "用户不存在!";
34         }else if(!password.equals("test")){
35             responseResult = "密码错误!";
36         }else{
37             responseResult = "登录成功!";
38         }
39         return responseResult;
40     }
41
42 }

  接下来将分别对HttpURLConnection,Basic-HTTP-Client,Volley三种方式的具体实现进行详细的讲解。

  (一)HttpURLConnection

  HttpURLConnection被用来通过网络发送和接收数据,也可以用来发送和接收长度未知的流数据。这段概念指出了HttpURLConnection的两种常见用途,一是向服务器提交表单数据,二是向服务器上传、下载文本等流数据。

  对HttpURLConnection的使用可以按照下面的步骤:

  (1)调用URL.openConnection()获得URLConnection实例对象,然后将其转换为HttpURLConnection;

  (2)设置Request的相关内容,包括请求的URL,请求头,请求体。若Request包含请求体,则setDoOutPut(true),同时通过getOutPutStream()获得输出流来向服务器提交数据;

  (3)读取来自服务器的响应,通过getInputStream()获得来自服务器的响应的内容(Content);

  (4)通过disconnect()关闭网络连接,释放连接资源。

  套用上面的步骤,解决登录问题的代码如下:

  1 public class SignInActivity extends Activity {
  2     private TextView mTextView;
  3     private EditText userNameText;
  4     private EditText passwordText;
  5     private Button mButton;
  6     private Handler handler;
  7
  8     @Override
  9     protected void onCreate(Bundle savedInstanceState) {
 10         super.onCreate(savedInstanceState);
 11         setContentView(R.layout.activity_sign_in);
 12         initView();
 13         mButton.setOnClickListener(new OnClickListener() {
 14
 15             @Override
 16             public void onClick(View v) {
 17                 signIn();
 18             }
 19         });
 20
 21         handler = new Handler() {
 22             @Override
 23             public void handleMessage(Message msg) {
 24                 switch (msg.what) {
 25                 case 1:
 26                     Bundle data = msg.getData();
 27                     String result = data.getString("result");
 28                     mTextView.setVisibility(View.VISIBLE);
 29                     mTextView.setText(result);
 30                     break;
 31                 }
 32             }
 33         };
 34     }
 35
 36     private void initView() {
 37         mTextView = (TextView) this.findViewById(R.id.signin_textview_result);
 38         userNameText = (EditText) this
 39                 .findViewById(R.id.signin_edittext_username);
 40         passwordText = (EditText) this
 41                 .findViewById(R.id.signin_edittext_password);
 42         mButton = (Button) this.findViewById(R.id.signin_button_login);
 43     }
 44
 45     private void signIn() {
 46         new Thread(new Runnable() {
 47
 48             @Override
 49             public void run() {
 50                 HttpURLConnection urlConnection = null;
 51                 try {
 52                     URL url = new URL("http://192.168.31.221:8080/Frutiger_v1.0/SignIn.do");
 53                     urlConnection = (HttpURLConnection)url.openConnection();
 54                     urlConnection.setDoOutput(true);
 55                     urlConnection.setDoInput(true);
 56
 57                     OutputStream out = new BufferedOutputStream(urlConnection
 58                             .getOutputStream());
 59                     writeStream(out);
 60
 61                     String result = "网络异常,请检查网络连接...";
 62                     int responseCode = urlConnection.getResponseCode();
 63                     Log.i(TAG, "responseCode:" + responseCode);
 64                     if(responseCode == 200){
 65                         InputStream in = new BufferedInputStream(urlConnection.getInputStream());
 66                         result = readStream(in);
 67                     }
 68
 69                     /**
 70                      * 更新UI主线程控件;
 71                      */
 72                     Message msg = new Message();
 73                     msg.what = 1;
 74                     Bundle data = new Bundle();
 75                     data.putString("result", result);
 76                     msg.setData(data);
 77                     handler.sendMessage(msg);
 78                 } catch (IOException e) {
 79                     e.printStackTrace();
 80                 }
 81                 finally{
 82                     urlConnection.disconnect();
 83                 }
 84             }
 85         }).start();
 86     }
 87
 88     private void writeStream(OutputStream out) {
 89         Map<String, String > params = getRequestParams();
 90         byte[] buffer = wrapRequestParams(params);
 91         try {
 92             out.write(buffer);
 93         } catch (IOException e) {
 94             e.printStackTrace();
 95         }
 96         finally{
 97             try {
 98                 out.close();
 99             } catch (IOException e) {
100                 e.printStackTrace();
101             }
102         }
103     }
104
105     private Map<String, String > getRequestParams(){
106         Map<String, String > params = new HashMap<String, String>();
107         params.put("userName", userNameText.getText().toString());
108         params.put("password", passwordText.getText().toString());
109         return params;
110     }
111
112     private byte[] wrapRequestParams(Map<String, String> params){
113         StringBuffer stringBuffer = new StringBuffer();
114         for(Map.Entry<String, String> entry : params.entrySet()){
115             stringBuffer.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
116         }
117         stringBuffer.deleteCharAt(stringBuffer.length() - 1);
118         return stringBuffer.toString().getBytes();
119     }
120
121     private String readStream(InputStream in) {
122         String result = "";
123         byte[] buffer  = new byte[256];
124         int len;
125         try {
126             while((len = in.read(buffer)) != -1){
127                 result += new String(buffer, 0, len, "UTF-8");
128             }
129         } catch (IOException e) {
130             e.printStackTrace();
131         }
132         finally{
133             try {
134                 in.close();
135             } catch (IOException e) {
136                 e.printStackTrace();
137             }
138         }
139         return result;
140     }
141
142 }

  根据上述代码可知,点击Sign In按钮后触发相应的signIn()方法,在signIn()中打开了新的线程来向服务器提交数据,接收到服务器端的响应后,将响应结果转换为字符串,通过Handler来更新主线程的TextView。当然,要想成功执行上述代码,还需要加上<uses-permission android:name="android.permission.INTERNET" />网络权限。要注意的是使用HttpURLConnection连接服务器,必须要在打开流读取、写入数据后及时关闭相应的输入、输出流,否则可能得不到想要的结果。

  笔者把上面代码中涉及到网络的操作剥离了出来,封装成了工具类,需要的读者可以参考下:

  1 package org.warnier.zhang.support.v1.net;
  2
  3 import java.io.BufferedInputStream;
  4 import java.io.BufferedOutputStream;
  5 import java.io.InputStream;
  6 import java.io.OutputStream;
  7 import java.net.HttpURLConnection;
  8 import java.net.URL;
  9 import java.util.Map;
 10
 11 /**
 12  * 处理POST请求的HTTP协议工具类,调用HttpPost的类必须实现
 13  * HttpPost.CallBacks回调接口,来处理HTTP响应。
 14  * 调用HttpPost类的步骤:
 15  * (1)实现HttpPost.CallBacks回调接口;
 16  * (2)传递spec参数;
 17  * (3)设置请求体params参数;
 18  * (4)调用send()方法;
 19  * @author Warnier-zhang
 20  *
 21  */
 22 public class HttpPost {
 23
 24     private String spec;
 25     private Map<String, String> params;
 26     private CallBacks callBacks;
 27
 28     public HttpPost(CallBacks callBacks){
 29         this.callBacks = callBacks;
 30     }
 31
 32     public HttpPost(CallBacks callBacks, String spec) {
 33         this(callBacks);
 34         this.spec = spec;
 35     }
 36
 37     /**
 38      * 设置URL字符串;
 39      */
 40     public void setSpec(String spec) {
 41         this.spec = spec;
 42     }
 43
 44     /**
 45      * 设置POST请求的请求体;
 46      */
 47     public void setParameters(Map<String, String> params) {
 48         this.params = params;
 49     }
 50
 51     /**
 52      * 格式化POST请求的请求体;
 53      */
 54     private byte[] formatParameters(Map<String, String> params) {
 55         StringBuffer stringBuffer = new StringBuffer();
 56         for (Map.Entry<String, String> entry : params.entrySet()) {
 57             stringBuffer.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
 58         }
 59         stringBuffer.deleteCharAt(stringBuffer.length() - 1);
 60         return stringBuffer.toString().getBytes();
 61     }
 62
 63     /**
 64      * 发送POST请求;
 65      */
 66     public void send(){
 67         send(callBacks);
 68     }
 69
 70     /**
 71      * 处理发送POST请求细节;
 72      */
 73     private void send(CallBacks callBacks){
 74         HttpURLConnection httpURLConnection = null;
 75         try {
 76             URL url = new URL(spec);
 77             httpURLConnection = (HttpURLConnection)url.openConnection();
 78             httpURLConnection.setDoOutput(true);
 79             httpURLConnection.setDoInput(true);
 80
 81             OutputStream outputStream = new BufferedOutputStream(httpURLConnection.getOutputStream());
 82             byte[] requestBody = formatParameters(params);
 83             outputStream.write(requestBody);
 84             outputStream.close();
 85
 86             int responseCode = httpURLConnection.getResponseCode();
 87             if(responseCode == 200){
 88                 InputStream inputStream = new BufferedInputStream(httpURLConnection.getInputStream());
 89                 callBacks.handleResponse(inputStream);
 90                 inputStream.close();
 91             }
 92         } catch (Exception e) {
 93             e.printStackTrace();
 94         }finally{
 95             httpURLConnection.disconnect();
 96         }
 97     }
 98
 99     /**
100      * 调用类HttpPost的父类必须实现该接口,以处理HTTP请求的响应;
101      */
102     public static interface CallBacks{
103         void handleResponse(InputStream inputStream);
104     }
105 }

  HttpPost.Callbacks接口定义为泛型的效果可能更好。

  其实提到Android HTTP Client时,始终存在一个问题,即Apache HTTP Client和HttpURLConnection选择哪个?由于在Android的早期版本中(Android 2.2之前),HttpURLConnection存在一些“令人厌恶”,搞不定的bug,大而全的Apache HTTP Client成为进行网络操作的不二选择。但是随着Android 2.3(Gingerbread )的发布,HttpURLConnection得到了很大的改善,能够对服务器端的响应进行透明压缩、缓存,加快了交互的速度,而Apache HTTP Client反而因其大而全在完善api过程中很难保证前后版本的一致性,导致Android官方停止了对其更新。因此,考虑现下的知名ROM都是基于Android 4.4及更高的版本定制的,是时候抛弃Apache HTTP Client了。

  (二)Basic-HTTP-Client

  既然HttpURLConnection已经很好了,为什么还需要Basic-HTTP-Client?

  HttpURLConnection是不错,但是HttpURLConnection是一个低级别的api,在使用的过程中,每次都需要对URL编码,设置内容类型,处理输入、输出流,以及捕获各种异常。如果不对代码进行适当的封装,很容易会造成重复的代码,产生耦合。所以为了消除每次HTTP请求都要重复上述操作的问题,有必要将涉及到网络的操作封装成一个单独的模块,从系统中独立出来。Android大神David M.Chandler就替我们做了这样一件事,官方链接:https://github.com/turbomanage/basic-http-client。

  Basic-HTTP-Client具有如下的特点:

  (1)能够发送GET,POST,PUT,和DELETE请求;

  (2)能够发送异步的请求;

  (3)能够用RequestHandler来定制请求;

  (4)能够将请求自动包裹在Android异步任务中。

  采用Basic-HTTP-Client解决登录问题的部分示例代码如下:

1 BasicHttpClient httpClient = new BasicHttpClient();
2 ParameterMap params = httpClient.newParams()
3     .add("userName", ameText.getText().toString())
4     .add("password", wordText.getText().toString());
5 httpClient.setReadTimeout(3000);
6 HttpResponse httpResponse = httpClient.post(Config.SIGN_IN_URL, params);

  (三)Volley

  Volley是Google在Google I/O 2013大会上推出的全新、高效的Android HTTP协议通信框架。Volley能够简单地发送HTTP请求,也能轻松加载网络上的图片。Volley的设计目标是“Great for RPC-style network operations that populate UI; Fine for background RPCs; Terrible for larger payloads。”,也就是说Volley是专门为数据量不大,但是网络操作频繁设计的,对于像下载大文件这样的大数据量操作,Volley不适用。

  (1)下载Volley

  可以借助于Git来获得Volley库:git clone https://android.googlesource.com/platform/frameworks/volley。这种方式获得的Volley没有打包成.jar文档,可以自己打包或者到Google I/O 2013的开源Android app中找到.jar包。

  (2)Volley教程

  Volley的用法很简单,下面简单介绍发送一个HTTP请求,同时接收一个HTTP响应。在Volley中,网络请求是有一个RequestQueue(请求队列)来管理的,最好的使用RequestQueue的方式是将RequestQueue初始化为一个Singleton(单件实例),如下:

 RequestQueue mRequestQueue = Volley.newRequestQueue(this);

接下来,通过向RequestQueue中添加,移除Request来发送或取消HTTP请求。为了发送请求,我们需要创建一个StringRequest对象。

 1  StringRequest stringRequest = new StringRequest("http://www.cnblogs.com/Warnier-zhang/", new Response.Listener<String>() {
 2
 3    @Override
 4       public void onResponse(String response) {
 5           Log.i(TAG, response);
 6         }
 7       }, new Response.ErrorListener() {
 8
 9         @Override
10           public void onErrorResponse(VolleyError error) {
11               Log.i(TAG, error.getMessage());
12               }
13        });
14        mRequestQueue.add(request);

  可见上面的代码创建了一个StringRequest实例,构造方法需要三个参数,第一个是请求的URL,第二个是对请求的响应进行监听的监听器,第三个是对请求错误进行监听的监听器。

  当然,上面是发送了一个GET请求,如果要发送一个POST请求应该怎么做?其实,StringRequest还提供了一个构造方法可以设置HTTP协议的请求方法,例如:

1 StringRequest stringRequest = new StringRequest(method, url, listener, errorListener);

  通过设置method为Method.Post来指定POST请求方式。那么如何向服务器提交参数呢?遗憾的是,StringRequest本身并没有提供设置POST请求参数的方法,但是当StringRequest请求被发送时会调用其父类Request的getParams()方法获取提交给服务器的POST参数。因此,我们可以通过重写Request类中的getParams()方法来设置POST请求的参数,例如:

 1 StringRequest stringRequest = new StringRequest(Method.POST, url, listener, errorListener){
 2
 3     @Override
 4     protected Map<String, String> getParams() throws AuthFailureError {
 5       Map<String, String> params = new HashMap<>();
 6        params.put("userName", userName);
 7        params.put("password", password);
 8        return params;
 9     }
10
11 };    

  采用Volley解决登录问题的完整代码如下:

public class SignInActivity3 extends Activity {
    private TextView mTextView;
    private EditText userNameText;
    private EditText passwordText;
    private Button mButton;
    private Handler handler;
    private RequestQueue mRequestQueue;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sign_in);
        initView();
        mButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                signIn();
            }
        });

        handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                case 1:
                    Bundle data = msg.getData();
                    String result = data.getString("result");
                    mTextView.setVisibility(View.VISIBLE);
                    mTextView.setText(result);
                    break;
                }
            }
        };

        mRequestQueue = Volley.newRequestQueue(this);
    }

    private void initView() {
        mTextView = (TextView) this.findViewById(R.id.signin_textview_result);
        userNameText = (EditText) this
                .findViewById(R.id.signin_edittext_username);
        passwordText = (EditText) this
                .findViewById(R.id.signin_edittext_password);
        mButton = (Button) this.findViewById(R.id.signin_button_login);
    }

    private void signIn() {
        new Thread(new Runnable() {

            @Override
            public void run() {
                StringRequest request = new StringRequest(Method.POST,
                        Config.SIGN_IN_URL, new Response.Listener<String>() {

                            @Override
                            public void onResponse(String response) {
                                Message msg = new Message();
                                msg.what = 1;
                                Bundle data = new Bundle();
                                Log.i("Response >>", response);
                                String result = "网络异常,请检查网络连接...";
                                try {
                                    result = new String(response.getBytes("ISO-8859-1"), "UTF-8");
                                } catch (UnsupportedEncodingException e) {
                                    e.printStackTrace();
                                }
                                data.putString("result", result);
                                msg.setData(data);
                                handler.sendMessage(msg);
                            }
                        }, new Response.ErrorListener() {

                            @Override
                            public void onErrorResponse(VolleyError error) {
                                Log.i("Response Error >>", error.getMessage());
                            }
                        }) {

                    /**
                     * 重写父类的Request方法,添加POST请求参数;
                     */
                    @Override
                    protected Map<String, String> getParams()
                            throws AuthFailureError {
                        Map<String, String> params = new HashMap<>();
                        params.put("userName", userNameText.getText()
                                .toString());
                        params.put("password", passwordText.getText()
                                .toString());
                        return params;
                    }

                };
            }
        }).start();
    }

}

  到此,本片博客就结束了。接下来的Android HTTP协议编程的内容将会重点研究Volley的使用方法。本篇博客中涉及到的源码包下载链接:http://pan.baidu.com/s/10i1u2 密码: p2jy。

  由于笔者水平有限,可能有些错误,欢迎读者留言交流。

时间: 2024-11-08 10:30:53

Android:HTTP协议编程(一)的相关文章

Android 的网络编程

android的网络编程分为2种:基于socket的,和基于http协议的. 基于socket的用法 服务器端: 先启动一个服务器端的socket     ServerSocket svr = new ServerSocket(8989); 开始侦听请求 Socket s = svr.accept(); 取得输入和输出 DataInputStream dis = new DataInputStream(s.getInputStream()); DataOutputStream dos = new

Android C++高级编程

简介 <Android C++高级编程--使用NDK>提供了Java原生接口(JNI)的概述.Bionic API.POSIX 线程和套接字.C++支持.原生图形和声音API以及NEON/SIMD优化.   本文将记录主要知识点. 详解 1.深入了解Android NDK Android NDK不是一个独有的工具:它是一个包含API.交叉编译器.链接程序.调试器.构建工具.文档和实例应用程序的综合工具集.组件如下:ARM.x86和MIPS交叉编译器构建系统JAVA原生接口头文件C库Math库P

android开发常见编程错误总结

1.设置TextView的文本颜色 1 2 3 TextView tv; ... tv.setTextColor(R.color.white); 其实这样设置的颜色是 R.color.white的资源ID值所代表的颜色值,而不是资源color下的white颜色值:正确的做法如下: 1 tv.setTextColor(getResources().getColor(R.color.white)); 这个出错的概率满高的,就是因为二者都是int类,导致编译器不报错. 2.读取Cursor中的值 1

Android中多线程编程(四)AsyncTask类的详细解释(附源码)

Android中多线程编程中AsyncTask类的详细解释 1.Android单线程模型 2.耗时操作放在非主线程中执行 Android主线程和子线程之间的通信封装类:AsyncTask类 1.子线程中更新UI 2.封装.简化异步操作. 3.AsyncTask机制:底层是通过线程池来工作的,当一个线程没有执行完毕,后边的线程是无法执行的.必须等前边的线程执行完毕后,后边的线程才能执行. AsyncTask类使用注意事项: 1.在UI线程中创建AsyncTask的实例 2.必须在UI线程中调用As

Android开发经典书籍下载——《Android 4高级编程》《疯狂Android讲义》《Android应用开发详解(郭宏志)》《Android应用案例开发大全》《Android 3D游戏开发技术》

这是我收集的关于android开发方面的经典书籍,高清PDF电子版,可以在我的百度网盘免费下载,希望对需要的朋友有帮助. 目录: <Android 4高级编程>(附完整源代码) <疯狂Android讲义> <Android应用开发详解(郭宏志)> <Android应用案例开发大全> <Android 3D游戏开发技术> <Android内核剖析 柯元旦> <深入理解Android  卷1> <深入理解Android

Android Xmpp协议讲解

http://www.cnblogs.com/luxiaofeng54/archive/2011/03/14/1984026.html Android Xmpp协议讲解,布布扣,bubuko.com

Java中的UDP协议编程

一. UDP协议定义   UDP协议的全称是用户数据报,在网络中它与TCP协议一样用于处理数据包.在OSI模型中,在第四层——传输层,处于IP协议的上一层.UDP有不提供数据报分组.组装和不能对数据包的排序的缺点,也就是说,当报文发送之后,是无法得知其是否安全完整到达的. 二. 使用UDP的原因 它不属于连接型协议,因而具有资源消耗小,处理速度快的优点,所以通常音频.视频和普通数据在传送时使用UDP较多,因为它们即使偶尔丢失一两个数据包, 也不会对接收结果产生太大影响.比如我们聊天用的ICQ和O

超全Android JNI&NDK编程总结

由于网上关于JNI/NDK相关的知识点介绍的比较零散而且不具备参照性,所以写了这篇JNI/NDK学习笔记,便于作为随时查阅的工具类型的文章,本文主要的介绍了在平时项目中常用的命令.JNI数据类型.签名等,便于查阅相关资料.文末相关参考资料比较适合刚接触或者不熟悉Android NDK开发的朋友参阅. 常用命令 javac 编译java源文件生成.class文件 由于JNI对应的头文件由javah工具根据对应的.class文件生成,所以在进行JNI编程之前,写好Java代码后需要先编译,在使用ja

iOS-Swift 面向协议编程/组件化(模块化)编程思想

转载注明出处:http://blog.csdn.net/qxuewei/article/details/53945445 因为OC 的局限性, 使得iOS 开发组件化编程变得不可能,得益于面向对象语言的特性 (封装,继承,多态) 在我们熟悉的设计模式中渐渐形成统一的软件开发思想. 在抽取某些功能作为基类的不断运用中,代码的可移植性逐渐减弱. 就如同一棵树,从主干到各个分支,每个分支再长成细枝末叶.代码的耦合性也相应增加. 随着苹果 swift 语言的推出, 对于传统OC 语言取其精华,弃其糟粕.