跳到主要内容

Unity 推送通知 SDK(版本 6.2.1)

SDK 正常运行条件​

以下是 push 通知的工作条件。

重要
  • 应用程序不同类型版本(debug、release 等)的签名和包名(package name)可能互不相同。 在这种情况下,您必须在 RuStore 控制台 的“推送通知 > 项目”部分为每种版本类型创建一个项目。。
  • 正在使用最新版本的 SDK。
  • 已在 RuStore 控制台 的“推送通知 > 项目”部分上传应用程序数据。
  • 用户设备上已安装分发应用(如 RuStore 等)

    若要检查分发应用程序是否已安装在用户设备上,请使用 RuStorePushClient.checkPushAvailability 方法。。
  • 如果安装了 RuStore 应用程序,则允许其在后台模式下运行。 如果没有此权限,推送通知仍会送达,但会有明显的延迟。
  • 设备上安装的应用程序签名指纹与 RuStore 控制台 的“推送通知 > 项目”部分中添加的应用程序签名指纹一致。

实现示例​

请参阅 示例应用程序,以了解如何正确集成推送通知 SDK。

连接到项目​

请从 upm_tgz 页面下载以下包以进行连接:

  • ru.rustore.core-x.y.z.tgz
  • ru.rustore.push-x.y.z.tgz

通过 Package Manager 将包导入到项目中 (Window > Package Manager > + > Add package from tarball...)。

依赖项将通过 External Dependency Manager 自动连接:

  1. 打开包管理器窗口 (Window > Package Manager > + > Add package from git URL...)。
  2. 使用链接 https://github.com/googlesamples/unity-jar-resolver.git?path=/upm 来连接该包。
  3. 为了消除 Google.IOSResolver.dll will not be loaded 错误,请为您的 Unity 版本安装 iOS 构建模块 (UnityHub > Installs > 您的 Unity 版本 > Add modules > iOS Build Support)。
Google.IOSResolver.dll 错误
Assembly 'Packages/com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.182/Google.IOSResolver.dll' will not be loaded due to errors:
Unable to resolve reference 'UnityEditor.iOS.Extensions.Xcode'. Is the assembly missing or incompatible with the current platform?
Reference validation can be disabled in the Plugin Inspector.
提示

如果您使用的是 macOS 操作系统,请更改归档实用程序的设置。 在 Archive Utility 设置中,取消勾选“尽可能继续解压 (Keep expanding if possible)”。 否则,项目存档将无法正确下载。

为了正确处理 SDK 依赖项,请执行以下设置。

  1. 打开项目设置:Edit → Project Settings → Player → Android Settings。

  2. 在 Publishing Settings 部分,启用以下设置:

    • Custom Main Manifest。
    • Custom Main Gradle Template。
    • Custom Gradle Properties Template。
  3. 在 Other Settings 部分,配置:

    • package name。
    • Minimum API Level = 24。
    • Target API Level = 34。
  4. 打开 External Dependency Manager 设置:Assets → External Dependency Manager → Android Resolver → Settings,启用以下设置:

    • Use Jetifier。
    • Patch mainTemplate.gradle。
    • Patch gradleTemplate.properties。
  5. 更新项目依赖项:Assets → External Dependency Manager → Android Resolver → Force Resolve。

编辑应用清单文件​

声明 RuStoreUnityMessagingService 服务。

<service
android:name="ru.rustore.unitysdk.pushclient.RuStoreUnityMessagingService"
android:exported="true"
tools:ignore="ExportedService">
<intent-filter>
<action android:name="ru.rustore.sdk.pushclient.MESSAGING_EVENT" />
</intent-filter>
</service>

如果需要更改标准通知的图标或颜色,请添加以下代码。

AndroidManifest.xml
<meta-data
android:name="ru.rustore.sdk.pushclient.default_notification_icon"
android:resource="@drawable/ic_baseline_android_24" />
<meta-data
android:name="ru.rustore.sdk.pushclient.default_notification_color"
android:resource="@color/your_favorite_color" />

如果需要重定义通知渠道,请添加以下代码。

