RuStore Push SDK: получение и отображение пуш-уведомлений

- Подключён ru.rustore.sdk:pushclient:7.4.0 (репозиторий Rustore).
- SvxApp: канал svx_push и инициализация RuStorePushClient с project_id;
  push-токен устройства сохраняется в настройках.
- PushMessagingService (RuStoreMessagingService): onNewToken, onMessageReceived
  (data-only пуши показываем сами), onDeletedMessages, onError.
- Манифест: сервис, значок/канал по умолчанию, android.useAndroidX=true
  (зависимость тянет androidx).
- README: как рассылать пуши через vkpns.rustore.ru API.
This commit is contained in:
ua1zbe
2026-09-16 08:35:42 +03:00
parent 1af9bfda80
commit fac8192499
9 changed files with 177 additions and 2 deletions

View File

@@ -43,6 +43,33 @@ Android-клиент для подключения к рефлекторам **S
- Android 8.0 (API 26) и выше - Android 8.0 (API 26) и выше
- Доступ к сети и микрофону - Доступ к сети и микрофону
- Рефлектор SVXLink с включённой поддержкой протокола v2 (AUTH_KEY) - Рефлектор SVXLink с включённой поддержкой протокола v2 (AUTH_KEY)
- Для push-уведомлений: установить и авторизоваться в приложении **RuStore** на устройстве (оно является дистрибьютором пуша)
## Push-уведомления (RuStore)
Приложение интегрировано с [RuStore Push SDK](https://www.rustore.ru/help/sdk/push-notifications) (аналог Firebase Cloud Messaging). Для доставки на устройстве должен быть установлен и авторизован RuStore (или приложение-дистрибьютор экосистемы VK).
Рассылка выполняется с сервера через API RuStore:
```
POST https://vkpns.rustore.ru/v1/projects/{project_id}/messages:send
Authorization: Bearer {service-token}
Content-Type: application/json
{
"message": {
"token": "{push-token устройства}",
"notification": {
"title": "Говорит UA1ZBE1 TG12",
"body": "..."
}
}
}
```
- `project_id` и `service-token` берутся в консоли RuStore: **Push-уведомления > Проекты**.
- `push-token` устройства приходит в callback `onNewToken` (лог `RuStorePush`), сохраняется в настройках приложения.
- Если в запросе есть секция `notification` — уведомление отображает сам SDK; если переданы только `data` (`title`/`body`) — приложение показывает его самостоятельно.
## Установка ## Установка

View File

@@ -43,6 +43,7 @@ kotlin {
} }
dependencies { dependencies {
implementation("ru.rustore.sdk:pushclient:7.4.0")
testImplementation("junit:junit:4.13.2") testImplementation("junit:junit:4.13.2")
} }

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.RECORD_AUDIO" />
@@ -9,6 +10,7 @@
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<application <application
android:name=".SvxApp"
android:label="@string/app_name" android:label="@string/app_name"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:usesCleartextTraffic="true" android:usesCleartextTraffic="true"
@@ -20,6 +22,22 @@
android:exported="false" android:exported="false"
android:foregroundServiceType="mediaPlayback" /> android:foregroundServiceType="mediaPlayback" />
<service
android:name=".PushMessagingService"
android:exported="true"
tools:ignore="ExportedService">
<intent-filter>
<action android:name="ru.rustore.sdk.pushclient.MESSAGING_EVENT" />
</intent-filter>
</service>
<meta-data
android:name="ru.rustore.sdk.pushclient.default_notification_icon"
android:resource="@drawable/ic_launcher_fg" />
<meta-data
android:name="ru.rustore.sdk.pushclient.default_notification_channel_id"
android:value="@string/push_channel_id" />
<activity <activity
android:name=".SettingsActivity" android:name=".SettingsActivity"
android:exported="false" android:exported="false"

View File

