Home | 简体中文 | 繁体中文 | 杂文 | Github | 知乎专栏 | Facebook | Linkedin | Youtube | 打赏(Donations) | About
知乎专栏

120.2. OkHttp - An HTTP & HTTP/2 client for Android and Java applications

http://square.github.io/okhttp/

120.2.1. Gradle

再 app/build.gradle 文件中增加依赖包

		
implementation "com.squareup.okhttp3:okhttp:4.10.0"
		
		

app/build.gradle

		
neo@MacBook-Pro ~/AndroidStudioProjects/okhttp % cat app/build.gradle
		
apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "cn.netkiller.okhttp"
        minSdkVersion 28
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support:design:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    implementation 'com.squareup.okhttp3:okhttp:3.11.0'
}
		
		
		

120.2.2. AndroidManifest.xml 开启网络访问权限

在 app/src/main/AndroidManifest.xml 文件中增加

		
<uses-permission android:name="android.permission.INTERNET" />		
		
		

否则 okhttp 无法访问网络,添加后的效果如下。

		
neo@MacBook-Pro ~/AndroidStudioProjects/okhttp % cat app/src/main/AndroidManifest.xml 
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="cn.netkiller.okhttp">
    <uses-permission android:name="android.permission.INTERNET" />
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>		
		
		

120.2.3. okhttp 默认是 HTTPS 开启 HTTP

如果你尝试使用 http 链接 OkHttp3 就抛出异常: CLEARTEXT communication to " + host + " not permitted by network security policy

开发过程中由于 https ssl 需要购买证书,费用较高,通常测试环境我们仍然使用 http 下面方法是开启 http 模式,

创建文件 res/xml/network_security_config.xml 内容如下

		
neo@MacBook-Pro ~/AndroidStudioProjects/okhttp % cat app/src/main/res/xml/network_security_config.xml 
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="true">localhost</domain>
    </domain-config>
</network-security-config>		
		
		

再 app/src/main/AndroidManifest.xml 文件中增加 android:networkSecurityConfig="@xml/network_security_config"

		
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="cn.netkiller.okhttp">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:networkSecurityConfig="@xml/network_security_config">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

    <uses-permission android:name="android.permission.INTERNET" />
</manifest>		
		
		

120.2.4. 连接池

		
      ConnectionPool connectionPool = new ConnectionPool(10, 5, TimeUnit.MINUTES);

        OkHttpClient client = new OkHttpClient.Builder()
                .connectionPool(connectionPool)
                .retryOnConnectionFailure(true)
                .connectTimeout(5, TimeUnit.MINUTES)
                .readTimeout(5, TimeUnit.MINUTES)
                .writeTimeout(Duration.ofSeconds(90))
                .callTimeout(Duration.ofSeconds(90))
                .build();
        client.dispatcher().setMaxRequestsPerHost(200);
        client.dispatcher().setMaxRequests(200);		
		
		

120.2.5. GET

		
	OkHttpClient client = new OkHttpClient();
 
    Request request = new Request.Builder()
            .url("http://www.netkiller.cn")
            .build();
 
    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) {
        throw new IOException("服务器端错误: " + response);
    }
 
    Headers responseHeaders = response.headers();
    for (int i = 0; i < responseHeaders.size(); i++) {
        System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
    }
 
    System.out.println(response.body().string());		
		
		

120.2.5.1. URL 组装

			
		HttpUrl.Builder urlBuilder = HttpUrl.parse("https://www.netkiller.cn").newBuilder();
		urlBuilder.addPathSegments("search/cache_vector_chatgpt");
		urlBuilder.addQueryParameter("question", "Helloworld!!!");
		String url = urlBuilder.build().toString();
		
		Log.d("API", url);
		OkHttpClient client = new OkHttpClient();
		try {
		    Request request = new Request.Builder().url(url).build();
		    Response response = client.newCall(request).execute();
		    if (response.isSuccessful()) {
		        Log.d("API", response.body().string());
		    }
		} catch (IOException e) {
		    throw new RuntimeException(e);
		}			
			
			

URL 输出

			
https://www.netkiller.cn/search/cache_vector_chatgpt?question=Helloworld%21%21%21
			
			

120.2.5.2. Get 异步调用

			
        OkHttpClient client = new OkHttpClient();

        Request request = new Request.Builder().url(url).build();
        Call call = client.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {

            }

            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                if (response.isSuccessful()) {
                    Log.i("TAG", "Async: " + response.body().string());
                }
            }
        });			
			
			

