新闻详情

新闻详情

首页 / 资讯中心 / 详情

Flutter安卓推送实战:极光推送与本地通知集成全攻略

发布时间:2026/9/28 12:08:26来源:尧图网络
Flutter安卓推送实战:极光推送与本地通知集成全攻略
做过 Flutter 移动端开发的兄弟应该都有体会推送这件事说大不大说小不小但绝对绕不开。尤其是安卓端既要搞定系统级的本地通知调度又得接远程推送通道一不小心就被各种厂商、各种版本、各种回调折腾得头皮发麻。今天这篇就把我最近在一个项目里同时接入极光推送JPush和本地通知的全过程掰开揉碎讲清楚从环境配置到代码实现从通知渠道到厂商适配再到排查收不到消息的坑一次性给你补齐。这项目本身是一个工具类 App需要两类通知能力一类是服务端运营下发的远程推送比如活动提醒、订单状态变更另一类是用户自己设定的本地通知比如待办提醒、定时打卡。我选型时没有纠结太久远程侧直接走极光推送本地侧用官方主流的本地通知插件两者并行互不干扰。这篇实战记录适合正在做 Flutter 安卓推送接入的开发者也适合那些刚入门、想知道“推送到底是怎么一回事”的朋友我会把关键的配置文件和代码逻辑都贴出来。1. 整体设计与方案选型思路先聊清楚为什么这么选能帮你少走弯路。1.1 为什么远程推送选择极光Flutter 社区里远程推送的第三方方案不少JPush 算是最老牌的一家了。极光对 Flutter 的官方支持插件jpush_flutter维护得比较勤版本跟随 Flutter SDK 的更新速度尚可并且文档和 demo 都相对齐全这对团队排期紧的项目来说很关键。选极光还有一个很现实的原因它把安卓端各家厂商通道的适配封装掉了一部分。安卓推送的碎片化问题人尽皆知小米、华为、OPPO、vivo、荣耀都有自己的推送服务系统级推送通道的权限和墓碑机制也完全不同。如果每一个厂商都手工去调 SDK光这个工作量就足够劝退一个小团队。极光提供统一的后台配置入口配合厂商通道的自动注册逻辑能省下不少维护成本。当然选极光也要接受它的代价后台请求链路变长了。从你的服务器推到极光极光再推给厂商通道厂商通道再投递到系统链路多一跳故障排查的复杂度就上来了。但这属于可用性换开发效率的取舍项目前期完全能接受。真到了需要极细化控制推送到达率的阶段再考虑直接接厂商原生 SDK 也不迟。1.2 远程推送和本地通知的边界这是很多初学者最容易混的一点。远程推送Remote Notification依赖网络消息从服务端到达极光服务器再通过长连接或厂商通道下发到客户端本地通知Local Notification完全在设备端解决App 自己设定一个触发时间到点之后由系统调度并展示一条通知。两者看上去都是“弹出通知栏消息”但背后的机制和适用场景差别很大。比如我们这个项目里的“待办提醒”用户设定上午九点提醒喝水。这个触发事件完全不应该依赖服务端也不应该走远程推送——用户设定了提醒结果断网了九点钟通知没弹这个体验非常糟糕。所以这类需求必须本地通知数据存在本地系统 Schedule 到点自己弹。而“订单被卖家发货了推送一条消息给买家”这类场景触发点在服务端业务系统里客户端根本不知道什么时刻该弹这就必须要走远程推送。梳理清这个边界后面的技术方案才不会拧巴。很多团队一提到“推送”就全都往极光上堆结果本地定时提醒这种东西也在服务端维护定时任务纯属给自己找事。2. 环境准备与工程基础配置动手前先确认环境避免后面编译时报一堆看不懂的错。2.1 Flutter 与第三方库版本核对先报一下我这次用的环境版本供你参考组件版本Flutter3.22.xDart3.4.xjpush_flutter3.3.9flutter_local_notifications17.xcompileSdk34targetSdk34minSdk21这里有一个非常容易踩的坑flutter_local_notifications这个插件从 17 版本开始对 Android 侧的要求已经转向Desugaring 支持。如果你的项目没有开启 core library desugaring编译时直接会报错提示你添加isCoreLibraryDesugaringEnabled和相关依赖。这玩意儿的报错信息比较隐晦我见过不少人在群里问“为什么我加了插件就编译失败”八九不离十都是这个原因。2.2 安卓侧 Gradle 基础改造打开android/app/build.gradle补上 desugaring 支持和编译选项。这一段是我反复测试之后的稳定配置android { compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 coreLibraryDesugaringEnabled true } } dependencies { coreLibraryDesugaring com.android.tools:desugar_jdk_libs:2.0.4 }不是所有 Flutter 项目都需要开 desugaring但只要接了flutter_local_notifications17 以上的版本这步基本是必做的。核心原因是插件内部的日历调度逻辑用到了java.time相关的 API这部分 API 在 Android 8 以下系统里不存在只能用 desugaring 来兼容。2.3 基础权限声明远程推送和本地通知能正常工作基础权限是前提。打开android/app/src/main/AndroidManifest.xml在manifest根节点下加入以下几个uses-permission android:nameandroid.permission.INTERNET / uses-permission android:nameandroid.permission.ACCESS_NETWORK_STATE / uses-permission android:nameandroid.permission.ACCESS_WIFI_STATE / uses-permission android:nameandroid.permission.VIBRATE / uses-permission android:nameandroid.permission.RECEIVE_BOOT_COMPLETED / uses-permission android:nameandroid.permission.POST_NOTIFICATIONS /倒数第二个权限容易被忽略RECEIVE_BOOT_COMPLETED。很多定时本地通知在用户重启手机之后就再也不触发了原因就是缺少这个权限导致系统重启后没有重新注册闹钟任务。这个坑我写进后面的排查清单里你如果遇到了记得回来对一下。最后一个权限是 Android 13 才推出的通知运行时权限后面单独讲。3. 极光推送接入实战这段是重头戏。我会按照“加依赖 - 原生配置 - Dart 侧初始化 - 事件监听 - 统计回调”的顺序走一遍完整流程。3.1 在 pubspec.yaml 中添加依赖并安装极光推送的官方 Flutter 插件是jpush_flutter在pubspec.yaml的 dependencies 段里面加dependencies: jpush_flutter: ^3.3.9然后执行flutter pub get这里要特别提醒一下不要盲目用最新的插件小版本。极光这类第三方推送插件对 Flutter 版本和 Android Gradle Plugin 版本都比较敏感有时候小版本升级会引入底层 SDK 的变更导致莫名其妙的编译问题。我的习惯是先锁一个大版本跑通流程之后再考虑升级。等业务稳定了再挑一个没有发版压力的时间窗口统一升插件版本并做回归测试。3.2 安卓原生清单文件配置 JPush 参数极光推送要求在每个应用里配置JPUSH_APPKEY和JPUSH_CHANNEL。这两个值要写进AndroidManifest.xml的application节点里面。极光后台申请应用之后会给一个 AppKey复制过来粘贴进android:value即可。application android:name.MainApplication android:label你的应用名称 android:iconmipmap/ic_launcher meta-data android:nameJPUSH_APPKEY android:value你的AppKey / meta-data android:nameJPUSH_CHANNEL android:valuedeveloper-default / /application注意JPUSH_CHANNEL是用来统计渠道来源的比如huawei、xiaomi或者应用商店的渠道名。没有特殊需求填developer-default就行但如果接厂商通道这个值最好和厂商后台渠道名统一方便后续分开统计推送效果。还有一步容易被漏掉主进程配置。极光官方文档要求MainActivity不要改动但如果你用了多进程需要在 manifest 里给极光需要的组件加上android:process配置。大多数中小项目没有多进程需求所以这部分只在多进程环境下才需要操心。3.3 Dart 侧初始化与登录流程打通安装好依赖、配置好原生参数之后回到 Dart 侧做初始化。建议在应用启动的最早期调用我是放在main函数里做的void main() async { WidgetsFlutterBinding.ensureInitialized(); // 初始化极光推送 await JPush.setup( appKey: your_app_key_here, channel: developer-default, production: false, ); runApp(const MyApp()); }production参数对应的是极光后台的推送环境开发环境填false生产环境填true。这里有个很坑的细节如果极光控制台上创建的是“生产环境”的推送而客户端production填的是false生产环境的消息是收不到的。很多人在测试期一切正常、上线后推送失效基本都是这个参数没切过来。初始化完成之后建议立刻设置别名和标签。别名的典型场景是一个用户对应多台设备登录后台给这个用户发消息时直接指定 alias而不是广播给所有安装了 App 的人。我在登录成功之后调用await JPush.setAlias(alias: user_${userId});同样地登出操作时要把别名清掉用deleteAlias否则下一台设备登录同账号会出现消息串台。3.4 通知与自定义消息的事件监听极光推送的消息分成两类通知消息Notification到达设备后由系统直接展示在通知栏不需要 App 做任何处理。自定义消息Custom Message透传给 App 的消息系统不会展示需要你在代码里接收并决定如何呈现。这两类消息的接收方式也不一样。监听通知和事件回调用JPush.addEventHandler示例代码如下JPush.addEventHandler( onReceiveNotification: (JPushMessage message) { debugPrint(收到通知: ${message.title} - ${message.alertContent}); }, onOpenNotification: (JPushMessage message) { debugPrint(用户点击了通知: ${message.extras}); }, onReceiveCustomNotification: (JPushMessage message) { debugPrint(收到自定义消息: ${message.alertContent}); }, );这里需要说明一个很多人疑惑的点onReceiveNotification只是理论上的“收到通知”但在某些情况下它并不代表通知已经展示到了系统通知栏也不代表用户一定看到了。如果你需要精确统计“展示数”你就需要结合厂商通道去处理。在纯极光通道下onReceiveNotification大约能代表消息到达了 App 进程但厂商系统级广播投递的消息能不能触发这个回调取决于通道类型。我在项目里拿到这个回调后主要用它来更新 App 内部的消息红点和未读数不会把它当严格的业务数据源。onOpenNotification是用户真正点击通知栏消息时触发的这个回调通常用来做页面跳转。点击通知栏跳转的具体实现方法放到后面“常见问题”里说因为里面牵涉到路由和初始化时机的坑。3.5 通知点击事件的业务跳转处理实际业务中用户点击推送进入的不一定是首页。比如推送内容是“订单详情”点击之后应该跳到订单详情页。做法是根据extras里的字段做路由转发。极光的JPushMessage.extras是一个 JSON 字符串转出来的 Map里面可以带上业务类型和业务 ID。onOpenNotification: (JPushMessage message) { final extras message.extras; if (extras ! null extras[route] order_detail) { // 通过全局路由跳转到订单详情页 navigatorKey.currentState?.pushNamed( /order_detail, arguments: extras[orderId], ); } },这里的关键点是如果 App 进程已经被用户杀掉点击通知栏消息会先拉起 Flutter 引擎然后再触发onOpenNotification回调。在这个冷启动场景下navigatorKey.currentState可能还没有就绪直接 push 路由会报空指针。保险的做法是在回调里做一个延迟跳转等待首帧渲染完成再跳。我在工具类里封装了一个简单的等待逻辑详细写法也在后面排查章节给你示例。4. 本地通知接入实战远程推送解决的是“服务端触达用户”的问题本地通知解决的是“用户设备自身按时提醒”的问题。这部分我用flutter_local_notifications来讲解这个插件在社区里使用率最高API 设计也相对清晰。4.1 本地通知的场景与限制边界本地通知很适合这些场景闹钟提醒、日程提醒、喝水打卡、每日学习提醒甚至是一些需要通过定时任务同步数据的场景。要明确的一点是本地通知只能由 App 在系统里预先注册一个触发时间App 不运行的情况下也能正常弹通知这是安卓系统调度器的能力。但前提是你得用系统的ScheduledNotification机制而不是在 App 进程里写一个 Timer 来弹出通知。很多新手直接在 Dart 侧用Timer延时几秒弹通知App 一旦退到后台被系统回收进程都没了通知自然没了。这是概念理解上的根子问题。4.2 初始化本地通知引擎在 Dart 侧初始化插件的方式final localNotifications FlutterLocalNotificationsPlugin(); Futurevoid initLocalNotifications() async { const initializationSettings InitializationSettings( android: AndroidInitializationSettings(mipmap/ic_launcher), iOS: DarwinInitializationSettings(), ); await localNotifications.initialize(initializationSettings); }AndroidInitializationSettings的默认小图标建议用系统自带图标测试正式项目里应该在drawable目录放一个小图标资源注意安卓通知栏小图标只能用纯 alpha 图层的资源不能用彩色图否则通知栏会显示成白色方块。这个问题我在审查设计稿时反复强调过是个非常容易出现的视觉效果坑。4.3 通知渠道的正确设计安卓 8.0 之后所有通知都必须绑定一个通知渠道NotificationChannel渠道一旦创建就不能修改其重要性等级只能删除应用或改未来渠道。所以渠道设计要提前想清楚我按照通知的重要程度拆了三个渠道渠道 ID名称重要性用途high_importance高优提醒IMPORTANCE_HIGH闹钟、待办default_notifications默认消息IMPORTANCE_DEFAULT一般提醒low_importance低优通知IMPORTANCE_LOW更新提示、活动通知在代码里创建渠道const androidChannel AndroidNotificationChannel( high_importance, 高优提醒, description: 用于闹钟和待办提醒, importance: Importance.high, playSound: true, enableVibration: true, ); await localNotifications .resolvePlatformSpecificImplementation AndroidFlutterLocalNotificationsPlugin() ?.createNotificationChannel(androidChannel);渠道重要性要谨慎IMPORTANCE_HIGH允许弹出横幅并响铃但也被系统限制为“需要用户授权允许通知”。用户在系统设置里关闭某个渠道的通知权限之后App 侧是无权再次开启的只能引导用户去设置页手动开启。4.4 定时通知与周期通知的调度示例定时通知的调度接口很直观指定了一个未来时间点后系统按时触发Futurevoid scheduleDailyReminder(Time time) async { await localNotifications.zonedSchedule( id: 1001, title: 喝水提醒, body: 一天八杯水别忘了这一杯, scheduledDate: nextInstanceOf(time), notificationDetails: const NotificationDetails( android: AndroidNotificationDetails( high_importance, 高优提醒, channelDescription: 用于闹钟和待办提醒, importance: Importance.high, priority: Priority.high, ), ), androidScheduleMode: AndroidScheduleMode.inexactAllowWhileIdle, ); }nextInstanceOf负责计算“下一次这个时刻”比如用户设定每天上午九点提醒那需要计算从当前时间到下一个九点的时间差DateTime nextInstanceOf(Time targetTime) { final now DateTime.now(); var scheduledDate DateTime( now.year, now.month, now.day, targetTime.hour, targetTime.minute, ); if (scheduledDate.isBefore(now)) { scheduledDate scheduledDate.add(const Duration(days: 1)); } return scheduledDate; }注意androidScheduleMode的参数选择这里有一个精确度和电量的平衡问题。inexactAllowWhileIdle允许系统在 Doze 模式下延迟触发适合大多数提醒场景exactAllowWhileIdle是精确定时但 Android 12 以上需要额外申请USE_EXACT_ALARM或SCHEDULE_EXACT_ALARM权限而且国内不少 ROM 默认不给精确闹钟权限直接硬上会导致崩溃或静默失败。我自己的处理原则是能容忍分钟级误差的场景一律用 inexact只有闹钟这种精确性要求极高的功能才申请精确闹钟权限。4.5 取消通知与通知权限检查用户取消提醒时要按通知 ID 取消对应的本地通知await localNotifications.cancel(1001);如果用户设置了多个提醒就用一个本地数据库表维护通知 ID 和业务 ID 的关系。幂等性很重要否则同一个提醒被用户重复触发创建会出现多条相同通知。我在项目里用shared_preferences存了一个自增 ID 列表创建通知前先根据业务 key 查一下是否已存在存在就先 cancel 再重新调度。通知权限检查bool? areNotificationsEnabled() { return localNotifications .resolvePlatformSpecificImplementation AndroidFlutterLocalNotificationsPlugin() ?.areNotificationsEnabled(); }Android 13 以上这个值由运行时权限决定Android 13 以下默认是开启的。如果返回 false要引导用户去系统设置页打开。5. 厂商通道与兼容性踩坑这一章是实操里最容易让人崩溃的部分。极光推送文档写得比较分散厂商适配的坑必须单独梳理。5.1 为什么必须适配厂商通道安卓系统有两个推送通道一个是极光自有的长连接通道一个是各手机厂商系统级的推送服务。极光长连接通道在 App 进程存活时表现良好但国产 ROM 的杀后台机制很激进App 进程一死长连接就断了推送就收不到了。厂商通道的逻辑不同小米推送、华为推送这类服务跑在系统级进程里即使 App 被杀也能展示通知。适配厂商通道的意义就是让推送投递不再依赖 App 存活。要接厂商通道一般是先在手机厂商开放平台创建应用拿到 AppID 和 AppKey然后在极光控制台填写对应渠道的密钥信息客户端申请厂商推送服务。5.2 极光 Flutter 插件里接入厂商通道的路径需要分别在小米、华为、OPPO、vivo 的控制台申请对应的 AppID 和 AppKey然后填到极光的控制台里。客户端这边还需要在build.gradle里面配置JPush的厂商通道扩展implementation cn.jiguang.sdk.plugin:xiaomi:3.8.0 implementation cn.jiguang.sdk.plugin:huawei:3.8.0 implementation cn.jiguang.sdk.plugin:oppo:3.8.0 implementation cn.jiguang.sdk.plugin:vivo:3.8.0接入厂商通道之后的实际效果差异很大。最明显的例子是小米手机上极光长连接通道和厂商通道同时注册时优先走厂商通道推送能稳定到达。而 OPPO 和 vivo 在用户手动关闭了应用的通知权限后厂商通道也会同步失效这种属于系统级限制没有任何技术方案能绕过只能靠运营文案引导用户打开权限。5.3 一个特别坑的华为渠道问题华为手机上如果你同时集成了华为厂商通道 SDK 和极光本身应用升级后可能会出现PushKit能力绑定冲突。具体表现是华为手机上消息到达率骤降后台日志出现HMSService绑定失败。这个问题排查了很久才定位到华为推送服务需要在 AndroidManifest 中声明com.huawei.hms相关的 service而极光的jpush_flutter插件在自动集成时可能没有把华为的 SDK 资源合并干净。最后在build.gradle里显式添加了华为推送依赖并在 manifest 里补上了缺失的 service 声明问题才解决。这块如果遇到优先对比极光官方文档中“厂商通道配置”的 manifest 片段。5.4 Android 13 通知权限适配Android 13 新增了POST_NOTIFICATIONS运行时权限。如果你的 targetSdk 升到了 33 及以上用户必须弹窗同意后才能收到通知。这个权限要主动在 Dart 侧申请final plugin localNotifications.resolvePlatformSpecificImplementation AndroidFlutterLocalNotificationsPlugin(); await plugin?.requestNotificationsPermission();有两件事必须注意一是申请时机。不要在 App 一启动就立刻弹窗会很突兀用户很容易点“不允许”。建议结合业务场景用户触发一个需要通知的功能时再申请权限配合一个自定义说明页告知“我们需要通知权限用于及时提醒您”这样通过率高很多。二是权限被拒之后的降级处理。如果用户拒绝权限本地通知不会崩溃但调度了也不会展示。你需要感知权限状态在 App 内提供引导入口把用户带到系统设置页手动开启。6. 常见问题与排查经验实录这一节把我在整个接入过程中踩过的、以及被同行问过最多的问题集中复盘一遍。6.1 收不到极光远程推送的排查顺序这个问题如果处理不得法会浪费大量时间。我总结了一个固定排查顺序确认 AppKey 和 production 环境匹配。开发环境的 App 不能用生产环境推送反之亦然。确认onReceiveNotification是否触发。回调没触发说明消息压根没到 App 进程查通道注册回调触发了但通知栏不显示大概率是厂商通道配置异常或者通知权限被关闭。确认应用进程是否存活。杀掉 App 后能收到推送说明厂商通道生效杀不掉就收不到说明只有极光长连接通道在跑。确认后台下发时选对推送方式。运营同事经常把自定义消息当成通知消息下发结果用户在通知栏什么都看不到这种需要技术侧在后台配置上做校验和提示。6.2 点击通知栏消息不跳转这个问题有两个高频原因。第一个是冷启动时路由未就绪我们之前提过我的解决方式是做一个延迟跳转的工具方法Futurevoid pushAfterFirstFrame(WidgetsBinding binding, String routeName, Object? args) async { // 等待首帧渲染完成后再跳转 for (var i 0; i 10; i) { if (binding.rootElement ! null) { navigatorKey.currentState?.pushNamed(routeName, arguments: args); return; } await Future.delayed(const Duration(milliseconds: 200)); } }第二个原因是Activity 的 launchMode 配置导致路由栈异常。极光文档要求MainActivity使用singleInstance或singleTask模式如果你改成了standard点击通知拉起 Activity 时每次都会新建一个实例路由栈就和之前的对不上了。检查AndroidManifest.xml里的MainActivity配置。我在项目里用的是android:launchModesingleTask配合navigatorKey做全局跳转稳定很多。6.3 本地定时通知到点不触发出现这种问题按优先级排查是否申请了RECEIVE_BOOT_COMPLETED权限没有该权限时手机重启后所有定时通知都会失效。是否开启了电池优化白名单很多 ROM 会把 App 的闹钟调度挂起需要引导用户将 App 加入电池白名单。是否设置的是exactAllowWhileIdle但系统版本是 Android 12 以上精确闹钟权限没有授予会导致静默失败。是否在调试时设置了 Android 模拟器部分模拟器对zonedSchedule的 Doze 模拟不完整到点不触发可能是模拟器问题而非代码问题真机大部分就没问题。我项目中遇到的案例是用户反馈所有本地通知都不弹了。定位后发现是那次更新升级 targetSdk 之后ROM 把应用的通知权限默认重置了而我们的代码在权限被拒后继续调度结果消息全都成了“哑巴”。后来我在所有调度入口加了权限检查不通过就引导用户开启顺手把这个问题解决掉。6.4 测试过程中的三个隐藏坑这里分享三个我实测遇到的隐藏坑踩一次就很难忘。第一个坑是极光控制台的推送环境切换。如果你在控制台点的是“生产环境”推送而客户端production: false那么永远收不到。我建议在测试包上直接写死production: false上生产包之前通过构建配置注入true避免人工改错。第二个坑是 Android 模拟器收不到厂商通道推送。模拟器一般没有真实的厂商推送服务测试厂商通道效果必须用真机。第三个坑是重复初始化。JPush.setup不要放在每次进入页面时反复调用否则可能造成底层长连接多次注册导致消息重复展示。我见过有人把setup放在首页initState里然后又偷偷在main里调了一次最后同一台设备收到了两遍通知。6.5 一条可以直接抄的初始化组合代码把零散逻辑汇总一下给出我项目里最终使用的初始化代码片段。这段代码我直接放在main函数开头的WidgetsFlutterBinding.ensureInitialized()之后既保证极光尽早注册也保证本地通知引擎在首页渲染前准备好Futurevoid bootstrap() async { WidgetsFlutterBinding.ensureInitialized(); await JPush.setup( appKey: const String.fromEnvironment(JPUSH_APP_KEY, defaultValue: dev_app_key), channel: developer-default, production: bool.fromEnvironment(JPUSH_PRODUCTION, defaultValue: false), ); JPush.addEventHandler( onOpenNotification: (JPushMessage message) { final extras message.extras; final route extras?[route]; if (route ! null) { pushAfterFirstFrame(WidgetsBinding.instance, route, extras); } }, ); await initLocalNotifications(); }这段代码的要点是极光相关配置通过String.fromEnvironment注入。这样开发环境、生产环境不用改代码直接用dart-define传参构建即可。生产环境是flutter build apk --dart-defineJPUSH_APP_KEY生产Key --dart-defineJPUSH_PRODUCTIONtrue一句话搞定不需要动任何代码。这个方式比在配置文件里写死要干净得多而且不容易出现“测试包带生产配置”的失误。末尾忍不住多聊两句整个接入过程里我觉得最花时间的其实不是写代码而是理解“通知这条链路到底穿了哪些环节”。从极光服务器到厂商服务器从厂商服务器到系统通知服务最后从通知栏到用户手指点击每一环都有自己独立的超时、重试、权限机制。真正开发的时候不要只盯着 Flutter 侧要多看系统日志、多看极光后台的投递数据、多对比不同真机的表现差异。哪天你发现同一条推送在小米上秒到、在 OPPO 上延迟十几分钟别慌先把厂商通道的注册结果打出来看看八成是 push token 没注册成功。另外还想提醒一点推送和本地通知的测试数据是两套逻辑。远程推送必须在后台有一台真实的推送服务器或控制台可用本地通知则完全可以纯客户端调试。我当时给团队定的流程是先跑通本地通知的调度逻辑再联调远程推送的服务端这样两边的问题不会混在一起排查效率会高很多。这个顺序如果你觉得合适项目里可以直接照抄。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Superpowers实战:将AI编程助手从问答机器变成靠谱的结对程序员 2026/9/28 22:46:17

Superpowers实战:将AI编程助手从问答机器变成靠谱的结对程序员

如果你每天都在跟代码打交道,尤其是最近开始依赖 AI 编程助手来写需求、改 Bug、做重构,那你大概率遇到过这样的场景:AI 写得头头是道,结果一跑就报错;上下文一长,它就把你最开始说的需求忘得一干二净&…

阅读更多 →
Substrate深度解析:模块化区块链Runtime架构与工程实践 2026/9/28 22:46:17

Substrate深度解析:模块化区块链Runtime架构与工程实践

1. 这不是另一个区块链框架:Substrate 是什么,它到底在解决谁的痛点Substrate 不是“又一个区块链开发工具”,它是把区块链底层基础设施从“造轮子”变成“搭积木”的一次系统性重构。我第一次接触 Substrate 是在2020年,当时团队…

阅读更多 →
STM32音乐播放器实战:从PWM到DAC的WAV音频解码与输出 2026/9/28 22:46:10

STM32音乐播放器实战:从PWM到DAC的WAV音频解码与输出

1. 项目缘起与整体设计思路1.1 为什么选择STM32做音乐播放器手头攒了几块STM32F103C8T6的最小系统板,一直想找个能同时练手定时器、DMA、DAC和外设综合调度的项目。市面上现成的MP3模块虽然便宜好用,但串口一发指令就出声,中间的黑盒太多&…

阅读更多 →
人机协同工业质检落地:MCP协议与VLA模型工程化实践 2026/9/28 22:46:10

人机协同工业质检落地:MCP协议与VLA模型工程化实践

1. 为什么“人机协同”不是口号,而是工业现场算得过账的必然选择1.1 从“机器换人”到“人机搭班”的认知转弯前几年聊工业智能化,十个人里有八个第一反应是“机器换人”——把产线上的工人换掉,把质检员换掉,把巡检工换掉。这个叙…

阅读更多 →
工业AI人机协同:MCP协议与VLA模型落地实践 2026/9/28 22:46:10

工业AI人机协同:MCP协议与VLA模型落地实践

1. 为什么“人机协同”突然成了工业AI的焦点1.1 从“机器换人”到“人机搭班”的认知转变前几年聊工业AI,大家嘴里挂着的词是“无人化”“黑灯工厂”“机器换人”。逻辑很直白:把人的不确定性拿掉,用机器和算法接管一切,效率自然就…

阅读更多 →
Klipper上位机迁移实战:红米Note4x避坑指南 2026/9/28 22:46:03

Klipper上位机迁移实战:红米Note4x避坑指南

1. 从一台红米Note4x说起:Klipper上位机迁移到底难在哪很多玩3D打印的朋友都有过这样的经历:原本用得好好的Klipper上位机,换了一台设备之后,打印机突然就不听使唤了。要么是MCU连不上,要么是配置文件报错,…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