uni-app 中实现 onLaunch 异步回调后执行 onLoad 最佳实践

前言

好久没写博客了,由于公司业务需要,最近接触uiapp比较多,一直想着输出一些相关的文章。正好最近时间富余,有机会来一波输出了。

问题描述

在使用 uni-app 开发项目时,会遇到需要在 onLaunch 中请求接口返回结果,并且此结果在项目各个页面的 onLoad 中都有可能使用到的需求,比如微信小程序在 onLaunch 中进行登录后取得 openid 并获得 token,项目各页面需要带上该 token 请求其他接口。

问题原因

在onLaunch 中的请求是异步的,也就是说在执行 onLaunch 后页面 onLoad 就开始执行了,而不会等待 onLaunch 异步返回数据后再执行,这就导致了页面无法拿到 onLaunch 中异步获取的数据。

解决问题

知道问题原因之后,解决起来就容易了。作为资深白嫖党,先是搜索了相关资料,发现了下面的解决方案。

解决方案一

既然在onLaunch中请求是异步的原因导致这个问题,那改成同步的不就行了,这里利用Promise来解决这个问题。步骤如下。

步骤一

在 main.js 中增加如下代码:

Vue.prototype.$onLaunched = new Promise(resolve => {
    Vue.prototype.$isResolve = resolve
})
步骤二

在 App.vue 的 onLaunch 中增加代码 this.$isResolve(),具体如下:

onLaunch () {
    uni.login({
        provider: 'weixin',
        success: loginRes => {
            login({ // 该接口为我们自己写的获取 openid/token 的接口,请替换成自己的
                code: loginRes.code
            }).then(res => {
                try {
                    console.info(res.data.token)
                    uni.setStorageSync('token', res.data.token)
                    this.$isResolve()
                } catch (e) {
                    console.error(e)
                }
            })
        }
    })
}
步骤三

在页面 onLoad 中增加代码 await this.$onLaunched,具体如下:

async onLoad(option) {
    await this.$onLaunched

    let token = ''
    try {
        token = uni.getStorageSync('token')
    } catch(e) {
        console.error(e)
    }

    // 下面就可以使用 token 调用其他相关接口
}

有了这个解决方案,我就开始在实际项目中是用来了。但随着项目的复杂度增加,发现这个方案使用起来有一些弊端。每个页面都需要在 onLoad 中增加代码代码也太烦人了。

有没有更优雅的方案呢?继续查找资料,有个解决方案是定制一个页面钩子,然后注册全局的异步任务,定义钩子的触发条件,满足条件时即可自动执行页面里相关的钩子。相关方案见参考资料2。

但这个方案我也不太满意,仍然需要在页面添加一些函数去响应请求。后面突然想到,可以监听路由变动,在路由跳转之前完成请求。

解决方案二(推荐)

正好项目中用到了uni-simple-router插件,提供了全局前置守卫事件beforeEach,其本质是代理了所有的生命周期,让生命周期更加可控,这样就可以很好的解决我们面临的问题了。步骤如下:

步骤一

在 route.js 增加如下代码:

// 登录(可放在公共函数里面)
const login = () => {
    return new Promise(function(resolve, reject) {
        uni.login({
            provider: 'weixin',
            success: loginRes => {
                login({ // 该接口为我们自己写的获取 openid/token 的接口,请替换成自己的
                    code: loginRes.code
                }).then(res => {
                    resolve(res);
                }, err => {
                    reject(err);
                })
            },
            fail: err => {
                reject(err);
            }
        });
    });
}

// 获取token(可放在公共函数里面)
const getToken = () => {
    let token = ''
    try {
        token = uni.getStorageSync('token')
    } catch(e) {
        console.error(e)
    }
    return token;
}

// 是否登录
let hasLogin = false;
router.beforeEach(async (to, from, next) => {
    // 首次进来,没有登录并且token不存在先请求数据
    if(!hasLogin&&!getToken()){
        const res = await login();
        try {
            console.info(res.data.token)
            uni.setStorageSync('token', res.data.token)
            hasLogin = true
        } catch (e) {
            console.error(e)
        }
    }
    next()
})
步骤二

