UniApp App端设备信息与网络请求的完整配置指南

在跨平台应用开发中,UniApp因其"一次开发,多端运行"的特性备受开发者青睐。然而,当应用运行在App端时,许多开发者往往只关注基础的UserAgent设置,却忽略了设备信息获取与网络请求配置这一完整生态链。本文将带你深入探索UniApp在App端的全套配置方案,从设备指纹到网络状态管理,构建真正健壮的移动端应用。

1. 理解UniApp的运行时环境

UniApp在App端运行时,实际上是在原生WebView环境中执行JavaScript代码。这个环境通过 plus 对象提供了丰富的原生能力接口,而理解这个对象的结构是掌握App端开发的关键。

plus 对象是5+ Runtime提供的核心对象,包含了设备、界面、网络等各类原生功能。与浏览器环境中的 window 对象类似,它是所有原生API的入口点。但在实际开发中,我们需要注意几个重要特性:

  • 异步加载 plus 对象在页面初始化时可能还未完全加载,需要通过 plusready 事件确保可用性
  • 平台差异 :虽然API接口统一,但Android和iOS的实现细节可能存在差异
  • 权限控制 :部分API需要配置相应权限才能正常调用
document.addEventListener('plusready', function() {
  // 确保plus对象可用后再执行相关操作
  console.log('5+ Runtime已就绪', plus.os.name);
}, false);

2. 设备信息获取全攻略

2.1 基础设备信息采集

在UniApp中获取设备信息主要有两种方式:通过 plus.device 获取硬件信息和通过 uni.getSystemInfo 获取系统信息。这两者各有侧重,需要根据场景选择使用。

关键API对比:

功能维度 plus.device uni.getSystemInfo
设备型号 plus.device.model res.model
操作系统 plus.os.name res.platform
屏幕分辨率 plus.screen.resolutionWidth res.screenWidth
设备唯一标识 plus.device.uuid 不提供
网络状态 plus.networkinfo.getCurrentType() res.networkType

实际开发中,我们通常会组合使用这两个API:

function getCompositeDeviceInfo() {
  return new Promise((resolve) => {
    // 获取uni提供的系统信息
    uni.getSystemInfo({
      success: (sysRes) => {
        // 获取5+ Runtime提供的设备信息
        const deviceInfo = {
          ...sysRes,
          uuid: plus.device.uuid,
          vendor: plus.device.vendor,
          screenScale: plus.screen.scale,
          networkType: plus.networkinfo.getCurrentType()
        };
        resolve(deviceInfo);
      }
    });
  });
}

2.2 高级设备指纹方案

对于需要设备唯一标识的场景,简单的UUID可能不够可靠。我们可以构建一个更健壮的设备指纹方案:

async function generateDeviceFingerprint() {
  const info = await getCompositeDeviceInfo();
  const fingerprintData = {
    platform: info.platform,
    model: info.model,
    osVersion: info.system,
    screen: `${info.screenWidth}x${info.screenHeight}`,
    pixelRatio: info.pixelRatio,
    language: info.language,
    uuid: info.uuid,
    vendor: info.vendor
  };
  
  // 使用SHA-256生成指纹哈希
  const encoder = new TextEncoder();
  const data = encoder.encode(JSON.stringify(fingerprintData));
  const hashBuffer = await crypto.subtle.digest('SHA-256', data);
  const hashArray = Array.from(new Uint8Array(hashBuffer));
  return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}

注意:设备指纹生成需要考虑用户隐私政策,在收集前应获取用户授权,并提供明确的隐私说明。

3. 网络请求的深度配置

3.1 UserAgent的高级管理

UserAgent设置绝非简单的字符串拼接,而是需要策略性的规划。以下是几个关键考量点:

  1. 信息分层 :将固定信息和动态信息分开管理
  2. 版本控制 :便于服务端区分不同版本客户端的请求
  3. 扩展性 :预留字段应对未来需求变化
class UserAgentManager {
  constructor() {
    this.baseUA = `MyApp/${plus.runtime.version}`;
    this.dynamicParts = [];
  }
  
  addDynamicPart(key, value) {
    this.dynamicParts.push(`${key}/${value}`);
    this._updateUA();
  }
  
  removeDynamicPart(key) {
    this.dynamicParts = this.dynamicParts.filter(part => !part.startsWith(key));
    this._updateUA();
  }
  
  _updateUA() {
    const dynamicStr = this.dynamicParts.length > 0 
      ? ` ${this.dynamicParts.join(' ')}` 
      : '';
    plus.navigator.setUserAgent(`${this.baseUA}${dynamicStr}`);
  }
  