120.2.6. POST

120.2.6.1. POST Form Data

from 数据如下

			
key=value&other=data			
			
			
			
		String url = "https://api.netkiller.cn/oauth/token";

		OkHttpClient client = new OkHttpClient();

        RequestBody formBody = new FormBody.Builder()
                .add("grant_type", "password")
                .add("username", "netkiller")
                .add("password", "123456")
                .build();

        Request request = new Request.Builder()
                .url(url)
                .post(formBody)
                .build();

        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) {
            throw new IOException("服务器端错误: " + response);
        }
			
			

120.2.6.2. POST RAW JSON

POST RAW Json 数据,数据的形态这样的

			
{"key":"value"}
			
			
			
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();
}		
			
			

120.2.6.3. 数据流提交

			
OkHttpClient client = new OkHttpClient();
final MediaType MEDIA_TYPE_TEXT = MediaType.parse("text/plain");
final String postBody = "Hello World";

RequestBody requestBody = new RequestBody() {
    @Override
    public MediaType contentType() {
        return MEDIA_TYPE_TEXT;
    }
    @Override
    public void writeTo(BufferedSink sink) throws IOException {
        sink.writeUtf8(postBody);
    }
    @Override
    public long contentLength() throws IOException {
        return postBody.length();
    }
};

Request request = new Request.Builder()
        .url("https://www.google.com")
        .post(requestBody)
        .build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) {
    throw new IOException("服务器端错误: " + response);
}
System.out.println(response.body().string());			
			
			

120.2.7. HTTP PUT 请求

		
import java.util.concurrent.TimeUnit;

import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

public class Test {
  public static void main(String[] args) throws Exception {
    String url = "http://****";
    String json = "{\"param\":\"value2\",\"key\":\"value\"}";
    OkHttpClient client = new OkHttpClient().newBuilder() //
        .readTimeout(60, TimeUnit.SECONDS) // 设置读取超时时间
        .writeTimeout(60, TimeUnit.SECONDS) // 设置写的超时时间
        .connectTimeout(60, TimeUnit.SECONDS) // 设置连接超时时间
        .build();

    MediaType JSON = MediaType.parse("application/json; charset=utf-8");
    RequestBody body = RequestBody.create(json, JSON);
    Request request = new Request.Builder().url(url).put(body).build();
    try (Response response = client.newCall(request).execute()) {
      System.out.println(response.body().string());
    }
  }
}
		
		
		

120.2.8. http header 相关设置

120.2.8.1. 设置 HTTP 头

添加Http头

			
Request request = new Request.Builder()
        .url(url)
        .addHeader("CLIENT", "AD")
        .addHeader("USERID", "343")
        .build();		
			
			

覆盖 HTTP 头

			
        Request request = new Request.Builder()
                .header("Authorization", "replace this text with your token")
                .url(url)
                .build();		
			
			
			
public class Headers {
   public static void main(String[] args) throws IOException {
    OkHttpClient client = new OkHttpClient();
 
    Request request = new Request.Builder()
            .url("http://www.netkiller.cn")
            .header("User-Agent", "Apple Mac")
            .addHeader("Accept", "text/html")
            .build();
 
    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) {
        throw new IOException("服务器端错误: " + response);
    }
 
    System.out.println(response.header("Server"));
    System.out.println(response.headers("Set-Cookie"));
   }
}		
			
			

120.2.8.2. Cookie 管理

			
OkHttpClient mHttpClient = new OkHttpClient.Builder().cookieJar(new CookieJar() {
    private final HashMap<String, List<Cookie>> cookieStore = new HashMap<>();
    @Override
    public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
        cookieStore.put(url.host(), cookies);
    }
    @Override
    public List<Cookie> loadForRequest(HttpUrl url) {
        List<Cookie> cookies = cookieStore.get(url.host());
        return cookies != null ? cookies : new ArrayList<Cookie>();
    }
}).build();			
			
			

120.2.8.3. 禁用缓存

			
Request request = new Request.Builder()
    .cacheControl(new CacheControl.Builder().noCache().build())
    .url(url)
    .build();			
			
			

120.2.8.4. 设置缓存 max-age

			
// 等同于 nocache			
Request request = new Request.Builder()
    .cacheControl(new CacheControl.Builder()
        .maxAge(0, TimeUnit.SECONDS)
        .build())
    .url("https://www.netkiller.cn")
    .build();
    