@@ -0,0 +1,60 @@
package ru.ua1zbe.svxremote
import android.app.Notification
import android.app.NotificationManager
import android.util.Log
import ru.rustore.sdk.pushclient.messaging.exception.RuStorePushClientException
import ru.rustore.sdk.pushclient.messaging.model.RemoteMessage
import ru.rustore.sdk.pushclient.messaging.service.RuStoreMessagingService
import ru.ua1zbe.svxremote.prefs.Prefs
/**
* Приём пуш-уведомлений RuStore.
*
* Если сервер прислал секцию `notification` — SDK сам отображает уведомление.
* Если присланы только `data` (title/body) — отображаем уведомление сами.
*/
class PushMessagingService : RuStoreMessagingService() {
override fun onNewToken(token: String) {
Log.i(TAG, "onNewToken: $token")
Prefs.setPushToken(this, token)
}
override fun onMessageReceived(message: RemoteMessage) {
if (message.notification != null) {
Log.i(TAG, "push displayed by SDK: ${message.data}")
return
}
val title = message.data["title"] ?: getString(R.string.app_name)
val body = message.data["body"] ?: ""
if (title.isNotEmpty() || body.isNotEmpty()) {
showPush(title, body)
}
}
override fun onDeletedMessages() {
Log.i(TAG, "onDeletedMessages")
}
override fun onError(errors: List<RuStorePushClientException>) {
errors.forEach { Log.w(TAG, "error", it) }
}
private fun showPush(title: String, body: String) {
val nm = getSystemService(NotificationManager::class.java)
val n = Notification.Builder(this, CHANNEL_ID)
.setContentTitle(title)
.setContentText(body)
.setSmallIcon(R.drawable.ic_launcher_fg)
.setAutoCancel(true)
.build()
nm.notify(NOTIFICATION_ID, n)
}
companion object {
private const val TAG = "RuStorePush"
const val CHANNEL_ID = "svx_push"
private const val NOTIFICATION_ID = 2
}
}

View File

@@ -0,0 +1,55 @@
package ru.ua1zbe.svxremote
import android.app.Application
import android.app.NotificationChannel
import android.app.NotificationManager
import android.util.Log
import ru.rustore.sdk.pushclient.RuStorePushClient
import ru.ua1zbe.svxremote.prefs.Prefs
/**
* Приложение: создаём канал пуша и инициализируем RuStore Push SDK.
* project_id берём из консоли RuStore (Push-уведомления > Проекты).
*/
class SvxApp : Application() {
override fun onCreate() {
super.onCreate()
createPushChannel()
initRuStorePush()
}
private fun createPushChannel() {
getSystemService(NotificationManager::class.java).createNotificationChannel(
NotificationChannel(
PushMessagingService.CHANNEL_ID,
getString(R.string.push_channel_name),
NotificationManager.IMPORTANCE_HIGH
)
)
}
private fun initRuStorePush() {
try {
RuStorePushClient.init(
application = this,
projectId = RUSTORE_PROJECT_ID
)
RuStorePushClient.getToken()
.addOnSuccessListener { token ->
Log.i(TAG, "push token: $token")
Prefs.setPushToken(this, token)
}
.addOnFailureListener { e ->
Log.w(TAG, "getToken failed", e)
}
} catch (e: Exception) {
Log.w(TAG, "init failed", e)
}
}
companion object {
private const val TAG = "RuStorePush"
private const val RUSTORE_PROJECT_ID = "Yd7tDjVDHqsnTJwHNYN08cYIvy8fsWU-"
}
}

View File

@@ -178,4 +178,14 @@ object Prefs {
fun setRxStatsEnabled(context: Context, enabled: Boolean) { fun setRxStatsEnabled(context: Context, enabled: Boolean) {
sp(context).edit().putBoolean("rx_stats_enabled", enabled).apply() sp(context).edit().putBoolean("rx_stats_enabled", enabled).apply()
} }
// ---------- RuStore Push ----------
/** Пуш-токен устройства RuStore (для серверной рассылки). */
fun pushToken(context: Context): String =
sp(context).getString("push_token", "") ?: ""
fun setPushToken(context: Context, token: String) {
sp(context).edit().putString("push_token", token).apply()
}
} }

View File

@@ -80,6 +80,8 @@
<string name="notif_connected">Соединение с рефлектором активно</string> <string name="notif_connected">Соединение с рефлектором активно</string>
<string name="notif_talking">Говорит: %1$s</string> <string name="notif_talking">Говорит: %1$s</string>
<string name="notif_last_talker">Последний: %1$s</string> <string name="notif_last_talker">Последний: %1$s</string>
<string name="push_channel_id">svx_push</string>
<string name="push_channel_name">Пуш-уведомления</string>
<string name="btn_connect">Подключиться</string> <string name="btn_connect">Подключиться</string>
<string name="btn_disconnect">Отключиться</string> <string name="btn_disconnect">Отключиться</string>

View File

@@ -5,4 +5,5 @@ org.gradle.daemon=false
org.gradle.parallel=false org.gradle.parallel=false
org.gradle.caching=false org.gradle.caching=false
kotlin.compiler.execution.strategy=in-process kotlin.compiler.execution.strategy=in-process
android.nonTransitiveRClass=true android.nonTransitiveRClass=true
android.useAndroidX=true

View File

@@ -11,6 +11,7 @@ dependencyResolutionManagement {
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
maven("https://nexus-external.rustore.ru/repository/maven-rustore-exposed/")
} }
} }