如何在開車時(shí)優(yōu)雅地讀取微信通知消息毡熏?

Hike! - a photo by Chris Burkard from 500px

本文閱讀時(shí)間:約15分鐘

目錄

  1. 想法來源

  2. 動(dòng)手開發(fā)

  3. 代碼開源

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

  5. 優(yōu)酷視頻演示鏈接

說明:由于我也是初學(xué)者,文中提到的概念或者知識(shí)是我個(gè)人的理解侣诵,有可能與事實(shí)不符痢法,請(qǐng)隨時(shí)指正,不甚感激杜顺。如果能幫助到同樣喜歡程序開發(fā)的你财搁,那真是太棒了,歡迎一起討論躬络,我的郵箱:lcjfly@gmail.com

1. 想法來源

由于我每天上下班時(shí)間加起來約有3個(gè)小時(shí)是在車上度過的尖奔,自己開車或者與同事搭車。我經(jīng)常遇到以下這種尷尬情況:在開車時(shí)朋友發(fā)來一條微信消息穷当,這時(shí)你會(huì)拿起手機(jī)看還是置之不理提茁?拿手機(jī)看消息會(huì)影響駕車安全,不看的話擔(dān)心錯(cuò)過重要消息[笑哭臉]馁菜。

同時(shí)茴扁,最近我養(yǎng)成了在上下班開車時(shí)間手機(jī)藍(lán)牙連接汽車中控,并使用得到app收聽知識(shí)新聞的習(xí)慣汪疮。因此峭火,我萌生了以下想法:

能否讓手機(jī)在收到微信通知后,通過汽車藍(lán)牙用語(yǔ)音播報(bào)微信收到的消息內(nèi)容智嚷,比如:“張三發(fā)來微信消息卖丸,今天天氣不錯(cuò),要不要去動(dòng)物園玩盏道?”稍浆,我聽到語(yǔ)音提示后,決定是否要立即回復(fù)猜嘱,這樣既不影響開車衅枫,也不會(huì)錯(cuò)過重要消息了。

基于以上想法泉坐,才有了本文为鳄。

2. 動(dòng)手開發(fā)

由于我使用的是Android手機(jī),因此本文是針對(duì)Android進(jìn)行開發(fā)腕让。

要實(shí)現(xiàn)以上所述的功能孤钦,核心流程如下:

核心流程圖

核心流程圖
a. 通過無障礙服務(wù)AccessibilityService在收到微信消息通知時(shí)捕獲通知內(nèi)容

b. 使用AudioManager請(qǐng)求音頻播放權(quán)限(這一步驟與Android開發(fā)規(guī)范相關(guān):當(dāng)你的應(yīng)用需要輸出像樂音和通知之類的音頻時(shí),你應(yīng)該總是請(qǐng)求音頻焦點(diǎn)AudioFocus.一旦應(yīng)用具有了播放權(quán)限纯丸,它就可以自由的使用音頻輸出)

c. 使用Android系統(tǒng)自帶的TextToSpeech文本轉(zhuǎn)語(yǔ)音接口播放微信消息內(nèi)容

核心代碼如下:

2.1 無障礙服務(wù)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,"消息放入隊(duì)列:"+ttsText);
                TTSService.ttsTextLinkedList.push(ttsText);
            }
        }
    }
}

同時(shí)需要新建一個(gè)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 請(qǐng)求音頻播放權(quán)限AudioManager && 文本轉(zhuǎn)語(yǔ)音TextToSpeech

新建一個(gè)TTSService用于請(qǐng)求音頻權(quán)限和文本轉(zhuǎn)語(yǔ)音操作

public class TTSService extends Service {

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

