Android网络编程之HttpUrlConnection

相关知识点延伸:

上一篇介绍JavaSe中HttpUrlConnection使用情况,在Android中使用HttpUrlConnection还是存在一些差别的。


Android中HttpUrlConnection的使用:

1. 使用说明:

  • Secure Communication with HTTPS(使用HTTPS进行安全通信):

    使用一个https方案的URL调用penConnection(),将会返回一个HttpsURLConnection,这允许覆盖默认的HostnameVerifier 和 SSLSocketFactory。
    
     一个运用提供的SSLSocketFactory创建是通过一个SSLContext, 
     SSLSocketFactory 能够提供自定义的用于验证证书链的X509TrustManager  
     和自定义的用于提供客户端证书的X509TrustManager 。 
    
  • Response Handling(相应处理):

      HttpURLConnection 最多可以跟踪5个Http重定向。它将会跟随重定向,从一个起始服务器到其他服务器。
      这种重定向的跟随实现,不会支持从Https服务器跳转到http的服务器重定向。反过来,也是一样的。
    
  • Posting Content(传递内容):

       1. 上传数据到web服务器 ,输出的链接需配置setDoOutput(true)。
       2. 为了最佳性能。
         2. 1 若是知道这内容长度,可以调用setFixedLengthStreamingMode(int) 。 
               反之,调用setChunkedStreamingMode(int)。
         2. 2 若是没有进行上一步设置操作,HttpURLConnection将会在内存中强制缓存整个请求的body,直到被传输完。 
              这种后果是导致浪费(可能消耗完)堆,增加迟缓时间。
    
  • Performance(性能):

    • 内存性能:

      这类返回的输出流和输入流不进行缓冲。大多数开发者应该使用BufferedOutputStream或者BufferedInputStream进行缓存。使用批量读取或者写入操作的使用者可以忽略缓冲。
      传输大量数据到服务器或者从服务器中读取大量数据时,使用流的操作,同时应该进行限制内存中数据量的操作。
      除非你需要一次性将内容存储到内存中,将它作为流处理,而不是将整个内容作为单独一个btye数据或者单独一个String进行存储。

    • 处理时间:

      为了减少延迟,这类进行多个请求/响应对的操作时,会重用相同的地城socket.因此,Http连接开着时间可能比所需时间,调用disconnect()可以将返回一个socket添加到连接的socket池中。
      这种行为是可以被禁止的。通过设置http.keepAlive属性为false,在发送http请求前。
      http.maxConnections属性可以用于控制与每个服务器保持多少个空闲连接。

    • 默认情况:

      默认情况下,HttpURLConnection 的实现请求,服务器将数据使用Gzip压缩后再返回,HttpUrlConnection会自动解压数据通过回调getInputStream().
      在这种情况下,Content-Encoding 和Content-Length response headers 是清除的。
      Gzip压缩可以被禁止。通过设置请求的header上接受的编码格式: urlConnection.setRequestProperty(“Accept-Encoding”, “identity”);
      设置请求的标头上的Accept-Encoding属性禁止自动解压,且保留响应的标头。使用者必须根据响应的标头上的Content-Encoding来处理所需要的解压。

    • 注意点:
      getContentLength():返回传输的字节数,但是不能用于预测可以读出多少字节数。
      getInputStream(): 获取压缩流。相反,读取这流直到耗尽,即read()返回-1.
  • Handling Network Sign-On:

    某些wifi网络操作会阻止互联网访问,直到用户点击登录页面。
    这种登录页面通常呈现是通过使用htttp重定向。可以使用getURL来测试连接是否意外重定向。
    在连接操作的响应标头被接受后,这种测试才是有效的。可以调用getHeaderFields() or getInputStream()来触发响应的标头。这里的案例:检查响应是否意外重定向到其他主机:

      HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
         try {
               InputStream in = new BufferedInputStream(urlConnection.getInputStream());
                if (!url.getHost().equals(urlConnection.getURL().getHost())) {
                     // we were redirected! Kick the user out to the browser to sign on?
                }
                    ...
          } finally {
                urlConnection.disconnect();
     }
    
  • HTTP Authentication(http验证):

    HttpURLConnection支持 HTTP basic authentication。 使用 Authenticator 来设置 the VM-wide authentication handler:

      Authenticator.setDefault(new Authenticator() {
          protected PasswordAuthentication getPasswordAuthentication() {
            return new PasswordAuthentication(username, password.toCharArray());
          }
        });
    

    除非与https配对,这种机制不是用户身份验证的安全机制。特别是,用户名,密码和请求和响应都是没有在加密情况下通过网络传输的。

  • Sessions with Cookies:

    为了在客户端与服务器间建立和维护一个潜在的长期session,HttpURLConnection 包含一个可以扩展的coockie管理器。

    使用CookieHandler 和CookieManager来开启VM范围中coockie 管理:

       CookieManager cookieManager = new CookieManager();
           CookieHandler.setDefault(cookieManager); 
    

    默认情况下,CookieManager只接受起始服务的coockie.还包括其他两个策略:ACCEPT_ALL and ACCEPT_NONE
    实现CookiePolicy以定义自定义策略。

    默认的CookieManager 会将接受到coockie保存到内存中,当VM退出时,会消除这些coockie. 实现CookieStore以定义自定义Cookie存储

    除了由Http响应设置的Cookie之外,还可以通过编码方式来设置Cookie.要包含在Http 请求的标头中,Cookies必须要设置域和路径的属性

    默认情况下,HttpCookie的新实例与只支持RFC 2965 cookies的服务器工作。许多web服务器仅支持旧的RFC 2109。
    为了与大多数web服务器兼容,需将cookie版本设置为0。

    一个案例:用法语来接收www.twitter.com

         HttpCookie cookie = new HttpCookie("lang", "fr");
             cookie.setDomain("twitter.com");
             cookie.setPath("/");
             cookie.setVersion(0);
             cookieManager.getCookieStore().add(new URI("http://twitter.com/"), cookie);
    
  • HTTP Methods:

    HttpURLConnection 默认的请求方式是get. 若是使用post请求,则应该调用setDoOutput(true) 。
    改变请求方式(OPTIONS, HEAD, PUT, DELETE and TRACE),调用setRequestMethod(String)来设置.

  • Proxies(代理):

    默认情况下,这类是直接连接起始服务器。它也能通过http或者socks代理连接。
    要使用代理,在创建连接的时候调用URL.openConnection(Proxy)。

  • IPv6 Support:
    这类包含IPv6的支持。对于具备IPv4和IPv6地址的主机,它将会尝试连接到每一个主机的地址,知道建立连接。

  • Avoiding Bugs In Earlier Releases:
    在android 2.2之前,这里存在一些错误。特别是,在一个可读的InputSteam中调用clos()会危害连接池,应该禁止使用连接池来解决问题。

      private void disableConnectionReuseIfNecessary() {
                // Work around pre-Froyo bugs in HTTP connection reuse.
               if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION_CODES.FROYO) {
                    System.setProperty("http.keepAlive", "false");
               }
       }
    

    每一个HttpURLConnection 实例可以用于一个请求/响应对。这个类的实例不是线程安全的。

  • android 官方关于HttpUrlConnection的链接:
    https://developer.android.com/reference/java/net/HttpURLConnection.html

