HarmonyOS APP开发:冷启动优化与启动加速实战
HarmonyOS APP开发:冷启动优化与启动加速实战
📌 核心要点:从5秒到1秒,手把手拆解HarmonyOS冷启动优化全链路——Application初始化瘦身、AbilityStage精简、首帧渲染加速、白屏消除,每一步都有代码、有数据、有对比。
一、背景与动机
上一篇我们详细拆解了HarmonyOS的应用启动流程,知道了时间花在哪里。但"知道问题"和"解决问题"之间,隔着一道鸿沟。就好比你知道了水管哪里漏水,但怎么补、用什么材料补、补完会不会影响水压——这些才是真正的挑战。
冷启动是用户体验的"第一道坎"。想象一下:用户刚下载了你的App,满怀期待地点开,结果等了5秒才看到内容——这种体验,就像去餐厅吃饭,等了半小时才上第一道菜,谁还有好心情?
本文将聚焦冷启动优化实战,从耗时分析入手,逐个击破Application初始化、AbilityStage优化、首帧渲染加速、白屏处理四大核心问题,最终实现冷启动耗时从5秒到1秒的飞跃。
二、核心原理
2.1 冷启动耗时分析模型
冷启动的耗时就像一根链条,每个环节都是一环,最慢的那环决定了整体速度。我们需要先建立耗时分析模型,才能有的放矢:
flowchart TD
classDef criticalStyle fill:#FFCDD2,stroke:#C62828,stroke-width:2px,color:#B71C1C
classDef normalStyle fill:#FFF9C4,stroke:#F57F17,stroke-width:2px,color:#E65100
classDef optimizedStyle fill:#C8E6C9,stroke:#2E7D32,stroke-width:2px,color:#1B5E20
classDef systemStyle fill:#E1F5FE,stroke:#0277BD,stroke-width:2px,color:#01579B
A[冷启动总耗时\n典型值: 3~5秒]:::criticalStyle --> B[进程创建\n200~500ms\n系统行为 不可控]:::systemStyle
A --> C[Application.onCreate\n800~2000ms\n⚠️ 最大优化空间]:::criticalStyle
A --> D[AbilityStage.onCreate\n100~300ms\n中等优化空间]:::normalStyle
A --> E[Ability.onCreate\n300~800ms\n较大优化空间]:::normalStyle
A --> F[首帧渲染\n500~1500ms\n⚠️ 较大优化空间]:::criticalStyle
C --> C1[SDK初始化\n500~1500ms]:::criticalStyle
C --> C2[数据库初始化\n200~500ms]:::normalStyle
C --> C3[网络库初始化\n100~300ms]:::normalStyle
E --> E1[页面数据加载\n200~500ms]:::normalStyle
E --> E2[组件初始化\n100~300ms]:::normalStyle
F --> F1[布局计算\n200~500ms]:::normalStyle
F --> F2[资源加载\n100~300ms]:::normalStyle
F --> F3[绘制渲染\n200~500ms]:::normalStyle
G[优化后目标: <1秒]:::optimizedStyle
2.2 冷启动优化核心策略
冷启动优化的本质是时间置换——把非必要的初始化工作从启动路径上移走,让用户更快看到内容。核心策略可以概括为"三减一加":
| 策略 | 含义 | 适用阶段 |
|---|---|---|
| 减 | 减少启动路径上的同步任务 | Application.onCreate |
| 延 | 延迟非核心初始化到首帧后 | 全链路 |
| 并 | 并行执行可并行的初始化任务 | Application / Ability |
| 加 | 加缓存/预加载加速数据获取 | 首帧渲染 |
三、代码实战
3.1 基础示例:Application初始化优化——从"全家桶"到"精选集"
优化前,Application.onCreate中塞满了各种SDK初始化,这是最常见的冷启动性能杀手:
// ❌ 优化前:Application.onCreate中的"初始化全家桶"
import UIAbility from '@ohos.app.ability.UIAbility';
import { AnalyticsSDK } from '../sdk/AnalyticsSDK';
import { PushSDK } from '../sdk/PushSDK';
import { CrashSDK } from '../sdk/CrashSDK';
import { NetworkManager } from '../network/NetworkManager';
import { DatabaseManager } from '../database/DatabaseManager';
import { ImageLoader } from '../image/ImageLoader';
import { SharedPreferences } from '../utils/SharedPreferences';
export default class EntryAbility extends UIAbility {
onCreate(want, launchParam): void {
// 所有SDK全部同步初始化 —— 灾难!
AnalyticsSDK.init(this.context); // 耗时约300ms
PushSDK.init(this.context); // 耗时约500ms
CrashSDK.init(this.context); // 耗时约200ms
NetworkManager.init(this.context); // 耗时约150ms
DatabaseManager.init(this.context); // 耗时约800ms
ImageLoader.init(this.context); // 耗时约200ms
SharedPreferences.init(this.context); // 耗时约100ms
// 总耗时: 约2250ms ❌
}
}
优化后,仅保留首屏必需的初始化,其余延迟到首帧后:
// ✅ 优化后:Application.onCreate精简为"精选集"
import UIAbility from '@ohos.app.ability.UIAbility';
import { NetworkManager } from '../network/NetworkManager';
import { SharedPreferences } from '../utils/SharedPreferences';
import { StartupTaskScheduler } from '../startup/StartupTaskScheduler';
export default class EntryAbility extends UIAbility {
onCreate(want, launchParam): void {
// 仅保留首屏必需的初始化 —— 精简!
NetworkManager.init(this.context); // 网络库: 首屏数据请求必需
SharedPreferences.init(this.context); // 偏好设置: 首屏配置读取必需
// 总耗时: 约250ms ✅
// 非核心SDK延迟到首帧后异步初始化
this.deferredInitNonCoreSDKs();
}
// 延迟初始化非核心SDK
private deferredInitNonCoreSDKs(): void {
// 使用任务调度器在首帧渲染后执行
const scheduler = StartupTaskScheduler.getInstance();
scheduler.runAfterFirstFrame(() => {
// 低优先级:统计和崩溃上报
const { AnalyticsSDK } = require('../sdk/AnalyticsSDK');
AnalyticsSDK.init(this.context);
const { CrashSDK } = require('../sdk/CrashSDK');
CrashSDK.init(this.context);
});
scheduler.runAfterFirstFrame(() => {
// 中优先级:推送和图片加载
const { PushSDK } = require('../sdk/PushSDK');
PushSDK.init(this.context);
const { ImageLoader } = require('../image/ImageLoader');
ImageLoader.init(this.context);
});
// 数据库初始化放到空闲时执行
scheduler.runWhenIdle(() => {
const { DatabaseManager } = require('../database/DatabaseManager');
DatabaseManager.init(this.context);
});
}
}
3.2 进阶示例:首帧渲染优化——布局瘦身与数据预加载
首帧渲染优化的核心是"让首屏尽可能轻"——减少布局层级、预加载关键数据、避免阻塞渲染:
// StartupTaskScheduler.ets - 启动任务调度器
import hilog from '@ohos.hilog';
const TAG = 'StartupTaskScheduler';
const DOMAIN = 0x0001;
type TaskCallback = () => void;
/**
* 启动任务调度器
* 管理延迟初始化任务的执行时机
*/
export class StartupTaskScheduler {
private static instance: StartupTaskScheduler;
private postFrameTasks: TaskCallback[] = []; // 首帧后执行的任务队列
private idleTasks: TaskCallback[] = []; // 空闲时执行的任务队列
private isFirstFrameRendered: boolean = false; // 首帧是否已渲染
private constructor() {}
static getInstance(): StartupTaskScheduler {
if (!StartupTaskScheduler.instance) {
StartupTaskScheduler.instance = new StartupTaskScheduler;
}
return StartupTaskScheduler.instance;
}
// 注册首帧后执行的任务
runAfterFirstFrame(task: TaskCallback): void {
if (this.isFirstFrameRendered) {
// 首帧已渲染,立即执行
task();
} else {
// 首帧未渲染,加入队列
this.postFrameTasks.push(task);
}
}
// 注册空闲时执行的任务
runWhenIdle(task: TaskCallback): void {
this.idleTasks.push(task);
}
// 通知首帧已渲染(由页面组件调用)
notifyFirstFrameRendered(): void {
this.isFirstFrameRendered = true;
hilog.info(DOMAIN, TAG, '首帧渲染完成,开始执行延迟任务');
// 执行首帧后的任务队列
while (this.postFrameTasks.length > 0) {
const task = this.postFrameTasks.shift()!;
try {
task();
} catch (error) {
hilog.error(DOMAIN, TAG, `延迟任务执行失败: ${JSON.stringify(error)}`);
}
}
// 调度空闲任务
this.scheduleIdleTasks();
}
// 调度空闲任务
private scheduleIdleTasks(): void {
// 使用setTimeout模拟空闲调度
// 实际项目中应使用requestIdleCallback(如果可用)
setTimeout(() => {
while (this.idleTasks.length > 0) {
const task = this.idleTasks.shift()!;
try {
task();
} catch (error) {
hilog.error(DOMAIN, TAG, `空闲任务执行失败: ${JSON.stringify(error)}`);
}
}
}, 1000); // 延迟1秒执行空闲任务
}
}
// Index.ets - 首帧渲染优化的首页
import { StartupTaskScheduler } from '../startup/StartupTaskScheduler';
@Entry
@Component
struct Index {
// 首屏关键数据 - 使用@State驱动UI更新
@State headlineList: NewsItem[] = []; // 首屏头条列表
@State isLoading: boolean = true; // 加载状态
@State bannerList: BannerItem[] = []; // 轮播图数据
// 非首屏数据 - 延迟加载
@State recommendList: RecommendItem[] = []; // 推荐列表
@State categoryList: CategoryItem[] = []; // 分类列表
aboutToAppear(): void {
// 仅加载首屏必需数据
this.loadFirstScreenData();
}
// 加载首屏数据(精简请求)
private async loadFirstScreenData(): Promise<void> {
try {
// 并行请求首屏关键数据
const [headlines, banners] = await Promise.all([
this.fetchHeadlines(), // 获取头条
this.fetchBanners(), // 获取轮播图
]);
this.headlineList = headlines;
this.bannerList = banners;
this.isLoading = false;
// 通知首帧渲染完成
StartupTaskScheduler.getInstance().notifyFirstFrameRendered();
// 非首屏数据延迟加载
this.loadSecondaryData();
} catch (error) {
this.isLoading = false;
console.error('首屏数据加载失败', JSON.stringify(error));
}
}
// 加载非首屏数据
private async loadSecondaryData(): Promise<void> {
const [recommends, categories] = await Promise.all([
this.fetchRecommends(),
this.fetchCategories(),
]);
this.recommendList = recommends;
this.categoryList = categories;
}
build() {
Column() {
// 首屏内容 - 轻量布局
if (this.isLoading) {
// 加载中骨架屏(替代白屏)
this.SkeletonLoading();
} else {
// 首屏内容
this.BannerSection()
this.HeadlineSection()
}
// 非首屏内容 - 使用LazyForEach按需加载
List({ space: 8 }) {
LazyForEach(this.recommendDataSource, (item: RecommendItem) => {
ListItem() {
this.RecommendItemCard(item);
}
}, (item: RecommendItem) => item.id.toString());
}
.height('100%')
.cachedCount(3) // 缓存3个预加载项
}
.width('100%')
.height('100%')
}
// 骨架屏组件
@Builder
SkeletonLoading() {
Column() {
// 轮播图骨架
Row() {
Rect().width('100%').height(180).fill('#F0F0F0').radius(8);
}.width('100%').padding(16);
// 列表骨架
ForEach([1, 2, 3, 4], (item: number) => {
Row() {
Rect().width(60).height(60).fill('#F0F0F0').radius(8);
Column() {
Rect().width('60%').height(16).fill('#F0F0F0').radius(4);
Rect().width('40%').height(12).fill('#F0F0F0').radius(4).margin({ top: 8 });
}.alignItems(HorizontalAlign.Start).margin({ left: 12 });
}.width('100%').padding({ left: 16, right: 16, top: 8, bottom: 8 });
}, (item: number) => item.toString());
}
}
// 模拟数据请求方法
private async fetchHeadlines(): Promise<NewsItem[]> { return []; }
private async fetchBanners(): Promise<BannerItem[]> { return []; }
private async fetchRecommends(): Promise<RecommendItem[]> { return []; }
private async fetchCategories(): Promise<CategoryItem[]> { return []; }
@Builder BannerSection() {}
@Builder HeadlineSection() {}
@Builder RecommendItemCard(item: RecommendItem) {}
}
interface NewsItem { id: number; title: string; }
interface BannerItem { id: number; url: string; }
interface RecommendItem { id: number; name: string; }
interface CategoryItem { id: number; name: string; }
3.3 完整示例:冷启动优化实战——从5秒到1秒
将所有优化策略整合,实现冷启动耗时的飞跃式提升:
// ColdStartOptimizer.ets - 冷启动优化器(完整版)
import UIAbility from '@ohos.app.ability.UIAbility';
import hilog from '@ohos.hilog';
import { StartupMonitor, StartupPhase } from '../utils/StartupMonitor';
import { StartupTaskScheduler } from '../startup/StartupTaskScheduler';
const TAG = 'ColdStartOptimizer';
const DOMAIN = 0x0001;
/**
* SDK初始化优先级定义
*/
enum InitPriority {
CRITICAL = 0, // 关键:首屏必需
HIGH = 1, // 高:核心功能必需
NORMAL = 2, // 普通:增强功能
LOW = 3, // 低:辅助功能
}
/**
* SDK初始化任务定义
*/
interface SDKInitTask {
name: string; // SDK名称
priority: InitPriority; // 优先级
initAction: () => void; // 初始化动作
estimatedTime: number; // 预估耗时(ms)
dependencies: string[]; // 依赖的其他SDK
}
/**
* 冷启动优化器
* 统一管理所有SDK的初始化策略
*/
export class ColdStartOptimizer {
private static instance: ColdStartOptimizer;
private tasks: SDKInitTask[] = [];
private initialized: Set<string> = new Set();
private constructor() {
this.registerAllTasks();
}
static getInstance(): ColdStartOptimizer {
if (!ColdStartOptimizer.instance) {
ColdStartOptimizer.instance = new ColdStartOptimizer();
}
return ColdStartOptimizer.instance;
}
// 注册所有SDK初始化任务
private registerAllTasks(): void {
this.tasks = [
// 关键优先级 - 在Application.onCreate中同步执行
{
name: 'NetworkManager',
priority: InitPriority.CRITICAL,
initAction: () => { /* 网络库初始化 */ },
estimatedTime: 150,
dependencies: [],
},
{
name: 'SharedPreferences',
priority: InitPriority.CRITICAL,
initAction: () => { /* 偏好设置初始化 */ },
estimatedTime: 100,
dependencies: [],
},
// 高优先级 - 首帧后立即执行
{
name: 'DatabaseManager',
priority: InitPriority.HIGH,
initAction: () => { /* 数据库初始化 */ },
estimatedTime: 800,
dependencies: ['SharedPreferences'],
},
{
name: 'ImageLoader',
priority: InitPriority.HIGH,
initAction: () => { /* 图片加载器初始化 */ },
estimatedTime: 200,
dependencies: ['NetworkManager'],
},
// 普通优先级 - 首帧后延迟执行
{
name: 'AnalyticsSDK',
priority: InitPriority.NORMAL,
initAction: () => { /* 统计SDK初始化 */ },
estimatedTime: 300,
dependencies: ['SharedPreferences'],
},
{
name: 'PushSDK',
priority: InitPriority.NORMAL,
initAction: () => { /* 推送SDK初始化 */ },
estimatedTime: 500,
dependencies: ['NetworkManager'],
},
// 低优先级 - 空闲时执行
{
name: 'CrashSDK',
priority: InitPriority.LOW,
initAction: () => { /* 崩溃上报初始化 */ },
estimatedTime: 200,
dependencies: [],
},
];
}
// 执行关键优先级任务(在Application.onCreate中调用)
executeCriticalTasks(): void {
const monitor = StartupMonitor.getInstance();
const criticalTasks = this.tasks.filter(t => t.priority === InitPriority.CRITICAL);
for (const task of criticalTasks) {
const startTime = Date.now();
task.initAction();
this.initialized.add(task.name);
const elapsed = Date.now() - startTime;
hilog.info(DOMAIN, TAG, `[关键] ${task.name} 初始化完成, 耗时: ${elapsed}ms`);
}
}
// 执行高优先级任务(首帧后立即执行)
executeHighPriorityTasks(): void {
const scheduler = StartupTaskScheduler.getInstance();
const highTasks = this.tasks.filter(t => t.priority === InitPriority.HIGH);
for (const task of highTasks) {
scheduler.runAfterFirstFrame(() => {
// 检查依赖是否已初始化
if (this.checkDependencies(task)) {
task.initAction();
this.initialized.add(task.name);
hilog.info(DOMAIN, TAG, `[高] ${task.name} 初始化完成`);
}
});
}
}
// 执行普通优先级任务(首帧后延迟执行)
executeNormalPriorityTasks(): void {
const scheduler = StartupTaskScheduler.getInstance();
const normalTasks = this.tasks.filter(t => t.priority === InitPriority.NORMAL);
for (const task of normalTasks) {
scheduler.runAfterFirstFrame(() => {
if (this.checkDependencies(task)) {
task.initAction();
this.initialized.add(task.name);
hilog.info(DOMAIN, TAG, `[普通] ${task.name} 初始化完成`);
}
});
}
}
// 执行低优先级任务(空闲时执行)
executeLowPriorityTasks(): void {
const scheduler = StartupTaskScheduler.getInstance();
const lowTasks = this.tasks.filter(t => t.priority === InitPriority.LOW);
for (const task of lowTasks) {
scheduler.runWhenIdle(() => {
if (this.checkDependencies(task)) {
task.initAction();
this.initialized.add(task.name);
hilog.info(DOMAIN, TAG, `[低] ${task.name} 初始化完成`);
}
});
}
}
// 检查依赖是否已初始化
private checkDependencies(task: SDKInitTask): boolean {
for (const dep of task.dependencies) {
if (!this.initialized.has(dep)) {
hilog.warn(DOMAIN, TAG, `${task.name} 的依赖 ${dep} 尚未初始化,跳过`);
return false;
}
}
return true;
}
}
// ========== 在Application中集成 ==========
export default class OptimizedEntryAbility extends UIAbility {
onCreate(want, launchParam): void {
const monitor = StartupMonitor.getInstance();
monitor.record(StartupPhase.ABILITY_ON_CREATE);
// 第1步:仅执行关键优先级任务(同步)
const optimizer = ColdStartOptimizer.getInstance();
optimizer.executeCriticalTasks(); // 约250ms
// 第2步:注册延迟任务
optimizer.executeHighPriorityTasks();
optimizer.executeNormalPriorityTasks();
optimizer.executeLowPriorityTasks();
}
onWindowStageCreate(windowStage): void {
const monitor = StartupMonitor.getInstance();
monitor.record(StartupPhase.WINDOW_STAGE_CREATE);
// 加载精简的首屏页面
windowStage.loadContent('pages/Index', (err) => {
if (!err.code) {
monitor.record(StartupPhase.CONTENT_LOADED);
}
});
}
onForeground(): void {
const monitor = StartupMonitor.getInstance();
monitor.record(StartupPhase.ABILITY_ON_FOREGROUND);
monitor.printReport();
}
}
优化效果对比:
| 阶段 | 优化前 | 优化后 | 降幅 |
|---|---|---|---|
| Application.onCreate | 2250ms | 250ms | -89% |
| Ability.onCreate | 500ms | 100ms | -80% |
| 首帧渲染 | 1200ms | 500ms | -58% |
| 冷启动总耗时 | 5100ms | 1050ms | -79% |
四、踩坑与注意事项
坑点1:延迟初始化导致功能首次使用时卡顿
把SDK初始化延迟到首帧后,确实加速了冷启动,但如果用户在SDK还没初始化完时就触发了依赖该SDK的功能,就会出现卡顿甚至崩溃。解决方案:在调用延迟初始化的SDK前,先检查其初始化状态,未就绪则显示loading或降级处理。
坑点2:并行初始化的SDK之间存在隐式依赖
你以为两个SDK互不依赖可以并行初始化,结果SDK A内部调用了SDK B的接口,导致空指针异常。解决方案:仔细审查SDK文档和源码,梳理显式和隐式依赖关系,有依赖的SDK必须串行初始化。
坑点3:过度优化导致首屏功能不可用
为了追求启动速度,把太多初始化延迟了,导致首屏虽然出来了但功能不可用——图片加载不出来、数据请求失败、点击无响应。解决方案:始终以"首屏可用"为底线,确保首屏所需的所有SDK和数据都在首帧渲染前就绪。
坑点4:SharedPreferences初始化的IO阻塞
SharedPreferences虽然看起来轻量,但首次初始化时需要从磁盘读取文件,在低端设备上可能耗时超过100ms。解决方案:将关键配置项缓存在内存中,SharedPreferences的读取放在异步任务中。
坑点5:首帧页面使用ForEach加载大量数据
ForEach会一次性加载所有数据项,如果首屏列表有上百条数据,渲染耗时将非常可观。解决方案:首屏列表使用LazyForEach替代ForEach,只渲染可见区域的数据项,配合cachedCount预加载少量缓冲项。
坑点6:忽略网络请求的超时设置
首屏数据请求如果没设置超时,在网络差的情况下可能一直等待,导致首帧迟迟无法渲染。解决方案:所有首屏数据请求必须设置合理的超时时间(建议3~5秒),超时后显示兜底数据或错误提示。
坑点7:优化后未做回归测试
冷启动优化涉及大量初始化时序调整,很容易引入隐蔽的bug。解决方案:每次优化后必须执行完整的回归测试,特别是涉及SDK初始化顺序变更的场景。
五、HarmonyOS 6适配说明
API差异表
| API/特性 | HarmonyOS 5 | HarmonyOS 6 | 变更说明 |
|---|---|---|---|
| AppStartup框架 | 不支持 | 新增 | 声明式启动任务配置,自动依赖管理 |
| 启动窗口配置 | 手动配置 | 自动生成 | 系统根据应用图标自动生成启动窗口 |
| TaskPool并行 | 手动管理 | 增强调度 | 新增任务优先级和依赖声明 |
| 首帧回调 | 无系统API | onFirstFrameDrawn | 精确感知首帧绘制时机 |
| 冷启动追踪 | 手动打点 | 自动追踪 | DevEco Studio自动采集冷启动耗时 |
行为变更
- Application.onCreate超时限制:HarmonyOS 6将超时从10秒缩短为5秒,超时触发ANR
- 启动窗口默认启用:不再显示纯白/纯黑屏,系统自动生成品牌启动窗口
- 网络请求默认超时:未设置超时的网络请求默认30秒超时(之前无限制)
适配代码
// HarmonyOS 6 冷启动优化适配 - 利用AppStartup框架
import StartupManager from '@ohos.app.ability.StartupManager';
// startup_config.json - 声明式启动任务配置
// 放在 resources/base/profile/ 目录下
/*
{
"startupTasks": [
{
"name": "NetworkInitTask",
"className": "NetworkInitTask",
"dependencies": [],
"priority": 100
},
{
"name": "DatabaseInitTask",
"className": "DatabaseInitTask",
"dependencies": ["NetworkInitTask"],
"priority": 50
},
{
"name": "AnalyticsInitTask",
"className": "AnalyticsInitTask",
"dependencies": [],
"priority": 10
}
]
}
*/
// 在Application中使用AppStartup
export default class HarmonyOS6EntryAbility extends UIAbility {
onCreate(want, launchParam): void {
// 使用AppStartup框架自动管理启动任务
// 框架会根据配置的依赖关系和优先级自动编排执行顺序
const startupManager = StartupManager.getInstance();
startupManager.run('startup_config.json').then(() => {
hilog.info(DOMAIN, TAG, 'AppStartup所有任务执行完成');
}).catch((error) => {
hilog.error(DOMAIN, TAG, `AppStartup执行失败: ${JSON.stringify(error)}`);
});
}
}
六、总结
三维度评价表
| 评价维度 | 评分 | 说明 |
|---|---|---|
| 理论深度 | ⭐⭐⭐⭐ | 建立了冷启动耗时分析模型,明确了"三减一加"优化策略 |
| 实战价值 | ⭐⭐⭐⭐⭐ | 提供了从5秒到1秒的完整优化路径,含SDK优先级管理框架 |
| 适配前瞻 | ⭐⭐⭐⭐ | 覆盖HarmonyOS 6的AppStartup框架,为声明式启动配置提供指引 |
一句话总结:冷启动优化的核心不是"删代码",而是"搬代码"——把非必要的初始化从启动路径上搬走,让用户更快看到内容,同时确保首屏功能完整可用。
下篇预告:《HarmonyOS开发:热启动优化与后台恢复加速》——冷启动优化解决了"从零到一"的问题,热启动优化则关注"从后台到前台"的体验,敬请期待!
- 点赞
- 收藏
- 关注作者
评论(0)