AndroidManifest.xml
<meta-data
android:name="ru.rustore.sdk.pushclient.default_notification_channel_id"
android:value="@string/pushes_notification_channel_id" />

在添加 push 通知渠道时,您必须自行创建该渠道。

为了能够通过点击推送消息跳转到应用程序,您必须将 RuStoreUnityActivity 设置为主 Activity。 为此,请在项目的清单文件中添加以下记录。

<activity android:name="ru.rustore.unitysdk.RuStoreUnityActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

同时,您必须从 com.unity3d.player.UnityPlayerActivity 这个 Activity 中删除以下 <intent-filter>。

<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

与其他插件的兼容性​

RuStoreUnityActivity 类将处理 Intents 并启动 UnityPlayerActivity。 这是为了正确处理点击推送通知进入应用程序的跳转。

如果您需要更改此行为,请修改 RuStoreUnityActivity.java 文件(路径:RuStoreSDK > PushClient > Android),将 UnityPlayerActivity 替换为您自己的类。

在 Android 13+ 中请求显示通知的权限​

Android 13 版本引入了一个用于显示 push 通知的新权限。 这将影响所有在 Android 13 或更高版本上运行并使用 RuStore Push SDK 的应用程序。

默认情况下,1.4.0 及更高版本的 RuStore Push SDK 在清单文件中包含了 POST_NOTIFICATIONS 权限。 但是,应用程序还需要在运行时通过 android.permission.POST_NOTIFICATIONS 常量请求此权限。 只有在用户授予权限后,应用程序才能显示 push 通知。

请求显示 push 通知的权限。

Activity/Fragment
// Declare the launcher at the top of your Activity/Fragment:
private final ActivityResultLauncher<String> requestPermissionLauncher =
registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {
if (isGranted) {
// RuStore Push SDK (and your app) can post notifications.
} else {
// TODO: Inform user that your app will not show notifications.
}
});

private void askNotificationPermission() {
// This is only necessary for API level>= 33 (TIRAMISU)
if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) ==
PackageManager.PERMISSION_GRANTED) {
// RuStore Push SDK (and your app) can post notifications.
} else if (shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)) {
// TODO: display an educational UI explaining to the user the features that will be enabled
// by them granting the POST_NOTIFICATION permission. This UI should provide the user
// "OK" and "No thanks" buttons. If the user selects "OK," directly request the permission.
// If the user selects "No thanks," allow the user to continue without notifications.
} else {
// Directly ask for the permission
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS);
}
}
}

初始化​

初始化需要 来自 RuStore 控制台 的项目 ID。 要获取该 ID,请在应用程序页面中转到 推送通知 > 项目 部分,并复制 项目 ID 字段中的值。

img
请注意

请注意,Push SDK 不支持在多个进程中同时运行。
如果您的应用程序使用多个进程,则必须仅在主进程中初始化 SDK。

如果在辅助进程中进行初始化,可能会导致 push 通知工作异常。

自动初始化​

请在 AndroidManifest.xml 中添加以下代码。

AndroidManifest.xml
<meta-data
android:name="ru.rustore.sdk.pushclient.project_id"
android:value="i5UTx96jw6c1C9LvdlE4cdNrWHMNyRBt" />

<meta-data
android:name="ru.rustore.sdk.pushclient.params_class"
android:value="ru.rustore.unitysdk.pushclient.RuStorePushClientParamsExample" />
  • projectId — 来自 RuStore 控制台 的项目 ID。 要获取该 ID,请在应用程序页面中转到 推送通知 > 项目 部分,并复制 项目 ID 字段中的值。
  • params_class(可选)— 自定义 AbstractRuStorePushClientParams 实现类的完整名称。 该参数用于指定 push 客户端初始化时的附加参数。

RuStorePushClientParamsExample 的实现示例。

RuStorePushClientParamsExample.java
package ru.rustore.unitysdk.pushclient;

