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

《如何在开车时优雅地读取微信通知消息?》 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. 优酷视频演示链接

点击观看优酷演示视频

    原文作者:鲁辰杰
    原文地址: https://www.jianshu.com/p/6f86ef5c2182
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