feat: 网络
This commit is contained in:
parent
22ff4384ca
commit
570de3c4e7
|
|
@ -45,8 +45,11 @@ dependencies {
|
|||
implementation(libs.androidx.activity)
|
||||
implementation(libs.androidx.constraintlayout)
|
||||
implementation(libs.logger.timber)
|
||||
implementation(libs.okhttp)
|
||||
implementation(libs.okhttp.logging.interceptor)
|
||||
implementation(libs.androidx.work.ktx)
|
||||
implementation(libs.retrofit)
|
||||
implementation(libs.retrofit.converter.gson)
|
||||
// implementation(libs.gson)
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
androidTestImplementation(libs.androidx.espresso.core)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,12 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
<application
|
||||
android:name=".RokidApplication"
|
||||
android:allowBackup="true"
|
||||
|
|
@ -11,6 +17,7 @@
|
|||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:theme="@style/Theme.Rokid">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
|
|
@ -21,6 +28,7 @@
|
|||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<service android:name="com.yuanxuan.rokid.keeplive.KeepLiveService" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
@ -7,9 +7,20 @@ import androidx.appcompat.app.AppCompatActivity
|
|||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.yuanxuan.rokid.dependencies.AppDependencies
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.yuanxuan.rokid.network.http.ApiResponse
|
||||
import com.yuanxuan.rokid.network.http.Test
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import timber.log.Timber
|
||||
import java.lang.reflect.Type
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
|
@ -21,9 +32,26 @@ class MainActivity : AppCompatActivity() {
|
|||
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
|
||||
insets
|
||||
}
|
||||
AppDependencies.deviceServiceManager.playTTS("测试一下")
|
||||
val json = "{\n" +
|
||||
" \"code\": 0,\n" +
|
||||
" \"msg\": \"success\",\n" +
|
||||
" \"data\": {\n" +
|
||||
" \"userId\": \"123\",\n" +
|
||||
" \"name\": \"Alice\"\n" +
|
||||
" }\n" +
|
||||
"}"
|
||||
|
||||
val x = test<Test>(json)
|
||||
|
||||
// onBackPressedDispatcher.addCallback {
|
||||
// }
|
||||
}
|
||||
|
||||
private inline fun <reified T> test(responseBody: String): T {
|
||||
val typeToken = object : TypeToken<ApiResponse<T>>() {}
|
||||
val typeOfT: Type = typeToken.type
|
||||
val apiResponse: ApiResponse<T> = Gson().fromJson(responseBody, typeOfT)
|
||||
return apiResponse.data
|
||||
|
||||
onBackPressedDispatcher.addCallback {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -18,13 +18,18 @@ class RokidApplication : Application() {
|
|||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
Timber.plant(Timber.DebugTree())
|
||||
AppDependencies.init(this, ApplicationDependencyProvider(this))
|
||||
AppDependencies.init(this, ApplicationDependencyProvider(this, applicationScope))
|
||||
/**
|
||||
* 启动APP必须先获取到SN 后面网络依赖
|
||||
*/
|
||||
applicationScope.launch {
|
||||
val sn = AppDependencies.deviceServiceManager.getSn()
|
||||
Timber.d("sn = $sn")
|
||||
runCatching {
|
||||
AppDependencies.deviceServiceManager.getSn()
|
||||
}.onSuccess {
|
||||
AppDependencies.webSocketManager.connect()
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 这个服务保证心跳包准时发
|
||||
|
|
|
|||
|
|
@ -2,6 +2,12 @@ package com.yuanxuan.rokid.dependencies
|
|||
|
||||
import android.app.Application
|
||||
import com.yuanxuan.rokid.device.DeviceServiceManager
|
||||
import com.yuanxuan.rokid.network.http.ApiRepository
|
||||
import com.yuanxuan.rokid.network.http.ApiService
|
||||
import com.yuanxuan.rokid.network.http.OkHttpManager
|
||||
import com.yuanxuan.rokid.network.http.RequestApi
|
||||
import com.yuanxuan.rokid.network.http.RetrofitClient
|
||||
import com.yuanxuan.rokid.network.websocket.WebSocketManager
|
||||
|
||||
/**
|
||||
* 项目的服务管理器,所有依赖集中管理
|
||||
|
|
@ -11,6 +17,9 @@ object AppDependencies {
|
|||
private lateinit var _application: Application
|
||||
private lateinit var provider: Provider
|
||||
|
||||
val application: Application
|
||||
get() = _application
|
||||
|
||||
fun init(application: Application, provider: Provider) {
|
||||
if (this::_application.isInitialized || this::provider.isInitialized) {
|
||||
return
|
||||
|
|
@ -24,8 +33,20 @@ object AppDependencies {
|
|||
provider.provideDeviceServiceManager()
|
||||
}
|
||||
|
||||
val webSocketManager by lazy {
|
||||
provider.provideWebSocketManager()
|
||||
}
|
||||
|
||||
val requestApi by lazy {
|
||||
provider.provideApiRepository(
|
||||
apiService = RetrofitClient.apiService
|
||||
)
|
||||
}
|
||||
|
||||
interface Provider {
|
||||
fun provideDeviceServiceManager(): DeviceServiceManager
|
||||
fun provideWebSocketManager(): WebSocketManager
|
||||
fun provideApiRepository(apiService: ApiService): ApiRepository
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -2,9 +2,30 @@ package com.yuanxuan.rokid.dependencies
|
|||
|
||||
import android.app.Application
|
||||
import com.yuanxuan.rokid.device.DeviceServiceManager
|
||||
import com.yuanxuan.rokid.network.http.ApiRepository
|
||||
import com.yuanxuan.rokid.network.http.ApiService
|
||||
import com.yuanxuan.rokid.network.http.OkHttpManager
|
||||
import com.yuanxuan.rokid.network.http.RequestApi
|
||||
import com.yuanxuan.rokid.network.websocket.WebSocketManager
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
class ApplicationDependencyProvider(val context: Application) : AppDependencies.Provider {
|
||||
class ApplicationDependencyProvider(val context: Application, val scope: CoroutineScope) :
|
||||
AppDependencies.Provider {
|
||||
override fun provideDeviceServiceManager(): DeviceServiceManager {
|
||||
return DeviceServiceManager(context)
|
||||
}
|
||||
|
||||
override fun provideWebSocketManager(): WebSocketManager {
|
||||
return WebSocketManager(
|
||||
context = context,
|
||||
scope = scope
|
||||
)
|
||||
}
|
||||
|
||||
override fun provideApiRepository(apiService: ApiService): ApiRepository {
|
||||
return ApiRepository(
|
||||
apiService = apiService
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -25,6 +25,7 @@ import kotlinx.coroutines.launch
|
|||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import timber.log.Timber
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
|
||||
|
||||
class DeviceServiceManager(val context: Application) {
|
||||
|
|
@ -157,7 +158,12 @@ class DeviceServiceManager(val context: Application) {
|
|||
}
|
||||
|
||||
suspend fun getSn() = suspendCancellableCoroutine { continuation ->
|
||||
systemFuncServiceTodo { service ->
|
||||
ServiceManager.getSystemFuncService(context) { service ->
|
||||
if (service == null) {
|
||||
continuation.resumeWithException(Throwable("没有拿到SN 要重启APP"))
|
||||
return@getSystemFuncService
|
||||
}
|
||||
systemFuncService = service
|
||||
sn = service.sn
|
||||
continuation.resume(sn)
|
||||
}
|
||||
|
|
@ -187,7 +193,6 @@ class DeviceServiceManager(val context: Application) {
|
|||
}
|
||||
systemFuncService = service
|
||||
todo.invoke(service)
|
||||
Timber.d(" ${service.sn}")
|
||||
}
|
||||
} else {
|
||||
todo.invoke(systemFuncService!!)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
package com.yuanxuan.rokid.network
|
||||
|
||||
import timber.log.Timber
|
||||
import java.net.Inet4Address
|
||||
import java.net.NetworkInterface
|
||||
|
||||
object NetUtils {
|
||||
|
||||
fun getBaseUrl() = "http://${getLocalIPV4address()}.83:8765"
|
||||
|
||||
|
||||
/**
|
||||
* 获取局域网IP
|
||||
*/
|
||||
fun getLocalIPV4address(): String? {
|
||||
try {
|
||||
val networkInterfaces = NetworkInterface.getNetworkInterfaces()
|
||||
while (networkInterfaces.hasMoreElements()) {
|
||||
val networkInterface = networkInterfaces.nextElement()
|
||||
val inetAddresses = networkInterface.inetAddresses
|
||||
while (inetAddresses.hasMoreElements()) {
|
||||
val inetAddress = inetAddresses.nextElement()
|
||||
if (!inetAddress.isLoopbackAddress && inetAddress is Inet4Address) {
|
||||
val fullIp = inetAddress.hostAddress
|
||||
if (fullIp.isNullOrEmpty())
|
||||
return null
|
||||
val lastDotIndex = fullIp.lastIndexOf(".")
|
||||
// 返回第一个找到的IPv4地址
|
||||
return fullIp.take(lastDotIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "获取局域网IP地址时发生异常")
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.yuanxuan.rokid.network.http
|
||||
|
||||
class ApiRepository(private val apiService: ApiService) {
|
||||
|
||||
suspend fun test(): Test? {
|
||||
apiService.test()
|
||||
return null
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.yuanxuan.rokid.network.http
|
||||
|
||||
data class ApiResponse<T>(
|
||||
val code: String,
|
||||
val message: String,
|
||||
val data: T
|
||||
) {
|
||||
fun isSuccess() = code == "200"
|
||||
}
|
||||
|
||||
data class Test(
|
||||
val userId: String,
|
||||
val name: String
|
||||
)
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.yuanxuan.rokid.network.http
|
||||
|
||||
import com.yuanxuan.rokid.network.NetUtils
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Url
|
||||
|
||||
interface ApiService {
|
||||
|
||||
@GET
|
||||
suspend fun test(@Url url: String = "${NetUtils.getBaseUrl()}/test"): ApiResponse<Test>
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.yuanxuan.rokid.network.http
|
||||
|
||||
import java.io.IOException
|
||||
|
||||
class NetworkException(message: String, cause: Throwable? = null) : IOException(message, cause)
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
package com.yuanxuan.rokid.network.http
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.yuanxuan.rokid.network.NetUtils
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.Call
|
||||
import okhttp3.Callback
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okhttp3.Response
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import timber.log.Timber
|
||||
import java.io.IOException
|
||||
import java.lang.Exception
|
||||
import java.lang.reflect.Type
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
class OkHttpManager {
|
||||
|
||||
private val soTimeoutMillis = 30.milliseconds
|
||||
private val gson by lazy {
|
||||
Gson()
|
||||
}
|
||||
|
||||
private val okhttpClient by lazy {
|
||||
val httpLoggingInterceptor by lazy {
|
||||
HttpLoggingInterceptor { message ->
|
||||
Timber.tag("OkHttp").d(message)
|
||||
}.apply {
|
||||
level = HttpLoggingInterceptor.Level.BODY
|
||||
}
|
||||
}
|
||||
OkHttpClient.Builder()
|
||||
.connectTimeout(soTimeoutMillis)
|
||||
.readTimeout(soTimeoutMillis)
|
||||
.addInterceptor(httpLoggingInterceptor)
|
||||
.build()
|
||||
}
|
||||
|
||||
suspend fun testApi(): Test {
|
||||
return makeRequest<Test>(
|
||||
path = "",
|
||||
method = "GET",
|
||||
)
|
||||
}
|
||||
|
||||
private suspend inline fun <reified T> makeRequest(
|
||||
path: String,
|
||||
method: String,
|
||||
jsonBody: String? = null,
|
||||
headers: Map<String, String> = emptyMap(),
|
||||
): T = withContext(Dispatchers.IO) {
|
||||
|
||||
val requestBody = jsonBody?.toRequestBody("application/json".toMediaTypeOrNull())
|
||||
val request = buildRequest(
|
||||
path = path,
|
||||
method = method,
|
||||
body = requestBody,
|
||||
headers = headers
|
||||
)
|
||||
try {
|
||||
val response = okhttpClient.newCall(request).executeAwait()
|
||||
val responseBody = response.body.string()
|
||||
//过滤http错误
|
||||
if (response.isSuccessful.not()) {
|
||||
throw NetworkException(
|
||||
message = "Http error: ${response.code} ${response.message}"
|
||||
)
|
||||
}
|
||||
val typeToken = object : TypeToken<ApiResponse<T>>() {}
|
||||
val typeOfT: Type = typeToken.type
|
||||
val apiResponse: ApiResponse<Test> = gson.fromJson(responseBody, typeOfT)
|
||||
if (apiResponse.isSuccess().not()) {
|
||||
throw NetworkException(
|
||||
message = "Business error: ${apiResponse.code} ${apiResponse.message}"
|
||||
)
|
||||
}
|
||||
return@withContext apiResponse.data as T
|
||||
} catch (e: Exception) {
|
||||
throw NetworkException("Http error: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildRequest(
|
||||
path: String,
|
||||
method: String,
|
||||
body: RequestBody?,
|
||||
headers: Map<String, String>
|
||||
): Request {
|
||||
val request = Request.Builder()
|
||||
.url(NetUtils.getBaseUrl() + path)
|
||||
.method(method, body)
|
||||
headers.forEach { (key, value) ->
|
||||
request.addHeader(key, value)
|
||||
}
|
||||
return request.build()
|
||||
}
|
||||
|
||||
private suspend fun Call.executeAwait(): Response =
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
enqueue(object : Callback {
|
||||
override fun onFailure(call: Call, e: IOException) {
|
||||
if (continuation.context.isActive) {
|
||||
continuation.resumeWithException(e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
//协程被取消忽略请求结果
|
||||
if (continuation.context.isActive) {
|
||||
continuation.resume(response)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
continuation.invokeOnCancellation {
|
||||
try {
|
||||
//协程被取消 终端网络请求
|
||||
cancel()
|
||||
} catch (_: Throwable) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.yuanxuan.rokid.network.http
|
||||
|
||||
class RequestApi(
|
||||
private val okHttp: OkHttpManager
|
||||
) {
|
||||
|
||||
suspend fun testApi(): Result<Test> {
|
||||
return runCatching {
|
||||
okHttp.testApi()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.yuanxuan.rokid.network.http
|
||||
|
||||
import com.google.gson.GsonBuilder
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import timber.log.Timber
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
object RetrofitClient {
|
||||
private val okHttpClient: OkHttpClient by lazy {
|
||||
val httpLoggingInterceptor by lazy {
|
||||
HttpLoggingInterceptor { message ->
|
||||
Timber.tag("OkHttp").d(message)
|
||||
}.apply {
|
||||
level = HttpLoggingInterceptor.Level.BODY
|
||||
}
|
||||
}
|
||||
OkHttpClient.Builder()
|
||||
.connectTimeout(15.seconds)
|
||||
.readTimeout(30.seconds)
|
||||
.writeTimeout(30.seconds)
|
||||
.addInterceptor(httpLoggingInterceptor)
|
||||
.build()
|
||||
}
|
||||
private val retrofit: Retrofit by lazy {
|
||||
Retrofit.Builder()
|
||||
.client(okHttpClient)
|
||||
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().create()))
|
||||
.build()
|
||||
}
|
||||
|
||||
val apiService: ApiService by lazy {
|
||||
retrofit.create(ApiService::class.java)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
package com.yuanxuan.rokid.network.websocket
|
||||
|
||||
import com.yuanxuan.rokid.network.NetUtils
|
||||
import com.yuanxuan.rokid.network.websocket.WebSocketConnection.Companion.DEFAULT_SEND_TIMEOUT
|
||||
import com.yuanxuan.rokid.network.websocket.WebSocketConnection.Companion.PING_INTERVAL_TIME
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import okhttp3.WebSocket
|
||||
import okhttp3.WebSocketListener
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import timber.log.Timber
|
||||
import java.net.Inet4Address
|
||||
import java.net.NetworkInterface
|
||||
|
||||
class OkHttpWebSocketConnection() : WebSocketListener(), WebSocketConnection {
|
||||
|
||||
private val _webSocketConnectionStateFlow: MutableStateFlow<WebSocketConnectionState> =
|
||||
MutableStateFlow(WebSocketConnectionState.DISCONNECTED)
|
||||
val webSocketConnectionStateFlow = _webSocketConnectionStateFlow.asStateFlow()
|
||||
|
||||
private var client: WebSocket? = null
|
||||
private val httpLoggingInterceptor by lazy {
|
||||
HttpLoggingInterceptor { message ->
|
||||
Timber.tag("OkHttp").d(message)
|
||||
}.apply {
|
||||
level = HttpLoggingInterceptor.Level.BODY
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun connect() {
|
||||
val okHttpClient = OkHttpClient.Builder()
|
||||
.readTimeout(DEFAULT_SEND_TIMEOUT)
|
||||
.pingInterval(PING_INTERVAL_TIME)
|
||||
.addInterceptor(httpLoggingInterceptor)
|
||||
.build()
|
||||
|
||||
val request = Request.Builder().url(NetUtils.getBaseUrl()).build()
|
||||
_webSocketConnectionStateFlow.update {
|
||||
WebSocketConnectionState.CONNECTING
|
||||
}
|
||||
client = okHttpClient.newWebSocket(request, this)
|
||||
}
|
||||
|
||||
override fun isDead(): Boolean {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun disconnect() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun sendRequest(
|
||||
request: WebSocketRequestMessage,
|
||||
timeoutSeconds: Long
|
||||
) {
|
||||
client?.send(request.requestId)
|
||||
}
|
||||
|
||||
override fun onMessage(webSocket: WebSocket, text: String) {
|
||||
}
|
||||
|
||||
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
|
||||
Timber.e("websocket断开连接 ${t}")
|
||||
_webSocketConnectionStateFlow.update { WebSocketConnectionState.FAILED }
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.yuanxuan.rokid.network.websocket
|
||||
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
interface WebSocketConnection {
|
||||
|
||||
companion object {
|
||||
val DEFAULT_SEND_TIMEOUT = 10.seconds
|
||||
val PING_INTERVAL_TIME = 30.seconds
|
||||
}
|
||||
|
||||
fun connect()
|
||||
|
||||
fun isDead(): Boolean
|
||||
|
||||
fun disconnect()
|
||||
|
||||
fun sendRequest(request: WebSocketRequestMessage) {
|
||||
return sendRequest(request, DEFAULT_SEND_TIMEOUT.inWholeSeconds)
|
||||
}
|
||||
|
||||
fun sendRequest(
|
||||
request: WebSocketRequestMessage,
|
||||
timeoutSeconds: Long
|
||||
)
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.yuanxuan.rokid.network.websocket;
|
||||
|
||||
public enum WebSocketConnectionState {
|
||||
DISCONNECTED,
|
||||
CONNECTING,
|
||||
CONNECTED,
|
||||
DISCONNECTING,
|
||||
AUTHENTICATION_FAILED,
|
||||
REMOTE_DEPRECATED,
|
||||
FAILED;
|
||||
|
||||
public boolean isFailure() {
|
||||
return this == AUTHENTICATION_FAILED || this == REMOTE_DEPRECATED || this == FAILED;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package com.yuanxuan.rokid.network.websocket
|
||||
|
||||
import android.content.Context
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Date
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
class WebSocketManager(context: Context, scope: CoroutineScope) {
|
||||
|
||||
private val webSocketConnection = OkHttpWebSocketConnection()
|
||||
|
||||
init {
|
||||
scope.launch {
|
||||
webSocketConnection.webSocketConnectionStateFlow.collectLatest { state ->
|
||||
when (state) {
|
||||
WebSocketConnectionState.DISCONNECTED,
|
||||
WebSocketConnectionState.CONNECTING,
|
||||
WebSocketConnectionState.CONNECTED,
|
||||
WebSocketConnectionState.DISCONNECTING,
|
||||
WebSocketConnectionState.AUTHENTICATION_FAILED,
|
||||
WebSocketConnectionState.REMOTE_DEPRECATED -> {
|
||||
}
|
||||
|
||||
WebSocketConnectionState.FAILED -> {
|
||||
WebSocketReconnectWorker.schedule(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
while (true) {
|
||||
delay(5.seconds.inWholeMilliseconds)
|
||||
webSocketConnection.sendRequest(
|
||||
WebSocketRequestMessage(
|
||||
requestId = "测试数据 ${Date().time}"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun connect() {
|
||||
webSocketConnection.connect()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package com.yuanxuan.rokid.network.websocket
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.NetworkType
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.OutOfQuotaPolicy
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
import com.yuanxuan.rokid.dependencies.AppDependencies
|
||||
import timber.log.Timber
|
||||
|
||||
class WebSocketReconnectWorker(context: Context, workerParams: WorkerParameters) : Worker(
|
||||
context,
|
||||
workerParams
|
||||
) {
|
||||
override fun doWork(): Result {
|
||||
Timber.d("尝试开始重连")
|
||||
AppDependencies.deviceServiceManager.playTTS("开始重连")
|
||||
AppDependencies.webSocketManager.connect()
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val WORE_NAME = "WebSocketReconnectWorker"
|
||||
fun schedule(context: Context) {
|
||||
val constraints = Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
.build()
|
||||
|
||||
val request = OneTimeWorkRequestBuilder<WebSocketReconnectWorker>()
|
||||
.setConstraints(constraints)
|
||||
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
|
||||
.build()
|
||||
WorkManager.getInstance(context).enqueueUniqueWork(
|
||||
uniqueWorkName = WORE_NAME,
|
||||
existingWorkPolicy = ExistingWorkPolicy.REPLACE,
|
||||
request = request
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.yuanxuan.rokid.network.websocket
|
||||
|
||||
data class WebSocketRequestMessage(
|
||||
val requestId: String
|
||||
)
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.yuanxuan.rokid.network.websocket
|
||||
|
||||
data class WebSocketResponseMessage(
|
||||
val requestId: String
|
||||
)
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<domain-config cleartextTrafficPermitted="true">
|
||||
<domain includeSubdomains="true">192.168.2.83</domain>
|
||||
</domain-config>
|
||||
</network-security-config>
|
||||
|
||||
|
|
@ -11,6 +11,9 @@ activity = "1.8.0"
|
|||
constraintlayout = "2.1.4"
|
||||
timber = "5.0.1"
|
||||
okhttp = "5.3.0"
|
||||
worker = "2.11.0"
|
||||
retrofit = "3.0.0"
|
||||
gson = "2.13.2"
|
||||
|
||||
[libraries]
|
||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||
|
|
@ -24,6 +27,10 @@ androidx-constraintlayout = { group = "androidx.constraintlayout", name = "const
|
|||
logger-timber = { group = "com.jakewharton.timber", name = "timber", version.ref = "timber" }
|
||||
okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" }
|
||||
okhttp-logging-interceptor = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" }
|
||||
androidx-work-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "worker" }
|
||||
retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
|
||||
retrofit-converter-gson = { group = "com.squareup.retrofit2", name = "converter-gson", version.ref = "retrofit" }
|
||||
gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" }
|
||||
|
||||
|
||||
[plugins]
|
||||
|
|
|
|||
Loading…
Reference in New Issue