// 设置缓存 365 天
Request request = new Request.Builder()
    .cacheControl(new CacheControl.Builder()
        .maxStale(365, TimeUnit.DAYS)
        .build())
    .url("https://www.netkiller.cn")
    .build();    	
			
			

120.2.8.5. 强制缓存

			
Request request = new Request.Builder()
    .cacheControl(new CacheControl.Builder()
        .onlyIfCached()
        .build())
    .url("https://www.netkiller.cn/helloworld.txt")
    .build();
Response forceCacheResponse = client.newCall(request).execute();
if (forceCacheResponse.code() != 504) {
  // The resource was cached! Show it.
} else {
  // The resource was not cached.
}			
			
			

120.2.9. HTTP Base Auth

		
        OkHttpClient client = new OkHttpClient.Builder().authenticator(
                new Authenticator(){
                    public Request authenticate(Route route, Response response) {
                        String credential = Credentials.basic("api","secret");
                        return response.request().newBuilder().header("Authorization", credential).build();
                    }
                }
        ).build();		
		
		

120.2.10. HttpUrl.Builder 组装 URL 地址参数

使用字符串拼接 URL地址特别容易出错

		
String url = "https://www.netkiller.cn/article?username="+ username + "&category="+ category;		
		
		

较好的处理方式是使用 HttpUrl.Builder

		
		HttpUrl.Builder builder = HttpUrl.parse("https://www.netkiller.cn/article").newBuilder();
        builder.addQueryParameter("username", "netkiller");
        builder.addQueryParameter("category", "android");
        String url = builder.build().toString();

        Log.d("okhttp", url);		
		
		

输出结果

		
https://www.netkiller.cn/article?username=netkiller&category=android		
		
		

120.2.11. Android Activity Example

		
package cn.netkiller.okhttp;

import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

import java.io.IOException;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class HttpActivity extends AppCompatActivity {

    TextView textView;
    private Handler mHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_http);
        
        textView = (TextView) findViewById(R.id.textView);

        mHandler = new Handler(Looper.getMainLooper()) {
            @Override
            public void handleMessage(Message msg) {
                textView.setText((String) msg.obj);
            }
        };

        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder().url("https://api.netkiller.cn/member/json").build();
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.d("okhttp", "Connect timeout. " + e.getMessage());
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                String text = response.body().string();
                Log.d("okhttp", "HTTP Code " + response.code() + " TEXT " + text);

                Message msg = new Message();
                msg.what = 0;
                msg.obj = text;
                mHandler.sendMessage(msg);

            }
        });

    }
}
		
		

120.2.12. Android Oauth2 + Jwt example

Oauth 返回数据,通过 Gson 的 fromJson 存储到下面类中。

		
package cn.netkiller.okhttp.pojo;

public class Oauth {
    private String access_token;
    private String token_type;
    private String refresh_token;
    private String expires_in;
    private String scope;
    private String jti;

    public String getAccess_token() {
        return access_token;
    }

    public void setAccess_token(String access_token) {
        this.access_token = access_token;
    }

    public String getToken_type() {
        return token_type;
    }

    public void setToken_type(String token_type) {
        this.token_type = token_type;
    }

    public String getRefresh_token() {
        return refresh_token;
    }

    public void setRefresh_token(String refresh_token) {
        this.refresh_token = refresh_token;
    }

    public String getExpires_in() {
        return expires_in;
    }

    public void setExpires_in(String expires_in) {
        this.expires_in = expires_in;
    }

    public String getScope() {
        return scope;
    }

    public void setScope(String scope) {
        this.scope = scope;
    }

    public String getJti() {
        return jti;
    }

    public void setJti(String jti) {
        this.jti = jti;
    }

    @Override
    public String toString() {
        return "Oauth{" +
                "access_token='" + access_token + '\'' +
                ", token_type='" + token_type + '\'' +
                ", refresh_token='" + refresh_token + '\'' +
                ", expires_in='" + expires_in + '\'' +
                ", scope='" + scope + '\'' +
                ", jti='" + jti + '\'' +
                '}';
    }
}	
		
		

Activity 文件

		
package cn.netkiller.okhttp;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

import com.google.gson.Gson;

import java.io.IOException;

import cn.netkiller.okhttp.pojo.Oauth;
import okhttp3.Authenticator;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Credentials;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.Route;

public class Oauth2jwtActivity extends AppCompatActivity {

