Если ты читаешь эту статью, наверно тебе, как и мне понадобилось подключить SignalR в своем мобильном приложении. К сожалению в русскоязычном сегменте не так уж много информации о том, как это сделать, а то что есть уже устарело.
В рамках этой статьи я не буду касаться серверной части, но постараюсь предоставить все необходимое примеры кода с комментариями для реализации SignalR в Android, а получившийся пример выложу на GitHub.
Что такое SignalR и когда он используется?
Это библиотека с открытым исходным кодом, которая упрощает добавление веб-функций в режиме реального времени в приложения. Веб-функции в режиме реального времени позволяют коду на стороне сервера мгновенно отправлять содержимое на клиенты.
Управляет автоматическим управлением соединениями.
Отправляет сообщения всем подключенным клиентам одновременно. Например, комната чатов.
Отправляет сообщения конкретным клиентам или группам клиентов.
Масштабируется для управления увеличением трафика.
Схематично
Создаем проект и подключаем зависимости.
Создаем новый проект на основе Empty Activity, я назову его SignalR_Example
Создание нового проекта
Добавляем 3 необходимые зависимости в build.gradle проекта:
implementation "com.microsoft.signalr:signalr:5.0.10"
Смотрим актуальную версию здесь
implementation "com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.2"
Смотрим актуальную версию здесь
implementation 'com.google.code.gson:gson:2.8.8'
Смотрим актуальную версию здесь
Зависимости в build.gradle
Создаем необходимые классы
Класс SignalRListener и соответствующий ему интерфейс ISignalRListener
Он будет содержать подключение к HubConnection и основную реализацию слушателя внешних событий.
Класс SignalRListener
/**
* Представляет SignalR-реализацию слушателя внешних событий.
* @param sslSocketFactory Модифицированный протокол безопасной передачи данных.
* @param trustAllCerts Модифицированный X509TrustManager для доверия всем сертификатам.
*/
class SignalRListener (
private val sslSocketFactory: SSLSocketFactory,
private val trustAllCerts: TrustAllCerts
) : ISignalRListener {
private val apiUrl = "https://something.com/api/" //Адрес Вашего api
private val hubConnection = HubConnectionBuilder.create(apiUrl + "GatewayHub") //
.setHttpClientBuilderCallback {
it.sslSocketFactory(sslSocketFactory, trustAllCerts)
}
.build()
override val connectionId: String?
get() = hubConnection.connectionId
override val connectionState: HubConnectionState
get() = hubConnection.connectionState
override fun startConnection() {
hubConnection.start()
}
override fun stopConnection() {
hubConnection.stop()
}
override fun subscribe(
eventName: String,
handler: (event: LinkedTreeMap<String, String>) -> Unit
) {
hubConnection.on(
eventName,
handler,
LinkedTreeMap::class.java
)
}
override fun unsubscribe(eventName: String) {
hubConnection.remove(eventName)
}
}
Интерфейс ISignalRListener
/**
* Описывает реализацию слушателя внешних событий.
*/
interface ISignalRListener {
/**
* Возвращает id подключения к SignalR.
*/
val connectionId: String?
/**
* Возвращает статус подключения к SignalR.
*/
val connectionState: HubConnectionState
/**
* Выполняет подключение к серверу SignalR.
*/
fun startConnection()
/**
* Выполняет отключение от сервера SignalR.
*/
fun stopConnection()
/**
* Подписывается на событие.
* @param eventName Имя события.
* @param handler Обработчик события.
*/
fun subscribe(eventName: String, handler: (LinkedTreeMap<String, String>) -> Unit)
/**
* Отписывается от события.
* @param eventName Имя события.
*/
fun unsubscribe(eventName: String)
}
Класс SignalRService и соответствующий ему интерфейс ISignalRService
Он будет содержать реализацию подписок на ресурсы.
Класс SignalRService
/**
* Представляет реализацию подписок на ресурсы.
*/
class SignalRService (
private val signalRListener: ISignalRListener
) : ISignalRService {
/**
* Возвращает или устанавливает подписку на ресурсы.
*/
private val resourcesSubscriptions = mutableListOf<ResourceSubscriptionModel>()
init {
subscribeOnResourceChanged()
}
/**
* Подписывается на события изменения ресурса.
*/
private fun subscribeOnResourceChanged() {
signalRListener.subscribe(
"ResourceChangedEventOccurred"
) { event ->
resourcesSubscriptions.forEach { subscription ->
val resourceName = event["resourceName"]
if (resourceName != null && resourceName.contains(subscription.resourceName)) {
//Реагируем на изменение ресурса.
subscription.callback()
}
}
}
}
override fun subscribe(subscriptionsForAdd: List<ResourceSubscriptionModel>) {
subscriptionsForAdd.forEach { resourceSubscription ->
resourcesSubscriptions.add(resourceSubscription)
}
}
override fun unsubscribe(subscriptionsForRemove: List<ResourceSubscriptionModel>) {
subscriptionsForRemove.forEach { resourceSubscription ->
resourcesSubscriptions.remove(resourceSubscription)
}
}
}
Интерфейс ISignalRService
/**
* Описывает методы подписки на внешние события.
*/
interface ISignalRService {
/**
* Добавляет ресурсы в подписку.
* @param subscriptionsAdd Список ресурсов для подписки.
*/
fun subscribe(subscriptionsAdd: List<ResourceSubscriptionModel>)
/**
* Удаляет ресурсы из подписки.
* @param subscriptionsRemove Список ресурсов для отписки.
*/
fun unsubscribe(subscriptionsRemove: List<ResourceSubscriptionModel>)
}
Дата класс ResourceSubscriptionModel
Он будет представлять подписку на ресурс.
Дата класс ResourceSubscriptionModel
/**
* Представляет подписку на ресурс.
*/
data class ResourceSubscriptionModel(
/**
* Имя ресурса для отслеживания событий.
*/
val resourceName: String,
/**
* Метод выполняемый при получении события.
*/
val callback: () -> Unit
)
Класс TrustAllCerts
Он позволит доверять всем сертификатам.
Класс TrustAllCerts
/**
* Позволяет доверять всем сертификатом.
*/
class TrustAllCerts : X509TrustManager {
override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
override fun checkClientTrusted(certs: Array<X509Certificate>, authType: String) = Unit
override fun checkServerTrusted(certs: Array<X509Certificate>, authType: String) = Unit
}
Класс SignalRApplication
Он представляет класс нашего приложения и будет хранить синглтоны ISignalRListener и ISignalRService. По хорошему следует использовать какой ни будь DI инструмент, но для упрощения примера сделаем так. Не забываем добавлять его в манифест.
Класс SignalRApplication
/**
* Представляет приложение.
*/
class SignalRApplication : Application() {
lateinit var signalRListener: ISignalRListener // Слушатель внешних событий.
lateinit var signalRService: ISignalRService // Сервис подписок на ресурсы.
companion object {
lateinit var application: SignalRApplication
}
override fun onCreate() {
super.onCreate()
application = this
// Модифицированный X509TrustManager для доверия всем сертификатам.
val trustAllCerts = TrustAllCerts()
// Модифицированный протокол безопасной передачи данных.
val sslSocketFactory = SSLContext.getInstance("TLSv1.2").apply {
init(null, arrayOf<TrustManager>(trustAllCerts), SecureRandom())
}.socketFactory
signalRListener = SignalRListener(sslSocketFactory, trustAllCerts)
signalRService = SignalRService(signalRListener)
}
}
Абстрактный класс BaseActivity
Он будет представлять базовый класс для наших Activity и содержать методы на подписку и отписку от событий.
Абстрактный класс BaseActivity
/**
* Представляет базовый класс Activity
*/
abstract class BaseActivity : AppCompatActivity() {
/**
* Сервис подписок на ресурсы.
*/
private val _signalRService = SignalRApplication.application.signalRService
/**
* Лист подписок.
*/
private val _resourceSubscriptions: MutableList<ResourceSubscriptionModel> = mutableListOf()
override fun onStop() {
super.onStop()
_signalRService.unsubscribe(_resourceSubscriptions)
}
/**
* Устанавливает подписки на ресурсы.
* @param resourceSubscriptions Лист подписок.
*/
fun setSignalRSubscriptions(
resourceSubscriptions: List<ResourceSubscriptionModel>
) {
_resourceSubscriptions.addAll(resourceSubscriptions)
_signalRService.subscribe(resourceSubscriptions)
}
}
Комментарии (2)
korsetlr473
29.09.2021 17:11не очень понял по вашему коду некоторые вопросы
1) если из клиента подключаешься к двум разных хабам , браузер это менеджерет и будет все равно 1 коннекшен?
2) если я сделаю из клиента через NEW websocket сразу 2 соединения к Одному и тому же хабу , то это будет референс на одно и тоже соединение ?
FirsofMaxim
Что-то история с TrustAllCerts вообще не нравится...