美文网首页
Android HTTP访问

Android HTTP访问

作者: 古早味蛋糕 | 来源:发表于2023-03-27 19:18 被阅读0次

    一、简介
    主要有okhttp在App接口访问中的详细用法,内容包括通过okhttp调用HTTP接口的三种方式(GET方式、表单格式的POST请求、JSON格式的POST请求)、如何使用okhttp下载网络文件以及如何将本地文件上传到服务器、如何借助下拉刷新和上拉加载技术实现网络信息的分页访问。
    1、通过okhttp调用HTTP接口
    虽然使用HttpURLConnection能够实现大多数的网络访问操作,但是它的用法实在烦琐,很多细节都要开发者关注,一不留神就可能导致访问异常。于是许多网络开源框架纷纷涌现,比如声名显赫的Apache的HttpClient、Square的okhttp。Android从9.0开始正式弃用HttpClient,使得okhttp成为App开发流行的网络框架。
    因为okhttp属于第三方框架,所以使用之前要修改build.gradle,增加下面一行依赖配置:

    implementation 'com.squareup.okhttp3:okhttp:4.9.2'

    访问网络之前得先申请上网权限,也就是在AndroidManifest.xml里面补充以下权限:

     <!-- 互联网 -->
     <uses-permission android:name="android.permission.INTERNET" />
    

    除此之外,Android 9开始默认只能访问以HTTPS开头的安全地址,不能直接访问以HTTP开头的网络地址。如果应用仍想访问以HTTP开头的普通地址,就得修改AndroidManifest.xml,给application节点添加如下属性,表示继续使用HTTP明文地址:

    android:usesCleartextTraffic="true"

    二、okhttp的网络访问功能十分强大,单就HTTP接口调用而言,它就支持三种访问方式:GET方式的请求、表单格式的POST请求、JSON格式的POST请求,下面分别进行说明:
    1、GET方式的请求
    不管是GET方式还是POST方式,okhttp在访问网络时都离不开下面4个步骤:
    (1)使用OkHttpClient类创建一个okhttp客户端对象。创建客户端对象的示例代码如下:

    OkHttpClient client = new OkHttpClient();  // 创建一个okhttp客户端对象
    

    (2)使用Request类创建一个GET或POST方式的请求结构。采取GET方式时调用get方法,采取POST方式时调用post方法。此外,需要指定本次请求的网络地址,还可添加个性化HTTP头部信息。创建请求结构的示例代码如下:

        // 创建一个GET方式的请求结构
        Request request = new Request.Builder()
                //.get() // 因为OkHttp默认采用get方式,所以这里可以不调get方法
                .header("Accept-Language", "zh-CN") // 给http请求添加头部信息
                .url(URL_STOCK) // 指定http请求的调用地址
                .build();
    

    (3)调用第1步骤中客户端对象的newCall方法,方法参数为第2步骤中的请求结构,从而创建Call类型的调用对象。创建调用对象的示例代码如下:

    Call call = client.newCall(request);  // 根据请求结构创建调用对象
    

    (4)调用第3步骤中Call对象的enqueue方法,将本次请求加入HTTP访问的执行队列,并编写请求失败与请求成功两种情况的处理代码。加入执行队列的示例代码如下:

        // 加入HTTP请求队列。异步调用,并设置接口应答的回调方法
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) { // 请求失败
                // 回到主线程操纵界面
                runOnUiThread(() -> tv_result.setText("调用股指接口报错:"+e.getMessage()));
            }
    
            @Override
            public void onResponse(Call call, final Response response) throws IOException { // 请求成功
                String resp = response.body().string();
                // 回到主线程操纵界面
                runOnUiThread(() -> tv_result.setText("调用股指接口返回:\n"+resp));
            }
        });
    

    以上四步就是使用okhttp访问网络的完整步骤,具体的接口调用代码如下:OkhttpCallActivity完整代码

    // 发起GET方式的HTTP请求
    private void doGet() {
        OkHttpClient client = new OkHttpClient(); // 创建一个okhttp客户端对象
        // 创建一个GET方式的请求结构
        Request request = new Request.Builder()
                //.get() // 因为OkHttp默认采用get方式,所以这里可以不调get方法
                .header("Accept-Language", "zh-CN") // 给http请求添加头部信息
                .url(URL_STOCK) // 指定http请求的调用地址
                .build();
        Call call = client.newCall(request); // 根据请求结构创建调用对象
        // 加入HTTP请求队列。异步调用,并设置接口应答的回调方法
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) { // 请求失败
                // 回到主线程操纵界面
                runOnUiThread(() -> tv_result.setText("调用股指接口报错:"+e.getMessage()));
            }
    
            @Override
            public void onResponse(Call call, final Response response) throws IOException { // 请求成功
                String resp = response.body().string();
                // 回到主线程操纵界面
                runOnUiThread(() -> tv_result.setText("调用股指接口返回:\n"+resp));
            }
        });
    }
    

    2、表单格式的POST请求
    由于表单格式不能传递复杂的数据,因此App在与服务端交互时经常使用JSON格式。设定好JSON串的字符编码后再放入RequestBody结构中,示例代码如下:

        // 创建一个POST方式的请求结构
        RequestBody body = RequestBody.create(jsonString, MediaType.parse("text/plain;charset=utf-8"));
        Request request = new Request.Builder().post(body).url(URL_LOGIN).build();
    

    仍以登录功能为例,App先将用户名和密码组装进JSON对象,再把JSON对象转为字符串,后续便是常规的okhttp调用过程了。采取JSON格式的登录代码如下:

    // 发起POST方式的HTTP请求(报文为JSON格式)
    private void postJson() {
        String username = et_username.getText().toString();
        String password = et_password.getText().toString();
        String jsonString ="";
        try {
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("username",username);
            jsonObject.put("password",password);
            jsonString = jsonObject.toString();
        } catch (JSONException e) {
            e.printStackTrace();
        }
        // 创建一个POST方式的请求结构
        RequestBody body = RequestBody.create(jsonString, MediaType.parse("text/plain;charset=utf-8"));
        OkHttpClient client = new OkHttpClient(); // 创建一个okhttp客户端对象
        Request request = new Request.Builder().post(body).url(URL_LOGIN).build();
        Call call = client.newCall(request);
        // 加入HTTP请求队列。异步调用,并设置接口应答的回调方法
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) { // 请求失败
                // 回到主线程操纵界面
                runOnUiThread(() -> tv_result.setText("调用登录接口报错:"+e.getMessage()));
            }
    
            @Override
            public void onResponse(Call call, final Response response) throws IOException { // 请求成功
                String resp = response.body().string();
                // 回到主线程操纵界面
                runOnUiThread(() -> tv_result.setText("调用登录接口返回:\n"+resp));
            }
        });
    }
    

    要确保服务端的登录接口正常开启,并且手机和计算机连接同一个WiFi,再运行并测试该App。打开登录页面,填入登录信息后点击“发起接口调用”按钮,接收到服务端返回的数据,如图【JSON格式的POST请求结果】所示


    JSON格式的POST请求结果.png

    二、使用okhttp下载和上传文件
    (1)okhttp不但简化了HTTP接口的调用过程,连下载文件都变简单了。对于一般的文件下载,按照常规的GET方式调用流程,只要重写回调方法onResponse,在该方法中通过应答对象的body方法即可获得应答的数据包对象,调用数据包对象的string方法即可得到文本形式的字符串,调用数据包对象的byteStream方法即可得到InputStream类型的输入流对象,从输入流就能读出原始的二进制数据。
    以下载网络图片为例,位图工具BitmapFactory刚好提供了decodeStream方法,允许直接从输入流中解码获取位图对象。此时通过okhttp下载图片的示例代码如下:OkhttpDownloadActivity

    private final static String URL_IMAGE = "https://www.2008php.com/2019_Website_appreciate/2019-06-11/20190611170449.jpg";
    // 下载网络图片
    private void downloadImage() {
        tv_progress.setVisibility(View.GONE);
        iv_result.setVisibility(View.VISIBLE);
        // 创建一个okhttp客户端对象
        OkHttpClient client = new OkHttpClient();
        // 创建一个GET方式的请求结构
        Request request = new Request.Builder().url(URL_IMAGE).build();
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(@NonNull Call call, @NonNull IOException e) { // 请求失败
                // 回到主线程操纵界面
                runOnUiThread(() -> tv_result.setText("下载网络图片报错:" + e.getMessage()));
    
            }
    
            @Override // 请求成功
            public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
                InputStream is = response.body().byteStream();
                // 从返回的输入流中解码获得位图数据
                Bitmap bitmap = BitmapFactory.decodeStream(is);
                String mediaType = response.body().contentType().toString();
                long length = response.body().contentLength();
                String desc = String.format("文件类型:%s,文件大小:%d", mediaType, length);
                // 回到主线程操纵界面
                runOnUiThread(() -> {
                    tv_result.setText("下载网络图片返回:"+desc);
                    iv_result.setImageBitmap(bitmap);
                });
            }
        });
    }
    

    运行APP测试如图【下载网络图片的结果】所示


    okhttp下载网络图片的结果.png

    (2)网络文件不只是图片,还有其他各式各样的文件,这些文件没有专门的解码工具,只能从输入流老老实实地读取字节数据。不过读取字节数据有个好处,就是能够根据已经读写的数据长度计算下载进度,特别在下载大文件的时候,实时展示当前的下载进度非常有用。下面是通过okhttp下载普通文件的示例代码:

    private final static String URL_APK = "https://ptgl.fujian.gov.cn:8088/masvod/public/2021/03/19/20210319_178498bcae9_r38.mp4";
    // 下载网络文件
    private void downloadFile() {
        tv_progress.setVisibility(View.VISIBLE);
        iv_result.setVisibility(View.GONE);
        OkHttpClient client = new OkHttpClient(); // 创建一个okhttp客户端对象
        // 创建一个GET方式的请求结构
        Request request = new Request.Builder().url(URL_APK).build();
        Call call = client.newCall(request); // 根据请求结构创建调用对象
        // 加入HTTP请求队列。异步调用,并设置接口应答的回调方法
        call.enqueue(new Callback() {
            @Override
            public void onFailure(@NonNull Call call, @NonNull IOException e) { // 请求失败
                // 回到主线程操纵界面
                runOnUiThread(() -> tv_result.setText("下载网络文件报错:" + e.getMessage()));
            }
    
            @Override
            public void onResponse(@NonNull Call call, @NonNull Response response) { // 请求成功
                String mediaType = response.body().contentType().toString();
                long length = response.body().contentLength();
                String desc = String.format("文件类型为%s,文件大小为%d", mediaType, length);
                // 回到主线程操纵界面
                runOnUiThread(() -> tv_result.setText("下载网络文件返回:" + desc));
                String path = String.format("%s/%s.apk",
                        getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString(),
                        DateUtil.getNowDateTime());
                // 下面从返回的输入流中读取字节数据并保存为本地文件
                try {
                    InputStream is = response.body().byteStream();
                    FileOutputStream fos = new FileOutputStream(path);
                    byte[] buf = new byte[100 * 1024];
                    int sum = 0, len = 0;
                    while ((len = is.read(buf)) !=-1){
                        fos.write(buf,0,len);
                        sum += len;
                        int progress = (int)(sum * 1.0f/length *100);
                        String detail = String.format("文件保存在%s。已下载%d%%",path,progress);
                        // 回到主线程操纵界面
                        runOnUiThread(() -> tv_progress.setText(detail));
                    }
    
                } catch (Exception e) {
                    e.printStackTrace();
                }
    
            }
        });
    }
    

    运行测试如图【文件下载】所示:

    文件下载结束的画面.png
    (3)组合上传的业务场景
    okhttp不仅让下载文件变简单了,还让上传文件变得更加灵活易用。修改个人资料上传头像图片、在朋友圈发动态视频等都用到了文件上传功能,并且上传文件常常带着文字说明,比如上传头像时可能一并修改了昵称、发布视频时附加了视频描述,甚至可能同时上传多个文件等。
    像这种组合上传的业务场景,有了okhttp就好办多了。它引入分段结构MultipartBody及其建造器,并提供了名为addFormDataPart的两种重载方法,分别适用于文本格式与文件格式的数据。带两个输入参数的addFormDataPart方法,它的第一个参数是字符串的键名,第二个参数是字符串的键值,该方法用来传递文本消息。带三个输入参数的addFormDataPart方法,它的第一个参数是文件类型,第二个参数是文件名,第三个参数是文件体。
    举个带头像进行用户注册的例子,既要把用户名和密码送给服务端,也要把头像图片传给服务端,此时需多次调用addFormDataPart方法,并通过POST方式提交数据。虽然存在文件上传的交互操作,但整体操作流程与POST方式调用接口保持一致,唯一区别在于请求结构由MultipartBody生成。下面是上传文件之时根据MultipartBody构建请求结构的代码模板:
    private List<String> mPathList = new ArrayList<>(); // 头像文件的路径列表
    // 执行文件上传动作
    private void uploadFile() {
        if (mPathList.size() <= 0) {
            Toast.makeText(this, "请选择待上传的用户头像", Toast.LENGTH_SHORT).show();
            return;
        }
        // 创建分段内容的建造器对象
        MultipartBody.Builder builder = new MultipartBody.Builder();
        String username = et_username.getText().toString();
        String password = et_password.getText().toString();
        if (!TextUtils.isEmpty(username)) {
            // 往建造器对象添加文本格式的分段数据
            builder.addFormDataPart("username", username);
            builder.addFormDataPart("password", password);
        }
        for (String path : mPathList) { // 添加多个附件
            File file = new File(path); // 根据文件路径创建文件对象
            // 往建造器对象添加图像格式的分段数据
            builder.addFormDataPart("image", file.getName(),
                    RequestBody.create(file, MediaType.parse("image/*")));
        }
        RequestBody body = builder.build(); // 根据建造器生成请求结构
        OkHttpClient client = new OkHttpClient(); // 创建一个okhttp客户端对象
        // 创建一个POST方式的请求结构
        Request request = new Request.Builder().post(body).url(URL_REGISTER).build();
        Call call = client.newCall(request); // 根据请求结构创建调用对象
        // 加入HTTP请求队列。异步调用,并设置接口应答的回调方法
        call.enqueue(new Callback() {
            @Override
            public void onFailure(@NonNull Call call, @NonNull IOException e) { // 请求失败
                // 回到主线程操纵界面
                runOnUiThread(() -> tv_result.setText("调用注册接口报错:\n" + e.getMessage()));
            }
    
            @Override
            public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { // 请求成功
                String resp = response.body().string();
                // 回到主线程操纵界面
                runOnUiThread(() -> tv_result.setText("调用注册接口返回:\n" + resp));
            }
        });
    
    }
    

    确保服务端的注册接口正常开启服务端代码,并且手机和计算机连接同一个WiFi,再运行并测试该App。打开初始的注册界面,如图【尚未进行用户注册】所示,依次输入注册信息:

    尚未进行用户注册.png
    成功提交用户注册信息之后,如图所示:
    成功提交用户注册信息.png

    相关文章

      网友评论

          本文标题:Android HTTP访问

          本文链接:https://www.haomeiwen.com/subject/igvzrdtx.html