2021-04-22

​      首先跟大家介绍两个核心关键词:通知、透传,通知是发送后会在系统通知栏展现,同时响铃或震动提醒用户,并且点击通知栏可以自定义点击行为,透传是发送后消息后台处理,用户无感知,在相应方法中回调,执行对应操作。

      付呗app作为一款我们公司主要的移动收单应用,为商户提供收款语音提醒功能,在推送语音方面面临着很大的挑战:一、语音播报的稳定性,二、推送达到率的高低;
      面对商户日交易量增长,商户重需求,android碎片化严重,国产品牌手机严格控制后台推送等这些问题,android客户端在推送语音优化,是一项任重道远的事情,可以说这是一场苦战、鏖战,但是最终通过团队的群策群力,使我们的推送语音播报率有了极大的提升。但是面对飞速增长的交易笔数,战斗还未结束,优化之路就不会停止,借用我们雷总在年中会议说的一句话:保持战斗状态。

      遇到的问题:app语音播报最初通过百度语音合成的方式,播报一段收款提醒:付呗为您成功收款0.01元,可能是考虑到简单、方便,直接通过第三方语音合成方案,由于前期对百度语音合成研究不够深入,上线后出现了一系列的问题,比如百度语音初始化、离在线合成失败、音量小等。

      查找发现问题:我们增加了一些关键埋点,比如商户手机音量情况,百度语音初始化情况,合成过程中错误回调,通过这些埋点我们快速定位到问题有以下几种可能性:商户手机音量设置问题、android6.0存储权限问题(百度语音配置文件无法写入存储卡)、网络问题(网络慢可能会导致TTS流合成出错)、语音播报延迟,通过这些问题我们给出的方案是通过本地语音代替百度语音合成。

      解决方案:下面通过商户最常见的收单语音:付呗为您成功收款0.01元,给大家详细阐述本地语音方案。最初想到可能需要大量的本地语音文件,才能实现,但是想想,其实不然,我们可以将收单语音需求拆分成固定的几段语音,包括付呗(应用名)、为您成功收款、零、点、零、壹、元,这里难点在于解析金额,如下:

public static String transferAmount(String amount) {       
     if (amount == null || "".equals(amount)) {            
        return null;      
     }      
     String xiaoshu = "";        
     if (amount.contains(".")) {           
            int pos = amount.indexOf(".");             
            xiaoshu = amount.substring(pos + 1);             
            amount = amount.substring(0, pos);      
     }      
     String ret ;       
     if (amount.length() > 16) {          
            String low12Bit = amount.substring(amount.length() - 12);
            String highBit = amount.substring(0, amount.length() - 12); 
            ret = processHightBit(highBit) + "兆" + transfer16Bit(low12Bit);
     } else {          
            ret = transfer16Bit(amount);     
     }        
    if (amount.equals("0") && !xiaoshu.equals("") && ret.equals("")) {
            ret = "零";     
    }       
    if (xiaoshu.equals("") || xiaoshu.equals("00") || xiaoshu.equals("0")) { 
            return ret + "元";     
    }        
    return ret + "点" + transferXiaoshu(xiaoshu) + "元"; 
}

最终返回结果零点零壹元,这样我们就可以将对应的文字,匹配相应的本地音频文件

public static List<Integer> transFormNumFile(String num) {
        List<Integer> audio_list = new ArrayList();        
         for (int i = 0; i < num.length(); i++) {
            String n = num.substring(i, i + 1);           
           if (n.equals("点")) {
                audio_list.add(Integer.valueOf(R.raw.voice_point));
            } else if (n.equals("零")) {
                audio_list.add(Integer.valueOf(R.raw.voice_zero));
            } else if (n.equals("壹")) {
                audio_list.add(Integer.valueOf(R.raw.voice_one));
            } else if (n.equals("貮")) {
                audio_list.add(Integer.valueOf(R.raw.voice_two));
            } else if (n.equals("叁")) {
                audio_list.add(Integer.valueOf(R.raw.voice_three));
            } else if (n.equals("肆")) {
                audio_list.add(Integer.valueOf(R.raw.voice_four));
            } else if (n.equals("伍")) {
                audio_list.add(Integer.valueOf(R.raw.voice_five));
            } else if (n.equals("陆")) {
                audio_list.add(Integer.valueOf(R.raw.voice_six));
            } else if (n.equals("柒")) {
                audio_list.add(Integer.valueOf(R.raw.voice_seven));
            } else if (n.equals("捌")) {
                audio_list.add(Integer.valueOf(R.raw.voice_eight));
            } else if (n.equals("玖")) {
                audio_list.add(Integer.valueOf(R.raw.voice_nine));
            } else if (n.equals("拾")) {
                audio_list.add(Integer.valueOf(R.raw.voice_ten));
            } else if (n.equals("佰")) {
                audio_list.add(Integer.valueOf(R.raw.voice_hundred));
            } else if (n.equals("仟")) {
                audio_list.add(Integer.valueOf(R.raw.voice_thousand));
            } else if (n.equals("万")) {
                audio_list.add(Integer.valueOf(R.raw.voice_ten_thousand));
            } else if (n.equals("元")) {
                audio_list.add(Integer.valueOf(R.raw.voice_yuan));
            }
        }        return audio_list;
    }

