油老师

covercover

UniApp 通过 APP 保活实现实时消息推送

解决的问题

使用 UniApp Push 2.0 服务需要在手机厂商开发者获取相应的推送密钥,但是申请密钥的过程极其繁琐,甚至需要各种证、备案等,有些厂商甚至仅开放企业用户开发者注册。

原理

在安卓系统中,当应用进入后台或者被用户关闭后,通常会被系统回收资源以达到系统性能优化的目的。但是,有些应用程序需要在后台长时间运行,比如音乐播放器、即时通讯等,这时就需要使用一些技术手段来保持应用程序的运行状态,以确保应用程序能够正常运行。

本文主要使用后台播放音频和定时激活的方式解决无法实时推送,缺点是一段时间后还是会被系统杀后台,而且 APP 不能关闭,同时系统其他声音会被暂停。

实现

1️⃣进行 manifest.json 配置

  1. APP 权限
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.READ_LOGS"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-feature android:name="android.hardware.camera.autofocus"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.FLASHLIGHT"/>
<uses-feature android:name="android.hardware.camera"/>
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.GET_TASKS" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.BROADCAST_PACKAGE_ADDED" />
<uses-permission android:name="android.permission.BROADCAST_PACKAGE_CHANGED" />
<uses-permission android:name="android.permission.BROADCAST_PACKAGE_INSTALL" />
<uses-permission android:name="android.permission.BROADCAST_PACKAGE_REPLACED" />
<uses-permission android:name="android.permission.RESTART_PACKAGES" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
  1. 模块

2️⃣封装 Push 方法

let interval = null
const pushAlert = {
  title: '油油',
  content: '正在后台运行中',
}
const music = uni.createInnerAudioContext() // 创建播放器对象
music.src = '/static/sound/noSound.m4a' // 这里是无声音频文件地址,网上很多
music.loop = true // 循环

// 这个推送仅作为后台运行提示
music.onPlay(() => {
  uni.createPushMessage({
    ...pushAlert,
    cover: true,
  })
})

/**
 * 推送消息
 *
 * 通过播放无声音频和定位来实现 APP 常驻后台,防止 APP 被系统杀死
 *
 * **onShowApp** 显示时关闭声音和定位,在 `onShow` 中调用
 *
 * **onHideApp** 隐藏时播放声音和定位,在 `onHide` 中调用
 *
 * @author Yoniu // xu_jingzhi@200011.net
 *
 */
export default {
  setPushAlert(title = '油油', content = '正在后台运行中') {
    pushAlert.title = title
    pushAlert.content = content
  },
  pushEventListener() {
    // #ifdef APP-PLUS
    // 接收推送消息
    uni.onPushMessage((res) => {
      switch (res.type) {
        case 'click':
            /// 这里是点击消息后执行的方法
          break
        default:
          uni.createPushMessage({
            title: res.data.title,
            content: res.data.content,
            payload: res.data.payload,
            sound: 'system',
          })
          break
      }
    })
    // #endif
  },
  onShowApp() {
    // 关闭定时获取定位
    if (interval != null) {
      clearInterval(interval)
      interval = null
    }
  },
  onHideApp() {
    if (interval != null) {
      clearInterval(interval)
      interval = null
    }
    // 开始播放无声音频
    music.play()
    // 定时获取定位
    interval = setInterval(() => {
      uni.getLocation({
        type: 'wgs84',
        success() {
        },
      })
    }, 5 * 1000)
  },
}

3️⃣App.vue

<script>
import push from './methods/push.js'

export default {
  onLaunch() {
		push.pushEventListener()
  },
	onShow() {
		push.onShowApp()
	},
	onHide() {
		push.onHideApp()
	}
};
</script>

4️⃣推送云函数

'use strict';
const uniPush = uniCloud.getPushManager({ appId:"" }) //注意这里需要传入你的应用appId,用于指定接收消息的客户端
exports.main = async (event, context) => {
	try {
		
		if (!event.body) {
			throw new Error("请求必填参数为空")
		}
		
		const obj = JSON.parse(event.body)
		
		const sendObj = await uniPush.sendMessage({
			"push_clientid": obj.cids, // 设备id,支持多个以数组的形式指定多个设备,如["cid-1","cid-2"]
			"title": obj.title, // 标题  
			"content": obj.content, // 内容  
			"payload": obj.payload, // 数据
			"request_id": obj.request_id // 请求唯一标识号,10-32位之间;如果request_id重复,会导致消息丢失
		})
		
		return {
			code: "200",
			message: "推送成功",
			data: sendObj,
		}
	} catch(error) {
		return {
			code: "400",
			message: error.message || "系统在执行相关操作时出现错误",
			data: null,
		}
	}
};

5️⃣后端推送

后端只需要执行云函数就可以实现推送。

另外后端需要处理用户设备 id,通过 UniApp 获取设备 id:

uni.getPushClientId({
    success: (res) => {
        const push_clientid = res.cid // 设备 id
    },
    fail(err) {},
})

评论

Copyright © 2014 - 2025 油老师.