如何在开车时优雅地读取微信通知消息?

Hike! - a photo by Chris Burkard from 500px

本文阅读时间:约15分钟

目录

  1. 想法来源

  2. 动手开发

  3. 代码开源

  4. Android演示程序下载及使用方法

  5. 优酷视频演示链接

说明:由于我也是初学者,文中提到的概念或者知识是我个人的理解,有可能与事实不符,请随时指正,不甚感激。如果能帮助到同样喜欢程序开发的你,那真是太棒了,欢迎一起讨论,我的邮箱:lcjfly@gmail.com

1. 想法来源

由于我每天上下班时间加起来约有3个小时是在车上度过的,自己开车或者与同事搭车。我经常遇到以下这种尴尬情况:在开车时朋友发来一条微信消息,这时你会拿起手机看还是置之不理?拿手机看消息会影响驾车安全,不看的话担心错过重要消息[笑哭脸]。

同时,最近我养成了在上下班开车时间手机蓝牙连接汽车中控,并使用得到app收听知识新闻的习惯。因此,我萌生了以下想法:

能否让手机在收到微信通知后,通过汽车蓝牙用语音播报微信收到的消息内容,比如:“张三发来微信消息,今天天气不错,要不要去动物园玩?”,我听到语音提示后,决定是否要立即回复,这样既不影响开车,也不会错过重要消息了。

基于以上想法,才有了本文。

2. 动手开发

由于我使用的是Android手机,因此本文是针对Android进行开发。

要实现以上所述的功能,核心流程如下:

核心流程图

核心流程图
a. 通过无障碍服务AccessibilityService在收到微信消息通知时捕获通知内容

b. 使用AudioManager请求音频播放权限(这一步骤与Android开发规范相关:当你的应用需要输出像乐音和通知之类的音频时,你应该总是请求音频焦点AudioFocus.一旦应用具有了播放权限,它就可以自由的使用音频输出)

c. 使用Android系统自带的TextToSpeech文本转语音接口播放微信消息内容

核心代码如下:

2.1 无障碍服务AccessibilityService


public class TTSAccessibilityService extends AccessibilityService {

    private static finalStringTAG = TTSAccessibilityService.class.getSimpleName();

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        int eventType = event.getEventType();
        switch(eventType) {
            case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:
            handleNotification(event);
            break;
        }
    }

    private void handleNotification(AccessibilityEvent event) {
        List texts = event.getText();
        if(MainActivity.bSwitch && !texts.isEmpty()) {
            for(CharSequence text : texts) {
                String ttsText = text.toString().replaceFirst(":","说");
                Log.i(TAG,"消息放入队列:"+ttsText);
                TTSService.ttsTextLinkedList.push(ttsText);
            }
        }
    }
}

同时需要新建一个xml文件定义需要捕获的通知类型

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeNotificationStateChanged|typeWindowStateChanged|typeWindowContentChanged"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:accessibilityFlags="flagDefault"
    android:canRetrieveWindowContent="true"
    android:notificationTimeout="100"
    android:packageNames="com.tencent.mm" /> <!-- 指定获取微信app通知 -->

2.2 请求音频播放权限AudioManager && 文本转语音TextToSpeech

新建一个TTSService用于请求音频权限和文本转语音操作

public class TTSService extends Service {

    private static final String TAG = TTSService.class.getSimpleName();

    // 存放文本转语音的文本内容,等待线程不断读取
    public static LinkedList<String> ttsTextLinkedList = new LinkedList<String>();
    private String tempTTSStr = "";

    private AudioManager mAudioManager;

    private TextToSpeech mTextToSpeech;

    private Thread ttsThread;

    @Override
    public void onCreate() {
        super.onCreate();

        mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
        mTextToSpeech = new TextToSpeech(getApplicationContext(), mOnInitListener);

        ttsThread = new Thread() {

            public void run() {
                Log.i(TAG, "tts thread开始运行...");
                while(true) {
                    if(!MainActivity.bSwitch) {
                        ttsTextLinkedList.clear();
                        continue;
                    }

                    if(!ttsTextLinkedList.isEmpty()) {
                        Log.i(TAG, "检测到消息,请求audiofocus");
                        if (requestAudioFocus()) {
                            tempTTSStr = ttsTextLinkedList.pop();
                            Log.i(TAG, "请求audiofocus成功,开始播放:" + tempTTSStr);
                            mTextToSpeech.speak(tempTTSStr, TextToSpeech.QUEUE_ADD, null);
                            abandonAudioFocus();
                            continue;
                        } else {
                            Log.i(TAG, "请求audiofocus失败");
                        }
                    }

                    try {
                        Thread.sleep(500);
                    } catch(Exception e) {
                        Log.e(TAG, "start service sleep err");
                    }
                }
            }
        };

        startService();
    }

    public void startService() {
        ttsThread.start();
    }

    private boolean requestAudioFocus() {
        if(mOnAudioFocusChangeListener != null) {
            return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==
                    mAudioManager.requestAudioFocus(mOnAudioFocusChangeListener,
                        AudioManager.STREAM_NOTIFICATION,
                        AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
        }
        return false;
    }

    private boolean abandonAudioFocus() {
        if(mOnAudioFocusChangeListener != null) {
            return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==
                    mAudioManager.abandonAudioFocus(mOnAudioFocusChangeListener);
        }
        return false;
    }

    AudioManager.OnAudioFocusChangeListener mOnAudioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() {

        public void onAudioFocusChange(int focusChange) {
            switch (focusChange) {
                case AudioManager.AUDIOFOCUS_GAIN:
                    Log.i(TAG, "onAudioFocusChange: AUDIOFOCUS_GAIN");
                    ttsThread.notify();
                    break;
                case AudioManager.AUDIOFOCUS_LOSS:
                    Log.i(TAG, "onAudioFocusChange: AUDIOFOCUS_LOSS");
                    try {
                        ttsThread.wait();  // 音频播放请求被其他app获取时,暂停语音播报
                    } catch (Exception e) {
                        Log.e(TAG, "thread wait err");
                    }
                    abandonAudioFocus();
                    break;
                case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                    Log.i(TAG, "onAudioFocusChange: AUDIOFOCUS_LOSS_TRANSIENT");
                    ttsThread.notify();
                    break;
                case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                    Log.i(TAG, "onAudioFocusChange: AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK");
                    ttsThread.notify();
                    break;
            }
        }
    };

3. 代码开源

本文的完整代码已经托管在GitHub,点击查看:
https://github.com/lcjfly/AndroidNotificationTTS

4. Android演示程序下载及使用方法

apk文件下载链接

使用必备条件:

a. 一台Android5.0+的手机

使用方法:

a. 安装讯飞语音+app(Android手机内置的Pico TTS文本转语音引擎不支持中文,讯飞语音+由科大讯飞出品,中文语音发音效果目前是最好的)

在讯飞语音+中开启系统合成


图片发自简书App

在文字转语音输出中选择讯飞语音+


图片发自简书App

b. 下载并安装演示程序

c. 在设置->无障碍->打开“语音通知”无障碍功能

d. 打开演示程序

e. 让朋友发一条微信聊天消息

f. enjoy it!

5. 优酷视频演示链接

点击观看优酷演示视频

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容