import android.content.Context;
import com.vk.push.common.clientid.ClientId;
import com.vk.push.common.clientid.ClientIdCallback;
import com.vk.push.common.clientid.ClientIdType;
import ru.rustore.sdk.pushclient.common.logger.Logger;
import ru.rustore.sdk.pushclient.provider.AbstractRuStorePushClientParams;

public class RuStorePushClientParamsExample extends AbstractRuStorePushClientParams {

private boolean isTestModeEnabled;

public RuStorePushClientParamsExample(Context context) {
super(context);

int testMode = context.getResources().getIdentifier("rustore_PushClientSettings_testMode", "string", context.getPackageName());
isTestModeEnabled = context.getString(testMode).equalsIgnoreCase("true");
}

@Override
public Logger getLogger() {
return new UnityLogger("RuStoreUnityPushClient");
}

@Override
public boolean getTestModeEnabled() {
return isTestModeEnabled;
}

@Override
public ClientIdCallback getClientIdCallback() {
return () -> new ClientId("your_gaid_or_oaid", ClientIdType.GAID);
}
}

在 Application 中进行手动初始化​

如需手动初始化 SDK,请在项目中创建一个继承自 Application 类的 RuStorePushApplication 类。 在 onCreate 方法中调用 RuStoreUnityPushClient.INSTANCE.init。

RuStorePushApplication.java
package ru.rustore.unitysdk;

import android.app.Application;
import ru.rustore.unitysdk.pushclient.RuStoreUnityPushClient;
import ru.rustore.unitysdk.pushclient.RuStoreUnityLoggerMode;

public class RuStorePushApplication extends Application {
public final String PROJECT_ID = "-Yv4b5cM2yfXm0bZyY6Rk7AHX8SrHmLI";

@Override
public void onCreate() {
super.onCreate();

RuStoreUnityPushClient.INSTANCE.init(
this,
PROJECT_ID,
RuStoreUnityLoggerMode.UNITYLOGGER,
"RuStoreUnityPushClient",
false,
null,
null
);
}
}
  • application: Application — 一个 Application 类实例。
  • projectId: String — 来自 RuStore 控制台的项目标识符。
  • loggerMode: RuStoreUnityLoggerMode
    • DEFAULTLOGGER — 日志记录器,默认将输出到 logcat。
    • UNITYLOGGER — Unity 日志记录器,日志事件将自动传递给 ILogListener 的实现。
  • loggerTag: String — 用于输出日志记录的标签。
  • testModeEnabled: Boolean — SDK 的测试运行模式。
  • clientIdType: RuStoreUnityClientIdType? — 标识符类型:
    • ClientIdType.GAID — Google 广告标识符;
    • ClientIdType.OAID — 华为广告标识符。
  • clientIdValue: String? — 标识符的值 (your_gaid_or_oaid)。

在 AndroidManifest.xml 文件中,为 application 标签添加 android:name 属性。

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="rustore.unitysdk.sample" xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application android:name="ru.rustore.unitysdk.RuStorePushApplication">
<activity android:name="ru.rustore.unitysdk.RuStoreUnityActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="com.unity3d.player.UnityPlayerActivity"
android:theme="@style/UnityThemeSelector" android:exported="true">
<meta-data android:name="unityplayer.UnityActivity" android:value="true" />
</activity>
<service android:name="ru.rustore.unitysdk.pushclient.RuStoreUnityMessagingService" android:exported="true" tools:ignore="ExportedService">
<intent-filter>
<action android:name="ru.rustore.sdk.pushclient.MESSAGING_EVENT" />
</intent-filter>
</service>
</application>
</manifest>

初始化插件​

在调用插件方法之前,必须创建一个 RuStorePushClient push 通知客户端对象。 请执行 Init 方法以初始化客户端。

创建客户端
public class Example : MonoBehaviour, IMessagingServiceListener, ILogListener {
private void Awake() {
var pushConfig = new RuStorePushClientConfig() {
allowNativeErrorHandling = true,
messagingServiceListener = this,
logListener = this
};

RuStorePushClient.Instance.Init(pushConfig);
}

/* Реализация интерфейсов IMessagingServiceListener, ILogListener */
}

