手摸手教你实现uniApp的推送(android & IOS)

2,575 阅读5分钟

背景:为了开发效率提升我们部门给项目配套做的移动端框架使用的是 uniapp,一套代码多端运行。目前使用结果来看有坑,但还能接受,对于业务代码实现很方便,速度较快。对应的前端框架是 vue.js。所以上手入门较快,当然实际的应用效果体验肯定和原生的没法比,这些不多说。下面来说下我遇到的最大的一个坑,推送的处理情况。

android实现

新建一个项目后,默认当前项目是没有开通UniPush的,所以第一步是需要去开发者中心开通UniPush。

Android的包名和 IOS 的 bundleId 是一个东西,可以简单的认为是应用的唯一Id。这两个信息都要记住,在很多需要配置的地方都要使用。

这里的android包名随便写,这里也给出了示例。下面我以亚光App为模板描述如何配置

(1) 第一个的包名写好后需要获取 Android 的应用签名。这个签名文件就是打包证书,那么这个打包证书如何获取呢,通过jdk的工具行命令就能够得到。

keytool -genkey -v -keystore myApp.keystore -alias myApp.keystore -keyalg RSA -validity 30000

下面是生成命令行的描述信息

实际操作截图

如果本地没有jdk环境的话可以自行百度搭建

证书生成之后我们要获取他的指纹信息 命令如下 keytool -list -v -keystore myApp.keystore[注意此处是你的 jks 文件路径]

之后把指纹信息拷贝到 uniApp 的开发者平台中去,暂时可以不用设置 ios 的推送。

第二步骤 当你开通 uniPush 之后可以看到改界面,该处界面可以给你测试推送消息是否到达。

这里是基本的初版配置,如果需要消息更精确和支持离线消息需要配置厂商推送设置 (PS:如果初次为了尝试通知是否成功或者不需要配置厂商通道则直接跳到移动代码部分即可)

这里我们配置小米和华为的就足够。其中小米的开通很简单

创建成功后即可看到如下所示

在应用列表中可以找到你的应用并能看到启用的标示说明你开通推送服务成功。

3. 下面我们来看看华为的推送配置,略有复杂

华为的操作比较复杂,需要先创建项目,应用是在项目下。

创建项目成功后会看到如下所示

点击立即开通服务

服务开通后点击我的应用,开始新建

当填写完信息后出现如下类似界面说明配置完成

移动端代码

       /**
		 * 处理推送消息
		 */
		handlePush() {
			var that = this;
			// 监听网络状态变化
			uni.onNetworkStatusChange(function (res) {
			    // console.log('监听网络状态变化',res);
				that.globalData.isNetConnected = res.isConnected;
			});
			// #ifdef APP-PLUS
			const _handlePush = function(message) {
				// 获取自定义信息
				plus.push.clear(); //清空系统消息中心所有的推送消息
				// plus.push.getAllMessage(); //获取所有推送消息
				plus.runtime.setBadgeNumber(0); //设置程序快捷方式图标上显示的角标数字
				//跳转到消息页面
				if (plus.os.name == 'iOS') {
					var GeTuiSdk = plus.ios.importClass('GeTuiSdk');
					GeTuiSdk.setBadge(0);
				}
				
				try {
					let login = uni.getStorageSync('applogin');
					if(login) {
						// 携带自定义信息跳转应用页面
						uni.navigateTo({
							url: '/pages/message/msgcenter/msgcenter'
						})
					}
					
				} catch (e) {}
			};
			// 事件处理
			plus.push.addEventListener('click', _handlePush);
			
			plus.push.addEventListener("receive", (res) => { 
				console.log('receive  ====', res);
				
				if (plus.os.name == 'iOS') {
					var payload = res.payload;
					//【APP离线】收到消息,但没有提醒(发生在一次收到多个离线消息时,只有一个有提醒,但其他的没有提醒)  
					//【APP在线】收到消息,不会触发系统消息,需要创建本地消息,但不能重复创建。必须加msg.type验证去除死循环 
					// if (res.aps == null && res.type == "receive") {   
					if (res.type == "receive") {   
						var UIApplication = plus.ios.import("UIApplication");
						var app = UIApplication.sharedApplication();  
							
						//获取应用图标的数量  
						var oldNum = app.applicationIconBadgeNumber() || 1;  
						var newNum = oldNum ++;  
						//设置应用图标的数量
						plus.runtime.setBadgeNumber(newNum);  
						//导入个推原生类  
						var GeTuiSdk = plus.ios.importClass('GeTuiSdk');  
						GeTuiSdk.setBadge(newNum); 
							
						var messageTitle = res.title;  
						var messageContent = res.content;  
							
						//创建本地消息,发送的本地消息也会被receive方法接收到,但没有type属性,且aps是null  
						plus.push.createMessage(messageContent, JSON.stringify(payload), {title: messageTitle, cover: false});  
					}  
				} else {
					// android 推送通知由原生代码实现
					this.$myNJS.aOSNotify(res.title, res.content);
				}
				
				
				if (res.type) {
					// 接收到推送的通知消息后刷新首页
					uni.$emit('refreshHomePageNotifition');	// 触发刷新首页
					uni.$emit('refreshHomeMessageNotifition');	// 刷新消息界面
				}
			}, false);
			// #endif
		},