    private TextView token;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_oauth2jwt);

        token = (TextView) findViewById(R.id.token);

        try {
            get();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }


    public static Oauth accessToken() throws IOException {


        OkHttpClient client = new OkHttpClient.Builder().authenticator(
                new Authenticator() {
                    public Request authenticate(Route route, Response response) {
                        String credential = Credentials.basic("api", "secret");
                        return response.request().newBuilder().header("Authorization", credential).build();
                    }
                }
        ).build();

        String url = "https://api.netkiller.cn/oauth/token";

        RequestBody formBody = new FormBody.Builder()
                .add("grant_type", "password")
                .add("username", "blockchain")
                .add("password", "123456")
                .build();

        Request request = new Request.Builder()
                .url(url)
                .post(formBody)
                .build();

        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) {
            throw new IOException("服务器端错误: " + response);
        }

        Gson gson = new Gson();
        Oauth oauth = gson.fromJson(response.body().string(), Oauth.class);
        Log.i("oauth", oauth.toString());
        return oauth;
    }

    public void get() throws IOException {


        OkHttpClient client = new OkHttpClient.Builder().authenticator(
                new Authenticator() {
                    public Request authenticate(Route route, Response response) throws IOException {
                        return response.request().newBuilder().header("Authorization", "Bearer " + accessToken().getAccess_token()).build();
                    }
                }
        ).build();

        String url = "https://api.netkiller.cn/misc/compatibility";

        Request request = new Request.Builder()
                .url(url)
                .build();

        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                call.cancel();
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {

                final String myResponse = response.body().string();

                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Log.i("oauth", myResponse);
                        token.setText(myResponse);
                    }
                });

            }
        });
    }

	public void post() throws IOException {


        OkHttpClient client = new OkHttpClient.Builder().authenticator(
                new Authenticator() {
                    public Request authenticate(Route route, Response response) throws IOException {
                        return response.request().newBuilder().header("Authorization", "Bearer " + accessToken().getAccess_token()).build();
                    }
                }
        ).build();

        String url = "https://api.netkiller.cn/history/create";

        String json = "{\n" +
                "  \"assetsId\": \"5bced71c432c001c6ea31924\",\n" +
                "  \"title\": \"添加信息\",\n" +
                "  \"message\": \"信息内容\",\n" +
                "  \"status\": \"录入\"\n" +
                "}";

        final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
        RequestBody body = RequestBody.create(JSON, json);

        Request request = new Request.Builder()
                .url(url)
                .post(body)
                .build();

        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                call.cancel();
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {

                final String myResponse = response.body().string();

                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {

//                        Gson gson = new Gson();
//                        Oauth oauth = gson.fromJson(myResponse, Oauth.class);
//                        Log.i("oauth", oauth.toString());

                        token.setText(myResponse);
                    }
                });

            }
        });
    }

}
		
		

上面的例子演示了,怎样获取 access token 以及 HTTP 基本操作 GET 和 POST,再Restful接口中还可能会用到 PUT, DELETE 等等,原来相同,这里就不在演示。

120.2.13. HTTP/2

首先确认你的服务器是支持 HTTP2,HTTP2配置方法可以参考 《Netkiller Web 手札》

下面是我的例子仅供参考,Nginx 开启 http2 代理后面的 Spring Cloud 接口。

		
server {
    listen       80;
    listen 443 ssl http2;
    server_name  api.netkiller.cn;

    ssl_certificate ssl/netkiller.cn.crt;
    ssl_certificate_key ssl/netkiller.cn.key;
    ssl_session_cache shared:SSL:20m;
    ssl_session_timeout 60m;

    charset utf-8;
    access_log  /var/log/nginx/api.netkiller.cn.access.log;

    error_page  497              https://$host$uri?$args;

    if ($scheme = http) {
        return 301 https://$server_name$request_uri;
    }

    location / {
		proxy_pass   http://127.0.0.1:8000;
        proxy_http_version 1.1;
		proxy_set_header    Host    $host;
		proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;	
    }

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #    deny  all;
    #}
}		
		
		

安卓程序如下

		
		String url = "https://api.netkiller.cn/member/json";

        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder().url(url).build();
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.d("okhttp", "Connect timeout. " + e.getMessage());
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                String text = response.body().string();
                Log.d("okhttp", "HTTP Code " + response.code() + " TEXT " + text);
                Log.d("okhttp", "Protocol: " + response.protocol());
            }
        });
		
		