2. 其API和使用方式:请阅读JAVASE中HttpUrlConnection介绍和使用

3. 异步线程中使用案例:

在IntentService中长期执行网络任务,周期性读取服务器数据的案例。

案例思路: 周期性从服务器获取到信息,然后Gson解析Json数据,手机震动,Notification提醒,最后编辑信息标记为已读。

  public class MessageIntentSerce extends IntentService {
    public static final String TAG = MessageIntentSerce.class.getSimpleName();
    private String token;
    private String url1, url2;
    public boolean isStop = false;
    public NotificationManager notificationManager;
    public Vibrator vibrator;
    public static final int MESSAGENOTIFICATION_ID = 100;

    public static Intent openMessageServce(Context context, Bundle bundle) {
        Intent intent = new Intent(context, MessageIntentSerce.class);
        intent.putExtras(bundle);
        return intent;
    }

    private ThreadManager threadManager;

    public MessageIntentSerce() {
        super(TAG);
        url1 = Constants.COOMPANYTEST + Constants.UPDATAMESSAG;
        url2 = Constants.COOMPANYTEST + Constants.MESSAGEFINISH;
        threadManager = ThreadManager.getInstances();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        LogController.i(TAG,"service onCreate");
        //初始化相关配置
        notificationManager = (NotificationManager) 
              this.getSystemService(Context.NOTIFICATION_SERVICE);
        vibrator = (Vibrator) getSystemService(Service.VIBRATOR_SERVICE);
    }

    /**
     * 后台线程,以工作队列线程进行
     *
     * @param intent
     */
    @Override
    protected void onHandleIntent(Intent intent) {
        LogController.i(TAG,"service onHandleIntent");
        Bundle bundle = intent.getExtras();
        token = bundle.getString(MessageIntentSerce.TAG);
        if (token == null) {
            return;
        }
        //停止的标志
        while (threadManager.getStart()) {
            try {
                getMessageFromServce();
                Thread.sleep(20 * 1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }


    @Override//释放资源
    public void onDestroy() {
        LogController.i(TAG,"service destroy");
        notificationManager.cancelAll();

        vibrator.cancel();
        super.onDestroy();
    }

    public void getMessageFromServce() {
        try {
            HttpURLConnection urlConnection = createConnection(url1);
            String result = getStreamContent(urlConnection);
            LogController.i(TAG, "messagelist" + result);
            if (result == null) {
                return;
            }
            JsonBean_UpdataMessage message = parseJson(result, JsonBean_UpdataMessage.class);
            if (message != null && message.errcode == 0) {
                if (message.replydata != null && message.replydata.messagelist != null  
                    && message.replydata.messagelist.size() > 0) {

                    for (JsonBean_UpdataMessage.MessageList.Message msg :  
                         message.replydata.messagelist) {

                        notifi(msg.msubject, msg.mcontent);
                        updataMSG(msg.mpid);

                    }
                    vibrator.vibrate(1000);
                }


            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 创建httpUrlConnection对象,指定request的属性
     *
     * @param url
     * @return
     */
    public HttpURLConnection createConnection(String url) {
        HttpURLConnection urlConnection = null;
        try {
            urlConnection = (HttpURLConnection) new URL(url).openConnection();
            urlConnection.setConnectTimeout(10 * 1000);
            urlConnection.setReadTimeout(10 * 1000);
            //数据编码格式,这里utf-8
            urlConnection.setRequestProperty("Charset", "utf-8");
            //添加cookie,cookie规则需开发者自定
            urlConnection.setRequestProperty("Cookie", Constants.COOCKIE_KEY + token);
            //设置返回结果的类型,这里是json
            urlConnection.setRequestProperty("accept", "application/json");
            //这里设置post传递的内容类型,这里json
            urlConnection.setRequestProperty("Content-Type", "application/json");
            //设置这个,避免android低版本报错误
            if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION_CODES.FROYO) {
                System.setProperty("http.keepAlive", "false");
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return urlConnection;
    }

    /**
     * 添加http参数
     *
     * @param urlConnection
     * @param jsonObject
     */
    public void addParameter(HttpURLConnection urlConnection, JSONObject jsonObject) {
        try {
            //设置post请求方式
            urlConnection.setRequestMethod("POST");
            //允许往流中写入数据
            urlConnection.setDoOutput(true);
            //借助BufferedOutputStream提高速度
            OutputStream outputStream = new BufferedOutputStream(urlConnection.getOutputStream());
            byte[] body = jsonObject.toString().getBytes("utf-8");
            outputStream.write(body, 0, body.length);
            //刷新数据到流中
            outputStream.flush();
            //关闭流
            outputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取HttpUrlConnection的返回内容
     *
     * @param urlConnection
     * @return
     */
    public String getStreamContent(HttpURLConnection urlConnection) {
        ByteArrayOutputStream byteArrayOutputStream = null;
        BufferedInputStream bufferedInputStream = null;
        String result = null;
        try {
            //开启客户端与Url所指向的资源的网络连接
            urlConnection.connect();
            if (200 == urlConnection.getResponseCode()) {//HTTP_OK 即200,连接成功的状态码
                if (urlConnection.getContentLength() > 0) {
                    bufferedInputStream = new BufferedInputStream(urlConnection.getInputStream());
                    byteArrayOutputStream = new ByteArrayOutputStream();
                    //httpUrlConnection返回传输字节的长度,创建一个byte 数组。
                    byte[] b = new byte[urlConnection.getContentLength()];
                    int length;
                    while ((length = bufferedInputStream.read(b)) > 0) {
                        byteArrayOutputStream.write(b, 0, length);
                    }
                    result = byteArrayOutputStream.toString("utf-8");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (byteArrayOutputStream != null) {
                    byteArrayOutputStream.close();
                }
                if(bufferedInputStream!=null){
                    bufferedInputStream.close();
                }
                if (urlConnection != null) {
                    //解除连接,释放网络资源
                    urlConnection.disconnect();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
        return result;
    }

    /**
     * 解析json数据,这里Gson解析
     *
     * @param json
     * @param cls
     * @param <T>
     * @return
     */
    public <T> T parseJson(String json, Class<T> cls) {//解析基本的json(json)
        T t = null;
        try {
            Gson gson = new Gson();
            t = gson.fromJson(json, cls);
            return t;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public int i = 1;
    //弹出Notification
    public void notifi(String title, String content) {
        notificationManager.notify(MESSAGENOTIFICATION_ID + (i++),  
        NotificationBuidler.createMessageNotifaction(MessageIntentSerce.this,  
        title, content));
    }

    public void updataMSG(int mid) {
        JSONObject jsonObject = null;
        try {
            jsonObject = new JSONObject();
            jsonObject.put("mpid", mid);
            HttpURLConnection httpURLConnection = createConnection(url2);
            addParameter(httpURLConnection, jsonObject);
            String result = getStreamContent(httpURLConnection);
            LogController.i(TAG, "msgfinish" + result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


}  

4. Android模拟器连接本地电脑服务器的注意点:
在电脑模拟器上访问本地电脑服务器时,报错java.net.ConnectException: localhost/127.0.0.1:8080 - Connection refused

解决方式:

      android模拟器是在虚拟内部运行,这里127.0.0.1或者localhost是模拟器自己的环回路径。 
      因此,应该使用http://10.0.2.2:8080/来访问。

StackOverFlow上的解决方式:
http://stackoverflow.com/questions/5495534/java-net-connectexception-localhost-127-0-0-18080-connection-refused

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值