Init 方法接收一个 RuStorePushClientConfig 对象作为输入参数:

  • allowNativeErrorHandling — 允许在原生 SDK 中处理错误;
  • messagingServiceListener — 一个实现了 IMessagingServiceListener 接口的类对象;
  • logListener — 一个实现了 ILogListener 接口的类对象。 当在 Application 中手动初始化并将 loggerMode 字段设置为 RuStoreUnityLoggerMode.UNITYLOGGER,或在自动初始化时使用 UnityLogger 类时,必须指定此参数。

事件日志记录​

如果您需要记录推送通知库的事件,请在您的 AbstractRuStorePushClientParams 子类中实现 getLogger 方法。 getLogger 方法必须返回一个实现了 Logger 接口的对象。

Logger 接口
interface Logger {

fun verbose(message: String, throwable: Throwable? = null)
fun debug(message: String, throwable: Throwable? = null)
fun info(message: String, throwable: Throwable? = null)
fun warn(message: String, throwable: Throwable? = null)
fun error(message: String, throwable: Throwable? = null)

fun createLogger(tag: String): Logger
}
日志记录器实现示例
package ru.rustore.unitysdk.pushclient

import android.util.Log
import ru.rustore.sdk.pushclient.common.logger.Logger

class UnityLogger (
private val tag: String? = null,
) : Logger {

override fun verbose(message: String, throwable: Throwable?) {
Log.v(tag, message, throwable)
RuStoreUnityPushClient.Log("[V] $tag $message");
logException(throwable)
}

override fun debug(message: String, throwable: Throwable?) {
Log.d(tag, message, throwable)
RuStoreUnityPushClient.Log("[D] $tag $message");
logException(throwable)
}

override fun info(message: String, throwable: Throwable?) {
Log.i(tag, message, throwable)
RuStoreUnityPushClient.Log("[I] $tag $message");
logException(throwable)
}

override fun warn(message: String, throwable: Throwable?) {
Log.w(tag, message, throwable)
RuStoreUnityPushClient.LogWarning("[W] $tag $message");
logException(throwable)
}

override fun error(message: String, throwable: Throwable?) {
Log.e(tag, message, throwable)
RuStoreUnityPushClient.LogError("[E] $tag $message");
logException(throwable)
}

private fun logException(throwable: Throwable?) {
if (throwable != null) {
RuStoreUnityPushClient.LogException(throwable)
}
}

override fun createLogger(tag: String): Logger {
val newTag = if (this.tag != null) {
"${this.tag}:$tag"
} else {
tag
}

return UnityLogger(newTag)
}
}
getLogger 方法
@NonNull
@Override
public Logger getLogger() {
return UnityLogger("your_tag");
}

如果不传递 Logger,SDK 将使用基于 AndroidLog 的默认实现。

若要使用 Unity 脚本记录事件,请使用 ILogListener 接口的实现。 实现该接口的对象必须在插件初始化方法中传递。

ILogListener 接口
namespace RuStore.PushClient {

public interface ILogListener {

public void Log(string logString);
public void LogWarning(string logString);
public void LogError(string logString);
}
}

用户分群管理​

分群是指您根据特定参数选择的一组用户。 例如,带来最高收入的用户,或使用旧版本 Android 的用户。 有关分群的详细信息,请参阅 MyTracker 文档。

要开始使用分群,请在初始化 SDK 时指定 ClientIdType 和 ClientIdValue。

RuStorePushClientParamsExample.java
public class RuStorePushClientParamsExample extends AbstractRuStorePushClientParams {

/* Реализация AbstractRuStorePushClientParams */

@Override
public ClientIdCallback getClientIdCallback() {
return () -> new ClientId("your_gaid_or_oaid", ClientIdType.GAID);
}
}
  • CLIENT_ID_VALUE — 标识符的值 (your_gaid_or_oaid)。
  • CLIENT_ID_TYPE — 标识符类型:
    • ClientIdType.GAID — Google 广告标识符;
    • ClientIdType.OAID — 华为广告标识符。

检查接收推送通知的能力​

若要检查分发应用程序是否已安装在用户设备上,请使用 RuStorePushClient.checkPushAvailability 方法。
RuStorePushClient.Instance.CheckPushAvailability(
onFailure: (error) => {
// Process error
},
onSuccess: (response) => {
if (!response.isAvailable) {
// Process push unavailable
}
}
);

Push 令牌操作方法​

获取用户的 Push 令牌​

警告

如果用户没有 push 令牌,该方法将创建并返回一个新的 push 令牌。

在初始化库之后,您可以使用 RuStorePushClient.getToken() 方法来获取当前用户的 push 令牌。
调用 GetToken 方法
RuStorePushClient.Instance.GetToken(
onFailure: (error) => {
// Process error
},
onSuccess: (token) => {
// Process success
}
);

删除用户的 Push 令牌​

在初始化库之后,您可以使用 RuStorePushClient.deleteToken() 方法来删除当前用户的 push 令牌。
调用 DeleteToken 方法
RuStorePushClient.Instance.DeleteToken(
onFailure: (error) => {
// Process error
},
onSuccess: () => {
// Process success
}
);

Push 主题操作方法​

订阅主题 Push 通知​

初始化库后,您可以使用 SubscribeToTopic(your_topic_name) 方法订阅主题。
调用 SubscribeToTopic 方法
RuStorePushClient.Instance.SubscribeToTopic(
topicName: "your_topic_name",
onFailure: (error) => {
// Process error
},
onSuccess: () => {
// Process success
}
);

取消订阅主题 Push 通知​

初始化库后,您可以使用 UnsubscribeFromTopic(your_topic_name) 方法取消订阅主题。
调用 UnsubscribeFromTopic 方法
RuStorePushClient.Instance.UnsubscribeFromTopic(
topicName: "your_topic_name",
onFailure: (error) => {
// Process error
},
onSuccess: () => {
// Process success
}
);

从 RuStore SDK 获取数据​

为了在 Unity 端接收和处理推送通知数据,请实现 IMessagingServiceListener 接口。 实现该接口的对象必须在插件初始化方法中传递。

IMessagingServiceListener.cs
using System.Collections.Generic;

namespace RuStore.PushClient {

public interface IMessagingServiceListener {

public void OnNewToken(string token);
public void OnMessageReceived(RemoteMessage message);
public void OnDeletedMessages();
public void OnError(List<RuStoreError> errors);
}
}
方法名称描述

onNewToken

在收到新的推送令牌(push token)时调用此方法。 调用此方法后,您的应用程序负责将新的推送令牌发送到自己的服务器。 该方法返回新令牌的值。

onMessageReceived

在收到新的推送通知时调用此方法。

如果 notification 对象中包含数据,RuStore SDK 将自动显示通知。 如果您不希望这样做,请使用 data 对象,并将 notification 保持为空。 无论如何,该方法都会被调用。

可以通过 message.data 字段获取推送通知的 payload Dictionary<string, string>。

DeletedMessagesResponse

如果一条或多条推送通知未送达设备,则调用此方法。 例如,如果通知在送达前已过期。

在调用此方法时,建议与自己的服务器进行同步,以免丢失数据。

ErrorResponse

如果在初始化时发生错误,则调用此方法。 它返回一个包含错误对象的数组。

可能的错误:

  • UnauthorizedException — 用户未在 RuStore 中登录。 即使用户未在 RuStore 中登录,也可能不会出现此错误。 此行为在 Push SDK 中是动态管理的。 即使您的用户没有遇到此类错误,对其进行处理仍然是有意义的;
  • HostAppNotInstalledException — 用户设备上未安装 RuStore;
  • HostAppBackgroundWorkPermissionNotGranted — RuStore 没有后台运行权限。 此错误并非关键错误。 推送通知将继续送达,但效果不如授予后台运行权限时那样好。
所有可能的错误均在 错误处理 章节中描述。

通知结构​

