You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 09:09:11