  getCurrentUA() {
    return plus.navigator.getUserAgent();
  }
}

// 使用示例
const uaManager = new UserAgentManager();
uaManager.addDynamicPart('NetType', plus.networkinfo.getCurrentType());
uaManager.addDynamicPart('Lang', plus.os.language);

3.2 网络状态实时监控

网络状态变化是移动端常见场景,需要建立完善的监控和应对机制:

let networkState = {
  lastType: null,
  lastChangeTime: null,
  isOnline: true
};

function monitorNetwork() {
  // 初始状态
  updateNetworkState();
  
  // 监听变化
  plus.networkinfo.addEventListener('change', updateNetworkState);
}

function updateNetworkState() {
  const currentType = plus.networkinfo.getCurrentType();
  const wasOnline = networkState.isOnline;
  const nowOnline = currentType !== plus.networkinfo.CONNECTION_NONE;
  
  networkState = {
    lastType: currentType,
    lastChangeTime: new Date(),
    isOnline: nowOnline
  };
  
  // 触发网络变化事件
  if (wasOnline !== nowOnline) {
    uni.$emit('networkChange', {
      isOnline: nowOnline,
      networkType: getNetworkTypeName(currentType)
    });
    
    // 自动重连或提示用户
    handleNetworkTransition(wasOnline, nowOnline);
  }
}

function getNetworkTypeName(type) {
  const types = {
    [plus.networkinfo.CONNECTION_UNKNOW]: 'unknown',
    [plus.networkinfo.CONNECTION_NONE]: 'none',
    [plus.networkinfo.CONNECTION_ETHERNET]: 'ethernet',
    [plus.networkinfo.CONNECTION_WIFI]: 'wifi',
    [plus.networkinfo.CONNECTION_CELL2G]: '2g',
    [plus.networkinfo.CONNECTION_CELL3G]: '3g',
    [plus.networkinfo.CONNECTION_CELL4G]: '4g'
  };
  return types[type] || 'unknown';
}

function handleNetworkTransition(wasOnline, nowOnline) {
  if (!wasOnline && nowOnline) {
    // 网络恢复,自动重试待处理请求
    retryPendingRequests();
    uni.showToast({ title: '网络已恢复', icon: 'success' });
  } else if (wasOnline && !nowOnline) {
    // 网络断开,暂停敏感操作
    pauseUploadsAndDownloads();
    uni.showToast({ title: '网络连接已断开', icon: 'none' });
  }
}

4. 构建健壮的网络请求层

4.1 请求拦截与统一管理

基于uni.request封装更强大的网络工具类:

class NetworkService {
  constructor() {
    this.pendingRequests = new Map();
    this.requestQueue = [];
    this.isOnline = true;
    
    // 监听网络状态
    uni.$on('networkChange', ({ isOnline }) => {
      this.isOnline = isOnline;
      if (isOnline) this.processQueue();
    });
  }
  
  async request(config) {
    if (!this.isOnline) {
      return new Promise((resolve) => {
        this.requestQueue.push({ config, resolve });
      });
    }
    
    const requestId = Date.now();
    this.pendingRequests.set(requestId, true);
    
    try {
      // 添加统一UA
      config.header = config.header || {};
      config.header['User-Agent'] = plus.navigator.getUserAgent();
      
      // 添加设备信息
      config.header['X-Device-Id'] = await getDeviceFingerprint();
      
      const response = await uni.request(config);
      this.pendingRequests.delete(requestId);
      return this._transformResponse(response);
    } catch (error) {
      this.pendingRequests.delete(requestId);
      throw this._transformError(error);
    }
  }
  
  processQueue() {
    while (this.requestQueue.length > 0) {
      const { config, resolve } = this.requestQueue.shift();
      resolve(this.request(config));
    }
  }
  
  _transformResponse([err, res]) {
    if (err) throw this._transformError(err);
    return {
      data: res.data,
      status: res.statusCode,
      headers: res.header,
      config: res.config
    };
  }
  
  _transformError(err) {
    return {
      message: err.errMsg || 'Network Error',
      code: err.statusCode || -1,
      isNetworkError: !navigator.onLine,
      timestamp: new Date().toISOString()
    };
  }
  
  cancelAll() {
    this.requestQueue = [];
    this.pendingRequests.clear();
  }
}

// 单例模式导出
export const networkService = new NetworkService();

4.2 Cookie管理策略

App端的Cookie管理需要特别注意跨域和安全问题:

