OkHttp、Android的回调机制

规范用法

注:摘自《第一行代码》第二版
1、导包:
implementation ‘com.squareup.okhttp3:okhttp:3.4.1’
1、发送Get请求:
下面我们来看一下OkHttp的具体用法,首先需要创建一个OkHttpClient的实例,如下所示:
OkHttpClient client = new OkHttpClient();
接下来如果想要发起一条HTTP请求,就需要创建一个Request对象:
Request request = new Request.Builder().build();
当然,上述代码只是创建了一个空的Request对象,并没有什么实际作用,我们可以在最终的build()方法之前连缀很多其他方法来丰富这个Request对象。比如可以通过url()方法来设置目标的网络地址,如下所示:
Request request = new Request.Builder().url(“http://www.baidu.com”).build();
之后调用OkHttpClient的newCal1()方法来创建一个Cal1对象,并调用它的execute()方法来发送请求并获取服务器返回的数据,写法如下:
Response response = client.newCall(request).execute();
其中Response对象就是服务器返回的数据了,我们可以使用如下写法来得到返回的具体内容:
String responseData = response.body().string();
2、发送Post请求


OkHttpClient client = new OkHttpClient();

RequestBody requestBody = new FormBody.Builder()
.add("username","admin")
.add("password","123456")
.build();

然后在Request.Builder中调用一下post()方法,并将RequestBody对象传人:
Request request = new Request.Builder()
.url("http://www.baidu.com")
·post(requestBody)
.build();

Response response = client.newCall(request).execute();
String responseData = response.body().string();

GSON解析返回数据

导包:implementation ‘com.google.code.gson:gson:2.7’ //用来解析后面okhttp请求服务器后返回的json数据
比如说一段JSON格式的数据如下所示:{“name”:“Tom”,“age”:20}
那我们就可以定义一个Person类,并加入name和age这两个字段,然后只需简单地调用
如下代码就可以将JSON数据自动解析成一个Person对象了:
Gson gson = new Gson();
Person person = gson.fromJson(jsonData, Person.class);
如果需要解析的是一段JSON数组会稍微麻烦一点,我们需要借助TypeToken将期望解析成
的数据类型传入到fromJson()方法中,如下所示:
List people = gson.fromJson(jsonData, new TypeToken<List>(){}.getType());

常见报错

1、还是在调用远程接口,进行网络通信的时候,报了如下错误:
W/System.err: java.net.UnknownServiceException:
CLEARTEXT communication to xxx.xxx.xxx not permitted by network security policy
原因:
为保证用户数据和设备的安全,Google针对下一代 Android 系统(Android P) 的应用程序,将要求默认使用加密连接,这意味着 Android P 将禁止 App 使用所有未加密的连接,因此运行 Android P 系统的安卓设备无论是接收或者发送流量,未来都不能明码传输,需要使用下一代(Transport Layer Security)传输层安全协议,而 Android Nougat 和 Oreo 则不受影响。
在Android P系统的设备上,如果应用使用的是非加密的明文流量的http网络请求,则会导致该应用无法进行网络请求,https则不会受影响,同样地,如果应用嵌套了webview,webview也只能使用https请求。
(1)改用https请求
(2)targetSdkVersion 降到27以下
(3)在 application 元素中添加:
android:usesCleartextTraffic=”true”
![image.png](https://img-blog.csdnimg.cn/img_convert/458d6d282435b0fbcbee0b3c96449a0e.png#clientId=uc5f1ba96-dc98-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=246&id=u1adc76b0&margin=[object Object]&name=image.png&originHeight=306&originWidth=599&originalType=url&ratio=1&rotation=0&showTitle=false&size=36156&status=done&style=none&taskId=u740598ac-8510-4d4c-9488-4ad0a9d8f77&title=&width=481)
2、报错java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.webviewapplication/com.example.webviewapplication.OkHttpActivity}: android.os.NetworkOnMainThreadException在主线程中的网络异常错误

public class OkHttpActivity extends AppCompatActivity {

    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ok_http);
        textView = findViewById(R.id.response_text01);
        //直接在下面这种情况运行会报错,因为   在Android4.0以后,会发现,只要是写在主线程(就是Activity)中的HTTP请求,
        //运行时都会报错,这是因为Android在4.0以后为了防止应用的ANR(Aplication Not Response)异常,Android这个设
        //计是为了防止网络请求时间过长而导致界面假死的情况发生。
        //我们这里的代码是MainActivity跳转过来的,依然还是在主线程中
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder().url("https://www.baidu.com").build();
        try {
            Response response = client.newCall(request).execute();
            String respnseData = response.body().string();
            System.out.println("结果是"+respnseData);
        } catch (IOException e) {
            e.printStackTrace();
        }
        
    }
}