运行结果

		
D/okhttp: HTTP Code 200 TEXT {"status":false,"reason":"","code":0,"data":{"id":null,"username":"12322228888","mobile":"12322228888","password":"123456","wechat":"微信ID","role":"Organization","captcha":null,"createDate":"2018-10-25 09:25:23","updateDate":null}}
    Protocol: h2
		
		

输出 h2 表示当前接口与服务器连接是通过 http2 完成。

120.2.14. 异步更新 UI

		
 		buttonSend.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                HttpUrl.Builder urlBuilder = HttpUrl.parse(apiUrl).newBuilder();
                urlBuilder.addPathSegments("search/cache_vector_chatgpt");
                urlBuilder.addQueryParameter("question", editTextMessage.getText().toString());
                String url = urlBuilder.build().toString();
                Log.d("API", url);
                OkHttpClient client = new OkHttpClient();

                Request request = new Request.Builder().url(url).build();
                Call call = client.newCall(request);

                call.enqueue(new Callback() {
                    @Override
                    public void onFailure(@NotNull Call call, @NotNull IOException e) {

                    }

                    @Override
                    public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                        if (response.isSuccessful()) {
                            answer = response.body().string();
                            updateResult(answer);
                            Log.i("TAG", "Async: " + answer);
                        }
                    }
                });
            }
        });		
		
		
		
    private void updateResult(final String response) {
        //在子线程中更新UI
        getActivity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                // 在这里进行UI操作,将结果显示到界面上
                text_dashboard.setText(response);
            }
        });
    }		
		
		

Lambda 表达式

		
	private void showResponse(final String response) {
        //在子线程中更新UI
        runOnUiThread(() -> {
			// 在这里进行UI操作,将结果显示到界面上
			responseText.setText(response);
        });
    }		
		
		

在 Fragment 中需这样使用 getActivity().runOnUiThread()

		
        buttonSend.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                
                HttpUrl.Builder urlBuilder = HttpUrl.parse(apiUrl).newBuilder();
                urlBuilder.addPathSegments("search/cache_vector_chatgpt");
                urlBuilder.addQueryParameter("question", editTextMessage.getText().toString());
                String url = urlBuilder.build().toString();
                Log.d("API", url);
                OkHttpClient client = new OkHttpClient();

                Request request = new Request.Builder().url(url).build();
                Call call = client.newCall(request);

                call.enqueue(new Callback() {
                    @Override
                    public void onFailure(@NotNull Call call, @NotNull IOException e) {

                    }

                    @Override
                    public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                        if (response.isSuccessful()) {
                            answer = response.body().string();
                            getActivity().runOnUiThread(() -> {
                                text_dashboard.setText(answer);
                            });
                            Log.i("TAG", "Async: " + answer);
                        }
                    }
                });
            }
        });		
		
		

120.2.15. SSE 客户端

		
 public void postStream(String function, LinkedHashMap<String, String> params, String json) throws InterruptedException {

        HttpUrl.Builder urlBuilder = HttpUrl.parse(address).newBuilder();
        urlBuilder.addPathSegments(function);
        if (params != null) {
            for (Map.Entry<String, String> entry : params.entrySet()) {
                String key = entry.getKey();
                String value = entry.getValue();
                urlBuilder.addQueryParameter(key, value);
            }
        }
        String url = urlBuilder.build().toString();
        Log.d(TAG, "Post URL: " + url + " Json: " + json);

        MediaType mediaType = MediaType.parse("application/json; charset=utf-8");
        RequestBody body = RequestBody.create(json, mediaType);
        Request request = new Request.Builder()
                .url(url)
//                .addHeader("Authorization", "Bearer " + token)
                .addHeader("Accept", "text/event-stream")
//                .addHeader("accept-encoding", "gzip, deflate")
                .addHeader("token", this.token)
                .post(body).build();

        OkHttpClient client = this.client();

        RealEventSource realEventSource = new RealEventSource(request, new EventSourceListener() {
            @Override
            public void onOpen(EventSource eventSource, Response response) {
                Log.d(TAG, "onOpen");
            }

            @Override
            public void onEvent(EventSource eventSource, String id, String type, String data) {

                Log.d(TAG, "Stream Id: " + id + " Type: " + type + " Data: " + data);   // 请求到的数据

            }

            @Override
            public void onClosed(EventSource eventSource) {
                Log.d(TAG, "onClosed");
            }

            @Override
            public void onFailure(EventSource eventSource, Throwable throwable, Response response) {
                Log.d(TAG, "onFailure");//这边可以监听并重新打开
                Log.d(TAG, response.toString());
                Log.d(TAG, throwable.toString());
            }

        });
        realEventSource.connect(client);
    }		
		
		
		
 public void getStream(String function, LinkedHashMap<String, String> params, EventSourceListener listener) throws InterruptedException {

        HttpUrl.Builder urlBuilder = HttpUrl.parse(address).newBuilder();
        urlBuilder.addPathSegments(function);
        if (params != null) {
            for (Map.Entry<String, String> entry : params.entrySet()) {
                String key = entry.getKey();
                String value = entry.getValue();
                urlBuilder.addQueryParameter(key, value);
            }

        }
        String url = urlBuilder.build().toString();
//        Log.d(TAG, "Post URL: " + url + " params: " + params.toString());
        Request request = new Request.Builder()
                .url(url)
//                .addHeader("Authorization", "Bearer " + token)
                .addHeader("Accept", "text/event-stream; charset=utf-8")
//                .addHeader("accept-encoding", "gzip, deflate")
                .addHeader("token", this.token)
                .get().build();

        OkHttpClient client = this.client();
        EventSource.Factory createFactory = EventSources.createFactory(client);
//        EventSourceListener eventSourceListener = new EventSourceListener() {
//            @Override
//            public void onEvent(EventSource eventSource, String id, String type, String data) {
//
//                Log.d(TAG, "Stream Id: " + id + " Type: " + type + " Data: " + data);   // 请求到的数据
//            }
//
//            public void onFailure(EventSource eventSource, Throwable throwable, Response response) {
//                super.onFailure(eventSource, throwable, response);
//                Log.d(TAG, response.toString());
//                Log.d(TAG, throwable.toString());
//            }
//        };
        createFactory.newEventSource(request, listener);
    }		
		
		