tips: 这里推送判断的时候我分了 android 和 ios 两种情况,android是一个工具类,内部通知实现是原生代码实现,原因是我们实际应用测试中发现如果直接使用 createMessage 在某些机型上会有出现通知没有声音的情况,或者出现有些手机能打印出 receive 事件的消息但是消息出不来的情况(即认为 createMessage 创建失败)

myNJS全部源码

let myNJS = {
	'aOSNotify': aOSNotify,
	'aOSReceive': aOSReceive,
}

// 获取版本信息
const osVersion = {
	versionNum:() => {
		let SystemVersion = plus.os.version;
		let VersionNumber = Number(SystemVersion.split('.')[0]);  
		return VersionNumber;
	}
}
/**  
  * android原生通知发送  
  * @param content 通知内容  
  * @param data json对象,存储通知的隐藏数据,用于点击通知时接收使用  
  */  
function aOSNotify(title = '', content = '', data = {}, channelID = '1', channelName = '通知') {  
	// #ifdef APP-PLUS  
	// var title = '通知标题';  

	console.log('准备通知');  
	console.log(plus.os.name);  

	// Android平台下才使用此推送  
	if (plus.os.name != 'Android') {  
	  return false;  
	}

	//随机生成通知ID  
	var notifyID = Math.floor(Math.random() * 10000) + 1; 
	//先给channel一个默认值

	//传递参数
	if (osVersion.versionNum() >= 8){
		var payload = {
			'title':title,
			'msg':content,
			'channel_id':channelID,
			'notify_id':notifyID,  
			'data':data  
		};  
	} else {  
		var payload = {
			'title':title,
			'msg':content,
			'notify_id':notifyID,  
			'data':data  
		};      
	} 

	// 获取应用主Activity实例对象
	var main = plus.android.runtimeMainActivity();  
	// importClass: 导入Java类对象
	// 导入上下文(官方翻译)
	var Context = plus.android.importClass("android.content.Context");  
	// 导入通知管理相关
	var NotificationManager = plus.android.importClass("android.app.NotificationManager"); 
	var Notification = plus.android.importClass("android.app.Notification"); 
	// 
	var Intent = plus.android.importClass("android.content.Intent");  
	var PendingIntent = plus.android.importClass("android.app.PendingIntent");    
	var nm = main.getSystemService(Context.NOTIFICATION_SERVICE);  

	var intent = new Intent(main, main.getClass());  
	intent.putExtra("receive", JSON.stringify(payload));  

	// PendingIntent.getActivity的第二个参数需要设置为随机数,否则多个通知时会导致前面的通知被后面的通知替换Extra的数据,此处我将其和对应通知的id绑定起来
	var pendingIntent = PendingIntent.getActivity(main, notifyID, intent, PendingIntent.FLAG_CANCEL_CURRENT);  

	//可能用到R的一些资源文件
	var r = plus.android.importClass("android.R");
	
	var mNotification;
	//判断当前系统版本在8.0及以上  
	if (osVersion.versionNum() >= 8){
		if (nm.getNotificationChannel() == null){
			var NotificationChannel = plus.android.importClass('android.app.NotificationChannel');
			var channel = new NotificationChannel(channelID, channelName, NotificationManager.IMPORTANCE_HIGH);  
			nm.createNotificationChannel(channel);  
		}
		mNotification = new Notification.Builder(main, channelID);
		mNotification.setDefaults(Notification.DEFAULT_ALL); // mNotification.setDefaults(~0);
	} else {  
		mNotification = new Notification.Builder(main);   
		mNotification.setDefaults(Notification.DEFAULT_ALL);
	}  
	var i= notifyID.toString();
	mNotification.setVisibility(Notification.VISIBILITY_PUBLIC)		//设置可见性
	//mNotification.setOngoing(true)		//设置这是否为“正在进行中”通知
	mNotification.setContentTitle(title + i)       //设置标题  
	mNotification.setContentText(content);       //设置内容  
	mNotification.setAutoCancel(true);       //设置点击消失  
	mNotification.setShowWhen(true);       //显示通知时间,貌似不加这句也能显示   
	mNotification.setSmallIcon(r.drawable.stat_notify_chat);
	//mNotification.setSmallIcon(17301543);       //设置app通知小图标,暂只能使用R.drawable文件中的默认值 
	
	mNotification.setTicker(title);       //设置发送到无障碍服务的“ ticker”文本。 
	// mNotification.setDefaults(~0); 		//这两行结合上面的setTicker实现悬浮提示过几秒自动消失,基于覆盖考虑,把这个放到最上方
	mNotification.setPriority(Notification.PRIORITY_HIGH);     //通知优先级 

	//mNotification.setFullScreenIntent(pendingIntent, false);   //弹出式提示不消失
	mNotification.setContentIntent(pendingIntent);    
	var mNb = mNotification.build();  
	console.log(notifyID);

	//判断当前系统版本在8.0及以上    
	if (osVersion.versionNum() >= 8){  
		nm.notify(channelID, notifyID, mNb);    
	} else {  
		nm.notify(notifyID, mNb);  
	}  
	
	console.log('通知结束');  
	return true;  
	// #endif  
}

 /**  
   * android原生通知点击后所执行的内容  
   */  
