feat(background): implement user consent mechanism for auto-start functionality
Some checks failed
Frontend CI / ota (android) (push) Has been cancelled
Frontend CI / ota (ios) (push) Has been cancelled
Frontend CI / build-and-push-web (push) Has been cancelled
Frontend CI / build-android-apk (push) Failing after 7m23s

Add auto-start consent system that requires explicit user permission before enabling background sync services. Users can now choose between silent mode (no auto-start) or background mode (15-minute sync intervals).

- Add consent service with AutoStartMode enum and storage utilities
- Create withRemoveAutoStart Expo plugin to disable Android auto-start defaults
- Integrate consent checks into JPush service, background task manager, and foreground service
- Add auto-start consent UI in notification settings with descriptive dialog
- Update privacy policy with section 8 explaining auto-start scenarios and user controls
- Change default sync mode from BATTERY_SAVER to DISABLED
- Export reinitBackgroundService function for re-initializing after consent changes
- Merge OTA Android and iOS workflows into matrix build
- Add release signing config in withSigning plugin for Android
- Expand .dockerignore with native credential and build artifact exclusions
This commit is contained in:
lafay
2026-06-15 20:43:15 +08:00
parent d8ef51fa13
commit f3d54a3f4c
16 changed files with 746 additions and 173 deletions

View File

