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>

学习目标

通过本章节的学习,你应该能够:

  1. 理解 App 性能特点:掌握 uni-app App 的性能特点和运行机制
  2. 掌握 App 性能优化策略:能够从启动、渲染、内存、网络、资源等方面优化 App 性能
  3. 使用 App 性能监控工具:能够使用开发者工具和第三方监控平台分析性能问题
  4. 实现 App 性能优化:能够根据项目特点,实施有效的性能优化方案
  5. 持续监控 App 性能:建立性能监控机制,持续优化 App 性能

总结

uni-app App 性能优化是一个综合性的工作,需要从多个方面入手,结合 App 的特点实施针对性的优化策略。通过合理的优化,可以显著提升 App 的用户体验,提高用户留存率。

在实际项目中,我们需要注意以下几点:

  1. 性能分析:使用性能监控工具,定期分析 App 性能
  2. 优化优先级:根据用户体验影响程度,确定优化优先级
  3. 平台差异:考虑不同平台的性能特点,实施差异化的优化策略
  4. 持续优化:建立性能优化的持续迭代机制
  5. 测试验证:优化后进行充分测试,确保优化效果
  6. 用户反馈:关注用户反馈,及时解决性能问题

通过本章节的学习和实践,你应该能够熟练掌握 uni-app App 性能优化的方法和技巧,为用户提供更加流畅、快速的应用体验。

« 上一篇 uni-app 小程序性能优化 下一篇 » uni-app Web 端优化