在页面 onLoad 中直接就可以获取 token 并使用,具体如下:

onLoad(option) {
    let token = ''
    try {
        token = uni.getStorageSync('token')
    } catch(e) {
        console.error(e)
    }

    // 下面就可以使用 token 调用其他相关接口
}

这个解决方案就灵活很多,只需要在 route.js 中写入代码,其他任意地方都可以调用。不用担心新增页面忘记相关方法的引入,更加灵活自由。

由于这个解决方案基于uni-simple-router插件,在使用前需要引入这个插件。如果不想引入插件,可以自行实现代码生命周期功能。

PS:大家有更好的解决方案,欢迎在评论区交流。

参考资料

  1. uni-app 中利用 Promise 实现 onLaunch 异步回调后执行 onLoad
  2. 小程序app.onLaunch与page.onLoad异步问题的最佳实践
  3. 代理生命周期 | uni-simple-router
<think>我们正在处理一个关于uni-app获取设备唯一标识符的问题。根据用户提供的引用内容,我们可以总结出在uni-app中获取设备唯一标识符的方法,主要针对APP平台(Android和iOS)。 根据引用[1]和引用[2]: 在uni-app中,我们可以使用`plus.device`来获取设备信息。但是,由于不同平台(特别是Android和iOS)以及不同版本设备的差异,获取方式略有不同。 引用[1]中提供了一个`getClientId`方法,该方法尝试获取设备的唯一标识符(UUID或AAID)并存储到缓存中。具体步骤包括: 1. 对于iOS,使用`plus.device.getInfo`获取`uuid`。 2. 对于Android,首先尝试使用`plus.device.getAAID`获取`aaid`(这是Android的广告标识符,但注意它可能因用户重置广告标识而改变)。 3. 如果上述方法失败(例如在老版本Android设备或模拟器上),则使用`plus.device.uuid`作为备选。 引用[2]则展示了在页面加载时直接使用`plus.device.uuid`获取设备唯一标识符,并将其赋值给一个变量。 但是,需要注意的是,`plus.device.uuid`在iOS上获取的是UUID,而在Android上获取的是IMEI(需要权限)或随机生成的标识符(如果没有权限则可能每次安装都不同)。因此,为了更可靠地获取设备唯一标识,我们采用引用[1]的方法,并注意以下几点: - 在Android上,推荐使用AAID(广告标识符),因为它更稳定,且不需要权限(但用户可重置)。 - 在iOS上,UUID是设备的唯一标识,但注意在iOS5之后,应用卸载重装后UUID会改变。所以如果希望跨应用卸载保持唯一,需要使用Keychain存储,但这需要额外的处理。 然而,根据uni-app官方文档,`plus.device.getAAID`是HTML5+规范中定义的,用于获取安卓设备的匿名设备标识符(AAID)。该标识符在系统恢复出厂设置或用户重置广告标识时会改变。 因此,如果用户需要的是在设备生命周期内保持不变的标识符(如IMEI或序列号),则需要注意: - 在Android 10及以上,获取IMEI等设备标识符需要特殊权限(READ_PRIVILEGED_PHONE_STATE),而普通应用无法获得该权限,因此通常无法获取。 - 在iOS上,无法获取设备的IMEI等,只能获取UUID,且每次应用卸载重装都会改变(除非使用Keychain存储)。 考虑到这些,我们通常使用AAID(Android)和UUID(iOS)作为设备标识符,但要注意它们的限制。 下面我们提供一个改进的方法,结合引用[1]和引用[2]: 1. 首先,我们定义一个方法,用于异步获取设备唯一标识符,并将结果缓存到本地存储中,以便后续使用。 2. 对于Android,我们优先获取AAID,如果获取失败,则尝试使用`plus.device.uuid`(在较新的Android版本中,`plus.device.uuid`可能返回的是Android ID,即8位的十六进制字符串,但注意Android ID也有其限制,例如在Android 8.0以上,不同应用签名获取的Android ID不同)。 3. 对于iOS,我们使用`plus.device.getInfo`来获取UUID。 但是,根据HTML5+的文档,`plus.device.getInfo`在iOS上获取的UUID是系统提供的唯一标识,但该标识在应用重新安装时会改变。如果我们需要一个即使应用重新安装也不变的标识,则需要使用Keychain。但Keychain操作在uni-app中需要原生插件,这里我们只使用标准方法。 因此,我们提供以下代码示例(注意:这段代码需要运行在APP-PLUS环境下,即打包成APP): ```javascript // 获取设备唯一标识符,并缓存 function getDeviceId() { // 先从缓存中取 let deviceId = uni.getStorageSync('device_id'); if (deviceId) { return deviceId; } // 根据不同平台获取 // #ifdef APP-PLUS if (uni.getSystemInfoSync().platform === 'android') { // 安卓设备 plus.device.getAAID({ success: (e) => { console.log('getAAID success:', e.aaid); deviceId = e.aaid; uni.setStorageSync('device_id', deviceId); }, fail: (e) => { console.log('getAAID failed:', JSON.stringify(e)); // 获取AAID失败,使用uuid(注意:这个uuid在安卓上可能是Android ID或设备标识,但不同设备行为可能不同) deviceId = plus.device.uuid; uni.setStorageSync('device_id', deviceId); } }); } else { // iOS设备 plus.device.getInfo({ success: (e) => { console.log('getInfo success:', e.uuid); deviceId = e.uuid; uni.setStorageSync('device_id', deviceId); }, fail: (e) => { console.log('getInfo failed:', JSON.stringify(e)); // 如果失败,使用uuid(但iOS的getInfo失败后,plus.device.uuid可能不可靠) deviceId = plus.device.uuid; uni.setStorageSync('device_id', deviceId); } }); } // #endif // 如果同步获取不到,这里返回的deviceId可能是空字符串,因此建议使用回调或Promise确保获取到 return deviceId; // 注意:由于异步,这里可能返回空 } ``` 但是,上面的方法有一个问题:由于`getAAID`和`getInfo`都是异步操作,所以当函数返回时,可能还没有获取到deviceId,因此直接返回可能为空。 为了解决异步问题,我们可以将函数改为异步,使用回调或Promise。这里我们使用Promise: ```javascript function getDeviceId() { return new Promise((resolve, reject) => { let deviceId = uni.getStorageSync('device_id'); if (deviceId) { resolve(deviceId); return; } // #ifdef APP-PLUS if (uni.getSystemInfoSync().platform === 'android') { plus.device.getAAID({ success: (e) => { deviceId = e.aaid; uni.setStorageSync('device_id', deviceId); resolve(deviceId); }, fail: (e) => { console.log('getAAID failed:', JSON.stringify(e)); deviceId = plus.device.uuid; uni.setStorageSync('device_id', deviceId); resolve(deviceId); } }); } else { plus.device.getInfo({ success: (e) => { deviceId = e.uuid; uni.setStorageSync('device_id', deviceId); resolve(deviceId); }, fail: (e) => { console.log('getInfo failed:', JSON.stringify(e)); deviceId = plus.device.uuid; uni.setStorageSync('device_id', deviceId); resolve(deviceId); } }); } // #else // 非APP平台,可以尝试其他方法,比如H5使用浏览器指纹等,这里不展开 resolve(''); // 非APP环境返回空 // #endif }); } ``` 这样,我们在需要获取设备ID的地方,可以这样调用: ```javascript getDeviceId().then(deviceId => { console.log('设备唯一标识符:', deviceId); // 这里可以使用deviceId }); ``` 注意:在Android上,AAID的获取需要设备支持Google Play服务,如果设备没有Google Play服务,则可能会失败,此时我们使用`plus.device.uuid`。在iOS上,`plus.device.getInfo`获取的UUID是苹果提供的设备标识,但注意它可能会变(比如系统升级、恢复出厂设置等)。 另外,由于设备标识符可能涉及用户隐私,在获取和使用时,需要遵守相关法律法规,向用户说明并获取授权。 总结:在uni-app中获取设备唯一标识符,我们优先使用平台推荐的方法(Android用AAID,iOS用UUID),并将结果缓存,避免重复获取。同时,由于获取是异步的,我们使用Promise来确保在获取到后再使用。 以上代码示例可以直接在uni-app项目中使用。 根据用户的问题,我们提供了详细的获取方法和代码示例。同时,我们也要注意,不同平台和设备上的差异,以及隐私政策的要求。 相关问题: 1.uni-app中,如何获取设备的其他信息(如型号、系统版本)? 2.uni-app中,如何确保获取的设备唯一标识符符合隐私政策要求? 3. 如果用户重置了广告标识符(在Android上)或重装应用(在iOS上),设备唯一标识符会改变吗?如何应对?</think>在 uni-app 中获取设备唯一标识符,需要根据平台特性使用不同的方法。以下是完整实现方案: ### 核心实现方法 ```javascript // 获取设备唯一标识(推荐使用) export function getDeviceId() { // 1. 优先从缓存读取 let deviceId = uni.getStorageSync('device_id') if (deviceId) return deviceId // 2. 平台相关获取逻辑 // #ifdef APP-PLUS if (uni.getSystemInfoSync().platform === 'android') { // Android 设备 plus.device.getAAID({ success: (e) => { deviceId = e.aaid uni.setStorageSync('device_id', deviceId) }, fail: () => { // 备用方案:使用UUID(可能不稳定) deviceId = plus.device.uuid || Math.random().toString(36).substr(2) uni.setStorageSync('device_id', deviceId) } }) } else { // iOS 设备 plus.device.getInfo({ success: (e) => { deviceId = e.uuid uni.setStorageSync('device_id', deviceId) }, fail: () => { // 备用方案 deviceId = plus.device.uuid || 'ios_' + Date.now() uni.setStorageSync('device_id', deviceId) } }) } // #endif // 3.APP环境返回空 // #ifndef APP-PLUS deviceId = 'non-app-device' // #endif return deviceId || 'unknown-device' } ``` ### 使用说明 1. **缓存机制**:首次获取后自动缓存到本地存储,后续直接读取缓存 2. **平台区分**: - **Android**:优先使用 `getAAID()`(广告标识符) - **iOS**:使用 `getInfo().uuid` - 备用方案:使用 `plus.device.uuid` 或生成随机ID 3. **异步处理**:获取操作是异步的,首次调用可能返回空值,建议: ```javascript // 页面中使用 onLoad() { setTimeout(() => { const deviceId = getDeviceId() console.log('设备ID:', deviceId) }, 500) } ``` ### 注意事项 1. **隐私合规**: - 在 `manifest.json` 中添加权限声明: ```json "app-plus": { "distribute": { "android": { "permissions": [ "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>" ] } } } ``` - 需在《隐私政策》中说明标识符收集目的 2. **平台限制**: - iOS 获取的 UUID 在应用删除重装后会变化 - Android AAID 在用户重置广告标识时会变化 3. **调试技巧**: ```javascript // 开发阶段可添加调试前缀 if (process.env.NODE_ENV === 'development') { deviceId = 'dev_' + deviceId } ``` ### 最佳实践场景 ```javascript // 在App.vue中初始化获取 export default { onLaunch() { this.$nextTick(() => { const deviceId = getDeviceId() uni.$deviceId = deviceId // 挂载到全局 }) } } // 其他页面直接使用 export default { methods: { submitData() { const data = { deviceId: uni.$deviceId, // ...其他数据 } // 提交到服务器 } } } ``` > **关键提示**:设备标识符的稳定性和唯一性受系统限制,对于需要持久化标识的场景(如用户追踪),建议结合登录账号体系实现[^1][^2]。
评论 5
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值