@@ -2,6 +2,7 @@ import { useEffect, useRef } from 'react';
import { Platform } from 'react-native';
import { jpushService } from '../services/notification/jpushService';
import { isAutoStartAllowed } from '../services/consent/autoStartConsent';
export function useRegisterPushDevice(isAuthenticated: boolean, userID?: string) {
const deviceRegistered = useRef(false);
@@ -9,6 +10,12 @@ export function useRegisterPushDevice(isAuthenticated: boolean, userID?: string)
useEffect(() => {
if (Platform.OS === 'web' || !isAuthenticated || !userID) return;
// 仅在用户同意自启动后才初始化 JPush
if (!isAutoStartAllowed()) {
console.log('[useRegisterPushDevice] 用户未同意自启动,跳过 JPush 初始化');
return;
}
if (!deviceRegistered.current) {
deviceRegistered.current = true;

View File

@@ -29,6 +29,12 @@ import {
} from '@/services/notification';
import { useResponsive, useResponsiveSpacing } from '../../hooks';
import { backgroundSyncManager, BackgroundSyncMode } from '@/services/background';
import {
loadAutoStartConsent,
consentToAutoStart,
rejectAutoStart,
getAutoStartDescription,
} from '@/services/consent';
// 内容最大宽度
const CONTENT_MAX_WIDTH = 720;
@@ -49,8 +55,9 @@ export const NotificationSettingsScreen: React.FC = () => {
const [vibrationEnabled, setVibrationEnabledState] = useState(true);
const [pushEnabled, setPushEnabled] = useState(true);
const [soundEnabled, setSoundEnabled] = useState(true);
const [syncMode, setSyncMode] = useState<BackgroundSyncMode>(BackgroundSyncMode.BATTERY_SAVER);
const [syncMode, setSyncMode] = useState<BackgroundSyncMode>(BackgroundSyncMode.DISABLED);
const [systemPushEnabled, setSystemPushEnabled] = useState<boolean | null>(null);
const [autoStartConsented, setAutoStartConsented] = useState(false);
const { isWideScreen, isMobile } = useResponsive();
const insets = useSafeAreaInsets();
@@ -67,6 +74,10 @@ export const NotificationSettingsScreen: React.FC = () => {
setSoundEnabled(prefs.soundEnabled);
setSyncMode(backgroundSyncManager.getMode());
// 加载自启动同意状态
const consent = await loadAutoStartConsent();
setAutoStartConsented(consent.consented);
if (Platform.OS !== 'web') {
const enabled = await jpushService.checkNotificationPermission();
setSystemPushEnabled(enabled);
@@ -106,6 +117,41 @@ export const NotificationSettingsScreen: React.FC = () => {
};
const handleSyncModeChange = async (mode: BackgroundSyncMode) => {
// 如果要切换到需要自启动的模式,先请求用户同意
if (mode !== BackgroundSyncMode.DISABLED && !autoStartConsented) {
Alert.alert(
'后台消息接收',
getAutoStartDescription() + '\n\n开启后应用可在后台接收消息推送。',
[
{ text: '取消', style: 'cancel' },
{
text: '同意并开启',
onPress: async () => {
try {
await consentToAutoStart('接收实时消息推送');
setAutoStartConsented(true);
// 用户同意后,继续切换模式
await doSetSyncMode(mode);
} catch (error) {
console.error('同意自启动失败:', error);
}
},
},
]
);
return;
}
// 如果切换到禁用模式,撤销自启动同意
if (mode === BackgroundSyncMode.DISABLED && autoStartConsented) {
await rejectAutoStart();
setAutoStartConsented(false);
}
await doSetSyncMode(mode);
};
const doSetSyncMode = async (mode: BackgroundSyncMode) => {
if (mode === BackgroundSyncMode.REALTIME) {
Alert.alert(
'实时模式',
@@ -129,25 +175,56 @@ export const NotificationSettingsScreen: React.FC = () => {
}
};
const handleAutoStartModeChange = async (mode: AutoStartMode) => {
if (mode === AutoStartMode.BACKGROUND) {
Alert.alert(
'后台消息接收',
getAutoStartDescription() + '\n\n开启后应用可在后台接收消息推送。',
[
{ text: '取消', style: 'cancel' },
{
text: '同意并开启',
onPress: async () => {
await setAutoStartMode(AutoStartMode.BACKGROUND);
setAutoStartModeState(AutoStartMode.BACKGROUND);
setAutoStartConsented(true);
// 重新初始化后台服务以应用新设置
const { reinitBackgroundService } = await import('@/services/background');
await reinitBackgroundService();
Alert.alert('已开启', '后台消息接收已开启。您可随时在设置中关闭。');
},
},
]
);
} else {
await setAutoStartMode(AutoStartMode.SILENT);
setAutoStartModeState(AutoStartMode.SILENT);
setAutoStartConsented(false);
// 重新初始化后台服务以应用新设置
const { reinitBackgroundService } = await import('@/services/background');
await reinitBackgroundService();
}
};
const syncModeOptions: { mode: BackgroundSyncMode; title: string; subtitle: string; icon: string }[] = [
{
mode: BackgroundSyncMode.DISABLED,
title: '静默模式',
subtitle: '不自启动,仅在使用时接收消息',
icon: 'bell-off-outline',
},
{
mode: BackgroundSyncMode.BATTERY_SAVER,
title: '省电模式',
subtitle: '系统后台任务,每 15 分钟检查一次',
title: '后台模式',
subtitle: '允许自启动,每 15 分钟检查一次',
icon: 'leaf',
},
{
mode: BackgroundSyncMode.REALTIME,
title: '实时模式',
subtitle: '通知栏常驻保活,即时同步消息',
subtitle: '允许自启动,通知栏常驻保活',
icon: 'lightning-bolt',
},
{
mode: BackgroundSyncMode.DISABLED,
title: '禁用',
subtitle: '仅在应用打开时接收消息',
icon: 'close-circle-outline',
},
];
const handleRequestSystemPermission = async () => {
@@ -304,12 +381,30 @@ export const NotificationSettingsScreen: React.FC = () => {
))}
</View>
{/* 实时模式说明 */}
{/* 模式说明 */}
{syncMode === BackgroundSyncMode.DISABLED && (
<View style={styles.tipContainer}>
<MaterialCommunityIcons name="information-outline" size={16} color={colors.text.hint} />
<Text variant="caption" color={colors.text.hint} style={styles.tipText}>
</Text>
</View>
)}
{syncMode === BackgroundSyncMode.BATTERY_SAVER && (
<View style={styles.tipContainer}>
<MaterialCommunityIcons name="information-outline" size={16} color={colors.text.hint} />
<Text variant="caption" color={colors.text.hint} style={styles.tipText}>
15
</Text>
</View>
)}
{syncMode === BackgroundSyncMode.REALTIME && (
<View style={styles.tipContainer}>
<MaterialCommunityIcons name="shield-check-outline" size={16} color={colors.text.hint} />
<Text variant="caption" color={colors.text.hint} style={styles.tipText}>
</Text>
</View>
)}

View File

@@ -30,7 +30,7 @@ const THEME_COLORS = {
};
// 政策最后更新日期
const LAST_UPDATED = '2026年4月28日';
const LAST_UPDATED = '2026年6月15日';
// 隐私政策内容
const PRIVACY_SECTIONS = [
@@ -170,18 +170,68 @@ const PRIVACY_SECTIONS = [
• 使用目的:辅助消息推送通道管理
• 收集的个人信息设备推送令牌Push Token、设备标识符
3. WorkManagerAndroid系统组件
• 提供方Google LLC
• 使用目的:在用户同意自启动后,执行后台消息同步任务
• 收集的个人信息:无额外个人信息收集
• 隐私政策https://policies.google.com/privacy
4. expo-task-manager / expo-background-task
• 提供方Expo650 Industries, Inc.
• 使用目的:在用户同意自启动后,管理后台任务调度
• 收集的个人信息:无额外个人信息收集
5. expo-notifications
• 提供方Expo650 Industries, Inc.
• 使用目的:在用户同意自启动后,恢复通知服务
• 收集的个人信息:设备推送令牌
6. expo-callkit-telecom
• 提供方Expo650 Industries, Inc.
• 使用目的:通话功能相关服务
• 收集的个人信息:无额外个人信息收集
如您后续接入其他第三方SDK如微信登录、分享等功能我们将在本章节更新相关说明并告知您对应SDK收集的信息类型和用途。更新后的SDK目录将在应用内公布请以最新版本为准。`,
},
{
title: '八、隐私政策的更新',
content: `8.1 我们可能会不时更新本隐私政策。更新后的隐私政策将在应用内公布,并标注更新日期。
title: '八、应用自启动与关联启动说明',
content: `8.1 自启动/关联启动的目的与场景
为了及时向您推送消息通知,本应用可能需要在以下场景进行自启动或关联启动:
8.2 对于重大变更,我们会在变更生效前通过应用内通知、弹窗等方式告知您。
• 设备开机完成后:恢复后台消息推送服务,确保您能及时收到新消息提醒
• 应用更新后:恢复后台任务调度,保证消息同步功能正常运作
• 系统重启后:恢复通知服务,确保推送通道可用
• 关联启动场景:当系统或其他应用触发相关事件时,为保证推送服务连续性而进行关联启动
8.3 如您不同意变更后的内容,应立即停止使用本服务。如您继续使用本服务,即视为您已阅读并同意受变更后的隐私政策约束。`,
8.2 用户同意机制
• 自启动/关联启动功能仅在您明确同意后才启用
• 首次使用时,我们会在"通知设置"中向您说明自启动的目的、场景、规则及必要性,并征得您的同意
• 您可随时在"设置-通知设置-后台同步模式"中更改选择:
- 静默模式:不自启动,仅在使用应用时接收消息
- 后台模式同意自启动系统每15分钟检查一次新消息
- 实时模式:同意自启动,通知栏常驻保活,即时同步消息
8.3 关闭自启动的影响
• 选择静默模式后,应用不会自启动
• 您仅在打开应用时才能接收消息
• 此模式最省电,但可能错过实时消息
• 关闭自启动不会影响应用内的其他功能使用
8.4 我们承诺
• 自启动行为仅用于消息推送服务,不会用于收集额外个人信息
• 自启动行为不会用于广告推送或其他商业目的
• 我们仅在用户同意的范围内使用自启动功能`,
},
{
title: '九、联系我们',
title: '九、隐私政策的更新',
content: `9.1 我们可能会不时更新本隐私政策。更新后的隐私政策将在应用内公布,并标注更新日期。
9.2 对于重大变更,我们会在变更生效前通过应用内通知、弹窗等方式告知您。
9.3 如您不同意变更后的内容,应立即停止使用本服务。如您继续使用本服务,即视为您已阅读并同意受变更后的隐私政策约束。`,
},
{
title: '十、联系我们',
content: `如果您对本隐私政策有任何疑问、意见或建议,或者您希望行使您的权利,请通过以下方式与我们联系:
• 邮箱system@qczlit.cn

View File

@@ -12,25 +12,34 @@
import { AppState, AppStateStatus, Platform } from 'react-native';
import { ForegroundServiceModule } from './ForegroundServiceModule';
import AsyncStorage from '@react-native-async-storage/async-storage';
import {
loadAutoStartConsent,
saveAutoStartConsent,
AutoStartMode,
} from '../consent/autoStartConsent';
/**
* 后台同步模式
* 整合自启动同意机制:
* - DISABLED静默模式不自启动不注册后台任务
* - BATTERY_SAVER后台模式用户同意自启动后使用 WorkManager 后台任务15分钟间隔
* - REALTIME实时模式用户同意自启动后使用前台服务保活
*/
export enum BackgroundSyncMode {
/** 省电模式:仅依赖 JPush 推送 */
BATTERY_SAVER = 'battery_saver',
/** 实时模式:前台服务保活(仅用于通话) */
REALTIME = 'realtime',
/** 禁用后台同步 */
/** 静默模式:不自启动,仅在使用时接收消息 */
DISABLED = 'disabled',
/** 后台模式:用户同意自启动,使用 WorkManager 后台任务 */
BATTERY_SAVER = 'battery_saver',
/** 实时模式:用户同意自启动,前台服务保活 */
REALTIME = 'realtime',
}
const STORAGE_KEY = 'background_sync_mode';
class BackgroundSyncManager {
private mode: BackgroundSyncMode = BackgroundSyncMode.BATTERY_SAVER;
private mode: BackgroundSyncMode = BackgroundSyncMode.DISABLED;
private appStateSubscription: ReturnType<typeof AppState.addEventListener> | null = null;
private isInitialized: boolean = false;
private lastSyncAt: number = 0;
@@ -64,8 +73,18 @@ class BackgroundSyncManager {
/**
* 切换后台同步模式
* 需要用户同意自启动才能切换到 BATTERY_SAVER 或 REALTIME 模式
*/
async setMode(mode: BackgroundSyncMode): Promise<void> {
// 如果要切换到需要自启动的模式,先检查用户是否同意
if (mode !== BackgroundSyncMode.DISABLED) {
const consent = await loadAutoStartConsent();
if (!consent.consented || consent.mode !== AutoStartMode.BACKGROUND) {
console.log('[BackgroundSyncManager] 用户未同意自启动,无法切换到模式:', mode);
throw new Error('用户未同意自启动');
}
}
if (AppState.currentState !== 'active' && this.mode === BackgroundSyncMode.REALTIME) {
await ForegroundServiceModule.stop();
}
@@ -161,9 +180,9 @@ class BackgroundSyncManager {
private async loadMode(): Promise<BackgroundSyncMode> {
try {
const saved = await AsyncStorage.getItem(STORAGE_KEY);
return (saved as BackgroundSyncMode) || BackgroundSyncMode.BATTERY_SAVER;
return (saved as BackgroundSyncMode) || BackgroundSyncMode.DISABLED;
} catch (error) {
return BackgroundSyncMode.BATTERY_SAVER;
return BackgroundSyncMode.DISABLED;
}
}

View File

@@ -17,6 +17,11 @@ import {
} from './BackgroundSyncManager';
import { messageService } from '../message/messageService';
import { api } from '../core/api';
import {
loadAutoStartConsent,
isAutoStartAllowed,
AutoStartMode,
} from '../consent/autoStartConsent';
// 后台任务名称
const BACKGROUND_SYNC_TASK = 'background-sync-task';
@@ -36,6 +41,13 @@ TaskManager.defineTask(BACKGROUND_SYNC_TASK, async () => {
return BackgroundTask.BackgroundTaskResult.Success;
}
// 检查用户是否同意自启动
const consent = await loadAutoStartConsent();
if (!consent.consented || consent.mode !== AutoStartMode.BACKGROUND) {
console.log('[BackgroundService] 用户未同意自启动,跳过后台同步');
return BackgroundTask.BackgroundTaskResult.Success;
}
// 执行同步
await syncMessages();
@@ -82,6 +94,7 @@ async function syncMessages(): Promise<void> {
/**
* 注册后台任务expo-background-task
* 仅在用户同意自启动且模式为 BATTERY_SAVER 时注册
*/
async function registerBackgroundTask(): Promise<void> {
if (Platform.OS === 'web') {
@@ -89,6 +102,13 @@ async function registerBackgroundTask(): Promise<void> {
}
try {
// 检查用户是否同意自启动
const consent = await loadAutoStartConsent();
if (!consent.consented || consent.mode !== AutoStartMode.BACKGROUND) {
console.log('[BackgroundService] 用户未同意自启动,跳过注册后台任务');
return;
}
// 检查后台任务状态
const status = await BackgroundTask.getStatusAsync();
if (status !== BackgroundTask.BackgroundTaskStatus.Available) {
@@ -129,7 +149,7 @@ async function unregisterBackgroundTask(): Promise<void> {
}
/**
* 初始化后台保活服务
* 根据用户同意的自启动模式初始化后台服务
*/
export async function initBackgroundService(): Promise<boolean> {
if (isInitialized) {
@@ -142,14 +162,21 @@ export async function initBackgroundService(): Promise<boolean> {
}
try {
// 加载用户同意状态
await loadAutoStartConsent();
// 设置同步回调
backgroundSyncManager.setSyncMessagesCallback(syncMessages);
// 初始化后台同步管理器
await backgroundSyncManager.initialize();
// 注册 expo-background-task 任务(用于 BATTERY_SAVER 模式)
await registerBackgroundTask();
// 仅在用户同意自启动时注册后台任务
if (isAutoStartAllowed()) {
await registerBackgroundTask();
} else {
console.log('[BackgroundService] 用户选择静默模式,不注册后台自启动任务');
}
isInitialized = true;
console.log('[BackgroundService] 初始化完成');
@@ -160,6 +187,39 @@ export async function initBackgroundService(): Promise<boolean> {
}
}
/**
* 重新初始化后台服务(在用户更改自启动同意后调用)
*/
export async function reinitBackgroundService(): Promise<boolean> {
if (!isInitialized) {
return initBackgroundService();
}
try {
const consent = await loadAutoStartConsent();
if (consent.consented && consent.mode === AutoStartMode.BACKGROUND) {
// 用户同意自启动,注册后台任务
await registerBackgroundTask();
// 如果模式是实时模式,启动前台服务
if (backgroundSyncManager.getMode() === BackgroundSyncMode.REALTIME) {
await backgroundSyncManager.setMode(BackgroundSyncMode.REALTIME);
}
} else {
// 用户拒绝自启动,取消后台任务
await unregisterBackgroundTask();
// 停止前台服务
await backgroundSyncManager.setMode(BackgroundSyncMode.DISABLED);
}
console.log('[BackgroundService] 根据用户同意状态重新初始化完成');
return true;
} catch (error) {
console.error('[BackgroundService] 重新初始化失败:', error);
return false;
}
}
/**
* 停止后台保活服务
*/
@@ -205,6 +265,7 @@ export async function checkBackgroundStatus(): Promise<{
/**
* 设置后台同步模式
* 同时更新自启动同意状态
*/
export async function setBackgroundSyncMode(mode: BackgroundSyncMode): Promise<void> {
await backgroundSyncManager.setMode(mode);
@@ -239,6 +300,7 @@ export { BackgroundSyncMode };
// 后台服务实例
export const backgroundService = {
init: initBackgroundService,
reinit: reinitBackgroundService,
stop: stopBackgroundService,
setMode: setBackgroundSyncMode,
getMode: getBackgroundSyncMode,

View File

@@ -1,6 +1,7 @@
export {
backgroundService,
initBackgroundService,
reinitBackgroundService,
stopBackgroundService,
triggerVibration,
vibrateOnMessage,

View File

@@ -0,0 +1,201 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import { Platform } from 'react-native';
/**
* 自启动权限同意管理
*
* 管理用户对应用自启动/关联启动行为的同意状态
* 符合隐私合规要求:未经用户同意不得进行自启动
*/
const STORAGE_KEY = 'auto_start_consent';
const STORAGE_KEY_FIRST_LAUNCH = 'auto_start_first_launch';
export enum AutoStartMode {
/** 静默模式:不自启动,仅在使用时接收消息 */
SILENT = 'silent',
/** 后台模式:允许自启动以接收实时推送 */
BACKGROUND = 'background',
}
export interface AutoStartConsent {
/** 用户是否同意自启动 */
consented: boolean;
/** 当前模式 */
mode: AutoStartMode;
/** 同意时间 */
consentedAt?: string;
/** 用户同意的目的描述 */
purpose?: string;
}
const DEFAULT_CONSENT: AutoStartConsent = {
consented: false,
mode: AutoStartMode.SILENT,
};
let cachedConsent: AutoStartConsent = { ...DEFAULT_CONSENT };
/**
* 加载自启动同意状态
*/
export async function loadAutoStartConsent(): Promise<AutoStartConsent> {
if (Platform.OS === 'web') {
return { ...DEFAULT_CONSENT };
}
try {
const raw = await AsyncStorage.getItem(STORAGE_KEY);
if (raw) {
cachedConsent = JSON.parse(raw) as AutoStartConsent;
return { ...cachedConsent };
}
} catch (error) {
console.error('[AutoStartConsent] 加载同意状态失败:', error);
}
return { ...DEFAULT_CONSENT };
}
/**
* 保存自启动同意状态
*/
export async function saveAutoStartConsent(consent: AutoStartConsent): Promise<void> {
if (Platform.OS === 'web') {
return;
}
try {
cachedConsent = { ...consent };
await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(consent));
} catch (error) {
console.error('[AutoStartConsent] 保存同意状态失败:', error);
}
}
/**
* 获取同步的自启动同意状态
*/
export function getAutoStartConsentSync(): AutoStartConsent {
return { ...cachedConsent };
}
/**
* 检查是否是首次启动(用于显示首次同意弹窗)
*/
export async function isFirstLaunch(): Promise<boolean> {
if (Platform.OS === 'web') {
return false;
}
try {
const raw = await AsyncStorage.getItem(STORAGE_KEY_FIRST_LAUNCH);
if (raw === null) {
await AsyncStorage.setItem(STORAGE_KEY_FIRST_LAUNCH, 'false');
return true;
}
return false;
} catch {
return false;
}
}
/**
* 用户同意自启动(后台模式)
* @param purpose 用户同意的目的描述
*/
export async function consentToAutoStart(purpose: string = '接收实时消息推送'): Promise<void> {
await saveAutoStartConsent({
consented: true,
mode: AutoStartMode.BACKGROUND,
consentedAt: new Date().toISOString(),
purpose,
});
}
/**
* 用户拒绝自启动(静默模式)
*/
export async function rejectAutoStart(): Promise<void> {
await saveAutoStartConsent({
consented: false,
mode: AutoStartMode.SILENT,
consentedAt: new Date().toISOString(),
purpose: '用户拒绝自启动',
});
}
/**
* 切换自启动模式
*/
export async function setAutoStartMode(mode: AutoStartMode): Promise<void> {
const current = getAutoStartConsentSync();
if (mode === AutoStartMode.BACKGROUND) {
await saveAutoStartConsent({
consented: true,
mode: AutoStartMode.BACKGROUND,
consentedAt: new Date().toISOString(),
purpose: current.purpose || '接收实时消息推送',
});
} else {
await saveAutoStartConsent({
consented: false,
mode: AutoStartMode.SILENT,
consentedAt: new Date().toISOString(),
purpose: '用户选择静默模式',
});
}
}
/**
* 检查是否允许自启动
*/
export function isAutoStartAllowed(): boolean {
return cachedConsent.consented && cachedConsent.mode === AutoStartMode.BACKGROUND;
}
/**
* 获取当前模式
*/
export function getCurrentAutoStartMode(): AutoStartMode {
return cachedConsent.mode;
}
/**
* 重置同意状态(用于测试或用户撤销同意)
*/
export async function resetAutoStartConsent(): Promise<void> {
cachedConsent = { ...DEFAULT_CONSENT };
await AsyncStorage.removeItem(STORAGE_KEY);
await AsyncStorage.removeItem(STORAGE_KEY_FIRST_LAUNCH);
}
/**
* 获取自启动说明文本(用于隐私政策和弹窗)
*/
export function getAutoStartDescription(): string {
return `为了及时接收消息推送,应用需要在以下场景自启动:
1. 设备开机完成后:恢复后台消息推送服务
2. 应用更新后:恢复后台任务调度
3. 系统重启后:恢复通知服务
自启动行为仅用于消息推送,不会收集额外个人信息。
您可以在"设置-通知设置"中随时更改此选项。`;
}
/**
* 获取自启动目的说明(用于隐私政策)
*/
export function getAutoStartPurposeForPrivacy(): string {
return `应用自启动/关联启动的目的与规则:
• 目的:确保用户能够及时接收消息推送通知
• 场景:设备开机、应用更新、系统重启后
• 规则:仅在用户同意后才启用自启动功能
• 必要性:对于需要实时消息通知的用户是必要的
• 用户控制:用户可随时在设置中关闭此功能
• 关闭影响:关闭后需手动打开应用才能接收消息`;
}

View File

@@ -0,0 +1,16 @@
export {
AutoStartMode,
loadAutoStartConsent,
saveAutoStartConsent,
getAutoStartConsentSync,
isFirstLaunch,
consentToAutoStart,
rejectAutoStart,
setAutoStartMode,
isAutoStartAllowed,
getCurrentAutoStartMode,
resetAutoStartConsent,
getAutoStartDescription,
getAutoStartPurposeForPrivacy,
} from './autoStartConsent';
export type { AutoStartConsent } from './autoStartConsent';

View File

@@ -112,8 +112,10 @@ class JPushService {
// Register listeners only once (idempotent guard)
this._registerListeners();
// 退后台保持极光长连接,确保推送实时到达
this._setBackgroundKeepLongConn(true);
// 检查用户是否同意自启动,仅在同意后才保持后台长连接
const { isAutoStartAllowed } = await import('../consent/autoStartConsent');
const keepLongConn = isAutoStartAllowed();
this._setBackgroundKeepLongConn(keepLongConn);
// 初始化 SDK
JPush!.init({