const CookieManager = {
  set(key, value, options = {}) {
    const domain = options.domain || location.hostname;
    const path = options.path || '/';
    const expires = options.expires 
      ? new Date(Date.now() + options.expires * 1000).toUTCString() 
      : '';
    
    const cookieStr = `${encodeURIComponent(key)}=${encodeURIComponent(value)}` +
      `${domain ? `;domain=${domain}` : ''}` +
      `${path ? `;path=${path}` : ''}` +
      `${expires ? `;expires=${expires}` : ''}` +
      `${options.secure ? ';secure' : ''}` +
      `${options.httpOnly ? ';httponly' : ''}`;
    
    plus.navigator.setCookie(domain, cookieStr);
  },
  
  get(key) {
    const cookies = plus.navigator.getCookie(location.hostname);
    const match = cookies.match(new RegExp(`(^| )${encodeURIComponent(key)}=([^;]+)`));
    return match ? decodeURIComponent(match[2]) : null;
  },
  
  remove(key) {
    this.set(key, '', { expires: -1 });
  },
  
  clearAll() {
    plus.navigator.removeAllCookie();
  }
};

// 增强型Cookie操作:同步WebView和HTTP请求的Cookie
function syncCookiesToHttp() {
  const cookies = plus.navigator.getCookie(location.hostname);
  if (cookies) {
    uni.setStorageSync('http-cookies', cookies);
  }
}

// 在每次请求前注入Cookie
networkService.interceptors.request.use(config => {
  const cookies = uni.getStorageSync('http-cookies');
  if (cookies) {
    config.header = config.header || {};
    config.header.Cookie = cookies;
  }
  return config;
});

5. 实战:构建完整的网络模块

将上述各个部分整合为一个完整的网络模块:

// network.js
import { generateDeviceFingerprint } from './device';
import { UserAgentManager } from './user-agent';
import { NetworkService } from './network-service';

// 初始化UA管理器
export const uaManager = new UserAgentManager();

// 初始化网络服务
export const networkService = new NetworkService();

// 全局网络状态
export const networkState = {
  isOnline: true,
  type: 'unknown',
  lastChange: null
};

// 初始化网络模块
export function initNetworkModule() {
  // 设置基础UA
  uaManager.addDynamicPart('AppVersion', plus.runtime.version);
  uaManager.addDynamicPart('OS', `${plus.os.name} ${plus.os.version}`);
  
  // 监听网络变化
  plus.networkinfo.addEventListener('change', () => {
    const type = plus.networkinfo.getCurrentType();
    const isOnline = type !== plus.networkinfo.CONNECTION_NONE;
    
    networkState.isOnline = isOnline;
    networkState.type = getNetworkTypeName(type);
    networkState.lastChange = new Date();
    
    uni.$emit('networkStateChange', { ...networkState });
  });
  
  // 初始状态
  const initialType = plus.networkinfo.getCurrentType();
  networkState.isOnline = initialType !== plus.networkinfo.CONNECTION_NONE;
  networkState.type = getNetworkTypeName(initialType);
  networkState.lastChange = new Date();
  
  // 设备指纹初始化
  generateDeviceFingerprint().then(fingerprint => {
    uni.setStorageSync('device-fingerprint', fingerprint);
  });
  
  console.log('Network module initialized', networkState);
}

// 网络请求快捷方式
export const http = {
  get(url, config = {}) {
    return networkService.request({ ...config, url, method: 'GET' });
  },
  post(url, data, config = {}) {
    return networkService.request({ ...config, url, data, method: 'POST' });
  },
  // 其他HTTP方法...
};

// 在应用启动时初始化
document.addEventListener('plusready', initNetworkModule);

这个完整的网络模块提供了以下功能:

  1. 统一的设备信息采集和管理
  2. 智能的UserAgent管理
  3. 健壮的网络状态监控
  4. 强大的请求拦截和队列管理
  5. 完整的Cookie同步机制
  6. 简洁的API接口

在实际项目中,你可以这样使用:

import { http } from '@/network';

// 发起请求
http.get('/api/user/info', {
  params: { userId: 123 },
  headers: { 'X-Custom-Header': 'value' }
}).then(response => {
  console.log('用户数据:', response.data);
}).catch(error => {
  console.error('请求失败:', error);
});

// 监听网络状态
uni.$on('networkStateChange', (state) => {
  console.log('网络状态变化:', state);
  if (!state.isOnline) {
    showOfflineMessage();
  }
});

通过这样一套完整的解决方案,你的UniApp应用将具备:

  • 更准确的设备识别能力
  • 更可靠的网络请求机制
  • 更完善的错误处理和恢复能力
  • 更统一的信息管理策略

这些改进将显著提升App在真实用户环境中的稳定性和用户体验,减少因网络波动或设备差异导致的问题。

更多推荐