将这些本地音频文件存入数组List,这样通过循环,就能播报出付呗为您成功收款0.01元

for (i = 0; i < sound_map.size(); i++) {
       mp = sound_map.get(i); 
       if (mp != null) {
           mp.start();
           pauseWhile(mp.getDuration());
        }
}

但是我们还存在一些商户手机本身设置问题,因此在播报前,我们做了小小优化,手动将商户手机音量设置成最大,以及在语音设置,增加试听功能,去检测商户手机硬件是否存在问题。

      优化进阶:由于Android碎片化严重,以及国内厂商自定义系统,严格限制应用后台服务,像华为、小米、OV等厂商,在手机息屏状态下,会杀死后台推送服务,导致推送不到。这个问题也是行业通病,也是我们付呗app最根本的问题。
      在优化之前我们之前单纯的采用阿里云推送,但是出现了一系列问题,比如推送延迟、消息推送过期、推送服务器宕机、国内厂商机型推送率低等,在这个基础之上,我们进行了第一次推送优化:通过接入阿里云辅助通道,由于对推送文档不熟悉,在接入辅助通道时没有考虑到小米推送对标题长度有限制,导致用户初始化阿里云推送就失败了,最后放弃了辅助通道这种方案。于是我们进行了第二次推送优化:通过接入多推送通道方案,来优化国内厂商机型,例如华为手机采用华为推送通道、小米手机采用小米推送通道,其他机型采用阿里云推送或个推推送。大概流程如下:
2021-04-22

      商户启动应用,将商户基本信息,比如手机品牌(用于选择通道)、商户推送基本信息(推送ID),上报给服务器。根据各个推送文档,接入各个推送,将初始化成功的推送ID,分别上报给服务器,当商户发起支付后,后台根据我们预先设定的策略,比如小米手机采用小米推送、华为手机采用华为推送,选择对应推送通道下发消息。实践证明,推送情况并不是很理想,原因在于厂商维护的自身通道经常出现消息延迟。但是在这一过程中我们收集了一些有用的信息:比如阿里云推送过期、待处理等状态占比情况、设置对推送保活作用、后台支付回调慢等。
2021-04-22

      在收集这些数据后,我们立马投入到第三次推送优化:接入个推VIP,优化各个通道切换逻辑,完善推送统计、语音锦囊等功能。这里重点讲一下选择通道逻辑:付呗app选择推送通道分为两种:自动选择通道、手动选择通道,其中自动选择通道,原则上通过小米手机采用小米通道、华为手机采用华为通道,但是可能会存在一种情况,小米手机可能小米推送初始化失败,这样就无法选择小米推送通道,这种情况我们会根据各个推送优先级,选择最佳通道。
2021-04-22
      目前我们设置了阿里云推送优先级最高(sort字段),品牌对应的通道(parent_id),通过这张数据表,可以实现多个通道任意切换,一定程度上避免单一推送通道带来的风险。
      接入个推VIP,在线情况下推送到达率非常明显,通过接入OV看护,对OV机型,起到一定程度的保活作用,但是息屏状态下,作用不大,我们通过实践发现通过手机设置耗电保护、自启动、神秘模式等设置,可以起到很大的优化作用,这就是”语音锦囊”。

      最后:希望更多的小伙伴一起讨论、交流。

上一篇:opencv-python视频处理之录制视频


下一篇:Nacos在Windows环境下启动不起来? 常见解决方案