PDF Reader: Viewer, PDF Editor(Stackwise)v1.2.1 保活、拉活与通知逆向报告
PDF Reader: Viewer, PDF Editor(Stackwise)v1.2.1 保活、拉活与通知逆向报告返回竞品库
报告口径本页直接渲染本地报告真源;静态能力、动态观察、推断和线上未验证项不得互相替代。

PDF Reader: Viewer, PDF Editor(Stackwise)v1.2.1 保活、拉活与通知逆向报告#

竞品包名:com.pdfreader.pdf.viewer.pdfeditor
应用标签:PDF 阅读器:查看器,PDF 编辑器
样本版本:1.2.1(versionCode 22
静态分析与真机验证日期:2026-08-20

1. 结论先行#

  1. Stackwise 不是前台服务常驻型保活。其完整业务链路是:Firebase Remote Config 下发并启用 notification_configs 后,BaseActivity.onPause() 取消旧任务、以 WorkManager 排一条延迟 ReminderWorker;任务到点后发本地“不活跃提醒”,然后再排下一轮。它用于离开 App 后召回,不是维持进程常驻。
  2. 该链路有明确远端开关、时长和文案模型:is_enabletime_since_last_open_in_hourcontents[language_code].notifications[title, description]。APK 的安全默认值为 关闭 / 空文案 / 24 小时;因此没有从 APK 提取到可直接列举的营销标题或正文。
  3. ReminderScheduler 会先取消唯一工作名 inactivity_reminder_work,再以 ExistingWorkPolicy.REPLACE 重排 ReminderWorker。用户再次打开并退出主界面时,旧倒计时被替换,而非累计多条提醒。
  4. ReminderWorker 按保存语言选远端文案,匹配不到时回退 English;以 NotificationOpenTime % 列表长度 轮换条目,创建高重要级 inactivity_channel,在通知权限已授予时发布,并再次调度下一轮。这个变量在发布成功后递增,不能误写成真实“通知点击次数”。
  5. 通知点击只使用 getLaunchIntentForPackage() 回到 launcher。MainActivity 只有收到带 PDF URI 的 ACTION_VIEW 才进入文件查看器;提醒 PendingIntent 不带 URI 或业务 route,所以这条链路只能证明“回 App”,不能证明会直达文件、编辑器或会员页。
  6. 真机本次启动后退到后台,仅观察到两个 DataTransport 网络 Job,未出现 ReminderWorkerinactivity_reminder_work 的 WorkManager Job;且通知权限为拒绝。它说明本机这次没有生效的本地不活跃提醒,不能反推线上远端开关、人群或频控。
  7. 真机划掉最近任务后,任务条目、该包进程和该包 Service 均消失,包仍是 stopped=false;Force Stop 后包为 stopped=true,30 秒内没有该包 PID 或 Service 输出。未见划卡自恢复、前台服务保活或绕过 Android Force Stop 的证据。
  8. APK 含 Firebase Messaging 基础组件与 FCM fallback channel,但 Manifest 中没有 Stackwise 自己的 FirebaseMessagingService 子类。不能把“含 FCM”写成“已经验证 FCM 可拉活、可展示营销文案,或会启动上述提醒 Worker”。

2. 样本与证据口径#

项目
基础 APK PDF/pdf-reader-viewer-editor-v1.2.1/apk/base.apk
ABI / density split split_config.arm64_v8a.apk / split_config.xxhdpi.apk
base APK 大小 47,995,977 bytes
base SHA-256 5311bf856e3faf88706a8a479076218f76ec9da08df6107e6439d0ce9e4e03ea
arm64 split SHA-256 46194c2557c5e88a8d41889b5452ad71209136f361bf0613a2755a198fb0e878
xxhdpi split SHA-256 4ed05e97872bc102e83ce8a32fbfe6df151cab87f98f0606d1f7af10a1d81780
minSdk / targetSdk 32 / 37
Application / 主 Activity com.tuansaw.pdf.SawApp / com.tuansaw.pdf.MainActivity
动态环境 已授权 Android 16 / API 36 测试机;设备标识未写入本报告

本报告的证据层级:

  • 静态事实:Manifest、DEX 方法、序列化字段或 APK 资源中直接可见的内容。
  • 动态观察:本次测试机操作与 dumpsys、最近任务、进程及服务状态的结果。
  • 合理推断:基于已还原调用链的技术/产品解释,不能代替服务端事实。
  • 未验证:线上 Remote Config 值、推送下发、人群、频控、到达率和长时间系统行为。

3. 已还原的提醒与拉活调用链#

Firebase Remote Config
  └─ notification_configs[可选版本后缀]
       └─ 解析并缓存为 RemoteData.notification_configs
            ├─ is_enable(APK 默认 false)
            ├─ time_since_last_open_in_hour(APK 默认 24)
            └─ contents[language_code].notifications[title, description]
                         │
                         ▼
BaseActivity.onPause()(MainActivity 的父类)
  └─ is_enable=true 才调用 ReminderScheduler
       └─ 取消同名旧 Work → REPLACE 排 ReminderWorker(延迟 N 小时)
                         │
                         ▼
ReminderWorker
  ├─ 当前语言无内容时回退 English
  ├─ 以 NotificationOpenTime % 列表长度轮换文案
  ├─ 创建高重要级 inactivity_channel
  ├─ POST_NOTIFICATIONS 已授予才发布本地通知
  └─ 再次调用 ReminderScheduler,排下一轮
                         │
                         ▼
用户点击通知 → getLaunchIntentForPackage() → App launcher

3.1 方法级代码锚点#

代码锚点 可直接看到的动作 链路角色
com.tech.core.BaseActivity.onPause() 读取 RemoteManager.e.q.a(通知总开关);为真调用 ReminderScheduler.a(Context) 用户离开 Activity 时的调度入口
com.tech.core.notification.ReminderScheduler.a(Context) 取消 inactivity_reminder_work;构造 ReminderWorker 的 OneTimeWorkRequest;按小时设置 initial delay;同名唯一 Work 使用 REPLACE 重置和排入延迟提醒
com.tech.core.notification.ReminderWorker.a() 选语言、回退 English、取模轮换、建 channel、检查权限、发布并重新调度 到点后的展示与续排
com.tech.core.notification.NotificationHelper.a(Context, Intent, Notification) title / description 构建 NotificationCompat,写入 launcher PendingIntent,auto-cancel 通知内容和点击回流
com.tech.core.remote.RemoteManager$updateRemoteData$1 从 Firebase Remote Config 取 notification_configs(可拼运行时版本后缀),按 NotificationConfigs serializer 解析,写回 RemoteData 缓存 远端字段来源闭环
com.tuansaw.pdf.MainActivity.w(Intent) 仅对带 URI 的 ACTION_VIEW 取文件并导航 SawRoute.Viewer 证明提醒 launcher intent 不会自行进入 PDF 查看页

ExistingWorkPolicy.a 在当前依赖中可还原为 REPLACE。因此“重新离开 App”会替换旧的唯一任务,而不是向系统堆积多个 24 小时 Worker。

4. Remote Config、频率与通知文案#

4.1 配置模型与默认安全值#

RemoteData.NotificationConfigs 的无参初始化方法直接给出默认值:

字段 APK 默认值 用途 证据等级
is_enable false onPause() 的总开关 静态事实
contents 空列表 按语言存放通知文案组 静态事实
time_since_last_open_in_hour 24 Worker 首次/下一轮的延迟小时数 静态事实
contents[].language_code 无本地默认项 与 App 保存语言匹配 静态事实
contents[].notifications 空列表 该语言可用的提醒集合 静态事实
notifications[].title 无本地默认项 通知标题 静态事实
notifications[].description 无本地默认项 通知正文 静态事实

RemoteManager$updateRemoteData$1 会从 Firebase Remote Config 读取 notification_configs 加可选版本后缀的值,使用 NotificationConfigs serializer 解析;取不到值、值为空或解析失败时,会回退到上表所示本地默认对象,并把最终 RemoteData 写入偏好缓存。

这证明了配置来源和客户端回退行为;没有证明当前线上是启用、延迟为多少、发给哪些国家/版本,或某个用户实际命中了哪组内容。

4.2 有通知能力,但样本没有默认营销文案#

业务通知使用远端的 titledescription,不是打包在资源里的固定模板。本样本 contents 默认为空,因此无法像含本地模板的竞品一样列出“真实会出现的标题/正文”。

能直接列举的固定文字仅是系统 channel 元数据:

项目 边界
Channel ID inactivity_channel 本地不活跃提醒渠道
Channel 名称 Inactivity reminders 固定 English 渠道名
Channel 描述 Reminds you to come back to the app 固定 English 渠道描述
importance 4(HIGH) 请求高重要级;用户或系统可降级

这不是用户可见的营销正文。真机还存在 fcm_fallback_notification_channel(importance 3),只能证明 Firebase SDK 初始化了 fallback channel,不能据此推断某条 FCM 文案或发信策略。

4.3 语言、轮换与权限边界#

Worker 的具体顺序如下:

  1. 读取保存语言;为空时使用 English。
  2. contents 中找同语言项;找不到时再找 English;仍没有则使用空列表。
  3. NotificationOpenTime % notifications.size 选择一条文案;列表为空时不发布。
  4. 创建 channel,并用 package 的 launcher intent 构造 PendingIntent。
  5. POST_NOTIFICATIONS 已授予时才交给 NotificationManagerCompat 发布;成功发布后 NotificationOpenTime + 1
  6. 无论本轮是否有文案或权限,方法结尾均会重新调用 ReminderScheduler

变量名 NotificationOpenTime 易误导:本代码中递增点在发布成功之后,不在通知点击回调中。它的已证实作用是条目轮换,不是已验证的点击归因。

5. WorkManager 调度与点击拉活边界#

5.1 调度语义#

ReminderScheduler 的顺序是“取消同名 Work → 创建 OneTimeWorkRequest → 取远端小时值(空值回退 24)→ REPLACE 入队”。Worker 本身继承 CoroutineWorker,这条业务方法中没有 setForeground()ForegroundInfo 调用,也没有业务级网络、充电、空闲或精确闹钟约束。

因此它是一个请求在 N 小时后运行的本地延迟任务。实际执行时间仍受 Doze、后台配额、厂商限制和 Android 任务调度影响;它不等于“第 N 小时必达”,更不等于保活。

5.2 点击路径#

ReminderWorker 调用 PackageManager.getLaunchIntentForPackage(),随后 NotificationHelperPendingIntent.getActivity() 写入通知;没有额外的 Screen、文件 URI、route 或业务 ID。

MainActivity.w(Intent)ACTION_VIEW 的唯一已还原分支会读取 PDF URI 并进入 SawRoute.Viewer。提醒使用的是 launcher intent 而非该 ACTION_VIEW intent,所以通知点击只会把 App 带回默认启动流。冷启动、已有任务和启动流配置仍会影响最终画面,本次没有把它外推为固定页拉活。

6. 保活、启动入口与 FCM 的边界#

维度 已证实内容 不能写成的结论
前台服务 Manifest 有 FOREGROUND_SERVICE 权限及 WorkManager 通用 SystemForegroundService;ReminderWorker 不调用前台执行 API 不能称 Stackwise 正在用 FGS 常驻或保活
进程常驻 真机划掉任务后 PID、Service 均无该包条目 不能称存在划卡自恢复或常驻进程
本地提醒 已还原 onPause → OneTimeWork → ReminderWorker → 本地通知 → 续排 代码路径 不能称本机本次已收到该提醒,或线上已经全量开启
开机恢复 未声明 RECEIVE_BOOT_COMPLETED,未发现产品自定义 Boot receiver;Manifest 中仅有 WorkManager 的通用且 disabled 的 RescheduleReceiver 未做重启实验,不能绝对断言所有 WorkManager 跨重启语义
AlarmManager 这条不活跃提醒使用 WorkManager 不可据此断言整个 APK 及第三方 SDK 从不使用 AlarmManager
FCM 接收 FirebaseInstanceIdReceiver 和库内 FirebaseMessagingService 已合并进 Manifest;DEX 中另有 AppsFlyer 的 listener 类,但未作为 Stackwise 自定义服务在 Manifest 注册 不能称 FCM 到达会启动 ReminderWorker、发营销通知或拉起前台服务
FCM 点击 fcm_fallback_notification_channel 存在 不能由 fallback channel 推出 payload、通知正文、深链或送达率
Force Stop 本次强停后 stopped=true,30 秒未观察到该包 PID 或 Service 未发送真实 FCM,不用本次结果替代 Force Stop 下 FCM 的平台语义验证

特别说明:启动后曾观察到库内 Firebase Messaging Service 和 WebView sandbox service;它们的 startForegroundCount=0。这只能说明当时有 SDK/渲染服务绑定,不是本报告已证实的保活前台服务。

7. 真机验证结果#

场景 动态结果 可以得出的结论 不能外推的结论
通知权限与已有渠道 POST_NOTIFICATIONS: granted=false;存在 FCM fallback channel 本机当前不会由该 App 正常展示提醒;FCM SDK 已初始化 fallback channel 不代表线上关闭提醒或不存在真实文案
启动 → 退到后台 只看到两个该包的 DataTransport 网络 Job;未见 ReminderWorkerinactivity_reminder_work 或该包 WorkManager system job 本机本次退后台没有排入可见的不活跃提醒任务 不能确定是远端开关关闭、拉取未完成、远端内容为空或其他运行时条件未满足
启动后服务快照 Firebase Messaging / WebView service 的 startForegroundCount=0 当时没有前台服务运行证据 不能代表每个时刻或所有设备均无其他服务
真实划掉最近任务 启动后该包在 Recent tasks;划除后 Recent tasks 不再包含该包,psdumpsys activity services 无该包输出;包为 stopped=false 任务划除结束了当时应用运行态,未观察到自恢复 不排除远端 FCM 等外部事件日后短时启动进程
Force Stop stopped=true;30 秒观察期内无该包 PID 或 Service 输出 当前进程/服务没有回生证据 没有做小时级观察、重启或真实 FCM 到达实验

测试只启动、退后台、划卡和强停该竞品;未改通知权限、未清除数据、未读取用户文件、未修改任何竞品线上 Firebase/FCM 配置。

8. 与“保活/拉活”容易混淆的事项#

现象或组件 实际含义 不应混淆为
ReminderWorker 延迟运行并展示本地提醒的 WorkManager Worker 持续前台服务或进程保活
Worker 的下一轮续排 到点后继续请求下一次延迟执行 Android 保证的精确定时/永久任务
FCM 基础组件 / fallback channel 消息 SDK 与自动通知能力的基础设施 已验证的自定义推送文案、服务端频控或拉活链路
DataTransport 网络 Job Firebase/Google 传输与遥测调度 用户召回 Worker 或常驻保活
launcher PendingIntent 用户点击后打开应用默认启动流 带文件/功能上下文的深链拉活
FOREGROUND_SERVICE 权限 声明了可使用的系统能力 当前有正在运行的前台服务

9. 未验证项与继续取证条件#

  1. 当前线上 notification_configs 的开关、延迟、语言文案、国家/版本分流和频控。需要受控读取 Remote Config 或经授权的服务端证据,不能从 APK 默认值推断。
  2. 真实提醒的标题、正文、展示时间、点击后首屏。需要在不篡改竞品配置的前提下,由已启用的受控测试用户等待/触发,并在通知权限允许时记录。
  3. Doze、后台受限、重启、不同 OEM 设备上的实际 WorkManager 执行时机。本次没有跨小时等待、重启或机型矩阵测试。
  4. FCM 的 data/notification payload、是否 SDK 自动展示、服务端深链参数,以及 Force Stop 后到达行为。当前没有授权向竞品项目发送真实 FCM。
  5. shiny_notification_channel 的创建方和用途。它在设备渠道列表中存在,但在本次已还原的提醒方法和基础 DEX 字符串中没有形成可归因的业务调用链,不能纳入提醒结论。
竞品拆包报告库 · 逆向结论按证据等级阅读