本文閱讀時(shí)間:約15分鐘
目錄
想法來源
動(dòng)手開發(fā)
代碼開源
Android演示程序下載及使用方法
優(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演示程序下載及使用方法
使用必備條件:
a. 一臺(tái)Android5.0+的手機(jī)
使用方法:
a. 安裝訊飛語(yǔ)音+app(Android手機(jī)內(nèi)置的Pico TTS文本轉(zhuǎn)語(yǔ)音引擎不支持中文俊扭,訊飛語(yǔ)音+由科大訊飛出品,中文語(yǔ)音發(fā)音效果目前是最好的)
在訊飛語(yǔ)音+中開啟系統(tǒng)合成
在文字轉(zhuǎn)語(yǔ)音輸出中選擇訊飛語(yǔ)音+
b. 下載并安裝演示程序
c. 在設(shè)置->無障礙->打開“語(yǔ)音通知”無障礙功能
d. 打開演示程序
e. 讓朋友發(fā)一條微信聊天消息
f. enjoy it!