function aOSReceive() {  
	// #ifdef APP-PLUS  
	if (plus.os.name != 'Android') {  
		return false;  
	}  
	// android原生通知栏接收器(程序退出后无效)  
	var main = plus.android.runtimeMainActivity();  
	var intent = main.getIntent();  
	var message = intent && intent.getExtra != undefined ? intent.getExtra("receive") : null;  
	console.log(message);  
	message = message ? JSON.parse(message) : '';  
	
	if (message) {  
		//删除当前通知  
		var Context = plus.android.importClass("android.content.Context");  
		var nm = main.getSystemService(Context.NOTIFICATION_SERVICE);  
		console.log(message.notify_id);  
		if (osVersion.versionNum() >= 8){
			nm.cancel(message.channel_id, message.notify_id);//安卓版本大于等于8的      
		} else {
			nm.cancel(message.notify_id);//安卓版本小于8的  
		} 

		// 把消息数据置空,以免再次打开APP时重复执行此处  
		intent.putExtra("receive", '');  
		 
		 try {
		 	let login = uni.getStorageSync('applogin');
		 	if(login) {
		 		// 携带自定义信息跳转应用页面
		 		uni.navigateTo({
		 			url: '/pages/message/msgcenter/msgcenter'
		 		})
		 	}
		 } catch (e) {};
	}  
	// #endif  
} 
 
export default myNJS

这里是导出了一个工具类,在 main.js 中加入下面两行代码

import myNJS from "pages/utils/myNJS.js";
Vue.prototype.$myNJS = myNJS

到以上代码为止 android 推送基本没问题,测试了各个类型的手机锁屏和后台接收和展示通知情况都正常。

至于 ios 配置比较简单,包名设置之后直接上传 ios 的推送证书即可。

代码在上方的源码中也已经给出。

目前还有的问题:

  1. ios 在某些情况下的锁屏接收通知会有通知出现立刻消失的情况,可以听到通知到达手机的声音提示,但是通知一闪而过,目前没有彻底解决,加入了官方群提问也没有给出确切的答复和解决方案。
  2. android推送正常,不过遇到一个很奇怪的问题是我为了测试 ios 的推送所以把编译器版本升级到了最新的 3.0.7,配置了华为厂商推送需要上传的配置 json 文件(只有配置厂商推送才会出现该提示,说是因为华为升级了sdk)。然后遇到了华为手机推送通知不走 receive 的情况,其他类型手机没有测试。(说白了就是一开始功能好好地没问题,后来升级后发现代码都不走 receive 事件了,搞的我是一脸懵逼)

以上即为遇到的问题和解决的问题全部内容,如果有解决办法的大佬请留言联系我谢谢。

PS:还有一个很重要的一点是如果想要真机连接调试推送的话需要使用自定义的基座,很重要,使用自定义基座后可以获取到 clientId,就能够在uniapp的开发者后台调试了。如果使用标准基座推送是接收不到的。