V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
OneAPM
V2EX  ›  Android

Android 网络请求详解

  •  1
     
  •   OneAPM · 2015-12-29 15:04:49 +08:00 · 9620 次点击
    这是一个创建于 3286 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我们知道大多数的 Android 应用程序都是通过和服务器进行交互来获取数据的。如果使用 HTTP 协议来发送和接收网络数据,就免不了使用 HttpURLConnection 和 HttpClient ,而 Android 中主要提供了上述两种方式来进行 HTTP 操作。并且这两种方式都支持 HTTPS 协议、以流的形式进行上传和下载、配置超时时间、 IPv6 、以及连接池等功能。

    但是 Googl e 发布 6.0 版本的时候声明原生剔除 HttpClient ,但是笔者认为 HttpClient 会提供相应的 jar 包做支持,毕竟 Google 对向下兼容这方面一直都做的很好,相信在选择网络功能的时候我们会选自己喜欢的方法。

    HttpURLConnection

    接着我们来看一下如何使用 HttpURLConnection 来处理简单的网络请求。

    // Get 方式请求
    public static void requestByGet() throws Exception {
    String path = "10.128.7.34:3000/name=helloworld&password=android";
    // 新建一个 URL 对象
    URL url = new URL(path);
    // 打开一个 HttpURLConnection 连接
    HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
    // 设置连接超时时间
    urlConn.setConnectTimeout(6 * 1000);
    // 开始连接
    urlConn.connect();
    // 判断请求是否成功
    if (urlConn.getResponseCode() == HTTP_200) {
    // 获取返回的数据
    byte[] data = readStream(urlConn.getInputStream());
    Log.i(TAG_GET, new String(data, "UTF-8"));
    } else {
    Log.i(TAG_GET, "Get 方式请求失败");
    }
    // 关闭连接
    urlConn.disconnect();
    }

    // Post 方式请求  
    public static void requestByPost() throws Throwable {  
        String path = "http://10.128.7.34:3000/login";  
        // 请求的参数转换为 byte 数组  
        String params = "name+ URLEncoder.encode("helloworld", "UTF-8")  
                + "&password=" + URLEncoder.encode("android", "UTF-8");  
        byte[] postData = params.getBytes();  
        // 新建一个 URL 对象  
        URL url = new URL(path);  
        // 打开一个 HttpURLConnection 连接  
        HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();  
        // 设置连接超时时间  
        urlConn.setConnectTimeout(5 * 1000);  
        // Post 请求必须设置允许输出  
        urlConn.setDoOutput(true);  
        // Post 请求不能使用缓存  
        urlConn.setUseCaches(false);  
        // 设置为 Post 请求  
        urlConn.setRequestMethod("POST");  
        urlConn.setInstanceFollowRedirects(true);  
        // 配置请求 Content-Type  
        urlConn.setRequestProperty("Content-Type",  
                "application/x-www-form-urlencode");  
        // 开始连接  
        urlConn.connect();  
        // 发送请求参数  
        DataOutputStream dos = new DataOutputStream(urlConn.getOutputStream());  
        dos.write(postData);  
        dos.flush();  
        dos.close();  
        // 判断请求是否成功  
        if (urlConn.getResponseCode() == HTTP_200) {  
            // 获取返回的数据  
            byte[] data = readStream(urlConn.getInputStream());  
            Log.i(TAG_POST, new String(data, "UTF-8"));  
        } else {  
            Log.i(TAG_POST, "Post 方式请求失败");  
        }  
    }
    
    关于 HttpURLConnection 的相关开源工程

    由于 Google 已经表明了推荐广大开发者使用 HttpURLConnection ,那么想必是有一定原因的。

    xUtils3

    这里就推荐大伙都很熟悉的开源项目 xUtils 的“弟弟“, xUtils3 。 xUtils 包含了 view 注入,图片处理,数据库操作以及网络请求封装, xUtils 使用的网络请求封装是基于 HttpClient 的。 xUtils3 使用的网络请求是基于 HttpURLConnection ,我们着重说明一下 xUtils3 。
    ```
    RequestParams params = new RequestParams("http://10.128.7.34:3000/upload");
    // 加到 url 里的参数, http://xxxx/s?wd=xUtils
    params.addQueryStringParameter("wd", "xUtils");
    // 添加到请求 body 体的参数, 只有 POST, PUT, PATCH, DELETE 请求支持.
    // params.addBodyParameter("wd", "xUtils");

    // 使用 multipart 表单上传文件
        params.setMultipart(true);
        params.addBodyParameter(
                "file",
                new File("/sdcard/test.jpg"),
                null); // 如果文件没有扩展名, 最好设置 contentType 参数.
        params.addBodyParameter(
                "file2",
                new FileInputStream(new File("/sdcard/test2.jpg")),
                "image/jpeg",
                // 测试中文文件名
                "你+& \" 好.jpg"); // InputStream 参数获取不到文件名, 最好设置, 除非服务端不关心这个参数.
        x.http().post(params, new Callback.CommonCallback<String>() {
            @Override
            public void onSuccess(String result) {
                Toast.makeText(x.app(), result, Toast.LENGTH_LONG).show();
            }
    
            @Override
            public void onError(Throwable ex, boolean isOnCallback) {
                Toast.makeText(x.app(), ex.getMessage(), Toast.LENGTH_LONG).show();
            }
    
            @Override
            public void onCancelled(CancelledException cex) {
                Toast.makeText(x.app(), "cancelled", Toast.LENGTH_LONG).show();
            }
    
            @Override
            public void onFinished() {
    
            }
        });
    
    具有 cache 功能
    

    RequestParams params = new RequestParams("http://10.128.7.34:3000/upload");
    params.wd = "xUtils";
    // 默认缓存存活时间, 单位:毫秒.(如果服务没有返回有效的 max-age 或 Expires)
    params.setCacheMaxAge(1000 * 60);
    Callback.Cancelable cancelable
    // 使用 CacheCallback, xUtils 将为该请求缓存数据.
    = x.http().get(params, new Callback.CacheCallback<String>() {

    private boolean hasError = false;
            private String result = null;
    
            @Override
            public boolean onCache(String result) {
                // 得到缓存数据, 缓存过期后不会进入这个方法.
                // 如果服务端没有返回过期时间, 参考 params.setCacheMaxAge(maxAge)方法.
                //
                // * 客户端会根据服务端返回的 header 中 max-age 或 expires 来确定本地缓存是否给 onCache 方法.
                //   如果服务端没有返回 max-age 或 expires, 那么缓存将一直保存, 除非这里自己定义了返回 false 的
                //   逻辑, 那么 xUtils 将请求新数据, 来覆盖它.
                //
                // * 如果信任该缓存返回 true, 将不再请求网络;
                //   返回 false 继续请求网络, 但会在请求头中加上 ETag, Last-Modified 等信息,
                //   如果服务端返回 304, 则表示数据没有更新, 不继续加载数据.
                //
                this.result = result;
                return false; // true: 信任缓存数据, 不在发起网络请求; false 不信任缓存数据.
            }
    
            @Override
            public void onSuccess(String result) {
                // 注意: 如果服务返回 304 或 onCache 选择了信任缓存, 这里将不会被调用,
                // 但是 onFinished 总会被调用.
                this.result = result;
            }
    
            @Override
            public void onError(Throwable ex, boolean isOnCallback) {
                hasError = true;
                Toast.makeText(x.app(), ex.getMessage(), Toast.LENGTH_LONG).show();
                if (ex instanceof HttpException) { // 网络错误
                    HttpException httpEx = (HttpException) ex;
                    int responseCode = httpEx.getCode();
                    String responseMsg = httpEx.getMessage();
                    String errorResult = httpEx.getResult();
                    // ...
                } else { // 其他错误
                    // ...
                }
            }
    
            @Override
            public void onCancelled(CancelledException cex) {
                Toast.makeText(x.app(), "cancelled", Toast.LENGTH_LONG).show();
            }
    
            @Override
            public void onFinished() {
                if (!hasError && result != null) {
                    // 成功获取数据
                    Toast.makeText(x.app(), result, Toast.LENGTH_LONG).show();
                }
            }
        });
    
    post 请求直接更改 post 方式,以及需要提交的参数即可,篇幅问题这里就不在一一列举了。通过以上代码可以看到,我们在使用 xUtils 来请求网络的时候会非常的方便,只用在回调函数里面做对应的代码逻辑处理即可,不用关心线程问题,极大的解放了我们的生产力。
    Android Stuido Gradle 使用方法如下:
        >compile 'org.xutils:xutils:3.1.+'
    
    
    ######**Volley**
    在 Android 2.3 及以上版本,使用的是 HttpURLConnection ,而在 Android 2.2 及以下版本,使用的是 HttpClient 。鉴于现在的手机行业发展速度,我们已经不考虑 Android2.2 了。
    
    简单提供一些 Volley 的实例:
    

    //简单的 GET 请求
    mQueue = Volley.newRequestQueue(getApplicationContext());

    mQueue.add(new JsonObjectRequest(Method.GET, url, null,

    new Listener() {

    @Override

    public void onResponse(JSONObject response) {

    Log.d(TAG, "response : " + response.toString());

    }

    }, null));

    mQueue.start();

    ```

    //对 ImageView 的操作
    ImageListener listener = ImageLoader.getImageListener(imageView, android.R.drawable.ic_launcher, android.R.drawable.ic_launcher);  
    mImageLoader.get(url, listener);  
    
    //对 ImageView 网络加载的处理
    mImageView.setImageUrl(url, imageLoader);
    

    当然我们也可以定制自己的 request
    ```
    mRequestQueue.add( new GsonRequest(url, ListResponse.class, null,

    new Listener() {

    public void onResponse(ListResponse response) {

    appendItemsToList(response.item);

    notifyDataSetChanged();

    }

    }

    }

    ####**HttpClient**
    同样,我们来看一下 HttpClient 的简单请求。
    

    // HttpGet 方式请求

    public static void requestByHttpGet() throws Exception {

    String path = "http://10.128.7.34:3000/name=helloworld&password=android";

    // 新建 HttpGet 对象

    HttpGet httpGet = new HttpGet(path);

    // 获取 HttpClient 对象

    HttpClient httpClient = new DefaultHttpClient();

    // 获取 HttpResponse 实例

    HttpResponse httpResp = httpClient.execute( httpGet);

    // 判断是够请求成功

    if ( httpResp.getStatusLine().getStatusCode() == HTTP_200) {

    // 获取返回的数据

    String result = EntityUtils.toString( httpResp.getEntity(), "UTF-8");

    Log.i(TAG_HTTPGET, "HttpGet 方式请求成功,返回数据如下:");

    Log.i(TAG_HTTPGET, result);

    } else {

    Log.i(TAG_HTTPGET, "HttpGet 方式请求失败");

    }

    }

    // HttpPost 方式请求

    public static void requestByHttpPost() throws Exception {

    String path = "https://reg.163.com/login";

    // 新建 HttpPost 对象

    HttpPost httpPost = new HttpPost(path);

    // Post 参数

    List<NameValuePair> params = new ArrayList<NameValuePair>();

    params.add(new BasicNameValuePair("name", "helloworld"));

    params.add(new BasicNameValuePair("password", "android"));

    // 设置字符集

    HttpEntity entity = new UrlEncodedFormEntity(params, HTTP.UTF_8);

    // 设置参数实体

    httpPost.setEntity(entity);

    // 获取 HttpClient 对象

    HttpClient httpClient = new DefaultHttpClient();

    // 获取 HttpResponse 实例

    HttpResponse httpResp = httpClient.execute( httpPost);

    // 判断是够请求成功

    if ( httpResp.getStatusLine().getStatusCode() == HTTP_200) {

    // 获取返回的数据

    String result = EntityUtils.toString( httpResp.getEntity(), "UTF-8");

    Log.i(TAG_HTTPGET, "HttpPost 方式请求成功,返回数据如下:");

    Log.i(TAG_HTTPGET, result);

    } else {

    Log.i(TAG_HTTPGET, "HttpPost 方式请求失败");

    }

    }

    ```

    关于 HttpClinet 的相关开源工程

    HttpClient 也拥有这大量优秀的开源工程, afinal 、 xUtils 以及 AsyncHttpClient ,也可以为广大开发者提供已经造好的轮子。由于 xUtils 是基于 afinal 重写的,现在 xUtils3 也有替代 xUtils 的趋势,所以我们在这就简单介绍一下 AsyncHttpClient 。

    AsyncHttpClient

    见名知意, AsyncHttpClient 对处理异步 Http 请求相当擅长,并通过匿名内部类处理回调结果, Http 异步请求均位于非 UI 线程,不会阻塞 UI 操作,通过线程池处理并发请求处理文件上传、下载、响应结果自动打包 JSON 格式。使用起来会很方便。

    //GET 请求
    AsyncHttpClient client = new AsyncHttpClient();
    //当然这里也可以换成 new JsonHttpResponseHandler(),我们就能直接获得 JSON 数据了。
    client.get("http://www.google.com", new AsyncHttpResponseHandler() {
    
        @Override
        public void onStart() {
            // called before request is started
        }
    
        @Override
        public void onSuccess(int statusCode, Header[] headers, byte[] response) {
            // called when response HTTP status is "200 OK"
        }
    
        @Override
        public void onFailure(int statusCode, Header[] headers, byte[] errorResponse, Throwable e) {
            // called when response HTTP status is "4XX" (eg. 401, 403, 404)
        }
    
        @Override
        public void onRetry(int retryNo) {
            // called when request is retried
        }
    });
    
    //POST 请求
    AsyncHttpClient client = new AsyncHttpClient();
    RequestParams params = new RequestParams();
    params.put("key", "value");
    params.put("more", "data");
    //同上,这里一样可以改成处理 JSON 数据的方法
    client.get("http://www.google.com", params, new
        TextHttpResponseHandler() {
            @Override
            public void onSuccess(int statusCode, Header[] headers, String response ) {
                System.out.println(response);
            }
    
            @Override
            public void onFailure(int statusCode, Header[] headers, String responseBody, Throwable error ) {
                Log.d("ERROR", error);
            }    
        }
    );
    

    经过上面的代码发现, AsyncHttpClient 使用起来也是异常简洁,主要靠回调方法来处理成功或失败之后的逻辑。仔细想想, xUtils 的处理方式和这个处理方式很类似,看来好用设计还是很受人青睐的。

    OkHttp

    如果两种网络请求都想使用怎么办?那么 OkHttp 是一个最佳解决方案了。

    OkHttp 在网络请求方面的口碑很好,就连 Google 自己也有使用 OkHttp 。虽然 Google6.0 中剔除了 HttpClient 的 Api ,但是由于 OkHttp 的影响力以及其强大的功能,使用 OkHttp 无需重写您程序中的网络代码。同时最重要的一点 OkHttp 实现了几乎和 java.net.HttpURLConnection 一样的 API 。如果您用了 Apache HttpClient ,则 OkHttp 也提供了一个对应的 okhttp-apache 模块。足以说明 OkHttp 的强大,下面是一些例子。

    • 一般的 get 请求
    • 一般的 post 请求
    • 基于 Http 的文件上传
    • 文件下载
    • 加载图片
    • 支持请求回调,直接返回对象、对象集合
    • 支持 session 的保持

    简单代码实例

    //GET 请求
    OkHttpClient client = new OkHttpClient();
    
    String run(String url) throws IOException {
      Request request = new Request.Builder()
          .url(url)
          .build();
    
      Response response = client.newCall(request).execute();
      return response.body().string();
    }
    
    //POST 请求
    public static final MediaType JSON
        = MediaType.parse("application/json; charset=utf-8");
    
    OkHttpClient client = new OkHttpClient();
    
    String post(String url, String json) throws IOException {
      RequestBody body = RequestBody.create(JSON, json);
      Request request = new Request.Builder()
          .url(url)
          .post(body)
          .build();
      Response response = client.newCall(request).execute();
      return response.body().string();
    }
    

    Android Studio Gradle 使用方式:

    compile 'com.squareup.okhttp:okhttp:2.7.0'

    总结

    Android 开发应用少不了使用网络,移动互联时代,抢占终端入口变得异常重要,那么我们在开发过程中,不管使用哪一种网络请求, HttpURLConnection 或者是 HttpClient ,都可以满足我们和服务器的沟通。

    可是发布的 App 到用户手中后,有用 WIFI 的,有用流量的,网络环境多样,我们怎么能知道用户在什么样的情况下访问服务器的流畅度呢?

    答案很简单,只要集成了OneAPM Mobile Insight,即可轻松知晓网络交互情况,轻松了解用户在使用 App 的过程中哪里容易出问题,并对症下药。

    OneAPM Mobile Insight ,监控网络请求及网络错误,提升用户留存。访问 OneAPM 官方网站感受更多应用性能优化体验,想阅读更多技术文章,请访问 OneAPM 官方技术博客
    本文转自 OneAPM 官方博客

    4 条回复    2015-12-29 17:39:10 +08:00
    fashioncj
        1
    fashioncj  
       2015-12-29 15:41:35 +08:00   ❤️ 1
    。。。其实。。两个的底层都是 okhttp 的。。。请参考 android 源代码。。
    wanttofly
        2
    wanttofly  
       2015-12-29 16:49:15 +08:00
    我一直没太明白 retronic 和 okhttp 的区别, retronic2.0 使用了 okhttp ?怎么解释呢、
    kaedea
        3
    kaedea  
       2015-12-29 17:11:48 +08:00
    这只能算是 Demo 吧,不能说是详解…
    dullwit
        4
    dullwit  
       2015-12-29 17:39:10 +08:00
    用 HttpClient ,回调方法和各种 Handler 恶心死人;
    用 AsyncHttpClient 写法太麻烦,不适合连续性请求,而且和 UI 逻辑混杂在一起,耦合太高;
    用框架的话还得看架构的设计,不然也有上面说的通病。
    目前的话推荐使用 RxJava 和 RxAndroid 来解决网络异步问题
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2729 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 20ms · UTC 12:25 · PVG 20:25 · LAX 04:25 · JFK 07:25
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.