Commit c0af16dd authored by p x's avatar p x
Browse files

first

parent 0b4d4d4e
Pipeline #3217 failed with stages
in 0 seconds
package com.sd.cavphmi.highmap
/**
* 给unity 初始化json
*
* @property host
* @property tiles3d
* @constructor Create empty Tile json bean
*/
class TileJsonBean {
/* var host: String = MyContants.HOST_MAP
var tiles3d = listOf(
// "/gis_data/3dtiles/road/CurbStrip/tileset.json",
"/gis_data/3dtiles/road/LanePolygon/tileset.json",
"/gis_data/3dtiles/road/RoadSection/tileset.json",
"/gis_data/3dtiles/road/SafetyIsland/tileset.json",
"/gis_data/3dtiles/road/SepStrip/tileset.json",
"/gis_data/3dtiles/road/SideWalk/tileset.json",
"/gis_data/3dtiles/road/TraMarkA/tileset.json",
"/gis_data/3dtiles/road/TraMarkL/tileset.json",
"/gis_data/3dtiles/road/TraMarkP/tileset.json",
// "/gis_data/3dtiles/roadequip/FieldFacility/tileset.json",
// "/gis_data/3dtiles/roadequip/PlatForm/tileset.json",
// "/gis_data/3dtiles/roadequip/SafetyRail/tileset.json",
// "/gis_data/3dtiles/roadequip/SignStrct/tileset.json",//监控杆子和路标杆子
// "/gis_data/3dtiles/roadequip/StreetLight/tileset.json",//路灯
// "/gis_data/3dtiles/roadequip/TrafficLight/tileset.json",
// "/gis_data/3dtiles/roadequip/TrafficSign/tileset.json",//监控探头和路标牌子
// "/gis_data/3dtiles/roadequip/VideoCamera/tileset.json",
// "/gis_data/3dtiles/roadequip/xinhaodeng/tileset.json",
// "/gis_data/3dtiles/roadequip/baoganji/tileset.json",
// "/gis_data/3dtiles/roadequip/buguangdeng/tileset.json",
"/gis_data/3dtiles/roadequip/chongdianzhuang/tileset.json",
// "/gis_data/3dtiles/roadequip/jiaohuanji/tileset.json",
// "/gis_data/3dtiles/roadequip/xinhaoji/tileset.json",
"/gis_data/3dtiles/roadequip/yidongronghe/tileset.json",
"/gis_data/3dtiles/buildings/tileset.json",
"/gis_data/3dtiles/ground/tileset.json",
// "/gis_data/3dtiles/vegetation/Tree/tileset.json"//树
)*/
//我本地的
var host: String = "http://192.168.60.218"
var tiles3d = listOf(
"/data/avp/01road/tileset.json",
"/data/avp/02shebei/tileset.json",
"/data/avp/04jianzhu/tileset.json"
)
// var tiles3d = listOf(
// "/data/adas/LanePolygon/tileset.json",
// "/data/adas/RoadSection/tileset.json",
// "/data/adas/Yizhuang_Unicom_ground1018/tileset.json"
// )
//方本地的
/* var host = "http://192.168.60.164:5003"
var host = "http://192.168.60.73:5003"
var tiles3d = listOf("/data/3dtiles/LanePolygon-o/tileset.json",
// "/data/3dtiles/RoadSection/tileset.json",
// "/data/3dtiles/SafetyIsland/tileset.json",
// "/data/3dtiles/SepStrip/tileset.json",
// "/data/3dtiles/SideWalk/tileset.json",
// "/data/3dtiles/TraMarkA/tileset.json",
"/data/3dtiles/TraMarkL-o/tileset.json",
// "/data/3dtiles/TraMarkP/tileset.json",
// "/data/3dtiles/SignStrct/tileset.json",
// "/data/3dtiles/TrafficLight/tileset.json",
// "/data/3dtiles/TrafficSign/tileset.json",
//
// "/data/3dtiles/Yizhuang_Unicom_building/tileset.json",
// "/data/3dtiles/Yizhuang_Unicom_Ground/tileset.json"
)*/
//四维的
/* var host = "http://gz.tasks.city/3dtiles"
var tiles3d = listOf(
// "/1/CurbStrip/tileset.json",
"/1/LanePolygon/tileset.json",
"/1/RoadSection/tileset.json",
"/1/SafetyIsland/tileset.json",
// "/1/SepStrip/tileset.json",
// "/1/SideWalk/tileset.json",
"/1/TraMarkA/tileset.json",
"/1/TraMarkL/tileset.json",
"/1/TraMarkP/tileset.json",
"/2/FieldFacility/tileset.json",
"/2/PlatForm/tileset.json",
// "/2/SafetyRail/tileset.json",
// "/2/SignStrct/tileset.json",
// "/2/StreetLight/tileset.json",
// "/2/TrafficLight/tileset.json",
// "/2/TrafficSign/tileset.json",
// "/2/VideoCamera/tileset.json",
// "/2/xinhaodeng/tileset.json",
// "/3/baoganji/tileset.json",
// "/3/buguangdeng/tileset.json",
// "/3/chongdianzhuang/tileset.json",
// "/3/jiaohuanji/tileset.json",
// "/3/jiaohuanji/tileset.json",
// "/3/yidongronghe.clt/tileset.json",
// "/4/Yizhuang_Unicom_building/tileset.json",
// "/4/Yizhuang_Unicom_Ground/tileset.json",
// "/4/Yizhuang_Unicom_Tree/tileset.json"
)*/
}
\ No newline at end of file
package com.sd.cavphmi.intfaces
interface OnConCan {
fun onCon()
fun onCan()
}
\ No newline at end of file
package com.sd.cavphmi.intfaces
interface OnWebSocketCb {
// fun onError(ex: Exception?
fun onClose(code: Int, reason: String?, remote: Boolean)
fun onMsg(str: String)
}
\ No newline at end of file
package com.sd.cavphmi.moudule
import com.sd.cavphmi.net.RetrofitApi.retrofitBuild
import com.sd.cavphmi.net.httpmothod.ClientRetrofitMethod
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import java.security.SecureRandom
import java.security.cert.X509Certificate
import javax.inject.Qualifier
import javax.inject.Singleton
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.TrustManager
import javax.net.ssl.X509TrustManager
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class OriginOkHttpClient
//@Qualifier
//@Retention(AnnotationRetention.BINARY)
//annotation class AuthInterceptorOkHttpClient
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class NormalInterceptorOkHttpClient
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
private var logging = HttpLoggingInterceptor()
init {
logging.level = HttpLoggingInterceptor.Level.BODY
}
@OriginOkHttpClient
@Provides
fun provideOriginOkHttpClient(): OkHttpClient {
var sslData = getSSlSocketFactory()
return OkHttpClient.Builder()
.addInterceptor(logging)
.sslSocketFactory(sslData.socketFactory, sslData.trustAllCert)
.hostnameVerifier { hostname, session -> true }
.build()
}
@NormalInterceptorOkHttpClient
@Provides
fun provideSeeInterceptorOkHttpClient(
// headParamsInterceptor: HeadParamsInterceptor
// baseUrlInterceptor: BaseUrlInterceptor,
): OkHttpClient {
// 创建信任所有证书的 TrustManager
// val trustAllCerts = arrayOf<TrustManager>(
// object : X509TrustManager {
// override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {}
// override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {}
// override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
// }
// )
// // 初始化 SSLContext
// val sslContext = SSLContext.getInstance("TLS")
// sslContext.init(null, trustAllCerts, SecureRandom())
var sslData = getSSlSocketFactory()
return OkHttpClient.Builder()
// .addInterceptor(baseUrlInterceptor)
// .addInterceptor(headParamsInterceptor)
.addInterceptor(logging)
.hostnameVerifier { hostname, session -> true }
.sslSocketFactory(sslData.socketFactory, sslData.trustAllCert)
.build()
}
//------------------API 方法---------------
@Singleton
@Provides
fun provideClientRetrofitService(@OriginOkHttpClient okHttpClient: OkHttpClient): ClientRetrofitMethod {
return retrofitBuild.client(okHttpClient).build().create(ClientRetrofitMethod::class.java)
}
// @Singleton
// @Provides
// fun provideClientRetrofitSeeService(@NormalInterceptorOkHttpClient okHttpClient: OkHttpClient): ClientRetrofitMethodSee {
// return retrofitBuild.client(okHttpClient).build()
// .create(ClientRetrofitMethodSee::class.java)
// }
fun getSSlSocketFactory(): SslData {
// 创建信任所有证书的 TrustManager
val trustAllCerts = arrayOf<TrustManager>(
object : X509TrustManager {
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {}
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {}
override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
}
)
// 初始化 SSLContext
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(null, trustAllCerts, SecureRandom())
return SslData(sslContext.socketFactory, trustAllCerts[0] as X509TrustManager)
}
data class SslData(val socketFactory: SSLSocketFactory, var trustAllCert: X509TrustManager)
// 创建信任所有证书的 SSLSocketFactory
private fun createSSLSocketFactory(): SSLSocketFactory {
try {
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(null, arrayOf<TrustManager>(object : X509TrustManager {
override fun checkClientTrusted(
chain: Array<out X509Certificate?>?,
authType: String?
) {
}
override fun checkServerTrusted(
chain: Array<out X509Certificate?>?,
authType: String?
) {
}
override fun getAcceptedIssuers(): Array<out X509Certificate?>? {
return arrayOfNulls(0)
}
}), SecureRandom())
return sslContext.getSocketFactory()
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
\ No newline at end of file
package com.sd.cavphmi.net
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.Interceptor
import okhttp3.Response
import javax.inject.Inject
class BaseUrlInterceptor @Inject constructor() : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
//获取原始request
var original= chain.request()
//从request中获取原有的HttpUrl实例oldHttpUrl
val originalHttpUrl = original.url
// 获取request的创建者builder
var builder = original.newBuilder()
//从request中获取headers,通过给定的键url_name
val headerValue = original.header("urlname")
if (!headerValue.isNullOrEmpty() && headerValue.count()>0){
val newHttpUrl = headerValue.toHttpUrlOrNull()
val url: HttpUrl = originalHttpUrl.newBuilder()
.host(newHttpUrl!!.host) // 修改Host部分
.scheme(newHttpUrl.scheme) //
.port(newHttpUrl.port) //
.build()
//如果有这个header,先将配置的header删除,因此header仅用作app和okhttp之间使用
builder.removeHeader("urlname")
val newRequest = builder.url(url)
.build()
return chain.proceed(newRequest);
}
return chain.proceed(builder.build())
}
}
\ No newline at end of file
package com.sd.cavphmi.net
import com.sd.cavphmi.utils.MyContants
import okhttp3.Interceptor
import okhttp3.Response
import javax.inject.Inject
/**
* 公共参数拦截处理器 http://blog.csdn.net/spinchao/article/details/52932145
*/
class HeadParamsInterceptor @Inject constructor() : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
// LogUtil.d("----------token=" + MyPres.token)
var original = chain.request()
// var path = original.url.encodedPath.substringAfterLast("/")
var request = original.newBuilder()
if (MyContants.HTTP_TOKEN.isNotEmpty()) {
// request.header("token", MyContants.HTTP_TOKEN)
// request.header("Authorization", MyContants.HTTP_TOKEN)
}
// request.header("Referer", "${MyContants.HOST}/itdts-portal-v5/intelligence-parking"
// var path = original.url.toUrl().path
// if (path.equals("/api/avpweb/v1/avp/overview/getVehicleInfo")) {
// request.header(
// "Referer",
// "https://itg-yz.cu-sc.com:13443/itdts-portal-v5/intelligence-parking"
// )
// }
return chain.proceed(request.build())
}
}
\ No newline at end of file
package com.sd.cavphmi.net
/**
* A generic class that holds a value with its loading status.
* @param <T>
</T> */
data class MyBaseResource<out T>(val data: T, var code: Int, var msg: String)
sealed class MyResult<out T> {
data class Success<out T>(val data: T) : MyResult<T>()
data class Error(var eCode: Int, var msg: String) : MyResult<Nothing>()
}
package com.sd.cavphmi.net
/**
* Status of a resource that is provided to the UI.
*
*
* These are usually created by the Repository classes where they return
* `LiveData<Resource<T>>` to pass back the latest data to the UI with its fetch status.
*/
enum class NetLoadStatus {
SUCCESS,
ERROR,
TOAST,
LOADING,
COMPLETE,
EMPTY,
NOMOREDATA,
LOGINTIMEOUT;//登录超时
private var errorMsg = ""
private var toastMsg = ""
fun setErrorMsg(error: String) {
errorMsg = error
}
fun getErrorMsg(): String {
return errorMsg
}
fun getToastMsg(): String {
return toastMsg
}
fun setToastMsg(toastMsg: String) {
this.toastMsg = toastMsg
}
}
package com.sd.cavphmi.net
import com.google.gson.Gson
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
object RequestBodyUtil {
/**
* 将参数封装成requestBody形式上传参数
* @param param 参数
* @return RequestBody
*/
fun toRequestBody(map: Map<String, Any>): RequestBody {
val gson = Gson()
var param = gson.toJson(map)
return param.toRequestBody("application/json;charset=UTF-8".toMediaTypeOrNull())
}
fun toRequestBody(any: Any): RequestBody {
var gson = Gson()
return gson.toJson(any)
.toRequestBody("application/json;charset=UTF-8".toMediaTypeOrNull())
}
}
\ No newline at end of file
package com.sd.cavphmi.net
import com.google.gson.GsonBuilder
import com.sd.cavphmi.MyAppcation
import com.sd.cavphmi.utils.MyContants
import okhttp3.Cache
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.converter.scalars.ScalarsConverterFactory
import java.io.File
object RetrofitApi {
// val CLIENT_BASIC = Credentials.basic("river-chief-server", "123456")
var retrofitBuild: Retrofit.Builder
var cache: Cache
init {
//设置缓存路径
val httpCacheDirectory =
File(
MyAppcation.instance().applicationContext.externalCacheDir,
"okhttp"
)
//设置缓存
cache = Cache(httpCacheDirectory, 50 * 1024 * 1024)
val mGson = GsonBuilder()
// .registerTypeAdapter(HttpErrorBean::class.java, HttpErrorTypeAdapter())
// .setLenient() // 设置GSON的非严格模式setLenient()
.create()
retrofitBuild = Retrofit.Builder()
.baseUrl(MyContants.HOST)
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(mGson))
// .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
}
// fun getSSlSocketFactory(): SslData {
// val trustManagerFactory: TrustManagerFactory = TrustManagerFactory.getInstance(
// TrustManagerFactory.getDefaultAlgorithm()
// )
// trustManagerFactory.init(null as KeyStore?)
// val trustManagers: Array<TrustManager> = trustManagerFactory.getTrustManagers()
// check(!(trustManagers.size != 1 || trustManagers[0] !is X509TrustManager)) {
// ("Unexpected default trust managers:"
// + Arrays.toString(trustManagers))
// }
// val trustManager = trustManagers[0] as X509TrustManager
//
//
// val sslContext = SSLContext.getInstance("TLS")
// sslContext.init(null, arrayOf<TrustManager>(trustManager), null)
// val sslSocketFactory = sslContext.socketFactory
//
// return SslData(sslSocketFactory, trustManager)
// }
//
// data class SslData(val sslSocketFactory: SSLSocketFactory, var trustManager: X509TrustManager)
}
\ No newline at end of file
package com.sd.cavphmi.net
import android.os.Handler
import android.os.Looper
import com.sd.cavphmi.moudule.NetworkModule
import okhttp3.ConnectionPool
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.Response
import okhttp3.sse.EventSource
import okhttp3.sse.EventSourceListener
import okhttp3.sse.EventSources
import java.util.concurrent.TimeUnit
class SseManager {
// private var logging = HttpLoggingInterceptor().apply {
// level = HttpLoggingInterceptor.Level.BODY
// }
constructor() {
var sslData = NetworkModule.getSSlSocketFactory()
okHttpClient = OkHttpClient.Builder()
// .addInterceptor(logging)
.connectTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.readTimeout(0, TimeUnit.SECONDS)
.connectionPool(ConnectionPool(5, 30, TimeUnit.SECONDS)) // 连接池优化(可选)
.pingInterval(20, TimeUnit.SECONDS) // TCP心跳间隔
.retryOnConnectionFailure(true)
/* .connectionSpecs(
listOf(
ConnectionSpec.CLEARTEXT,
ConnectionSpec.MODERN_TLS
)
) // 支持 HTTP/HTTPS*/
.sslSocketFactory(sslData.socketFactory, sslData.trustAllCert)
.hostnameVerifier { hostname, session -> true }
.build()
}
private var okHttpClient: OkHttpClient
// SSE 连接实例( nullable,避免内存泄漏)
private var eventSource: EventSource? = null
// private var lastEventId: String? = null // 记录最后一个事件 ID
private val listeners = mutableListOf<SseCallback2?>() // 客户端数据监听器
// 重连延迟(指数退避:1s → 2s → 4s → ... → 30s 上限)
private var retryDelayMillis = 1000L
private val maxRetryDelay = 30 * 1000L // 最大重连延迟
// 连接状态回调(给外部使用)
private var callback: SseCallback2? = null
fun addListener(callback: SseCallback2) {
listeners.add(callback)
}
// 初始化 SSE 请求
fun connect(sseUrl: String, body: RequestBody?, headers: Map<String, String> = emptyMap()) {
// 1. 构建 SSE 请求(必须是 GET 方法,且支持 SSE 协议)
val requestBuilder = Request.Builder()
.url(sseUrl)
.header("Accept", "text/event-stream") // 关键:告知服务器接收 SSE 格式
.header("Cache-Control", "no-cache") // 禁用缓存,避免重复数据
.header("Connection", "keep-alive") // 保持长连接
if (body != null) {
requestBuilder.post(body)
}
// 添加自定义请求头(如 Token、User-Agent 等)
headers.forEach { (key, value) ->
requestBuilder.header(key, value)
}
// 断线重连时携带上次最后一个事件 ID(避免漏数据)
// lastEventId?.let {
// requestBuilder.header("Last-Event-ID", it)
// }
val request = requestBuilder.build()
// 2. 构建 EventSource(OkHttp SSE 核心)
val eventSourceListener = object : EventSourceListener() {
// 连接成功回调
override fun onOpen(eventSource: EventSource, response: Response) {
super.onOpen(eventSource, response)
listeners.forEach {
it?.onConnected()
}
retryDelayMillis = 1000L // 重置重连延迟(连接成功后恢复初始值)
}
// 接收服务器消息(核心回调)
override fun onEvent(
eventSource: EventSource,
id: String?,
type: String?,
data: String
) {
super.onEvent(eventSource, id, type, data)
listeners.forEach {
it?.onMessageReceived(data, type ?: "message")// type 是服务器定义的事件类型
}
}
// 连接断开回调(主动/被动断开都会触发)
override fun onClosed(eventSource: EventSource) {
super.onClosed(eventSource)
listeners.forEach {
it?.onDisconnected()
}
// 主动断开时不自动重连(通过 flag 控制)
if (!isManualDisconnect) {
scheduleReconnect(sseUrl, body, headers) // 被动断开则重连
}
}
// 连接失败回调(网络异常、服务器错误等)
override fun onFailure(
eventSource: EventSource,
t: Throwable?,
response: Response?
) {
super.onFailure(eventSource, t, response)
val errorMsg = t?.message ?: "未知错误"
listeners.forEach {
it?.onError(errorMsg, t)
}
scheduleReconnect(sseUrl, body, headers) // 失败后自动重连
}
}
// 创建并启动 SSE 连接
eventSource =
EventSources.createFactory(okHttpClient).newEventSource(request, eventSourceListener)
}
// 主动断开连接
fun disconnect() {
isManualDisconnect = true
eventSource?.cancel() // 关闭连接
eventSource = null
callback?.onDisconnected()
}
// 调度重连(指数退避策略)
private fun scheduleReconnect(
sseUrl: String,
body: RequestBody?,
headers: Map<String, String>
) {
callback?.onReconnecting(retryDelayMillis)
// 使用 Handler 延迟重连(避免主线程阻塞)
Handler(Looper.getMainLooper()).postDelayed({
if (!isManualDisconnect) {
connect(sseUrl, body, headers) // 重连
// 指数退避:延迟翻倍,不超过最大值
retryDelayMillis = minOf(retryDelayMillis * 2, maxRetryDelay)
}
}, retryDelayMillis)
}
// 防止内存泄漏:释放资源
fun release() {
disconnect()
callback = null
}
companion object {
// // 单例实例
//// @Volatile
// private var instance: SseManager? = null
// 是否主动断开(控制重连逻辑)
private var isManualDisconnect = false
val instance: SseManager by lazy { SseManager() }
// fun getInstance(context: Context, okHttpClient: OkHttpClient): SseManager {
// return instance ?: synchronized(this) {
// instance ?: SseManager(context.applicationContext, okHttpClient).also {
// instance = it
// }
// }
// }
}
// SSE 状态回调接口(外部实现)
interface SseCallback2 {
fun onConnected() // 连接成功
fun onDisconnected() // 连接断开
fun onMessageReceived(data: String, eventType: String) // 接收消息
fun onError(errorMsg: String, throwable: Throwable?) // 错误回调
fun onReconnecting(delayMillis: Long) // 重连中(可选)
}
}
package com.sd.cavphmi.net
import android.os.Handler
import android.os.Looper
import com.sd.cavphmi.moudule.NetworkModule
import okhttp3.ConnectionSpec
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.sse.EventSource
import okhttp3.sse.EventSourceListener
import okhttp3.sse.EventSources
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.TimeUnit
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
/**
* SSE 连接实体类:封装单个连接的核心信息
* @param connId 连接唯一标识(如 "chat", "notice", "status")
* @param sseUrl 连接的服务器地址
* @param eventSource SSE 连接实例(OkHttp 提供)
* @param callback 连接的回调接口(接收消息、状态)
*/
data class SseConnection(
val connId: String,
val sseUrl: String,
val eventSource: EventSource,
val callback: SseCallback
)
/**
* SSE 回调接口:每个连接的独立回调
*/
interface SseCallback {
// 连接成功
fun onConnected(connId: String)
// 接收自定义事件(服务器指定 event 字段)
fun onEventReceived(connId: String, eventType: String?, data: String)
// 连接失败
fun onFailed(connId: String, errorMsg: String)
// 连接关闭
fun onClosed(connId: String)
fun onReconnecting(delayMillis: Long) // 重连中(可选)
}
class SseMultiConnectionManager {
companion object {
// 是否主动断开(控制重连逻辑)
private var isManualDisconnect = false
val instance: SseMultiConnectionManager by lazy { SseMultiConnectionManager() }
}
var sslData = NetworkModule.getSSlSocketFactory()
// OkHttp 客户端(全局复用,避免重复创建;配置长连接适配 SSE)
private val okHttpClient = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(0, TimeUnit.SECONDS) // 长连接延长读取超时
.writeTimeout(30, TimeUnit.SECONDS)
// .connectionPool(ConnectionPool(5, 30, TimeUnit.SECONDS)) // 连接池优化(可选)
// .pingInterval(20, TimeUnit.SECONDS) // TCP心跳间隔
.retryOnConnectionFailure(false) // 多连接场景下禁用自动重试(避免冲突)
.connectionSpecs(
listOf(
ConnectionSpec.CLEARTEXT,
ConnectionSpec.MODERN_TLS
)
) // 支持 HTTP/HTTPS
.sslSocketFactory(sslData.socketFactory, sslData.trustAllCert)
.hostnameVerifier { hostname, session -> true }
.build()
// 存储所有活跃连接:key = connId(唯一标识),value = SseConnection
// 使用 ConcurrentHashMap 保证线程安全(支持并发读写)
private val activeConnections = ConcurrentHashMap<String, SseConnection>()
// 重入锁:确保启动/关闭连接时的原子操作(避免并发问题)
private val connectionLock = ReentrantLock()
// 重连延迟(指数退避:1s → 2s → 4s → ... → 30s 上限)
private var retryDelayMillis = 1000L
private val maxRetryDelay = 30 * 1000L // 最大重连延迟
/**
* 启动一个新的 SSE 连接
* @param connId 连接唯一标识(如 "chat_123", "system_notice")
* @param sseUrl 连接的服务器地址
* @param callback 该连接的独立回调(处理消息和状态)
*/
fun startConnection(
connId: String,
sseUrl: String,
body: RequestBody,
callback: SseCallback
) {
connectionLock.withLock { // 加锁确保原子操作
// 1. 先关闭同名连接(避免重复创建)
if (activeConnections.containsKey(connId)) {
stopConnection(connId)
callback.onClosed(connId)
}
// 2. 构建当前连接的 SSE 请求(必须是 GET + text/event-stream 头)
val request = Request.Builder()
.url(sseUrl)
.post(body)
.header("Accept", "text/event-stream") // 核心:SSE 格式标识
.header("Cache-Control", "no-cache") // 禁用缓存
.header("Connection", "keep-alive") // 保持长连接
// 可选:添加当前连接的独立头(如不同 Token、用户ID)
// .header("Authorization", "Bearer ${getTokenForConn(connId)}")
.build()
// 3. 创建当前连接的 EventSourceListener(绑定回调)
val listener = object : EventSourceListener() {
override fun onOpen(eventSource: EventSource, response: okhttp3.Response) {
super.onOpen(eventSource, response)
// println("-------hashCode = ${eventSource.hashCode()} ${eventSource.request().url}")
// 回调到当前连接的 callback(主线程)
callback.onConnected(connId)
}
override fun onEvent(
eventSource: EventSource,
id: String?,
type: String?,
data: String
) {
super.onEvent(eventSource, id, type, data)
// 自定义事件回调(服务器指定 event 字段)
callback.onEventReceived(connId, type, data)
}
override fun onFailure(
eventSource: EventSource,
t: Throwable?,
response: okhttp3.Response?
) {
super.onFailure(eventSource, t, response)
// 失败后移除连接(避免内存泄漏)
activeConnections.remove(connId)
// 失败回调(错误信息拼接)
val errorMsg = t?.message ?: "Unknown error"
callback.onFailed(connId, errorMsg)
// 主动断开时不自动重连(通过 flag 控制)
if (!isManualDisconnect) {
scheduleReconnect(connId, sseUrl, body, callback) // 被动断开则重连
}
}
override fun onClosed(eventSource: EventSource) {
super.onClosed(eventSource)
callback.onClosed(connId)
// 关闭后移除连接
activeConnections.remove(connId)
}
}
// 4. 创建 EventSource 实例并存储到集合
val eventSource = EventSources.createFactory(okHttpClient)
.newEventSource(request, listener)
// 5. 存储连接信息到 ConcurrentHashMap
activeConnections[connId] = SseConnection(
connId = connId,
sseUrl = sseUrl,
eventSource = eventSource,
callback = callback
)
}
}
// 调度重连(指数退避策略)
private fun scheduleReconnect(
connId: String,
sseUrl: String,
body: RequestBody,
sseCallback:SseCallback
) {
sseCallback.onReconnecting(retryDelayMillis)
// 使用 Handler 延迟重连(避免主线程阻塞)
Handler(Looper.getMainLooper()).postDelayed({
if (!isManualDisconnect) {
startConnection(connId,sseUrl, body,sseCallback) // 重连
// 指数退避:延迟翻倍,不超过最大值
retryDelayMillis = minOf(retryDelayMillis * 2, maxRetryDelay)
}
}, retryDelayMillis)
}
/**
* 关闭指定 ID 的 SSE 连接
* @param connId 连接唯一标识
*/
fun stopConnection(connId: String) {
isManualDisconnect = true
connectionLock.withLock {
val connection = activeConnections.remove(connId)
connection?.eventSource?.cancel() // 关闭连接
}
}
/**
* 关闭所有 SSE 连接(如 App 退出时)
*/
fun stopAllConnections() {
isManualDisconnect = true
connectionLock.withLock {
activeConnections.values.forEach { it.eventSource.cancel() }
activeConnections.clear()
}
}
/**
* 检查连接是否活跃
* @param connId 连接唯一标识
* @return true = 活跃,false = 已关闭/未创建
*/
fun isConnectionActive(connId: String): Boolean {
return activeConnections.containsKey(connId)
}
/**
* 获取所有活跃连接的 ID
*/
fun getActiveConnIds(): List<String> {
return activeConnections.keys.toList()
}
}
package com.sd.cavphmi.net.httpmothod
import com.sd.cavphmi.bean.AvpStatuBean
import com.sd.cavphmi.bean.BindCarBean
import com.sd.cavphmi.bean.LoginSuccBean
import com.sd.cavphmi.bean.SpaceInfoBean
import com.sd.cavphmi.bean.VehDetailBean
import okhttp3.RequestBody
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
interface ClientRetrofitMethod {
// @Headers("Cache-Control:public,max-age=3600")
// @GET("http://59.175.163.12/serv-addr/server.json")
// fun getConfigurations(): Observable<List<ConfigurationBean>>
//
/**登录***/
@POST("api/perm/admin/auth/passLogin")
suspend fun login(@Body body: RequestBody): LoginSuccBean
/**车辆详情***/
@POST("api/avpweb/v1/avp/overview/getVehicleInfo")
suspend fun getVehDetail(@Body body: RequestBody): VehDetailBean
/**车位占用情况***/
@POST("api/avpweb/v1/avp/overview/listSpaceInfoByCondition")
suspend fun getSpaceInfo(@Body body: RequestBody): SpaceInfoBean
/**获取可绑定车辆列表***/
// @Headers("urlname:https://172.24.124.130:14443")
@POST("api/avpweb/hmi/v1/queryVehicleList")
suspend fun getBindCar(): BindCarBean
// suspend fun getBindCar(@Body body: RequestBody): List<BindCarItem>
/***http 长连接 sse 协议 入参 {"id":车俩id}***/
// @POST("api/avpweb/app/monitor/v1/monitorDrivenStatus")
// suspend fun getCarPoseSee(@Body body: RequestBody): String
/***获取订单信息**/
// @POST("avp/avpMonitor/getOrderParkingInfoForPlate")
// suspend fun getOrderData(@Body body: RequestBody): OrderBean
//
// /***获取网络质量**/
// @POST("avp/siteLine/getNetworkQuality")
// suspend fun getTimeOut(): Any
//
// /***获取路径规划**/
// @POST("avp/linePlaning/get")
// suspend fun getLinePlaning(@Body body: RequestBody): ParkLinePlan
/**
* 话题根据评论ID查询所有回复
*/
// @GET("cm/topic/reply/v1/getByCommentId")
// fun getTopicByAnswerId(@Query("id") id: String): Observable<BaseResponse<List<UserAnswer>>>
}
\ No newline at end of file
package com.sd.cavphmi.repositorys
import com.sd.cavphmi.bean.BindResult
import com.sd.cavphmi.bean.SpaceInfoBean
import com.sd.cavphmi.bean.VehDetailBean
import com.sd.cavphmi.bean.req.SpaceInfo
import com.sd.cavphmi.net.MyResult
import com.sd.cavphmi.net.RequestBodyUtil
import com.sd.cavphmi.net.SseCallback
import com.sd.cavphmi.net.SseManager
import com.sd.cavphmi.net.SseManager.SseCallback2
import com.sd.cavphmi.net.SseMultiConnectionManager
import com.sd.cavphmi.net.httpmothod.ClientRetrofitMethod
import com.sd.cavphmi.utils.MyContants
import okhttp3.RequestBody
import retrofit2.HttpException
import javax.inject.Inject
/**AVP 接口**/
class AvpDataRepo @Inject constructor(
private var retrofitMethod: ClientRetrofitMethod
) {
// private var simpleSSEClient = SimpleSSEClient.instance
private var sseManager2 = SseManager.instance
// 初始化多连接管理器(全局单例更佳,可通过依赖注入)
private val sseManager = SseMultiConnectionManager.instance
// 连接 ID 定义(唯一标识每个连接)
private val CONN_ID_CAR = "CAR" // 聊天连接
private val CONN_ID_AVPSTATU = "AVP_STATU" // 系统通知连接
/**获取车辆详情
* @param id 正常应该是传场地ID,但是亦庄这个和太和桥车是一样的
*/
suspend fun getVehDetail(id: String = ""): MyResult<VehDetailBean> {
// var map = mapOf("id" to id)
// var map = mapOf()
var body = RequestBodyUtil.toRequestBody(mapOf())
try {
var bean = retrofitMethod.getVehDetail(body)
return MyResult.Success(bean)
} catch (e: HttpException) {
// println("e.message = ${e.message}")
return MyResult.Error(e.code(), e.message() ?: "error")
} catch (e: Exception) {
return MyResult.Error(MyContants.HTTP_ERROR, e.message ?: "error")
}
}
/**
* 获取车位占用情况
*/
suspend fun getSpaceInfo(): MyResult<SpaceInfoBean> {
try {
var spaceInfo = SpaceInfo().apply {
state = 1
}
var body = RequestBodyUtil.toRequestBody(spaceInfo)
var bean = retrofitMethod.getSpaceInfo(body)
return MyResult.Success(bean)
} catch (e: HttpException) {
// println("e.message = ${e.message}")
return MyResult.Error(e.code(), e.message() ?: "error")
} catch (e: Exception) {
return MyResult.Error(MyContants.HTTP_ERROR, e.message ?: "error")
}
}
/**
* 获取可绑车辆
*/
suspend fun getBindCar(): MyResult<List<BindResult>> {
try {
// var body = RequestBodyUtil.toRequestBody(mapOf())
var bean = retrofitMethod.getBindCar()
return MyResult.Success(bean.result)
} catch (e: HttpException) {
// println("e.message = ${e.message}")
return MyResult.Error(e.code(), e.message() ?: "error")
} catch (e: Exception) {
return MyResult.Error(MyContants.HTTP_ERROR, e.message ?: "error")
}
}
/**登录***/
suspend fun login(user: String, pwd: String, verifyCode: Int): MyResult<String> {
// if (id.isNullOrEmpty()) {
// return MyResult.Error(MyContants.HTTP_ERROR, "error")
// }
var map = mapOf("username" to user, "password" to pwd, "verifyCode" to verifyCode)
var body = RequestBodyUtil.toRequestBody(map)
try {
var bean = retrofitMethod.login(body)
return MyResult.Success(bean.result.token)
} catch (e: HttpException) {
// println("e.message = ${e.message}")
return MyResult.Error(e.code(), e.message() ?: "error")
} catch (e: Exception) {
return MyResult.Error(MyContants.HTTP_ERROR, e.message ?: "error")
}
}
/**
* 获取AVP状态信息
* 我们车辆位姿数据用的是 车俩基础信息的id 可绑车辆接口 返回的是 avp车俩id
* 所以这里的id 是绑定车辆列表返回的id
*/
fun getAvpStatus(url: String, body: RequestBody, sseCallback: SseCallback) {
sseManager.startConnection(CONN_ID_AVPSTATU, url, body, sseCallback)
/* sseManager2.addListener(object : SseCallback2 {
override fun onConnected() {
println("------------getAvpStatus onConnected")
}
override fun onDisconnected() {
}
override fun onMessageReceived(data: String, eventType: String) {
println("----------getAvpStatus = ${data}")
}
override fun onError(errorMsg: String, throwable: Throwable?) {
println("------------getAvpStatus errorMsg")
}
override fun onReconnecting(delayMillis: Long) {
}
})
sseManager2.connect(url, body)*/
}
/**获取车辆位姿****/
fun getCarPose(url: String, body: RequestBody, sseCallback: SseCallback) {
sseManager.startConnection(CONN_ID_CAR, url, body, sseCallback)
}
}
\ No newline at end of file
package com.sd.cavphmi.repositorys
import com.google.gson.Gson
import com.sd.cavphmi.bean.ParkLinePlan
import com.sd.cavphmi.bean.SpaceNoBean
import com.sd.cavphmi.bean.VehDetailBean
import com.sd.cavphmi.net.MyResult
import com.sd.cavphmi.net.RequestBodyUtil
import com.sd.cavphmi.net.httpmothod.ClientRetrofitMethod
import com.sd.cavphmi.utils.MyContants
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import retrofit2.HttpException
import javax.inject.Inject
class ParseSocketRepo @Inject constructor(private var clientRetrofitMethod: ClientRetrofitMethod) {
private var gson = Gson()
// private var orderCalled = AtomicBoolean(false)
/**解析网络数据***/
suspend fun <T> parseDataBean(str: String, clazz: Class<T>): T {
return withContext(Dispatchers.Default) {
gson.fromJson(str, clazz)
}
}
//
// /**测试网络超时***/
// suspend fun getTimeOut(): MyResult<Int> {
// try {
// var star = System.currentTimeMillis()
// clientRetrofitMethod.getTimeOut()
// var end = System.currentTimeMillis()
// return MyResult.Success(end.minus(star).toInt())
// } catch (e: HttpException) {
// return MyResult.Error(e.code(), e.message() ?: "error")
// } catch (e: Exception) {
// return MyResult.Error(MyContants.HTTP_ERROR, e.message ?: "error")
// }
// }
//
// /***查询车位信息
// * 苏州hmi用的这个接口判断空闲车位
// * ***/
// suspend fun getSpaceData(): MyResult<SpaceNoBean> {
// var map = mutableMapOf("pageNo" to 20, "pageSize" to 1000)
// var body = RequestBodyUtil.toRequestBody(map)
// try {
// var bean = clientRetrofitMethod.getSpaceData(body)
// return MyResult.Success(bean.data)
// } catch (e: HttpException) {
//// println("e.message = ${e.message}")
// return MyResult.Error(e.code(), e.message() ?: "error")
// } catch (e: Exception) {
// return MyResult.Error(MyContants.HTTP_ERROR, e.message ?: "error")
// }
// }
//
// /***获取路径规划***/
// suspend fun getLinePlaning(vehiclePlate: String = "吉AC242"): MyResult<ParkLinePlan> {
// var map = mutableMapOf("vehiclePlate" to vehiclePlate)
// var body = RequestBodyUtil.toRequestBody(map)
// try {
// var bean = clientRetrofitMethod.getLinePlaning(body)
// return MyResult.Success(bean)
// } catch (e: HttpException) {
// println("e.message = ${e.message}")
// return MyResult.Error(e.code(), e.message() ?: "error")
// } catch (e: Exception) {
// return MyResult.Error(MyContants.HTTP_ERROR, e.message ?: "error")
// }
// }
// /**联网车辆状态数据***/
// suspend fun<T> genVehStatus():T {
//
// }
}
\ No newline at end of file
package com.sd.cavphmi.repositorys
import com.google.gson.Gson
import com.sd.cavphmi.bean.SpaceNoBean
import com.sd.cavphmi.net.MyResult
import com.sd.cavphmi.net.RequestBodyUtil
import com.sd.cavphmi.net.httpmothod.ClientRetrofitMethod
import com.sd.cavphmi.websockets.MyWebSocketClient
import retrofit2.HttpException
import javax.inject.Inject
class SpaceNoRepo @Inject constructor(var clientRetrofitMethod: ClientRetrofitMethod) {
var client: MyWebSocketClient? = null
private var gson=Gson()
// 2.2. 联网车辆位姿数据topic
fun subVehicle(){
}
fun getSendData(){
}
}
\ No newline at end of file
package com.sd.cavphmi.ui
import android.Manifest
import android.content.Intent
import android.os.Build
import androidx.lifecycle.ViewModelProvider
import com.permissionx.guolindev.PermissionX
import com.sd.cavphmi.BR
import com.sd.cavphmi.R
import com.sd.cavphmi.base.BaseActivity
import com.sd.cavphmi.base.MyBaseViewModel
import com.sd.cavphmi.databinding.ActivityBootBinding
import com.sd.cavphmi.utils.ToastHelper
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class BootActivity : BaseActivity<ActivityBootBinding, MyBaseViewModel>() {
// private val tcpUpVM: TcpUpVM by viewModels()
override fun getStatuBarColor(): Int {
return -1
}
override fun initContentView(): Int {
return R.layout.activity_boot
}
override fun initViewModel(): MyBaseViewModel {
return ViewModelProvider(this).get(MyBaseViewModel::class.java)
}
override fun initVariableId(): Int {
return BR.vm
}
override fun initView() {
requestPers()
}
//到登录页面
private fun starLogin() {
var jump = Intent(this, LoginActivity::class.java)
startActivity(jump)
finish()
}
fun requestPers() {
var pers = mutableListOf<String>(
// Manifest.permission.ACCESS_BACKGROUND_LOCATION,
// Manifest.permission.ACCESS_FINE_LOCATION,
// Manifest.permission.ACCESS_COARSE_LOCATION,
// Manifest.permission.READ_PHONE_STATE
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
pers.add(Manifest.permission.READ_EXTERNAL_STORAGE)
pers.add(Manifest.permission.MANAGE_EXTERNAL_STORAGE)
} else {
pers.add(Manifest.permission.READ_EXTERNAL_STORAGE)
}
PermissionX.init(this)
.permissions(pers.toList())
.onExplainRequestReason { scope, deniedList ->
scope.showRequestReasonDialog(
deniedList,
"AVP 需要同意以下授权才能正常使用",
"好的",
"取消"
)
}
// .onForwardToSettings { scope, deniedList ->
// scope.showForwardToSettingsDialog(deniedList, "您需要手动在‘设置’中允许必要的权限", "OK", "Cancel")
// }
.request { allGranted, grantedList, deniedList ->
if (allGranted) {
// ToastHelper.showShort(this, "All permissions are granted")
starLogin()
} else {
ToastHelper.showShort(this, "权限被拒")
binding.root.postDelayed({
finish()
}, 500)
}
}
}
}
\ No newline at end of file
package com.sd.cavphmi.ui
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import com.sd.cavphmi.intfaces.OnConCan
class ExitAppDialog : DialogFragment() {
var onConCan: OnConCan? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
isCancelable=false
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return AlertDialog.Builder(requireContext())
// dialog.setIcon(android.R.drawable.ic_dialog_info)
.setTitle("提示")
.setMessage("确定退出应用")
.setPositiveButton(
"确定"
) { dialog1: DialogInterface?, which: Int ->
onConCan?.onCon()
}
.setNegativeButton("取消") { dialog, _ ->
onConCan?.onCan()
}.create()
}
}
\ No newline at end of file
package com.sd.cavphmi.ui
import android.content.Intent
import android.os.Bundle
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.jakewharton.rxbinding4.view.clicks
import com.sd.cavphmi.databinding.ActivityLoginBinding
import com.sd.cavphmi.ui.dialog.SeleBindCarDialog
import com.sd.cavphmi.utils.MMKVUtil
import com.sd.cavphmi.utils.MyContants
import com.sd.cavphmi.viewmodels.LoginVm
import com.sd.cavphmi.viewmodels.MainVm
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import java.util.concurrent.TimeUnit
@AndroidEntryPoint
class LoginActivity : AppCompatActivity() {
private lateinit var binding: ActivityLoginBinding
private val loginVm: LoginVm by viewModels()
private val mainVm: MainVm by viewModels()
private val PWD = "5803d205ac27f33ea5f53afed4eb81880bee49f06129ea5437c1b940d18da8f1"
private val VERIFYCODE = 285369
//车牌号选择
// private lateinit var seleBindCarDialog: SeleBindCarDialog
private val seleBindCarDialog: SeleBindCarDialog by lazy { SeleBindCarDialog.newInstance() }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityLoginBinding.inflate(layoutInflater)
setContentView(binding.root)
if (MMKVUtil.token.isNotEmpty()) {
MyContants.HTTP_TOKEN = MMKVUtil.token
getBinderCars()
}
// else {
var lo = binding.btLogin.clicks().throttleFirst(1, TimeUnit.SECONDS).subscribe {
login()
}
// }
setListener()
}
private fun login() {
// seleBindCarDialog.setPlates(plateNumbers)
// seleBindCarDialog.showNow(supportFragmentManager, "seleBindCarDialog")
// return
var user = binding.etAccount.text.toString()
// var pwd = binding.etPwd.text.toString()
// "4CIHV37pDF8sx0ZXYmYah6HSgys7F7ULSMmm39uzppc"
// var key = "Cusc@itmp-sm4key".toByteArray()
// var pp = SM4CryptoHelper.encryptECB(key, pwd.toByteArray())
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
loginVm.login(user, PWD, VERIFYCODE).collect {
// println("-----------登录回调 = ${it}")
//获取可绑定车辆
if (it == 1) {
getBinderCars()
}
}
}
}
}
private fun setListener() {
// HTTP获取车位占用情况
binding.btSpaceinfo.setOnClickListener {
mainVm.getSpaceInfo()
}
// HTTP获取AVp状态信息 以后就用socket订阅了,获取找、泊车状态,拿到全局路径lines,和局部(避障)路径xxx
binding.btAvpstatu.setOnClickListener {
mainVm.getAvpStatus()
}
binding.btCarpos.setOnClickListener {
mainVm.subVehicle()
}
//获取可绑定车辆
binding.btGetbindcar.setOnClickListener {
getBinderCars()
}
//直接到首页
binding.btMain.setOnClickListener {
startActivity(Intent(this, MainActivity::class.java))
finish()
}
}
//获取可绑定车辆
private fun getBinderCars() {
// seleBindCarDialog = SeleBindCarDialog.newInstance()
mainVm.getBindCar().observe(this) { bindCars ->
var plateNumbers = bindCars.map { it.plateNumber }
seleBindCarDialog.setPlates(plateNumbers)
seleBindCarDialog.itemClickListener = object : SeleBindCarDialog.OnItemClickListener {
override fun onItemClick(position: Int) {
MyContants.VEHICLEID = bindCars.get(position).id
startActivity(Intent(this@LoginActivity, MainActivity::class.java))
finish()
}
}
if (!seleBindCarDialog.isAdded) {
seleBindCarDialog.showNow(supportFragmentManager, "seleBindCarDialog")
}
// val dialog = CustomListDialog(this, "选择车辆", plateNumbers)
// dialog.setOnItemClickListener(object : CustomListDialog.OnItemClickListener {
// override fun onItemClick(position: Int, selectedItem: String) {
//// Toast.makeText(
//// this@LoginActivity,
//// "点击了第${position + 1}项: $selectedItem",
//// Toast.LENGTH_SHORT
//// ).show()
// MyContants.VEHICLEID = bindCars.get(position).id
// startActivity(Intent(this@LoginActivity, MainActivity::class.java))
// finish()
// }
// })
// dialog.show()
}
}
}
\ No newline at end of file
package com.sd.cavphmi.ui
import android.content.res.Configuration
import android.util.Log
import android.view.KeyEvent
import androidx.activity.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.minedata.minenavi.SDKInitializer
import com.minedata.minenavi.SDKInitializer.InitListener
import com.minedata.minenavi.map.MapView
import com.minedata.minenavi.map.MineMap
import com.minedata.minenavi.mapdal.LatLng
import com.minedata.minenavi.util.Tools
import com.sd.cavphmi.BR
import com.sd.cavphmi.R
import com.sd.cavphmi.base.BaseActivity
import com.sd.cavphmi.base.MyBaseViewModel
import com.sd.cavphmi.bean.AvpStatuBean
import com.sd.cavphmi.bean.CarVehicle
import com.sd.cavphmi.bean.PerceptionBean
import com.sd.cavphmi.databinding.ActivityMainBinding
import com.sd.cavphmi.highmap.HighMapApi
import com.sd.cavphmi.highmap.LockStatu
import com.sd.cavphmi.highmap.ParkStatu
import com.sd.cavphmi.highmap.Spinfo
import com.sd.cavphmi.highmap.UnityPtc
import com.sd.cavphmi.intfaces.OnConCan
import com.sd.cavphmi.ui.fragment.CarPanelFragment
import com.sd.cavphmi.ui.fragment.ExoPlayFragment
import com.sd.cavphmi.utils.AvpContants
import com.sd.cavphmi.utils.DisplayUtil
import com.sd.cavphmi.viewmodels.MainVm
import com.sd.cavphmi.viewmodels.MapOpt
import com.sd.cavphmi.viewmodels.MockVM
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@AndroidEntryPoint
class MainActivity : BaseActivity<ActivityMainBinding, MyBaseViewModel>() {
override fun getStatuBarColor(): Int {
return -1
}
override fun initContentView(): Int {
return R.layout.activity_main
}
override fun initViewModel(): MyBaseViewModel {
return ViewModelProvider(this).get(MyBaseViewModel::class.java)
}
override fun initVariableId(): Int {
return BR.vm
}
override fun onPause() {
super.onPause()
binding.mapView.onPause()
}
override fun onResume() {
super.onResume()
binding.mapView.onResume()
}
override fun onDestroy() {
super.onDestroy()
mainVm.cleanRes()
binding.mapView.onDestroy()
}
//用作模拟
private val mockVM: MockVM by viewModels()
//主页操作
private val mainVm: MainVm by viewModels()
//地图操作类,用于绘制
private val mapOpt: MapOpt by viewModels()
// private val avpMapVM: AvpMapVM by viewModels()
//是否进入泊车倒车状态
// private var isReversePark = false
//退出应用弹窗
private lateinit var dialogFragment: ExitAppDialog
//汽车仪表
private val carPanelFragment by lazy { CarPanelFragment.newInstance() }
//exo播放器
private val exoPlayFragment by lazy { ExoPlayFragment.newInstance() }
override fun initView() {
mainVm.mockVM = mockVM
adaptWidth()
//车辆仪表
var ft = supportFragmentManager.beginTransaction()
ft.add(R.id.map_car_pan, carPanelFragment, "1")
ft.commit()
//添加视频播放器,以后看需要控制显示隐藏时机,默认隐藏
// showVideoFragment(true)
initialMap()
}
private fun adaptWidth() {
var width = DisplayUtil.getScreenWidthPx()
var cWidth = (width * 0.271).toInt()
//车辆仪表
var params = binding.mapCarPan.layoutParams
params.width = cWidth
binding.mapCarPan.layoutParams = params
//小地图
params = binding.smallFLayout.layoutParams.apply {
this.width = (width * 0.17).toInt()
this.height = (this.width * 0.75).toInt()
}
binding.smallFLayout.layoutParams = params
//车内视频frag
params = binding.videoFrag.layoutParams.apply {
this.width = (width * 0.314).toInt()
this.height = (this.width * 0.54).toInt()
}
binding.videoFrag.layoutParams = params
}
private fun initialMap() {
SDKInitializer.debug(false)
// 隐私合规接口
SDKInitializer.setAgreePrivacy(true)
// SDKInitializer.setStyleUrl(MineMap.UrlType.satellite, MyContants.YZ_WMS)
SDKInitializer.initialize(this, object : InitListener {
override fun onInitSuccess() {
// println("---Map onInitSuccess")
setUpMap()
}
override fun onInitFailed(msg: String?) {
println("---Map onInitFailed msg = ${msg}")
}
})
}
private fun setUpMap() {
binding.mapView.addMapRenderCallback(object : MapView.OnMapReadyListener {
override fun onMapReady(mineMap: MineMap?) {
// mainVm.mMineMap = mineMap
mapOpt.mMineMap = mineMap
mockVM.mMineMap = mineMap
mineMap?.setZoomLevel(14f)
//设置中心点
// val point = Tools.latLngToPoint(LatLng(39.80913878, 116.50166926))
val point = Tools.latLngToPoint(LatLng(39.809135, 116.502434))
mineMap?.setPointToCenter(point.x, point.y)
}
})
}
override fun getToData() {
if (!mainVm.isMock) {
//开启websocket
getTarget()
getV2x()
//开启2个HTTP sse
getCarVehicle()
getAvpStatus()
}
//获取车位占用情况
// getSpaceInfo()
}
//获取车位占用情况
private fun getSpaceInfo() {
mainVm.getSpaceInfo().observe(this) { spaceInfo ->
//车辆占用情况
var spinfos = spaceInfo.result.map {
Spinfo().apply {
code = it.code
state = true
}
}
HighMapApi.setParkStatu(ParkStatu(spinfos))
}
}
//显示隐藏 视频
private fun showVideoFragment(show: Boolean) {
var ft = supportFragmentManager.beginTransaction()
if (!exoPlayFragment.isAdded) {
ft.add(R.id.video_frag, exoPlayFragment, "player")
}
if (show) {
ft.show(exoPlayFragment)
} else {
ft.hide(exoPlayFragment)
}
ft.commit()
}
override fun initListener() {
mockBt()
}
//获取AVP状态
private fun getAvpStatus() {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
mainVm.getAvpStatus().collect { avpStatu ->
showAvp(avpStatu)
// 车内视频
// isCarVideo(businessStatus)
}
}
}
}
//显示AVP特效
private fun showAvp(avpStatu: AvpStatuBean) {
if (avpStatu.haulingStageState == null || avpStatu.vehicleContext == null)
return
//业务类型
var businessType = avpStatu.businessType//NIL Park Call
//业务状态
var businessStatus = avpStatu.haulingStageState
//获取档位
var gearType = avpStatu.vehicleContext.vehicleDynamic.gearType
//画全局路径
mapOpt.drawAllLines(avpStatu.drivenDecision, businessStatus)
//生成小地图路线
mapOpt.takeSmallMapLine(avpStatu.drivenDecision, businessStatus)
//画终点
mapOpt.addEndMarker(avpStatu.drivenDecision.endPoint, businessStatus)
//是否显示车位流光效果
mapOpt.showParkLight(businessType, businessStatus, avpStatu.space)
//判断在泊车状态下的倒挡绘制倒车路线,改变镜头视角,,判断车是否到达停车点
mapOpt.drawReversePark(businessType, businessStatus, gearType, avpStatu.space)
//重置变量
resetVariable(businessStatus)
//根据AVP任务状态判停车特效是否关闭
// mapOpt.showEffectAvpStatu(businessType, businessStatus)
}
/***是否展示车内视频****/
private fun isCarVideo(businessStatus: String) {
if (businessStatus == AvpContants.TRANSPORT_PROGRESS) {
getVehDetail()
} else {
// exoPlayFragment.videoUrl = cameraUrl
showVideoFragment(false)
}
}
/***重置变量**/
private fun resetVariable(businessStatus: String) {
if (businessStatus == AvpContants.TRANSPORT_COMPLETED || businessStatus == AvpContants.TRANSPORT_CANCELED) {
mainVm.isGetVehDetail = false
// mapOpt.smallMapLine = false
mapOpt.sliceIndex = 0
// mapOpt.isDrawAreaCount = false
}
}
//联网车辆位姿数据
private fun getCarVehicle() {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
mainVm.subVehicle().collect { car ->
showVehicle(car)
}
}
}
//召车的时候临时开一下
mainVm.targetPre.observe(this) {
showPre(it)
}
}
//显示车辆位姿UI
private fun showVehicle(car: CarVehicle) {
if (car.vehiclePos == null || car.vehiclePos?.getOrNull(2) == null) {
//隐藏小车
return
}
//画小地图小车
mapOpt.drawSmallCar(car)
//刷新右下角小车位置
mapOpt.showNavingCarPosition(car)
//生成车前方矩形
mapOpt.genFronArea(car)
//刷新右下角小地图路径
mapOpt.upSmallMapLine()
//AVP鹰眼基于剩余路线最小矩形框为中心展示。
mapOpt.drawEndArea(car)
// println("----car.heading ${car.heading}")
//刷新主车位置
HighMapApi.setCarPosition(
car.vehiclePos!!.get(2),
car.vehiclePos!!.get(1),
car.vehiclePos!!.get(0),
20.80189
// car.elevation
)
// setCarCamera(1)
}
//联网车辆感知物
private fun getTarget() {
mainVm.subTarget().observe(this) { it ->
showPre(it)
}
}
/**绘制感知物****/
private fun showPre(it: PerceptionBean) {
if (it.getOrNull(0) == null) {
return
}
var parts = it.flatMap { it.participants }
if (parts.count() == 0) {
HighMapApi.clearPtcData()
return
}
var ptcList = parts.map {
var unityPtc = UnityPtc().apply {
lat = it.latitude
lon = it.longitude
ptcid = it.ptcId
heading = it.heading
pType = 1
if (it.ptcType == "car") {
pType = 1
} else if (it.ptcType == "pedestrian") {
pType = 2
}
}
return@map unityPtc
}
HighMapApi.setPtcData(ptcList)
}
//开启v2x预警
private fun getV2x() {
mainVm.subStartV2x().observe(this) { v2x ->
if (v2x == null)
return@observe
if (v2x.objects?.isEmpty() == true || v2x.objects?.getOrNull(1) == null)
return@observe
//获取预警感知目标物的id 第一个是自己 第二个是别人
var v2xId = v2x.objects?.get(1)?.id
// println("------------v2xId = ${v2xId}")
if (v2xId == null) {
return@observe
}
Log.e("V2x", "-------------v2xId= ${v2xId}")
// v2xId = "f117fdfa-feff-0100-85dc-35850000acb0"
mainVm.startWarning(v2xId)
}
}
//设置跟车视角
private fun setCarCamera(c: Int) {
if (c == 1) {
HighMapApi.setCameraAngle(30f)
HighMapApi.setCameraDistance(6f)
} else if (c == 2) {
HighMapApi.setCameraAngle(90f)
HighMapApi.setCameraDistance(80f)
}
}
//获取车辆详情,取车内摄像头地址打开左下角的车内视频
private fun getVehDetail() {
// if (car?.businessStatus.equals("EXITED")) {
// exoPlayFragment.videoUrl = ""
// showVideoFragment(false)
// return
// }
mainVm.getVehDetail().observe(this) { vehDetail ->
if (vehDetail.result.vehicleInfos.count() == 0)
return@observe
var cameraUrl = vehDetail.result.vehicleInfos.get(0).vehicleVideoUrl
println("---cameraUrl = ${cameraUrl}")
if (exoPlayFragment.videoUrl.isNotEmpty())
return@observe
if (!cameraUrl.isNullOrEmpty()) {
exoPlayFragment.videoUrl = cameraUrl
showVideoFragment(true)
}
}
}
private fun mockBt() {
//获取AVP状态
binding.btAvpStatu.setOnClickListener {
getAvpStatus()
}
//联网车辆位姿数据
binding.btVehicle.setOnClickListener {
getCarVehicle()
}
//感知目标物
binding.btTarget.setOnClickListener {
getTarget()
}
// HTTP获取车辆详情
binding.btVehinfo.setOnClickListener {
getVehDetail()
}
//v2x 预警
binding.btV2x.setOnClickListener {
getV2x()
}
//车位四周流光
binding.btParkround.setOnClickListener {
HighMapApi.setCameraAngle(75f)
lifecycleScope.launch {
HighMapApi.parkRoundLight("B021")
delay(5000)
HighMapApi.parkRoundLight("")
}
}
//停车位绘制
binding.btParkstatu.setOnClickListener {
var spinfos = listOf(Spinfo().apply {
code = "B020"
state = true
}, Spinfo().apply {
code = "B022"
state = true
})
HighMapApi.setParkStatu(ParkStatu(spinfos))
}
//地锁绘制
binding.btLock.setOnClickListener {
//释放http资源
// SimpleSSEClient.instance.cancelContect()
var lockStatu = LockStatu().apply {
code = "B021"
isHide = false
}
HighMapApi.setLockStatus(lockStatu)
/* lifecycleScope.launch {
delay(3000)
lockStatu.up = false
HighMapApi.setLockStatus(lockStatu)
delay(3000)
lockStatu.isHide = true
HighMapApi.setLockStatus(lockStatu)
}*/
}
//模拟移动
// binding.btMove.setOnClickListener {
// mainVm.mockFzLine()
// }
//预警车
binding.warnCar.setOnClickListener {
mainVm.mWarnCar()
}
//预警人
binding.warnPeo.setOnClickListener {
mainVm.mWarnPeo()
}
}
/**
* Show video play
* @param isHide false:隐藏 true:显示
*/
private fun showVideoPlay(isHide: Boolean) {
// exoPlayFragment.videoUri=""
var ft = supportFragmentManager.beginTransaction()
if (isHide) {
ft.hide(exoPlayFragment)
} else {
ft.show(exoPlayFragment)
}
ft.commit()
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
mainVm.windowConfiguration.value = newConfig
}
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
mainVm.hasFocus.value = hasFocus
}
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
if ((event.getKeyCode() == KeyEvent.KEYCODE_BACK) || event.getKeyCode() == KeyEvent.KEYCODE_HOME) {
// var ft = supportFragmentManager.beginTransaction()
dialogFragment = ExitAppDialog()
dialogFragment.onConCan = object : OnConCan {
override fun onCon() {
finish()
}
override fun onCan() {
}
}
dialogFragment.show(supportFragmentManager, "exit")
return true
} else {
return super.dispatchKeyEvent(event)
}
}
}
/* https://docs.qq.com/sheet/DVWdOYXZXdVVrQWts?tab=xxmysv socket文档
https://s.apifox.cn/e355c9e1-cdd1-49ab-acb2-54cfc66b1598/320994000e0 大屏文档
获取AVP状态信息 /v1/avp/overview/listAvpStatus 这个曲华烨要做成socket 的推送形式,通过这个接口获取全局路径和局部路径,车辆业务状态。
通过里面的vehicleId,调车辆详情(/v1/avp/overview/getVehicleInfo)获取车内视频流,
websocket 那几个连接,都需要传vehicleId,你也可以不传,不传就是获取所有的*/
//reType 不传就是飞渡,可以问张海胜,数据没有的问题可直接在51word群里反馈
//目前没有全局路径规划变化提醒,具体车位占用情况(用来在车位上绘制白模)
//需求文档里的气泡提醒,数据未必有,已实际数据为准
//【腾讯文档】AVP-HMI接口需求清单
//https://docs.qq.com/sheet/DVmNmZ3VhVEFxRkpV?tab=BB08J2
//高精地图沟通文档
//https://docs.qq.com/sheet/DQWhPRkdteGFNWVZi?tab=BB08J2
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment