PAYATHON 2026

Android 应用授权后为何仍收不到 OneSignal 推送通知?

排障赵师傅

检查通知权限和推送注册状态

点击“允许”后,Android 应用还需要完成 FCM 注册、建立 OneSignal Push Subscription,并将订阅状态同步到服务端,才能收到 OneSignal 推送。

仅凭现有代码还无法确定根因,先检查设备上的四项状态:

  • OneSignal.getNotifications().getPermission()
  • OneSignal.getUser().getPushSubscription().getId()
  • OneSignal.getUser().getPushSubscription().getToken()
  • OneSignal.getUser().getPushSubscription().getOptedIn()

External ID 和 OneSignal ID 是用户身份信息,不能用来判断设备是否能接收推送。Dashboard 中的 Never Subscribed 也需要结合设备日志和对应的 Subscription 判断原因。OneSignal 订阅说明

先确认点击的是系统授权弹窗

“启用通知以即时获取新闻”可能来自应用自定义的说明弹窗,也可能来自 OneSignal In-App Message。这句文案本身无法说明 Android 是否已经授予通知权限。

在 Android 13(API 33)及以上,通知通常需要申请 POST_NOTIFICATIONS 运行时权限。应用内说明弹窗中的“Allow”按钮需要继续触发系统权限请求,点击它本身不会授予系统权限。Android 通知权限说明

读取权限状态:

Log.d("OneSignal",
        "Permission=" + OneSignal.getNotifications().getPermission());

同时进入手机的“设置 → 应用 → BiratBazar → 通知”,确认通知开关已开启。

如果结果仍为 false,先检查权限请求是否执行。若已为 true,继续检查注册和同步流程,反复调用 optIn() 通常不会提供更多线索。

将权限请求与身份观察器分开

当前代码只有在 identityObserver 收到符合条件的回调后,才会调用:

requestOneSignalPermission();

权限请求因此依赖身份变化事件。一旦没有进入这个分支,请求就不会从这里发起。

代码中的两个条件也需要修正:

  • 当 isLoggedIn == true、savedUserId == null 时,代码不会调用 login(),身份判断也无法通过。
  • currentOnesignalId != null 没有排除空字符串,仍不足以判断身份是否就绪。

OneSignal.login(externalId) 用于关联业务用户身份。匿名用户也可以建立推送订阅,因此申请通知权限无需等待 External ID 匹配。OneSignal Android 用户管理说明

原代码的 onResume() 已经会在权限为 true 时调用 optIn()。如果系统权限确实已经授予,还需要检查其他环节,不能只归因于身份观察器。

修改 MainActivity 中的相关逻辑

保留原有页面初始化代码,分别处理身份登录、订阅观察和权限请求。以下代码使用问题中的 OneSignal 5.x Java API:

import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;

import com.onesignal.Continue;
import com.onesignal.OneSignal;
import com.onesignal.user.subscriptions.IPushSubscriptionObserver;

// 以下字段和方法放在现有 MainActivity 中。

private final IPushSubscriptionObserver pushObserver =
        state -> logPushState("subscription changed");

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // 保留原有 setContentView 等页面初始化代码。

    OneSignal.getUser()
            .getPushSubscription()
            .addObserver(pushObserver);

    // 同时读取当前状态,避免日志只记录后续变化。
    logPushState("onCreate");

    SharedPreferences prefs = getSharedPreferences(
            "biratbazar_prefs", Context.MODE_PRIVATE);

    boolean isLoggedIn = prefs.getBoolean("is_logged_in", false);
    String savedUserId = prefs.getString("user_id", null);

    if (isLoggedIn
            && savedUserId != null
            && !savedUserId.trim().isEmpty()) {
        OneSignal.login(savedUserId);
    }

    // 在用户点击“启用通知”按钮后调用:
    // requestOneSignalPermission();
}

private void requestOneSignalPermission() {
    OneSignal.getNotifications().requestPermission(
            true,
            Continue.with(result -> {
                if (!result.isSuccess()) {
                    Log.e("OneSignal",
                            "Permission request failed",
                            result.getThrowable());
                    return;
                }

                boolean granted =
                        Boolean.TRUE.equals(result.getData());

                Log.d("OneSignal", "Permission granted=" + granted);

                if (granted) {
                    OneSignal.getUser()
                            .getPushSubscription()
                            .optIn();
                }

                logPushState("permission callback");
            })
    );
}

private void logPushState(String source) {
    String subscriptionId =
            OneSignal.getUser().getPushSubscription().getId();
    String token =
            OneSignal.getUser().getPushSubscription().getToken();

    boolean hasToken = token != null && !token.isEmpty();

    Log.d("OneSignal",
            source
                    + ": Permission="
                    + OneSignal.getNotifications().getPermission()
                    + ", SubscriptionID=" + subscriptionId
                    + ", HasToken=" + hasToken
                    + ", OptedIn="
                    + OneSignal.getUser()
                            .getPushSubscription()
                            .getOptedIn());
}

@Override
protected void onResume() {
    super.onResume();
    logPushState("onResume");
}

@Override
protected void onDestroy() {
    OneSignal.getUser()
            .getPushSubscription()
            .removeObserver(pushObserver);
    super.onDestroy();
}

在现有“启用通知”按钮的点击事件中调用 requestOneSignalPermission()。参数 true 表示无法继续显示系统权限提示时,可以提供跳转设置的后备提示,并不会强制授予权限。

订阅 ID 和 token 是异步获取的,初始化后立即读到空值不一定有问题。需要结合后续订阅观察器的回调和完整启动日志判断。权限回调成功后,也仍需确认服务端同步是否完成。OneSignal SDK API 说明

不要在每次 onResume() 时默认重新调用 optIn()。如果应用有通知订阅开关,这样还会覆盖用户在应用中主动退订的选择。

简化 OneSignalManager 的登录封装

每次登录或退出都创建新的线程池,会增加调用顺序的不确定性,当前代码也没有关闭这些 executor。可以直接封装 SDK 调用:

public void login(String externalId) {
    if (externalId == null || externalId.trim().isEmpty()) {
        return;
    }
    OneSignal.login(externalId);
}

public void logout() {
    OneSignal.logout();
}

这项调整可以简化代码,但现有证据还不足以认定线程池是订阅失败的根因。

根据日志判断卡在哪一步

设备上的状态优先检查
Permission=false系统权限是否授予、按钮是否真正调用权限 API
Permission=true,但 HasToken=false 持续存在FCM 注册错误、Google Play services、网络、依赖兼容性
token 非空,但 Subscription ID 长期为空OneSignal 注册请求、App ID、网络或服务端错误
权限、ID、token 均正常,但 OptedIn=false是否调用过 optOut()、是否有其他订阅偏好逻辑
本地状态正常,Dashboard 仍异常是否查看了同一个 OneSignal App 和当前 Subscription,状态上传是否成功

自定义日志:

OneSignal initialized with App ID: ...

只能说明代码执行过初始化调用。isInitialized = true 同样是应用自己的标记,两者都不能证明 FCM 注册或服务端订阅已经成功。

原代码已经开启 LogLevel.VERBOSE。检查从应用启动到授权完成的完整 Logcat,重点查找注册异常、HTTP 错误、Google Play services 错误和网络错误。

核对 Firebase 与 OneSignal 的配置

OneSignal 后台必须配置 FCM 发送凭据

在对应 OneSignal App 的 Android 推送配置中,确认 FCM HTTP v1 服务账号凭据有效。注意区分两类文件:

文件用途
google-services.jsonAndroid 应用的 Firebase 客户端配置
Firebase 服务账号 JSON授权 OneSignal 服务端发送 FCM 消息

服务账号 JSON 应配置在 OneSignal 后台,不能打包进 APK。用于注册设备的 Firebase 项目也应与 OneSignal 的发送配置对应。

服务端凭据缺失或错误会影响消息发送,但无法解释所有 Never Subscribed 情况,还需要检查设备注册状态。OneSignal Firebase 配置说明

检查当前构建实际使用的 google-services.json

项目已经显式引入 Firebase Messaging、Analytics 和 Google Services 插件,需要确认:

  • 对应构建变体有正确的 google-services.json。
  • JSON 中的 Android 包名与实际 applicationId 匹配,包括可能存在的后缀。
  • 如果 Debug、Release 或不同 flavor 使用了不同的 Firebase 项目,OneSignal 配置也已同步调整。

Google Services 插件会根据当前包名选择 JSON 中的客户端配置。Gradle 中声明了插件,并不足以说明当前安装包使用了预期配置。Google Services 插件说明

Firebase 通常通过 FirebaseInitProvider 自动初始化。手动调用 FirebaseApp.initializeApp(this) 无法补齐缺失的配置,而且该方法在找不到配置时可能返回 null,所以只捕获异常还不足以判断初始化是否成功。FirebaseApp API

检查实际解析的依赖版本

这条依赖声明使用了版本范围:

implementation 'com.onesignal:OneSignal:[5.6.1, 5.9.99]'

应用实际使用的版本未必是 5.6.1。先查看当前构建最终解析到的版本:

./gradlew :app:dependencyInsight --dependency com.onesignal --configuration debugRuntimeClasspath
./gradlew :app:dependencyInsight --dependency firebase-messaging --configuration debugRuntimeClasspath

如果问题出现在 Release 或某个 flavor 中,改用对应的配置名称。排查期间可以将 OneSignal 固定为已确认的具体版本,避免不同构建解析出不同结果。

目前没有日志证据表明 Firebase BoM 34.12.0、AGP 8.2.2 或 Google Services 插件 4.4.4 是故障来源,因此不能把升降级当作确定的修复方法。

还需要检查 Merged Manifest 中是否存在以下配置,且值为 true:

firebase_messaging_installation_id_enabled

如果启用了 Firebase Installation ID 注册,旧版 OneSignal 的 token 注册路径可能无法工作。官方说明,这条路径要求 OneSignal Android SDK 5.11.0 及以上,同时满足相应的 Firebase Messaging 和设备要求。问题中的 OneSignal 版本范围不包含 5.11.0。

确认该开关实际启用后,再按对应要求升级,或评估关闭这种注册方式。当前贴出的 Manifest 没有该配置,但依赖库可能通过 Manifest 合并引入它,需要检查合并结果。Firebase Installation ID 注册说明

Manifest、通知扩展和 ProGuard 应怎样处理

Application 初始化位置基本合理

在 BiratBazarApp.onCreate() 中调用:

OneSignal.initWithContext(context, appId);

并通过:

android:name=".BiratBazarApp"

注册 Application,符合 OneSignal 的初始化方式。在 Merged Manifest 中确认该配置没有被覆盖,并核对代码中的 App ID 是否与当前查看的 Dashboard App 完全一致。OneSignal Android 接入说明

Manifest 中的:

tools:targetApi="31"

是 Lint 提示,不会设置应用的目标 Android 版本。实际 targetSdk 需要查看模块的 Gradle 配置。Android tools 属性说明

通知扩展不负责创建订阅

当前扩展通过 <meta-data> 注册的方式是正确的,类注释中关于 <action> 的描述需要修正。

如果只需原样显示通知,可以暂时移除自定义扩展,也可以保留空实现,让 SDK 自动显示。只有主动调用 event.preventDefault() 后,才需要在合适的时机调用 display()。

通知扩展处理的是接收通知后的显示流程,无法修复 FCM token 缺失或订阅创建失败的问题。OneSignal 通知服务扩展说明

com.google.firebase.messaging.default_notification_channel_id 则用于配置默认通知渠道。它不能作为 Honor/EMUI 的通用 FCM 修复方案,也无法让缺少 Google Play services 的设备获得 FCM 接收能力。

根据混淆构建结果检查 keep 规则

现有规则已经大范围保留了应用、OneSignal、Firebase 和 Google Play services 类。继续添加全包 -keep,无法解决权限、网络或服务端凭据错误。

先比较关闭混淆和开启混淆的构建结果:

  • 如果两者都失败,优先检查注册流程和配置。
  • 如果只有开启混淆后失败,再检查 R8 日志、自定义扩展类的保留规则,以及构建是否实际加载了该规则文件。

最后用当前 Subscription 做一次定向测试

取得非空 Subscription ID 后,在同一个 OneSignal App 中找到对应的设备记录,确认状态和最近活跃时间。重新安装、清除数据或切换用户后,都需要重新核对,避免查看旧记录。

先向当前 Subscription 定向发送测试通知。如果订阅正常但通知仍不显示,再检查通知渠道开关、前台拦截逻辑、自定义 FirebaseMessagingService、设备网络和后台限制。使用 FCM 时,先在 Google Play services 正常的设备上验证;如果通过系统设置强制停止过应用,需要手动重新打开后再测试。OneSignal 推送排查指南

备注:内容仅供参考。