開篇先吐槽下睦柴,在Android 平臺開發(fā)原生的SpeechRecognizer真是難受的,不像ios洋侨,無比輕松,平臺統(tǒng)一倦蚪。
由于Android 平臺的碎片化問題比較嚴重希坚,各個廠商都有自己的實現,尤其是語音助手出來以后陵且,每家的語音服務肯定是不一樣的裁僧。
目前Android原生的SpeechRecognizer做法應該有兩種
- 默認調用原生SpeechRecognizer,并稍作修改
- 調用第三方慕购,科大訊飛聊疲,百度等
這兩種做法中
- 1. 在Google原生系統(tǒng)是可以的,但是在國內的環(huán)境是需要修改沪悲,修改后能保證各個機型基本可以用获洲,至于識別效果就要看各個機型自己實現的怎么樣了
- 2. 最簡單省心省力,如果你的項目可以這么做殿如,那么兄弟恭喜你贡珊,你是最幸福的
這里我們不講第三方的,大家可以自己去集成第三方sdk涉馁,主要討論原生的開發(fā)
? 首先權限不要忘記(記得6.0以后動態(tài)請求權限)
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
? 在SpeechRecognizer.class有這樣SpeechRecognizer .isRecognitionAvailable一個方法
/**
* Checks whether a speech recognition service is available on the system. If this method
* returns {@code false}, {@link SpeechRecognizer#createSpeechRecognizer(Context)} will
* fail.
*
* @param context with which {@code SpeechRecognizer} will be created
* @return {@code true} if recognition is available, {@code false} otherwise
*/
public static boolean isRecognitionAvailable(final Context context) {
final List<ResolveInfo> list = context.getPackageManager().queryIntentServices(
new Intent(RecognitionService.SERVICE_INTERFACE), 0);
return list != null && list.size() != 0;
}
? 該方法在使用語音識別前建議要調用下门岔,該方法是檢查當前系統(tǒng)有沒有語音識別服務,我相信絕大多數廠商都有這個服務烤送,但是都有自己特別的實現寒随,但是它至少有,有就可以用。但是妻往,你像oppo的7.0以后機器互艾,這個方法調用后就是false,這時候就是毫無辦法了蒲讯。oppo 7.0以后就是這樣調用完后返回false忘朝,對于 oppo 這種情況,可以在手機上裝一個訊飛語音+的app判帮,語音識別就可以了局嘁,但是這種方法我估計沒人會用,用戶體驗太差晦墙。
? 如果該方法返回false在我們調用SpeechRecognizer.startListening();方法的時候會日志中發(fā)現這行l(wèi)og
no selected voice recognition service
? 該日志在SpeechRecognizer.startListening(final Intent recognizerIntent)方法中悦昵,大家可以進源碼查看這里就不貼了。
? 檢查完如果語音識別可用晌畅,接下來有兩種做法我們一個個來
- 直接創(chuàng)建實例啟動服務
mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(context);
mSpeechRecognizer.setRecognitionListener(this);
? 創(chuàng)建識別實例但指,并添加監(jiān)聽,這里沒有什么問題抗楔,在監(jiān)聽中我們可以拿到我們的想要的回調
/**
* Used for receiving notifications from the SpeechRecognizer when the
* recognition related events occur. All the callbacks are executed on the
* Application main thread.
* 值的注意的是棋凳,所有的回調都在主線程
*/
public interface RecognitionListener {
// 實例準備就緒
void onReadyForSpeech(Bundle params);
// 開始語音識別
void onBeginningOfSpeech();
// 聆聽分貝值 可能會有負數哦
void onRmsChanged(float rmsdB);
void onBufferReceived(byte[] buffer);
// 識別結束
void onEndOfSpeech();
// 錯誤碼
void onError(int error);
// 識別的結果,在某些國產機上连躏,這個結果會是空
void onResults(Bundle results);
// 識別的部分結果 有些過程機上 [onResults] 方法為空剩岳,可以在這里拿到結果
void onPartialResults(Bundle partialResults);
void onEvent(int eventType, Bundle params);
}
? 指的注意的是,如果 SpeechRecognizer .isRecognitionAvailable 方法返回false的話拍棕,即使注冊了監(jiān)聽mSpeechRecognizer.setRecognitionListener(this);回調方法不會走的勺良,因為沒有該服務的
? 下面就是樣板代碼了,都一樣的
// 啟動服務需要一個 Intent
mRecognitionIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
mRecognitionIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
mRecognitionIntent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true);
mRecognitionIntent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 3);
// mLocale 是一個語音種類尚困,可以根據自己的需求去設置
mRecognitionIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, mLocale);
// 開始語音識別 結果在 mSpeechRecognizer.setRecognitionListener(this);回調中
mSpeechRecognizer.startListening(mRecognitionIntent);
// 停止監(jiān)聽
mSpeechRecognizer.stopListening();
// 取消服務
mSpeechRecognizer.cancel();
? 在識別過程中,如果出錯蠢箩,錯誤碼很有用
// 錯誤碼
void onError(int error);
? 這里是錯誤碼的原因,可以做參考去排查忙芒,錯誤碼在SpeechRecognizer.class中讳侨,可以自行查閱
/** Network operation timed out. */
public static final int ERROR_NETWORK_TIMEOUT = 1;
/** Other network related errors. */
public static final int ERROR_NETWORK = 2;
/** Audio recording error. */
public static final int ERROR_AUDIO = 3;
/** Server sends error status. */
public static final int ERROR_SERVER = 4;
/** Other client side errors. */
public static final int ERROR_CLIENT = 5;
/** No speech input */
public static final int ERROR_SPEECH_TIMEOUT = 6;
/** No recognition result matched. */
public static final int ERROR_NO_MATCH = 7;
/** RecognitionService busy. */
public static final int ERROR_RECOGNIZER_BUSY = 8;
/** Insufficient permissions */
public static final int ERROR_INSUFFICIENT_PERMISSIONS = 9;
? 這里特別說明下呵萨,如果你所有操作都正常,可是在監(jiān)聽回調中一直出現 ERROR_INSUFFICIENT_PERMISSIONS 即錯誤碼返回一直是 9,這時候可以嘗試晨缈纾看各個廠商的語音助手有沒有在這里做處理潮峦,方法就是在說話的時候,這時打開語音助手嘱腥,會有語音助手提示賦予應用權限拘悦,這種情況我在小米手機上遇到過,小愛同學需要打開權限础米,打開就好了。
? 以上就是一般的做法医寿,但是不一定有用蘑斧,廠商會做什么事,我們是不知道滴竖瘾,具體問題需要具體對待,下面我們來討論另外一種實現
-
照舊在啟動服務前需要檢查服務是否存在 SpeechRecognizer.isRecognitionAvailable(context);如果返回false,要么歇菜(絕大多數不會出現)事扭,要么自己實現乐横,我自己實現不了今野。
如果返回true,說明有語音識別服務可以用催什,這時候我們需要記錄下當前系統(tǒng)內置的是哪個服務
String serviceComponent = Settings.Secure.getString(context.getContentResolver(),
"voice_recognition_service");
? serviceComponent就是我們的服務名稱宰睡,eg:華為手機返回"com.huawei.vassistant/com.huawei.ziri.service.FakeRecognitionService"從名字看就是 FakeRecognitionService偽造的語音識別服務蒲凶,就是說這個是不用的。這里多說下旋圆,華為使用的訊飛的語音識別服務麸恍。
? 組裝成組件
// 當前系統(tǒng)內置語音識別服務
ComponentName component = ComponentName.unflattenFromString(serviceComponent);
? 組裝成一個Component組件搀矫,后面我們需要用到
// 內置語音識別服務是否可用
boolean isRecognizerServiceValid = false;
ComponentName currentRecognitionCmp = null;
// 查找得到的 "可用的" 語音識別服務
List<ResolveInfo> list = context.getPackageManager().queryIntentServices(new Intent(RecognitionService.SERVICE_INTERFACE), MATCH_ALL);
if (list != null && list.size() != 0) {
for (ResolveInfo info : list) {
debugLog(TAG, "\t" + info.loadLabel(context.getPackageManager()) + ": "
+ info.serviceInfo.packageName + "/" + info.serviceInfo.name);
// 這里拿系統(tǒng)使用的語音識別服務和內置的語音識別比較瓤球,如果相同敏弃,OK我們直接直接使用
// 如果相同就可以直接使用mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(context);來創(chuàng)建實例,因為內置的可以使用
if (info.serviceInfo.packageName.equals(component.getPackageName())) {
isRecognizerServiceValid = true;
break;
} else {
// 如果服務不同绿饵,說明 內置服務 和 系統(tǒng)使用 不是同一個隅要,那么我們需要使用系統(tǒng)使用的
// 因為內置的系統(tǒng)不用,我們用了也沒有用
currentRecognitionCmp = new ComponentName(info.serviceInfo.packageName, info.serviceInfo.name);
}
}
} else {
// 這里既是查不到可用的語音識別服務步清,可以歇菜了
debugLog(TAG, "No recognition services installed");
return false;
}
? 根據判斷結果創(chuàng)建實例
// 當前系統(tǒng)內置語音識別服務可用
if (isRecognizerServiceValid) {
mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(context);
} else {
// 內置不可用廓啊,需要我們使用查找到的可用的
mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(context, currentRecognitionCmp);
}
mSpeechRecognizer.setRecognitionListener(this);
? 關于SpeechRecognizer createSpeechRecognizer(final Context context,final ComponentName serviceComponent)方法源碼如下
/**
* Factory method to create a new {@code SpeechRecognizer}. Please note that
* {@link #setRecognitionListener(RecognitionListener)} should be called before dispatching any
* command to the created {@code SpeechRecognizer}, otherwise no notifications will be
* received.
*
* Use this version of the method to specify a specific service to direct this
* {@link SpeechRecognizer} to. Normally you would not use this; use
* {@link #createSpeechRecognizer(Context)} instead to use the system default recognition
* service.
*
* @param context in which to create {@code SpeechRecognizer}
* @param serviceComponent the {@link ComponentName} of a specific service to direct this
* {@code SpeechRecognizer} to
* @return a new {@code SpeechRecognizer}
*/
public static SpeechRecognizer createSpeechRecognizer(final Context context,
final ComponentName serviceComponent) {
if (context == null) {
throw new IllegalArgumentException("Context cannot be null)");
}
checkIsCalledFromMainThread();
return new SpeechRecognizer(context, serviceComponent);
}
? 注釋寫的很明白谴轮,不建議我們使用,但是沒辦法第步,我們也不想折騰,各大廠商自己有實現廓推,只能這樣了翩隧。
? 使用該方法來做基本能滿足大多數手機的功能實現,但是這種方法的前提有一個SpeechRecognizer.isRecognitionAvailable(final Context context)該方法要返回true专缠,系統(tǒng)沒有服務可用淑仆,那是沒有辦法的。
? 以下是自己的實現嘁圈,可以根據自己使用修改
// 查找當前系統(tǒng)的內置使用的語音識別服務
// com.huawei.vassistant/com.huawei.ziri.service.FakeRecognitionService
String serviceComponent = Settings.Secure.getString(context.getContentResolver(),
"voice_recognition_service");
debugLog(TAG, "voice_recognition_service : " + serviceComponent);
if (TextUtils.isEmpty(serviceComponent)) {
return false;
}
ComponentName component = ComponentName.unflattenFromString(serviceComponent);
if (component == null) {
debugLog(TAG, "voice_recognition_service component == null");
return false;
}
debugLog(TAG, "serviceComponent : " + component.toShortString());
boolean isRecognizerServiceValid = false;
ComponentName currentRecognitionCmp = null;
// 查找得到的 "可用的" 語音識別服務
List<ResolveInfo> list = context.getPackageManager().queryIntentServices(new Intent(RecognitionService.SERVICE_INTERFACE), MATCH_ALL);
if (list != null && list.size() != 0) {
for (ResolveInfo info : list) {
debugLog(TAG, "\t" + info.loadLabel(context.getPackageManager()) + ": "
+ info.serviceInfo.packageName + "/" + info.serviceInfo.name);
if (info.serviceInfo.packageName.equals(component.getPackageName())) {
isRecognizerServiceValid = true;
break;
} else {
currentRecognitionCmp = new ComponentName(info.serviceInfo.packageName, info.serviceInfo.name);
}
}
} else {
debugLog(TAG, "No recognition services installed");
return false;
}
if (mSpeechRecognizer != null) {
return true;
}
debugLog(TAG, "isRecognitionAvailable: " + SpeechRecognizer.isRecognitionAvailable(context));
if (isRecognizerServiceValid) {
mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(context);
} else {
mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(context, currentRecognitionCmp);
}
mSpeechRecognizer.setRecognitionListener(this);
if (mRecognitionIntent == null) {
mRecognitionIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
mRecognitionIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
mRecognitionIntent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true);
mRecognitionIntent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 3);
}
return true;
到這里之后就是監(jiān)聽回調最住,回到了第一種方法的實現。就不貼代碼了轧粟。
如有錯誤脓魏,不吝賜教