完整通知的结构
public class RemoteMessage {
public string collapseKey;
public Dictionary< string, string > data;
public string messageId;
public Notification notification;
public int priority;
public sbyte [] rawData;
public int ttl;
public string from;
}
  • collapseKey — 通知组的标识符(目前不考虑)。
  • data — 一个字典,其中可以传递通知的附加数据。
  • messageId — 消息的唯一 ID。 它是每条消息的标识符。
  • notification — 通知对象。
  • priority — 返回优先级值(目前不考虑)。 目前定义了以下选项:
    • 0 — UNKNOWN;
    • 1 — HIGH;
    • 2 — NORMAL。
  • rawData — 以字节数组形式表示的 data 字典。
  • ttl — Int 类型的 push 通知生存时间,单位为秒。
  • from — 用于识别通知来源的字段:
    • 对于发送到主题(topic)的通知,该字段显示主题名称;
    • 在其他情况下,显示服务令牌的一部分。
Notification 对象的结构
public class Notification {
public string title;
public string body;
public string channelId;
public string imageUrl;
public string color;
public string icon;
public string clickAction;
public ClickActionType? clickActionType;
}
  • title — 通知标题。
  • body — 通知正文。
  • channelId — 用于指定发送通知的渠道。 适用于 Android 8.0 及更高版本。
  • imageUrl — 用于插入通知的图像直接链接。 图像大小不得超过 1 MB。
  • color — 以字符串形式表示的 HEX 格式通知颜色。 例如,#0077FF。
  • icon — 来自 res/drawable 的通知图标,以与资源名称一致的字符串格式表示。
    例如,res/drawable 中有一个 small_icon.xml 图标,可以通过 R.drawable.small_icon 在代码中访问。
    为了让图标在通知中显示,服务器必须在 icon 参数中指定 small_icon 的值
    。
  • clickAction — 点击通知时用于打开 Activity 的 intent action。
  • clickActionType — clickAction 字段的类型。
ClickActionType 枚举
namespace RuStore.PushClient
{
public enum ClickActionType
{
DEFAULT,
DEEP_LINK,
}
}

创建用于发送通知的渠道​

发送通知的渠道遵循以下优先级。

  • 如果推送通知中包含 channelId 字段,RuStore SDK 将把通知发送到指定的渠道。 您的应用程序必须提前创建此渠道。

  • 如果推送通知中没有 channelId 字段,但您的应用程序在 AndroidManifest.xml 中指定了渠道参数,则将使用该指定渠道。 您的应用程序必须提前创建此渠道。

  • 如果 push 通知中没有 channelId 字段,且 AndroidManifest.xml 中未指定默认通道,RuStore SDK 将创建该通道并将通知发送至其中。 此后,所有未明确指定通道的通知都将发送到该通道。

错误处理​

如果您收到 Failure 响应,不建议向用户显示错误。 显示错误可能会对用户体验产生负面影响。

RuStoreError 类
namespace RuStore {

public class RuStoreError {

public string name;
public string description;
}
}
  • name — 错误名称。 包含错误类的 simpleName 名称。
  • description — 错误消息。

如果在初始化 SDK 时传递了参数 allowNativeErrorHandling == true,在发生错误的情况下:

  • 将调用相应的 onFailure 处理程序。

  • 错误将传递给原生 SDK 的 resolveForPush 方法。 这样做是为了向用户显示错误对话框。

resolveForPush 方法
fun RuStoreException.resolveForPush(context: Context)

要禁用将错误传递给原生 SDK,请将 AllowNativeErrorHandling 属性的值设置为 false。

禁用原生错误处理
RuStorePushClient.Instance.AllowNativeErrorHandling = false;
注意

RuStorePushClient.Instance.AllowNativeErrorHandling 的值必须在插件初始化之后设置。

可能的错误​

  • RuStoreNotInstalledException — 用户设备上未安装 RuStore。

  • RuStoreOutdatedException — 用户设备上安装的 RuStore 版本不支持此 SDK。

  • RuStoreUserUnauthorizedException — 用户未在 RuStore 中登录。

  • RuStoreFeatureUnavailableException — RuStore 没有后台运行权限。

  • RuStoreException — RuStore 基础错误,其他错误均继承自此类。

另请参阅​