解决方法:启动一条子线程进行你的网络请求

public class OkHttpActivity extends AppCompatActivity {

    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ok_http);
        textView = findViewById(R.id.response_text01);
       
        //解决方法:启动一条子线程进行你的网络请求
        sendRequestWithOkHttp();

    }

    private void sendRequestWithOkHttp() {
        new Thread(new Runnable() {  //子线程发网络请求
            @Override
            public void run() {
                OkHttpClient client = new OkHttpClient();
                Request request = new Request.Builder().url("http://172.16.116.60/get_data.json").build();
                try {
                    Response response = client.newCall(request).execute();
                    String respnseData = response.body().string();
                    parseJSONWithJSONObject(respnseData);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    //由子线程调用的方法是不能直接修改主线程的UI的界面的,即textView.setText(sb);,但是
    //我们有这个修改UI主界面的需求怎么办呢?
    /*用runOnUiThread(new Runnable() {
            @Override
            public void run() { 将修改UI界面的代码写这里 }
        } 
                   */
    private void parseJSONWithJSONObject(String jsonData){
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                try {
                    StringBuilder sb = new StringBuilder();
                    JSONArray jsonArray = new JSONArray(jsonData);
                    for(int i = 0; i < jsonArray.length(); i++){
                        JSONObject jsonObject = jsonArray.optJSONObject(i);
                        String id = jsonObject.getString("id");
                        String name = jsonObject.getString("name");
                        String version = jsonObject.getString("version");
                        sb.append(id+":"+name+":"+version);
                        sb.append("\n");
                    }
                    textView.setText(sb);
                } catch (JSONException e) {
                    e.printStackTrace();
                }
        });
    }
}

JAVA的回调机制

目前你已经掌握了HttpURLConnection和OkHttp的用法,知道了如何发起HTTP请求,以及解析服务器返回的数据,但也许你还没有发现,之前我们的写法其实是很有问题的。因为一个应用程序很可能会在许多地方都使用到网络功能,而发送HTTP请求的代码基本都是相同的,如果我们每次都去编写一遍发送HTTP请求的代码,这显然是非常差劲的做法。没错,通常情况下我们都应该将这些通用的网络操作提取到一个公共的类里,并提供一个静态方法,当想要发起网络请求的时候,只需简单地调用一下这个方法即可。比如使用如下的写法:

public class HttpUtil {
    public static String sendHttpRequest(String address) {
        HttpURLConnection connection = null;
        try {
            URL url = new URL(address);
            connection = (HttpURLConnection) url.openConnection();
            connection. setRequestMethod ("GET");
            connection. setConnectTimeout (8000);
            connection.setReadTimeout(8000);
            connection.setDoInput(true);
            connection. setDoOutput(true);
            InputStream in = connection.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(in));
            StringBuilder response = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                response.append(line);
            }
            return response.tostring();
        } catch (Exception e) {
            e.printStackTrace();
            return e.getMessage();
        } finally {
            if (connection != null){
                connection.disconnect();
            }
        }
    }
}

以后每当需要发起一条HTTP请求的时候就可以这样写:

String address = "http://www.baidu.com";
String response = HttpUtil.sendHttpRequest(address);

在获取到服务器响应的数据后,我们就可以对它进行解析和处理了。但是需要注意,网络请求通常都是属于耗时操作,而 sendHttpRequest()方法的内部并没有开启线程,这样就有可能导致在调用sendHttpRequest()方法的时候使得主线程被阻塞住。
你可能会说,很简单嘛,在sendHttpRequest()方法内部开启一个线程不就解决这个问题了吗?其实没有你想象中的那么容易,因为如果我们在sendHttpRequest()方法中开启了一个线程来发起HTTP请求,那么服务器响应的数据是无法进行返回的**(主线程和子线程之间是异步执行的,子线程在请求服务器数据的时候会卡住等着,但是主线程可不会等子线程),所有的耗时逻辑都是在子线程里进行的,调用sendHttpRequest()方法的主线程会在子线程请求的服务器**还没来得及响应的时候就执行结束了,当然也就无法接受到返回响应的数据了。
那么遇到这种情况时应该怎么办呢?其实解决方法并不难,只需要使用Java的回调机制京可以了,下面就让我们来学习一下回调机制到底是如何使用的。首先需要定义一个接口,比如将它命名成HttpCallbackListener,代码如下所示:
public interface HttpCallbackListener {
void onFinish(String response);
void onError(Exception e);
}
可以看到,我们在接口中定义了两个方法,onFinish()方法表示当服务器成功响应我们请求的时候调用,onError()表示当进行网络操作出现错误的时候调用。这两个方法都带有参数,onFinish()方法中的参数代表着服务器返回的数据,而onError()方法中的参数记录着错误的详细信息。接着修改HttpUtil中的代码,如下所示:

public class HttpUtil {
    public static void sendHttpRequest(final String address, final
                                       HttpCallbackListener listener) {
        new Thread(new Runnable() {
            @0verride
            public void run() {
                HttpURLConnection connection = null;
                try {
                    URL url = new URL(address);
                    connection = (HttpURLConnection) url.openConnection();
                    connection.setRequestMethod("GET");
                    connection.setConnectTimeout(8000);
                    connection.setReadTimeout (8000);
                    connection.setDoInput(true);
                    connection.setDoOutput(true);
                    InputStream in = connection.getInputStream();
                    BufferedReader reader = new BufferedReader(new InputStreamReader(in));
                    StringBuilder response = new StringBuilder();
                    String line;
                    while ((line = reader.readLine()) != null) {
                        response.append(line);
                    }
                    if (listener != null) {
                        //回调onFinish()方法
                        listener.onFinish(response.toString());
                    }
                } catch (Exception e) {
                    if (listener != null) {
                        // 回调onError()方法
                        listener.onError(e);
                    }
                }finally {
                    if (connection != null) {
                        connection.disconnect();
                    }
                }
            }
        }).start();
    }

    public interface HttpCallbackListener {
        void onFinish(String response);
        void onError(Exception e);
    }
}                                         

我们首先给sendHttpRequest()方法添加了一个HttpCallbackListener参数,并在方法的内部开启了一个子线程,然后在子线程里去执行具体的网络操作。注意,子线程中是无法通过return 语句来返回数据的.因此这里我们将服务器响应的数据传入了HttpCallbackListener的onFinish()方法中,如果出现了异常就将异常原因传入到onError()方法中。
现在sendHttpRequest()方法接收两个参数了,因此我们在调用它的时候还需要将HttpCallbackListener的实例传入,如下所示:

HttpUtil.sendHttpRequest(address, new HttpCallbackListener() { //匿名内部类方式可new接口的
    @Override
    public void onFinish(String response){
        //在这里根据返回内容执行具体的逻辑
    }
    @Override
    public void onError(Exception e) {
        //在这里对异常情况进行处理
    }
});

当子线程收到服务器返回的数据,执行listener.onFinish(response.toString())的时候(这个时候主线程即使后面的代码已经执行完了,也不会结束进程,而是会等待回调函数的发生),就会重新回调到@Override的onFinish或onError执行。
这样的话,当服务器成功响应的时候,我们就可以在onFinish()方法里对响应数据进行处理了。类似地,如果出现了异常,就可以在onError()方法里对异常情况进行处理。如此一来,我们就巧妙地利用回调机制将响应数据成功返回给调用方了。
在这里插入图片描述
不过你会发现,上述使用HttpURLConnection的写法总体来说还是比较复杂的,那么使用OkHttp会变得简单吗?答案是肯定的,而且要简单得多,下面我们来具体看一下。在HttpUtil中加人一个sendOkHttpRequest()方法,如下所示:

public class HttpUtil {
    public static void sendOkHttpRequest(String address, okhttp3.Callback callback) {
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder ()
            .url(address)
            .build();
        client.newCall(request).enqueue(callback);
    }
}

可以看到,sendOkHttpRequest()方法中有一个okhttp3.Callback参数,这个是OkHttp库中自带的一个回调接口,类似于我们刚才自己编写的HttpCallbackListener。然后在client.newCall()之后没有像之前那样一直调用execute()方法,而是调用了一个enqueue()方法,并把okhttp3.Callback参数传入。相信聪明的你已经猜到了,OkHttp在enqueue()方法的内部已经帮我们开好子线程了,然后会在子线程中去执行HTTP请求,并将最终的请求结果回调到okhttp3.Callback当中。
那么我们在调用sendOkHttpRequest()方法的时候就可以这样写:

HttpUtil.sendOkHttpRequest("http://www.baidu.com", new okhttp3.Callback() {
    @Override
    public void onResponse(Call call, Response response) throws I0Exception {
        //得到服务器返回的具体内容
        String responseData = response.body().string();
    }
    @0verride
    public void onFailure(Call call, IOException e) {
        //在这里对异常情况进行处理
    }
});

由此可以看出,OkHttp的接口设计得确实非常人性化,它将一些常用的功能进行了很好的封装,使得我们只需编写少量的代码就能完成较为复杂的网络操作。当然这并不是OkHttp的全部,后面我们还会继续学习它的其他相关知识。
另外需要注意的是,不管是使用HttpURLConnection还是OkHttp,最终的回调接口都还是在子线程中运行的,因此我们不可以在这里执行任何的UI操作,除非借助runOnUiThread()方法来进行线程转换。至于具体的原因,我们很快就会在下一章中学习到了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值