uni-app App 性能优化
核心知识点
1. App 性能特点
uni-app App 与小程序相比,具有不同的性能特点:
- 运行环境:基于原生 WebView 或 Weex 渲染,性能更接近原生应用
- 资源管理:可以使用本地资源,减少网络依赖
- 内存管理:内存限制相对宽松,但仍需合理管理
- 启动机制:冷启动、热启动、温启动的性能表现不同
- 渲染方式:支持 WebView 渲染和原生渲染(Weex)
2. App 性能优化策略
有效的 App 性能优化策略包括:
- 启动优化:减少启动时间,提升首屏加载速度
- 渲染优化:提高页面渲染性能,减少卡顿
- 内存优化:合理管理内存,避免内存泄漏
- 网络优化:优化网络请求,减少数据传输时间
- 资源优化:优化图片、音频、视频等资源
- 代码优化:减少代码体积,提高代码执行效率
3. App 性能监控工具
常用的 App 性能监控工具:
- uni-app 开发者工具:性能监控功能
- Android Studio:Monitors、Profiler 等工具
- Xcode:Instruments 工具
- 第三方监控平台:如腾讯云、阿里云的 App 监控服务
实用案例分析
案例:App 性能优化实战
1. 启动优化
优化策略:
- 减少启动时间:优化应用初始化流程,减少启动时的同步操作
- 启动页优化:使用简洁的启动页,避免复杂动画
- 预加载资源:在启动时预加载常用资源
- 延迟初始化:将非必要的初始化操作延迟到首屏加载后
实现代码:
App.vue 中的启动优化:
<script>
export default {
onLaunch() {
// 1. 初始化必要的配置
this.initConfig();
// 2. 预加载用户信息
this.preloadUserInfo();
// 3. 延迟初始化非必要功能
setTimeout(() => {
this.initNonEssential();
}, 1000);
},
methods: {
initConfig() {
// 初始化配置信息
console.log('初始化配置');
},
preloadUserInfo() {
// 预加载用户信息
const userInfo = uni.getStorageSync('userInfo');
if (userInfo) {
getApp().globalData.userInfo = userInfo;
}
},
initNonEssential() {
// 初始化非必要功能
console.log('初始化非必要功能');
// 例如:初始化推送服务、初始化统计服务等
}
}
};
</script>2. 渲染优化
优化策略:
- 使用 Weex 渲染:对于性能要求高的页面,使用 Weex 原生渲染
- 减少 DOM 节点:减少页面 DOM 节点数量,避免深层嵌套
- 避免频繁重绘:减少 DOM 操作,避免频繁重绘和回流
- 使用虚拟列表:长列表使用虚拟列表,减少内存占用
- 图片懒加载:实现图片懒加载,减少初始加载时间
实现代码:
使用 Weex 渲染 (pages/home/home.vue):
<!-- 注意:需要在 pages.json 中配置 "renderer": "native" -->
<template>
<view class="home-container">
<view class="header">
<text class="title">首页</text>
</view>
<view class="content">
<view class="banner">
<image :src="bannerImage" class="banner-image"></image>
</view>
<view class="goods-list">
<view v-for="item in goodsList" :key="item.id" class="goods-item">
<image :src="item.image" class="goods-image"></image>
<view class="goods-info">
<text class="goods-name">{{ item.name }}</text>
<text class="goods-price">¥{{ item.price }}</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
bannerImage: 'https://via.placeholder.com/750x300',
goodsList: []
};
},
onLoad() {
this.loadGoodsList();
},
methods: {
loadGoodsList() {
// 模拟加载商品列表
const list = [];
for (let i = 0; i < 10; i++) {
list.push({
id: i,
name: `商品${i}`,
price: Math.random() * 1000,
image: `https://via.placeholder.com/200x200`
});
}
this.goodsList = list;
}
}
};
</script>
<style scoped>
.home-container {
flex: 1;
background-color: #f5f5f5;
}
.header {
height: 44px;
background-color: #007AFF;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
}
.title {
font-size: 18px;
font-weight: bold;
}
.content {
padding: 10px;
}
.banner {
margin-bottom: 10px;
}
.banner-image {
width: 100%;
height: 150px;
border-radius: 5px;
}
.goods-list {
background-color: #fff;
border-radius: 5px;
overflow: hidden;
}
.goods-item {
display: flex;
padding: 10px;
border-bottom: 1px solid #eee;
}
.goods-image {
width: 80px;
height: 80px;
border-radius: 5px;
}
.goods-info {
flex: 1;
margin-left: 10px;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.goods-name {
font-size: 16px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.goods-price {
font-size: 16px;
color: #ff4d4f;
font-weight: 500;
}
</style>图片懒加载组件 (components/lazy-image.vue):
<template>
<view class="lazy-image-container" :style="{ width: width + 'px', height: height + 'px' }">
<image
v-if="loaded"
:src="src"
class="lazy-image"
:style="{ width: width + 'px', height: height + 'px' }"
@load="handleLoad"
></image>
<view v-else class="loading" :style="{ width: width + 'px', height: height + 'px' }">
<uni-icon type="spinner" size="32" color="#999"></uni-icon>
</view>
</view>
</template>
<script>
export default {
props: {
src: {
type: String,
required: true
},
width: {
type: Number,
default: 200
},
height: {
type: Number,
default: 200
}
},
data() {
return {
loaded: false,
observer: null
};
},
mounted() {
this.checkVisibility();
this.bindIntersectionObserver();
},
methods: {
bindIntersectionObserver() {
// 绑定交叉观察器,监听元素是否进入视口
this.observer = uni.createIntersectionObserver(this);
this.observer.relativeToViewport({ threshold: 0.1 })
.observe(this.$el, (res) => {
if (res.intersectionRatio > 0 && !this.loaded) {
this.loaded = true;
this.observer.disconnect();
}
});
},
checkVisibility() {
// 初始检查元素是否在视口内
const rect = this.$el.getBoundingClientRect();
const windowHeight = uni.getSystemInfoSync().windowHeight;
if (rect.top < windowHeight && rect.bottom > 0) {
this.loaded = true;
}
},
handleLoad() {
// 图片加载完成
console.log('图片加载完成');
}
},
beforeDestroy() {
// 清理观察器
if (this.observer) {
this.observer.disconnect();
}
}
};
</script>
<style scoped>
.lazy-image-container {
position: relative;
overflow: hidden;
background-color: #f5f5f5;
}
.lazy-image {
width: 100%;
height: 100%;
transition: opacity 0.3s;
}
.loading {
display: flex;
align-items: center;
justify-content: center;
background-color: #f5f5f5;
}
</style>3. 内存优化
优化策略:
- 及时清理定时器:页面卸载时清理定时器
- 避免循环引用:避免对象之间的循环引用
- 合理使用事件监听器:及时移除不需要的事件监听器
- 减少全局变量:减少全局变量的使用,避免内存泄漏
- 使用 WeakMap:对于临时对象,使用 WeakMap 存储
- 图片资源管理:及时释放不需要的图片资源
实现代码:
内存管理工具 (utils/memory.js):
// 内存管理工具
const memoryManager = {
// 存储定时器
timers: [],
// 存储事件监听器
eventListeners: [],
// 存储图片对象
images: [],
// 添加定时器
setTimer(callback, delay) {
const timer = setInterval(callback, delay);
this.timers.push(timer);
return timer;
},
// 清除定时器
clearTimer(timer) {
if (timer) {
clearInterval(timer);
const index = this.timers.indexOf(timer);
if (index > -1) {
this.timers.splice(index, 1);
}
}
},
// 添加事件监听器
addEventListener(target, type, listener) {
target.addEventListener(type, listener);
this.eventListeners.push({ target, type, listener });
},
// 移除事件监听器
removeEventListener(target, type, listener) {
target.removeEventListener(type, listener);
const index = this.eventListeners.findIndex(item =>
item.target === target && item.type === type && item.listener === listener
);
if (index > -1) {
this.eventListeners.splice(index, 1);
}
},
// 加载图片
loadImage(src) {
const image = new Image();
image.src = src;
this.images.push(image);
return image;
},
// 释放图片资源
releaseImage(image) {
if (image) {
image.src = '';
const index = this.images.indexOf(image);
if (index > -1) {
this.images.splice(index, 1);
}
}
},
// 清理所有资源
clearAll() {
// 清理定时器
this.timers.forEach(timer => clearInterval(timer));
this.timers = [];
// 清理事件监听器
this.eventListeners.forEach(({ target, type, listener }) => {
target.removeEventListener(type, listener);
});
this.eventListeners = [];
// 释放图片资源
this.images.forEach(image => {
image.src = '';
});
this.images = [];
}
};
export default memoryManager;使用内存管理工具 (pages/detail/detail.vue):
<script>
import memoryManager from '@/utils/memory';
export default {
data() {
return {
timer: null,
scrollListener: null,
images: []
};
},
onLoad() {
this.startTimer();
this.bindScrollListener();
this.loadImages();
},
onUnload() {
this.clearResources();
},
methods: {
startTimer() {
// 使用内存管理工具添加定时器
this.timer = memoryManager.setTimer(() => {
console.log('Timer running...');
}, 1000);
},
bindScrollListener() {
// 使用内存管理工具添加事件监听器
this.scrollListener = (e) => {
console.log('Scroll position:', e.scrollTop);
};
memoryManager.addEventListener(window, 'scroll', this.scrollListener);
},
loadImages() {
// 加载图片
const imageUrls = [
'https://via.placeholder.com/400x300',
'https://via.placeholder.com/400x300',
'https://via.placeholder.com/400x300'
];
imageUrls.forEach(url => {
const image = memoryManager.loadImage(url);
this.images.push(image);
});
},
clearResources() {
// 清理资源
if (this.timer) {
memoryManager.clearTimer(this.timer);
}
if (this.scrollListener) {
memoryManager.removeEventListener(window, 'scroll', this.scrollListener);
}
this.images.forEach(image => {
memoryManager.releaseImage(image);
});
}
}
};
</script>4. 网络优化
优化策略:
- 请求合并:合并多个请求,减少网络请求次数
- 数据缓存:合理使用本地缓存,减少重复请求
- 图片优化:使用适当尺寸的图片,支持 WebP 格式
- CDN 加速:使用 CDN 加速静态资源
- HTTP/2:使用 HTTP/2,提高传输效率
- 离线缓存:实现离线缓存,支持离线访问
实现代码:
离线缓存工具 (utils/offline-cache.js):
// 离线缓存工具
const offlineCache = {
// 缓存名称
cacheName: 'app-cache-v1',
// 缓存资源
async cacheResources(resources) {
try {
// 检查是否支持 Service Worker
if ('serviceWorker' in navigator) {
// 使用 Service Worker 缓存
// 这里简化处理,实际项目中需要实现完整的 Service Worker
} else {
// 使用 localStorage 缓存小数据
resources.forEach(resource => {
if (resource.type === 'json') {
localStorage.setItem(`cache_${resource.url}`, JSON.stringify(resource.data));
}
});
}
console.log('资源缓存成功');
} catch (error) {
console.error('资源缓存失败:', error);
}
},
// 获取缓存资源
async getCachedResource(url) {
try {
// 检查是否支持 Service Worker
if ('serviceWorker' in navigator) {
// 使用 Service Worker 获取缓存
// 这里简化处理,实际项目中需要实现完整的 Service Worker
} else {
// 从 localStorage 获取缓存
const cachedData = localStorage.getItem(`cache_${url}`);
if (cachedData) {
return JSON.parse(cachedData);
}
}
return null;
} catch (error) {
console.error('获取缓存资源失败:', error);
return null;
}
},
// 清理缓存
async clearCache() {
try {
// 检查是否支持 Service Worker
if ('serviceWorker' in navigator) {
// 使用 Service Worker 清理缓存
// 这里简化处理,实际项目中需要实现完整的 Service Worker
} else {
// 清理 localStorage 缓存
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key.startsWith('cache_')) {
localStorage.removeItem(key);
}
}
}
console.log('缓存清理成功');
} catch (error) {
console.error('缓存清理失败:', error);
}
}
};
export default offlineCache;使用离线缓存 (pages/home/home.vue):
<script>
import offlineCache from '@/utils/offline-cache';
export default {
data() {
return {
goodsList: []
};
},
onLoad() {
this.loadGoodsList();
},
methods: {
async loadGoodsList() {
// 尝试从缓存获取
const cachedData = await offlineCache.getCachedResource('https://api.example.com/goods');
if (cachedData) {
this.goodsList = cachedData;
console.log('从缓存获取数据');
}
// 从网络获取最新数据
try {
const res = await uni.request({
url: 'https://api.example.com/goods',
method: 'GET'
});
if (res.data.code === 0) {
this.goodsList = res.data.data;
// 缓存数据
await offlineCache.cacheResources([
{
url: 'https://api.example.com/goods',
type: 'json',
data: res.data.data
}
]);
}
} catch (error) {
console.error('加载商品列表失败:', error);
}
}
}
};
</script>5. 资源优化
优化策略:
- 图片优化:使用适当尺寸的图片,支持 WebP 格式
- 音频视频优化:使用适当编码和压缩比
- 字体优化:使用字体子集,减少字体文件大小
- 资源压缩:压缩 CSS、JavaScript、HTML 文件
- 资源预加载:预加载可能需要的资源
实现代码:
资源优化工具 (utils/resource.js):
// 资源优化工具
const resourceOptimizer = {
// 优化图片
optimizeImage(src, options = {}) {
const {
width = 200,
height = 200,
quality = 0.8,
format = 'webp'
} = options;
// 构建优化后的图片 URL
// 这里假设使用云服务进行图片处理
const optimizedUrl = `https://api.example.com/image-process?src=${encodeURIComponent(src)}&width=${width}&height=${height}&quality=${quality}&format=${format}`;
return optimizedUrl;
},
// 预加载资源
preloadResources(resources) {
resources.forEach(resource => {
if (resource.type === 'image') {
const image = new Image();
image.src = resource.url;
} else if (resource.type === 'script') {
const script = document.createElement('script');
script.src = resource.url;
document.head.appendChild(script);
} else if (resource.type === 'style') {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = resource.url;
document.head.appendChild(link);
}
});
},
// 检测资源加载状态
checkResourceStatus(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('HEAD', url);
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve({ status: 'success', url });
} else {
resolve({ status: 'error', url, statusCode: xhr.status });
}
};
xhr.onerror = () => {
resolve({ status: 'error', url });
};
xhr.send();
});
}
};
export default resourceOptimizer;使用资源优化工具 (pages/detail/detail.vue):
<script>
import resourceOptimizer from '@/utils/resource.js';
export default {
data() {
return {
goodsDetail: {
id: '',
name: '',
price: 0,
images: []
}
};
},
onLoad(options) {
this.getGoodsDetail(options.id);
this.preloadResources();
},
methods: {
async getGoodsDetail(id) {
const res = await uni.request({
url: `https://api.example.com/goods/${id}`,
method: 'GET'
});
if (res.data.code === 0) {
this.goodsDetail = res.data.data;
// 优化图片
this.goodsDetail.images = this.goodsDetail.images.map(image =>
resourceOptimizer.optimizeImage(image, {
width: 400,
height: 400,
quality: 0.8,
format: 'webp'
})
);
}
},
preloadResources() {
// 预加载可能需要的资源
resourceOptimizer.preloadResources([
{
type: 'image',
url: 'https://via.placeholder.com/400x300'
},
{
type: 'script',
url: 'https://cdn.example.com/utils.js'
}
]);
}
}
};
</script>学习目标
通过本章节的学习,你应该能够:
- 理解 App 性能特点:掌握 uni-app App 的性能特点和运行机制
- 掌握 App 性能优化策略:能够从启动、渲染、内存、网络、资源等方面优化 App 性能
- 使用 App 性能监控工具:能够使用开发者工具和第三方监控平台分析性能问题
- 实现 App 性能优化:能够根据项目特点,实施有效的性能优化方案
- 持续监控 App 性能:建立性能监控机制,持续优化 App 性能
总结
uni-app App 性能优化是一个综合性的工作,需要从多个方面入手,结合 App 的特点实施针对性的优化策略。通过合理的优化,可以显著提升 App 的用户体验,提高用户留存率。
在实际项目中,我们需要注意以下几点:
- 性能分析:使用性能监控工具,定期分析 App 性能
- 优化优先级:根据用户体验影响程度,确定优化优先级
- 平台差异:考虑不同平台的性能特点,实施差异化的优化策略
- 持续优化:建立性能优化的持续迭代机制
- 测试验证:优化后进行充分测试,确保优化效果
- 用户反馈:关注用户反馈,及时解决性能问题
通过本章节的学习和实践,你应该能够熟练掌握 uni-app App 性能优化的方法和技巧,为用户提供更加流畅、快速的应用体验。