120.2.16. WebSocket Client

		
package cn.netkiller.service;

import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.reactivestreams.Publisher;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.WebSocketMessage;
import org.springframework.web.reactive.socket.WebSocketSession;
import org.springframework.web.reactive.socket.client.StandardWebSocketClient;
import org.springframework.web.reactive.socket.client.WebSocketClient;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.TimeUnit;

@Component
@Slf4j
public class WebSocketService {
    private final OkHttpClient webSocketClient;
    private final Request request;
    String url = "wss://socketsbay.com/wss/v2/1/demo/";
    private WebSocket webSocket;

    public WebSocketService() {
        webSocketClient = new OkHttpClient.Builder().pingInterval(30, TimeUnit.SECONDS).build();
        log.info("WebSocket url: {}", url);
        // 构造请求对象
        request = new Request.Builder().url(url).build();
        connect();
    }

    public void send(String text) {

        if (webSocket == null) {
            connect();
        }
        webSocket.send(text);
        log.info("Send: " + text);
    }

    public void connect() {
        // 调用websocket服务端
        webSocket = webSocketClient.newWebSocket(request, new MyWebSocketListener());
        log.info(webSocket.toString());

    }

    class MyWebSocketListener extends WebSocketListener {

        /**
         * websocket连接建立后来到这个方法
         *
         * @param webSocket
         * @param response
         */
        @Override
        public void onOpen(WebSocket webSocket, Response response) {
            super.onOpen(webSocket, response);
            try {
                log.info("onOpen: " + response.body().string());
            } catch (IOException e) {

            }
        }

        @Override
        public void onMessage(WebSocket webSocket, String text) {
            super.onMessage(webSocket, text);
            log.info("Receive: " + text);
        }

//        @Override
//        public void onMessage(WebSocket webSocket, ByteString bytes) {
//            super.onMessage(webSocket, bytes);
//        }

        @Override
        public void onClosing(WebSocket webSocket, int code, String reason) {
            super.onClosing(webSocket, code, reason);
            log.info("onClosing code: " + code + " reason: " + reason);
        }

        @Override
        public void onClosed(WebSocket webSocket, int code, String reason) {
            super.onClosed(webSocket, code, reason);
            log.info("onClosed code: " + code + " reason: " + reason);
        }

        @Override
        public void onFailure(WebSocket webSocket, Throwable t, Response response) {
            super.onFailure(webSocket, t, response);
			if (response == null) {
	            log.error("onFailure, response is null.");
	            return;
	        }
	        try {
	            log.error("onFailure, code: {}, errmsg: {}", response.code(), response.body().string());
	        } catch (IOException e) {
	            log.warn("onFailure failed, error: {}", e.getMessage());
	        }
        }
    }

}