本文阅读时间:约15分钟
目录
想法来源
动手开发
代码开源
Android演示程序下载及使用方法
优酷视频演示链接
说明:由于我也是初学者,文中提到的概念或者知识是我个人的理解,有可能与事实不符,请随时指正,不甚感激。如果能帮助到同样喜欢程序开发的你,那真是太棒了,欢迎一起讨论,我的邮箱: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演示程序下载及使用方法
使用必备条件:
a. 一台Android5.0+的手机
使用方法:
a. 安装讯飞语音+app(Android手机内置的Pico TTS文本转语音引擎不支持中文,讯飞语音+由科大讯飞出品,中文语音发音效果目前是最好的)
在讯飞语音+中开启系统合成
在文字转语音输出中选择讯飞语音+
b. 下载并安装演示程序
c. 在设置->无障碍->打开“语音通知”无障碍功能
d. 打开演示程序
e. 让朋友发一条微信聊天消息
f. enjoy it!