Xamarin Android中C#语法检测代码合并至语音转文字应用遇问题
把语法检测整合到Xamarin语音转文字应用的踩坑记录
我刚接触C#和Xamarin,最近想给我的语音转文字应用加个语法检测功能,找了相关代码整合进去,但跑起来完全不是预期的效果。下面是我写的代码,还有几个我后来琢磨出来的问题点,以及修改建议:
我的原始代码(注意末尾类定义有截断)
using System; using Android.App; using Android.Content; using Android.Runtime; using Android.Widget; using Android.OS; using Android.Speech; using Android.Util; using Newtonsoft.Json; using System.Collections.Generic; using System.Linq; using System.IO; using System.Net; namespace SpeechToText { [Activity(Label = "SpeechToText", MainLauncher = true, Icon = "@drawable/icon")] public class MainActivity : Activity, IRecognitionListener { public const string Tag = "VoiceRec"; SpeechRecognizer Recognizer { get; set; } Intent SpeechIntent { get; set; } TextView Label { get; set; } TextView Label1 { get; set; } protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); SetContentView(Resource.Layout.Main); Recognizer = SpeechRecognizer.CreateSpeechRecognizer(this); Recognizer.SetRecognitionListener(this); SpeechIntent = new Intent(RecognizerIntent.ActionRecognizeSpeech); SpeechIntent.PutExtra(RecognizerIntent.ExtraLanguageModel, RecognizerIntent.LanguageModelFreeForm); SpeechIntent.PutExtra(RecognizerIntent.ExtraCallingPackage, PackageName); var button = FindViewById<Button>(Resource.Id.btn); button.Click += ButtonClick; var Grammarbutton = FindViewById<Button>(Resource.Id.btn1); Grammarbutton.Click += new EventHandler(ButtonClick2); Label = FindViewById<TextView>(Resource.Id.tv); Label1 = FindViewById<TextView>(Resource.Id.tv1); } private void ButtonClick(object sender, EventArgs e) { Recognizer.StartListening(SpeechIntent); } public void OnResults(Bundle results) { var matches = results.GetStringArrayList(SpeechRecognizer.ResultsRecognition); if (matches != null && matches.Count > 0) { Label.Text = matches[0]; } } private void ButtonClick2(object sender, EventArgs e) { var api = new GingerItApi(); for (; ; ) { Console.Write("Text to check: "); var text = Label.Text; if (string.IsNullOrEmpty(text)) break; try { var result = api.Check(text); if (result?.Corrections?.Count != 0) { for (int i = 0; i < result.Corrections.Count; i++) { var item = result.Corrections[i]; var mistakes = string.Join(", ", item.Mistakes.Select(x => $"\"{text.Substring(x.From, x.To - x.From + 1)}\"")); var suggestions = string.Join(", ", item.Suggestions.Select(x => $"\"{x.Text}\"")); Label1.Text = $" {i + 1}: {mistakes} >> {suggestions}"; } } else { Console.WriteLine("Looks okay.\n"); } } catch (Exception ex) { Console.WriteLine($"**Error: {ex.Message}\n"); } } } public void OnReadyForSpeech(Bundle @params) { Log.Debug(Tag, "OnReadyForSpeech"); } public void OnBeginningOfSpeech() { Log.Debug(Tag, "OnBeginningOfSpeech"); } public void OnEndOfSpeech() { Log.Debug(Tag, "OnEndOfSpeech"); } public void OnError([GeneratedEnum] SpeechRecognizerError error) { Log.Debug("OnError", error.ToString()); } public void OnBufferReceived(byte[] buffer) { } public void OnEvent(int eventType, Bundle @params) { } public void OnPartialResults(Bundle partialResults) { } public void OnRmsChanged(float rmsdB) { } } } class GingerItApi { public CheckResult Check(string text) { var request = WebRequest.Create($"https://services.gingersoftware.com/Ginger/correct/jsonSecured/GingerTheTextFull?callback=jQuery172015406464511272344_1490987331365&apiKey=GingerWebSite&lang=US&clientVersion=2.0&text={text}&_=1490987518060") as HttpWebRequest; WebResponse response = null; try { response = request.GetResponse(); if (response != null) { using (var reader = new StreamReader(response.GetResponseStream())) { string data = reader.ReadToEnd(); var first = data.IndexOf('{'); var last = data.LastIndexOf('}'); var json = data.Substring(first, last - first + 1); return JsonConvert.DeserializeObject<CheckResult>(json); } } } catch (Exception) { throw; } return null; } } public class LrnFrgOrigIndx { [JsonProperty("From")] public int From { get; set; } [JsonProperty("To")] public int To { get; set; } } public class Mistake { [JsonProperty("Definition")] public string Definition { get; set; } [JsonProperty("CanAddToDict")] public bool CanAddToDict { get; set; } [JsonProperty("From")] public int From { get; set; } [JsonProperty("To")] public int To { get; set; } } public class Suggestion { [JsonProperty("Definition")] public string Definition { get; set; } [JsonProperty("LrnCatId")] public int LrnCatId { get; set; } [JsonProperty("Text")] public string Text { get; set; } } public class Correction { [JsonProperty("Confidence")] public int Confidence { get; set; } [JsonProperty("From")] public int From { get; set; } [JsonProperty("LrnFrg")] public string LrnFrg { get; set; } [JsonProperty("LrnFrgOrigIndxs")] public IList<LrnFrgOrigIndx> LrnFrgOrigIndxs { get; set; } // 原始代码此处截断,缺失Mistakes、Suggestions等属性 }
问题分析和修改建议
1. 无限循环直接卡UI
ButtonClick2里的for (; ; )是个死循环,只要识别的文本不为空就会一直跑,不仅会把UI线程卡死,而且每次循环都会覆盖Label1.Text,最后只能看到最后一个错误的提示。直接把这个循环去掉,改成单次检测就好。
2. 同步API调用阻塞界面
原来的GingerItApi.Check用的是同步的WebRequest.GetResponse(),在移动端这种耗时操作会让界面卡顿甚至无响应,换成HttpClient的异步方法才是正确的做法。
3. UI更新方式错误
原来的循环里每次给Label1.Text赋值,前面的修正信息都会被覆盖,应该用StringBuilder把所有错误信息拼接起来,一次性设置到TextView上。
4. 代码截断导致编译错误
Correction类的定义没写完,必须补全Mistakes和Suggestions这些属性,否则反序列化JSON的时候会出错,编译也通不过。
5. 空值判断不严谨
处理result.Corrections的时候,得先判断result和result.Corrections都不为null,再判断数量是否大于0,不然容易触发空引用异常。
修改后的关键代码示例
异步版的语法检测API类
class GingerItApi { private readonly HttpClient _httpClient = new HttpClient(); public async Task<CheckResult> CheckAsync(string text) { // 记得对要检测的文本做URL编码,避免特殊字符导致请求失败 var encodedText = Uri.EscapeDataString(text); var url = $"https://services.gingersoftware.com/Ginger/correct/jsonSecured/GingerTheTextFull?callback=jQuery172015406464511272344_1490987331365&apiKey=GingerWebSite&lang=US&clientVersion=2.0&text={encodedText}&_=1490987518060"; try { var data = await _httpClient.GetStringAsync(url); var first = data.IndexOf('{'); var last = data.LastIndexOf('}'); // 处理API返回的JSONP格式,提取出纯JSON部分 if (first == -1 || last == -1) return null; var json = data.Substring(first, last - first + 1); return JsonConvert.DeserializeObject<CheckResult>(json); } catch (Exception ex) { // 这里可以根据实际情况做错误处理,比如打印日志或者返回null Console.WriteLine($"API请求出错:{ex.Message}"); throw; } } }
修正后的检测按钮点击事件
private async void ButtonClick2(object sender, EventArgs e) { var text = Label.Text; if (string.IsNullOrEmpty(text)) { Label1.Text = "请先进行语音识别获取文本哦"; return; } // 可以在这里加个加载提示,比如Label1.Text = "正在检测语法..."; var api = new GingerItApi(); try { var result = await api.CheckAsync(text); if (result?.Corrections != null && result.Corrections.Count > 0) { StringBuilder correctionContent = new StringBuilder(); for (int i = 0; i < result.Corrections.Count; i++) { var item = result.Corrections[i]; var mistakes = string.Join(", ", item.Mistakes.Select(x => $"\"{text.Substring(x.From, x.To - x.From + 1)}\"")); var suggestions = string.Join(", ", item.Suggestions.Select(x => $"\"{x.Text}\"")); correctionContent.AppendLine($"{i + 1}. 错误内容:{mistakes},建议修正为:{suggestions}"); } Label1.Text = correctionContent.ToString(); } else { Label1.Text = "很棒!这段文本语法没有问题~"; } } catch (Exception ex) { Label1.Text = $"语法检测出错啦:{ex.Message}"; } }
补全的Correction类定义
public class Correction { [JsonProperty("Confidence")] public int Confidence { get; set; } [JsonProperty("From")] public int From { get; set; } [JsonProperty("LrnFrg")] public string LrnFrg { get; set; } [JsonProperty("LrnFrgOrigIndxs")] public IList<LrnFrgOrigIndx> LrnFrgOrigIndxs { get; set; } [JsonProperty("Mistakes")] public IList<Mistake> Mistakes { get; set; } [JsonProperty("Suggestions")] public IList<Suggestion> Suggestions { get; set; } // 如果API返回其他属性,也需要对应添加 }
内容的提问来源于stack exchange,提问作者kk2208
相关产品推荐
相关产品推荐