    // 存放文本轉(zhuǎn)語(yǔ)音的文本內(nèi)容偏形,等待線程不斷讀取
    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開始運(yùn)行...");
                while(true) {
                    if(!MainActivity.bSwitch) {
                        ttsTextLinkedList.clear();
                        continue;
                    }

                    if(!ttsTextLinkedList.isEmpty()) {
                        Log.i(TAG, "檢測(cè)到消息,請(qǐng)求audiofocus");
                        if (requestAudioFocus()) {
                            tempTTSStr = ttsTextLinkedList.pop();
                            Log.i(TAG, "請(qǐng)求audiofocus成功,開始播放:" + tempTTSStr);
                            mTextToSpeech.speak(tempTTSStr, TextToSpeech.QUEUE_ADD, null);
                            abandonAudioFocus();
                            continue;
                        } else {
                            Log.i(TAG, "請(qǐng)求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();  // 音頻播放請(qǐng)求被其他app獲取時(shí),暫停語(yǔ)音播報(bào)
                    } 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. 代碼開源

本文的完整代碼已經(jīng)托管在GitHub觉鼻,點(diǎn)擊查看:
https://github.com/lcjfly/AndroidNotificationTTS

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

apk文件下載鏈接

使用必備條件:

a. 一臺(tái)Android5.0+的手機(jī)

使用方法:

a. 安裝訊飛語(yǔ)音+app(Android手機(jī)內(nèi)置的Pico TTS文本轉(zhuǎn)語(yǔ)音引擎不支持中文俊扭,訊飛語(yǔ)音+由科大訊飛出品,中文語(yǔ)音發(fā)音效果目前是最好的)

在訊飛語(yǔ)音+中開啟系統(tǒng)合成


圖片發(fā)自簡(jiǎn)書App

在文字轉(zhuǎn)語(yǔ)音輸出中選擇訊飛語(yǔ)音+


圖片發(fā)自簡(jiǎn)書App

b. 下載并安裝演示程序

c. 在設(shè)置->無障礙->打開“語(yǔ)音通知”無障礙功能

d. 打開演示程序

e. 讓朋友發(fā)一條微信聊天消息

f. enjoy it!

5. 優(yōu)酷視頻演示鏈接

點(diǎn)擊觀看優(yōu)酷演示視頻

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末坠陈,一起剝皮案震驚了整個(gè)濱河市萨惑,隨后出現(xiàn)的幾起案子捐康,更是在濱河造成了極大的恐慌,老刑警劉巖庸蔼,帶你破解...
    沈念sama閱讀 216,843評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件解总,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡姐仅,警方通過查閱死者的電腦和手機(jī)花枫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來掏膏,“玉大人劳翰,你說我怎么就攤上這事÷睿” “怎么了佳簸?”我有些...
    開封第一講書人閱讀 163,187評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)行冰。 經(jīng)常有香客問我溺蕉,道長(zhǎng),這世上最難降的妖魔是什么悼做? 我笑而不...
    開封第一講書人閱讀 58,264評(píng)論 1 292
  • 正文 為了忘掉前任疯特,我火速辦了婚禮,結(jié)果婚禮上肛走,老公的妹妹穿的比我還像新娘漓雅。我一直安慰自己,他們只是感情好朽色,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,289評(píng)論 6 390
  • 文/花漫 我一把揭開白布邻吞。 她就那樣靜靜地躺著,像睡著了一般葫男。 火紅的嫁衣襯著肌膚如雪抱冷。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,231評(píng)論 1 299
  • 那天梢褐,我揣著相機(jī)與錄音旺遮,去河邊找鬼。 笑死盈咳,一個(gè)胖子當(dāng)著我的面吹牛耿眉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鱼响,決...
    沈念sama閱讀 40,116評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼鸣剪,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起筐骇,我...
    開封第一講書人閱讀 38,945評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤债鸡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后拥褂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體娘锁,經(jīng)...
    沈念sama閱讀 45,367評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,581評(píng)論 2 333
  • 正文 我和宋清朗相戀三年饺鹃,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片间雀。...
    茶點(diǎn)故事閱讀 39,754評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡悔详,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出惹挟,到底是詐尸還是另有隱情茄螃,我是刑警寧澤,帶...
    沈念sama閱讀 35,458評(píng)論 5 344
  • 正文 年R本政府宣布连锯,位于F島的核電站归苍,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏运怖。R本人自食惡果不足惜拼弃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,068評(píng)論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望摇展。 院中可真熱鬧吻氧,春花似錦、人聲如沸咏连。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)祟滴。三九已至振惰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間垄懂,已是汗流浹背骑晶。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留埠偿,地道東北人透罢。 一個(gè)月前我還...
    沈念sama閱讀 47,797評(píng)論 2 369
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像冠蒋,于是被迫代替她去往敵國(guó)和親羽圃。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,654評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容