HarmonyOS开发:留存分析——用户留存
HarmonyOS开发:留存分析——用户留存
📌 核心要点:留存率是衡量产品健康度的"体温计"——次日留存看产品第一印象,7日留存看功能价值,30日留存看长期粘性,留存曲线告诉你用户是在留下来还是在悄悄跑路。
背景与动机
你花了10万推广费,拉来10000个新用户。一周后还剩多少?2000?1000?还是500?
如果7日留存只有5%,意味着你花10万拉来的用户,一周后只剩500个。这500个用户能给你带来多少收入?够覆盖推广成本吗?
留存率低,说明你的产品留不住人。用户来了又走,你永远在"拉新→流失→再拉新"的死循环里打转。获客成本越来越高,利润越来越薄,最后就是慢性死亡。
留存分析要解决的核心问题:
- 用户留没留:次日/7日/30日留存率是多少,趋势是上升还是下降
- 谁留谁走:不同用户群体的留存差异,新用户vs老用户、付费vs免费
- 为什么走:流失用户有什么共同特征,他们在流失前做了什么
- 怎么留住:针对流失原因制定留存策略,提升用户粘性
鸿蒙应用的留存分析有其特殊性——用户可能在手机、平板、手表等多个设备上使用同一应用,留存率的计算需要考虑跨设备场景。
核心原理
留存分析的核心:定义留存 → 采集数据 → 计算留存率 → 分析留存曲线 → 制定留存策略。
flowchart TB
subgraph 定义留存
A[确定留存定义<br/>登录/活跃/核心行为] --> B[选择留存周期<br/>次日/7日/30日]
B --> C[划分用户群组<br/>按注册日期分组]
end
subgraph 数据采集
C --> D[记录用户注册日]
D --> E[记录每日活跃]
E --> F[记录核心行为]
end
subgraph 计算分析
F --> G[计算留存率]
G --> H[绘制留存曲线]
H --> I{曲线类型判断}
end
subgraph 策略输出
I -->|平滑下降| J[正常衰减<br/>优化核心体验]
I -->|骤降后平稳| K[找到Aha时刻<br/>引导用户到达]
I -->|持续下降| L[产品价值不足<br/>重新审视功能]
J --> M[留存优化策略]
K --> M
L --> M
end
classDef define fill:#6C5CE7,stroke:#5B4BC9,color:#fff
classDef collect fill:#00B894,stroke:#00A383,color:#fff
classDef analyze fill:#FDCB6E,stroke:#F0B429,color:#333
classDef decision fill:#FF7675,stroke:#D63031,color:#fff
classDef strategy fill:#74B9FF,stroke:#0984E3,color:#fff
class A,B,C define
class D,E,F collect
class G,H,I analyze
class J,K,L decision
class M strategy
留存率计算方法:
| 留存类型 | 计算公式 | 说明 |
|---|---|---|
| 次日留存 | 第2天回访用户数 / 新增用户数 × 100% | 衡量产品第一印象 |
| 7日留存 | 第7天回访用户数 / 新增用户数 × 100% | 衡量功能价值 |
| 30日留存 | 第30天回访用户数 / 新增用户数 × 100% | 衡量长期粘性 |
| N日留存 | 第N天回访用户数 / 新增用户数 × 100% | 通用公式 |
| 滚动留存 | 连续N天活跃用户数 / 新增用户数 × 100% | 更严格的留存定义 |
代码实战
基础用法:留存数据采集
先采集留存计算所需的基础数据——用户注册时间和每日活跃记录。
// RetentionCollector.ets - 留存数据采集
import { analytics } from '@kit.AnalyticsKit';
import { preferences } from '@kit.ArkData';
import { common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
// 用户留存记录
interface UserRetentionRecord {
userId: string;
registerDate: string; // 注册日期 YYYY-MM-DD
lastActiveDate: string; // 最后活跃日期
activeDates: string[]; // 所有活跃日期
isPaidUser: boolean; // 是否付费用户
registerChannel: string; // 注册渠道
}
export class RetentionCollector {
private static instance: RetentionCollector;
private pref: preferences.Preferences | null = null;
private currentUserId: string = '';
static getInstance(): RetentionCollector {
if (!RetentionCollector.instance) {
RetentionCollector.instance = new RetentionCollector();
}
return RetentionCollector.instance;
}
async init(context: common.UIAbilityContext): Promise<void> {
this.pref = await preferences.getPreferences(context, 'retention_data');
}
// 记录用户注册
async onUserRegister(userId: string, channel: string): Promise<void> {
if (!this.pref) return;
this.currentUserId = userId;
const today = this.getToday();
const record: UserRetentionRecord = {
userId: userId,
registerDate: today,
lastActiveDate: today,
activeDates: [today],
isPaidUser: false,
registerChannel: channel,
};
// 保存用户留存记录
await this.pref.put(`user_${userId}`, JSON.stringify(record));
// 更新注册日期索引
const dateUsersStr = await this.pref.get(`date_${today}`, '[]') as string;
const dateUsers: string[] = JSON.parse(dateUsersStr);
dateUsers.push(userId);
await this.pref.put(`date_${today}`, JSON.stringify(dateUsers));
await this.pref.flush();
// 上报注册事件
analytics.reportEvent('user_register', {
'user_id': userId,
'register_date': today,
'register_channel': channel,
});
console.info(`[Retention] 用户注册: ${userId}, 渠道: ${channel}`);
}
// 记录用户活跃——每次启动调用
async onUserActive(userId: string): Promise<void> {
if (!this.pref) return;
this.currentUserId = userId;
const today = this.getToday();
// 获取用户记录
const recordStr = await this.pref.get(`user_${userId}`, '') as string;
if (!recordStr) {
// 老用户没有记录,创建一条
await this.onUserRegister(userId, 'unknown');
return;
}
const record: UserRetentionRecord = JSON.parse(recordStr);
// 去重添加今日活跃
if (!record.activeDates.includes(today)) {
record.activeDates.push(today);
}
record.lastActiveDate = today;
// 保存
await this.pref.put(`user_${userId}`, JSON.stringify(record));
// 更新活跃日期索引
const dateUsersStr = await this.pref.get(`date_${today}`, '[]') as string;
const dateUsers: string[] = JSON.parse(dateUsersStr);
if (!dateUsers.includes(userId)) {
dateUsers.push(userId);
await this.pref.put(`date_${today}`, JSON.stringify(dateUsers));
}
await this.pref.flush();
// 上报活跃事件
analytics.reportEvent('user_active', {
'user_id': userId,
'active_date': today,
'days_since_register': this.daysBetween(record.registerDate, today),
});
}
// 更新付费状态
async updatePaidStatus(userId: string, isPaid: boolean): Promise<void> {
if (!this.pref) return;
const recordStr = await this.pref.get(`user_${userId}`, '') as string;
if (!recordStr) return;
const record: UserRetentionRecord = JSON.parse(recordStr);
record.isPaidUser = isPaid;
await this.pref.put(`user_${userId}`, JSON.stringify(record));
await this.pref.flush();
}
// ========== 工具方法 ==========
private getToday(): string {
return new Date().toISOString().split('T')[0];
}
private daysBetween(date1: string, date2: string): number {
const d1 = new Date(date1);
const d2 = new Date(date2);
return Math.floor((d2.getTime() - d1.getTime()) / (1000 * 60 * 60 * 24));
}
}
进阶用法:留存率计算
有了数据,就可以计算留存率了。
// RetentionCalculator.ets - 留存率计算
import { preferences } from '@kit.ArkData';
import { common } from '@kit.AbilityKit';
import { UserRetentionRecord } from './RetentionCollector';
// 留存率结果
export interface RetentionResult {
cohortDate: string; // 群组日期
newUsers: number; // 新增用户数
retentionData: Map<number, { // key=第N天
retainedUsers: number;
retentionRate: number;
}>;
}
// 留存曲线类型
export enum RetentionCurveType {
SMOOTH_DECAY = 'smooth_decay', // 平滑衰减——正常
SHARP_DROP_FLAT = 'sharp_drop_flat', // 骤降后平稳——有Aha时刻
CONTINUOUS_DECLINE = 'continuous_decline', // 持续下降——产品价值不足
}
export class RetentionCalculator {
private static instance: RetentionCalculator;
private pref: preferences.Preferences | null = null;
static getInstance(): RetentionCalculator {
if (!RetentionCalculator.instance) {
RetentionCalculator.instance = new RetentionCalculator();
}
return RetentionCalculator.instance;
}
async init(context: common.UIAbilityContext): Promise<void> {
this.pref = await preferences.getPreferences(context, 'retention_data');
}
// 计算指定日期群组的留存率
async calculateCohortRetention(
cohortDate: string,
days: number[] = [1, 2, 3, 7, 14, 30]
): Promise<RetentionResult> {
if (!this.pref) return this.emptyResult(cohortDate);
// 获取该日期注册的所有用户
const dateUsersStr = await this.pref.get(`date_${cohortDate}`, '[]') as string;
const userIds: string[] = JSON.parse(dateUsersStr);
const result: RetentionResult = {
cohortDate: cohortDate,
newUsers: userIds.length,
retentionData: new Map(),
};
if (userIds.length === 0) return result;
// 计算每个留存天数的留存率
for (const day of days) {
const targetDate = this.addDays(cohortDate, day);
let retainedCount = 0;
for (const userId of userIds) {
const recordStr = await this.pref.get(`user_${userId}`, '') as string;
if (!recordStr) continue;
const record: UserRetentionRecord = JSON.parse(recordStr);
if (record.activeDates.includes(targetDate)) {
retainedCount++;
}
}
result.retentionData.set(day, {
retainedUsers: retainedCount,
retentionRate: Math.round(retainedCount / userIds.length * 10000) / 100,
});
}
return result;
}
// 计算最近N天的群组留存
async calculateRecentRetention(
recentDays: number = 7,
retentionDays: number[] = [1, 7, 30]
): Promise<RetentionResult[]> {
const results: RetentionResult[] = [];
const today = new Date();
for (let i = 0; i < recentDays; i++) {
const date = new Date(today);
date.setDate(date.getDate() - i);
const dateStr = date.toISOString().split('T')[0];
const result = await this.calculateCohortRetention(dateStr, retentionDays);
results.push(result);
}
return results;
}
// 判断留存曲线类型
analyzeCurveType(result: RetentionResult): RetentionCurveType {
const day1 = result.retentionData.get(1)?.retentionRate || 0;
const day7 = result.retentionData.get(7)?.retentionRate || 0;
const day30 = result.retentionData.get(30)?.retentionRate || 0;
// 骤降后平稳:次日到7日降幅>30%,7日到30日降幅<20%
if (day1 > 0 && day7 > 0 && day30 > 0) {
const earlyDrop = (day1 - day7) / day1;
const lateDrop = (day7 - day30) / day7;
if (earlyDrop > 0.3 && lateDrop < 0.2) {
return RetentionCurveType.SHARP_DROP_FLAT;
}
}
// 持续下降:每个阶段降幅都>20%
if (day1 > 0 && day7 > 0 && day30 > 0) {
const earlyDrop = (day1 - day7) / day1;
const lateDrop = (day7 - day30) / day7;
if (earlyDrop > 0.2 && lateDrop > 0.2) {
return RetentionCurveType.CONTINUOUS_DECLINE;
}
}
return RetentionCurveType.SMOOTH_DECAY;
}
// 打印留存结果
printResult(result: RetentionResult): void {
console.info(`\n========== 留存分析: ${result.cohortDate} ==========`);
console.info(`新增用户: ${result.newUsers}`);
const sortedDays = Array.from(result.retentionData.keys()).sort((a, b) => a - b);
for (const day of sortedDays) {
const data = result.retentionData.get(day)!;
const bar = '█'.repeat(Math.round(data.retentionRate / 2));
console.info(
`第${String(day).padStart(2, ' ')}日: ` +
`${data.retainedUsers}人 ` +
`${data.retentionRate}% ${bar}`
);
}
const curveType = this.analyzeCurveType(result);
console.info(`曲线类型: ${curveType}`);
console.info('========================================\n');
}
// ========== 工具方法 ==========
private addDays(dateStr: string, days: number): string {
const date = new Date(dateStr);
date.setDate(date.getDate() + days);
return date.toISOString().split('T')[0];
}
private emptyResult(cohortDate: string): RetentionResult {
return {
cohortDate: cohortDate,
newUsers: 0,
retentionData: new Map(),
};
}
}
完整示例:留存策略引擎
根据留存数据自动匹配优化策略。
// RetentionStrategy.ets - 留存策略引擎
import { analytics } from '@kit.AnalyticsKit';
import { RetentionCalculator, RetentionResult, RetentionCurveType } from './RetentionCalculator';
import { common } from '@kit.AbilityKit';
// 留存问题类型
export enum RetentionIssue {
LOW_DAY1 = 'low_day1', // 次日留存低
LOW_DAY7 = 'low_day7', // 7日留存低
LOW_DAY30 = 'low_day30', // 30日留存低
CONTINUOUS_DECLINE = 'continuous', // 持续下降
NO_AHA_MOMENT = 'no_aha', // 没有Aha时刻
PAID_USER_CHURN = 'paid_churn', // 付费用户流失
}
// 留存策略
export interface RetentionAction {
issue: RetentionIssue;
description: string;
strategies: string[];
priority: number; // 1-5,5最紧急
}
export class RetentionStrategyEngine {
private static instance: RetentionStrategyEngine;
private calculator: RetentionCalculator;
static getInstance(): RetentionStrategyEngine {
if (!RetentionStrategyEngine.instance) {
RetentionStrategyEngine.instance = new RetentionStrategyEngine();
}
return RetentionStrategyEngine.instance;
}
constructor() {
this.calculator = RetentionCalculator.getInstance();
}
// 分析留存问题并生成策略
async analyze(result: RetentionResult): Promise<RetentionAction[]> {
const actions: RetentionAction[] = [];
const curveType = this.calculator.analyzeCurveType(result);
const day1 = result.retentionData.get(1)?.retentionRate || 0;
const day7 = result.retentionData.get(7)?.retentionRate || 0;
const day30 = result.retentionData.get(30)?.retentionRate || 0;
// 次日留存低
if (day1 < 30) {
actions.push({
issue: RetentionIssue.LOW_DAY1,
description: `次日留存仅${day1}%,低于30%警戒线`,
strategies: [
'优化首次启动体验,缩短到达核心功能的时间',
'添加新手引导,帮助用户快速理解产品价值',
'首日推送个性化内容,提高用户回访动力',
'分析流失用户行为,找到离开前的最后操作',
],
priority: 5,
});
}
// 7日留存低
if (day7 < 15) {
actions.push({
issue: RetentionIssue.LOW_DAY7,
description: `7日留存仅${day7}%,低于15%警戒线`,
strategies: [
'检查核心功能是否真正解决用户需求',
'添加签到/任务系统,增加回访理由',
'第3天推送"你错过了什么"消息',
'优化功能发现机制,让用户找到更多价值',
],
priority: 4,
});
}
// 30日留存低
if (day30 < 5) {
actions.push({
issue: RetentionIssue.LOW_DAY30,
description: `30日留存仅${day30}%,低于5%警戒线`,
strategies: [
'重新审视产品核心价值主张',
'添加社交功能,建立用户间连接',
'引入UGC内容,增加内容丰富度',
'建立用户成长体系,增加离开成本',
],
priority: 3,
});
}
// 持续下降型
if (curveType === RetentionCurveType.CONTINUOUS_DECLINE) {
actions.push({
issue: RetentionIssue.CONTINUOUS_DECLINE,
description: '留存曲线持续下降,没有稳定期',
strategies: [
'产品缺乏持续使用价值,需要增加高频功能',
'添加内容更新机制,给用户回访理由',
'建立用户社区,增加社交粘性',
'引入习惯养成机制(每日推荐/打卡)',
],
priority: 5,
});
}
// 骤降后平稳——找到Aha时刻
if (curveType === RetentionCurveType.SHARP_DROP_FLAT) {
actions.push({
issue: RetentionIssue.NO_AHA_MOMENT,
description: '留存骤降后趋于平稳,需要找到Aha时刻提前引导',
strategies: [
'分析留存用户的共同行为,找到Aha时刻',
'在首次使用时引导用户到达Aha时刻',
'缩短用户到达Aha时刻的路径',
'对未到达Aha时刻的用户做定向召回',
],
priority: 4,
});
}
// 按优先级排序
actions.sort((a, b) => b.priority - a.priority);
// 上报分析结果
analytics.reportEvent('retention_analysis', {
'cohort_date': result.cohortDate,
'new_users': result.newUsers,
'day1_retention': day1,
'day7_retention': day7,
'day30_retention': day30,
'curve_type': curveType,
'issue_count': actions.length,
});
return actions;
}
// 打印策略建议
printActions(actions: RetentionAction[]): void {
console.info('\n========== 留存优化策略 ==========');
actions.forEach((action, index) => {
console.info(`\n[${index + 1}] ${action.description}`);
console.info(` 优先级: ${'⭐'.repeat(action.priority)}`);
console.info(' 策略:');
action.strategies.forEach((s, i) => {
console.info(` ${i + 1}. ${s}`);
});
});
console.info('\n====================================\n');
}
}
踩坑与注意事项
1. 留存定义要明确
"留存"到底怎么定义?登录算留存?打开应用算留存?使用核心功能算留存?不同定义算出来的留存率差异巨大。
一般来说:
- 宽泛留存:打开应用就算——数据好看但水分大
- 活跃留存:有实际操作才算——数据真实但偏低
- 核心留存:使用核心功能才算——最真实但最严格
选哪种取决于你的业务。社交应用用"活跃留存",工具应用用"核心留存",别用"宽泛留存"自欺欺人。
2. 群组划分要科学
按注册日期划分群组是最基础的。但有时候你需要按渠道、按版本、按设备类型划分——不同渠道来的用户留存率可能差几倍。
3. 样本量要够大
10个用户的留存率没有统计意义。至少需要100个用户以上的群组,留存数据才有参考价值。小样本的波动太大,容易误判。
4. 留存率不是越高越好
100%的留存率意味着没有新用户进来——这比低留存更可怕。健康的留存曲线是:新用户持续进来,留存率稳定在合理水平。
不同类型应用的合理留存范围:
| 应用类型 | 次日留存 | 7日留存 | 30日留存 |
|---|---|---|---|
| 社交应用 | 40-60% | 20-40% | 10-25% |
| 工具应用 | 20-40% | 10-25% | 5-15% |
| 游戏应用 | 30-50% | 15-30% | 5-15% |
| 电商应用 | 25-45% | 10-20% | 5-10% |
5. 别忽视回流用户
有些用户流失后还会回来,这叫回流用户。回流率也是重要的留存指标——如果你的30日留存低但回流率高,说明产品有吸引力,只是用户使用频率低。
HarmonyOS 6适配说明
HarmonyOS 6在留存分析方面的更新:
- 跨设备留存归因:基于华为账号自动识别跨设备用户,留存率计算更准确
- 实时留存计算:新增
analytics.calculateRetention()接口,服务端实时计算留存率 - 留存预警:当留存率低于设定阈值时自动告警
- Aha时刻检测:AI自动分析留存用户的行为特征,找到Aha时刻
// HarmonyOS 6 实时留存计算
async function getRealtimeRetention(): Promise<void> {
try {
const result = await analytics.calculateRetention({
cohortDate: '2024-06-24',
retentionDays: [1, 7, 30],
definition: 'active', // active / core_action
});
console.info(`次日留存: ${result.day1}%`);
console.info(`7日留存: ${result.day7}%`);
console.info(`30日留存: ${result.day30}%`);
} catch (error) {
console.error('[Retention] 计算失败');
}
}
总结
留存率是产品健康度的核心指标——用户来了能留下,产品才有未来。次日留存看第一印象,7日留存看功能价值,30日留存看长期粘性。留存曲线的形状比具体数字更重要——骤降后平稳说明有Aha时刻,持续下降说明产品价值不足。
| 维度 | 评价 |
|---|---|
| 学习难度 | ⭐⭐⭐☆☆ 概念简单,但留存优化需要深度业务理解 |
| 使用频率 | ⭐⭐⭐⭐⭐ 每日监控,每周分析 |
| 重要程度 | ⭐⭐⭐⭐⭐ 决定产品生死 |
核心记住三点:留存定义要明确(别自欺欺人)、曲线形状比数字重要(看趋势)、优化要找到Aha时刻(引导用户到达)。留存优化不是一锤子买卖,而是持续监测、持续优化的长期工程。
- 点赞
- 收藏
- 关注作者
评论(0)