【Android App】实战项目之实现你问我答的智能语音机器人(超详细 附源码)

news2024/11/27 22:31:22

需要全部代码请点赞关注收藏后评论区留言私信~~~

一、需求描述

想必大家都见过商场里的智能语音机器人,你对它提问时它可以自动回答你的问题,接下来我们也实现这样一个机器人,它依靠语音技术完成问询服务 基本功能如下

1:接收人们的对话语音 并从语音中识别问题文本

2:对问题文本进行语义分析 判断本次提问想问什么 对应的答案又是什么 至于提问与答案的对应关系 既能由系统自动关联 也可以由用户手动关联

3:把答案文本合成为语音数据,再播放这段音频

二、功能分析 

机器人较少通过控件与用户交互,主要通过语音与用户交互,主要集成了以下技术

1:数据库框架Room

2:定位服务 机器人回答一些问题依赖于定位服务

3:网络通信框架 根据经纬度查询地址 根据城市名称获取城市代码天气等等

4:原始音频录制  机器人识别语音的时候,要不停的监听原始音频,再发给第三方语音平台处理

5:语音识别与语音合成 这里选用云知声第三方语音平台 它不但提供了语音识别与语音合成功能 而且集成比较方便

6:WebSocket接口

7:中文转拼音

下面介绍一下源码之间模块的关系

1:RobotActivity 机器人的交互界面 机器人在此聆听用户的提问并回答问题

2:QuestionEditActivity 自定义问答的编辑页面 既支持增加新问答 也支持编辑现有问答 还能切换到问答详情的浏览页面

3:QuestionListActivity 自定义问答的列表页面

4:getAddressTask 获得详细地址的异步任务

5:getCityCodeTask 获取城市代码的异步任务

6:getWeatherTask 获取城市天气的异步任务 

业务逻辑中主要步骤如下

1:首次运行时加载初始问答

2:侦听用户的提问并作答

3:判断用户的提问要怎么回答 

三、效果展示 

使用机器人前确保手机已经联网,并且开启了定位功能,为了方便观察语音问答的交互结果,机器人会把识别到的问题文本显示在界面上方,把合成后的答案文本显示在界面下方

开始界面如下 机器人欢迎你的到来

 接着询问今天的天气和住址,机器人高德地图获取当前城市的天气

同样你可以考察一下机器人的计算水平,对于一些简单的计算它能很快给出正确答案 

 

 机器人同样可以背诵一部分唐诗宋词

 

 你可以自己对加入问题与回答存入后台数据库,这样下次你问的时候就有了对应得答案

 

 上面添加的问题保存后可以使用

 

 四、代码

部分源码如下 按照上面源码之间对应关系的顺序排列 

代码太多此处省略部分 需要全部源码请点赞关注收藏后评论区留言私信~~~

1:主界面类

package com.example.voice;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;

import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;
import android.view.WindowManager;
import android.widget.TextView;
import android.widget.Toast;

import com.example.voice.bean.CityInfo;
import com.example.voice.constant.RobotConstant;
import com.example.voice.constant.SoundConstant;
import com.example.voice.dao.QuestionDao;
import com.example.voice.entity.PoemInfo;
import com.example.voice.entity.QuestionInfo;
import com.example.voice.task.AsrClientEndpoint;
import com.example.voice.task.GetAddressTask;
import com.example.voice.task.GetCityCodeTask;
import com.example.voice.task.GetWeatherTask;
import com.example.voice.task.TtsClientEndpoint;
import com.example.voice.task.VoiceRecognizeTask;
import com.example.voice.util.SoundUtil;
import com.example.voice.util.SwitchUtil;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;

public class RobotActivity extends AppCompatActivity {
    private final static String TAG = "RobotActivity";
    private TextView tv_question; // 声明一个文本视图对象
    private TextView tv_answer; // 声明一个文本视图对象
    private String mInitQuestion, mInitAnswer; // 初始的问题,初始的答案
    private MediaPlayer mMediaPlayer = new MediaPlayer(); // 媒体播放器
    private LocationManager mLocationMgr; // 声明一个定位管理器对象
    private boolean isLocated = false; // 是否已经定位
    private CityInfo mCityInfo; // 城市信息
    private Map<String, QuestionInfo> mQuestionSystemMap = new HashMap<>(); // 系统自带的问答映射
    private Map<String, QuestionInfo> mQuestionCustomMap = new HashMap<>(); // 用户添加的问答映射
    private List<PoemInfo> mPoemList = new ArrayList<>();
    private QuestionDao questionDao; // 声明一个问答的持久化对象
    private VoiceRecognizeTask mRecognizeTask; // 声明一个原始音频识别线程对象
    private boolean isPlaying = false; // 是否正在播放
    private boolean isComposing = false; // 是否正在合成
    private long mBeginTime; // 语音识别的开始时间
    private Timer mTimer = new Timer(); // 语音识别计时器
    private TimerTask mTimerTask; // 计时任务
    private Handler mHandler = new Handler(Looper.myLooper()); // 声明一个处理器对象

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_robot);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); // 保持屏幕常亮
        findViewById(R.id.iv_back).setOnClickListener(v -> finish());
        TextView tv_title = findViewById(R.id.tv_title);
        tv_title.setText("小小机器人");
        tv_question = findViewById(R.id.tv_question);
        tv_answer = findViewById(R.id.tv_answer);
        mInitQuestion = tv_question.getText().toString();
        mInitAnswer = tv_answer.getText().toString();
        findViewById(R.id.btn_question_list).setOnClickListener(v -> {
            Intent intent = new Intent(this, QuestionListActivity.class);
            startActivity(intent);
        });
        findViewById(R.id.btn_question_edit).setOnClickListener(v -> {
            Intent intent = new Intent(this, QuestionEditActivity.class);
            startActivity(intent);
        });
        SwitchUtil.checkLocationIsOpen(this, "需要打开定位功能才能查看定位信息");
        // 从App实例中获取唯一的问答持久化对象

        new Thread(() -> {
            RobotConstant.copySampleFiles(this);
            runOnUiThread(() -> playVoice(RobotConstant.SAMPLE_PATHS[0])); // 播放欢迎语音
        }).start(); // 启动线程把资产目录下的欢迎音频文件复制到存储卡
        new Thread(() -> loadAllPoem()).start(); // 启动线程加载所有诗歌
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        tv_question.setText(mInitQuestion);
        tv_answer.setText(mInitAnswer);
        playVoice(RobotConstant.SAMPLE_PATHS[0]); // 播放欢迎语音
    }

    @Override
    protected void onStart() {
        super.onStart();
        new Thread(() -> loadAllQuestion()).start(); // 启动线程加载所有问答
    }

    @Override
    protected void onStop() {
        super.onStop();
        releaseRobot(); // 释放机器人资源
    }

    // 释放机器人资源
    private void releaseRobot() {
        if (mRecognizeTask != null) {
            new Thread(() -> mRecognizeTask.cancel()).start(); // 启动取消识别语音的线程
        }
        isPlaying = false;
        if (mMediaPlayer.isPlaying()) { // 如果正在播放
            mMediaPlayer.stop(); // 停止播放
        }
        mTimer.cancel(); // 取消计时器
        if (mTimerTask != null) {
            mTimerTask.cancel(); // 取消计时任务
        }
    }

    // 加载所有问答
    private void loadAllQuestion() {
        RobotConstant.initSystemQuestion(this, questionDao); // 初始化系统设定的问题
        List<QuestionInfo> questionList = questionDao.queryAllQuestion(); // 加载所有问答信息
        for (QuestionInfo item : questionList) {
            if (item.getType() == 0) { // 系统问答
                mQuestionSystemMap.put(item.getQuestion(), item);
            } else { // 用户问答
                mQuestionCustomMap.put(item.getQuestion(), item);
            }
        }
        Log.d(TAG, "SystemMap.size()="+mQuestionSystemMap.size()+",CustomMap.size()="+mQuestionCustomMap.size());
    }

    // 加载所有诗歌
    private void loadAllPoem() {
        RobotConstant.initPoetryData(this, questionDao); // 初始化古诗数据
        mPoemList = questionDao.queryAllPoem(); // 加载所有诗歌信息
        Log.d(TAG, "mPoemList.size()="+mPoemList.size());
    }

    // 播放语音
    private void playVoice(String audioPath) {
        Log.d(TAG, "playVoice audioPath="+audioPath);
        if (mTimerTask != null) {
            mTimerTask.cancel(); // 取消计时任务
        }
        if (isPlaying) {
            return;
        }
        isPlaying = true;
        findViewById(R.id.iv_robot).setOnClickListener(null);
        mMediaPlayer.reset(); // 重置媒体播放器
        // 设置媒体播放器的完成监听器
        mMediaPlayer.setOnCompletionListener(mp -> new Thread(() -> onlineRecognize()).start());
        mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); // 设置音频流的类型为音乐
        try {
            mMediaPlayer.setDataSource(audioPath); // 设置媒体数据的文件路径
            mMediaPlayer.prepare(); // 媒体播放器准备就绪
            mMediaPlayer.start(); // 媒体播放器开始播放
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 在线识别实时语音
    private void onlineRecognize() {
        // 创建语音识别任务,并指定语音监听器
        AsrClientEndpoint asrTask = new AsrClientEndpoint(this, "",
                arg -> checkRecognize((boolean)arg[0], arg[2].toString()));
        SoundUtil.startSoundTask(SoundConstant.URL_ASR, asrTask); // 启动语音识别任务
        // 创建一个原始音频识别线程
        mRecognizeTask = new VoiceRecognizeTask(this, asrTask);
        mRecognizeTask.start(); // 启动原始音频识别线程
        isPlaying = false;
        findViewById(R.id.iv_robot).setOnClickListener(v -> {
            new Thread(() -> mRecognizeTask.cancel()).start(); // 启动取消识别语音的线程
            playVoice(RobotConstant.SAMPLE_PATHS[2]); // 播放样本音频
        });
        mBeginTime = System.currentTimeMillis();
        mTimer = new Timer(); // 创建一个录音计时器
        // 创建一个录音计时任务
        mTimerTask = new TimerTask() {
            @Override
            public void run() {
                long now = System.currentTimeMillis();
                Log.d(TAG, "interval="+(now - mBeginTime));
                if (now - mBeginTime > 10 * 1000) { // 超过10秒,则停止本次识别,重新开始识别
                    mTimer.cancel(); // 取消计时器
                    new Thread(() -> mRecognizeTask.cancel()).start(); // 启动取消识别语音的线程
                    runOnUiThread(() -> tv_answer.setText("我的回答是:"+RobotConstant.SYSTEM_ANSWERS[6]));
                    playVoice(RobotConstant.SAMPLE_PATHS[6]); // 播放样本音频
                }
            }
        };
        mTimer.schedule(mTimerTask, 0, 1000); // 计时器每隔一秒就检查识别语音是否超时了
    }

    // 检查已识别的文本是否为已设定的问题
    private void checkRecognize(boolean isEnd, String text) {
        tv_question.setText("您的问题是:"+text);
        for (Map.Entry<String, QuestionInfo> item : mQuestionCustomMap.entrySet()) {
            if (text.contains(item.getKey())) { // 匹配用户添加的问题
                if (!isEnd) { // 尚未结束识别
                    new Thread(() -> mRecognizeTask.cancel()).start(); // 启动取消识别语音的线程
                } else { // 已经结束识别
                    answerCustomQuestion(item.getValue()); // 回答用户问题
                }
                return;
            }
        }
        for (Map.Entry<String, QuestionInfo> item : mQuestionSystemMap.entrySet()) {
            if (text.matches(item.getKey())) { // 匹配系统自带的问题
                if (!isEnd) { // 尚未结束识别
                    new Thread(() -> mRecognizeTask.cancel()).start(); // 启动取消识别语音的线程
                } else { // 已经结束识别
                    answerSystemQuestion(text, item.getValue()); // 回答系统问题
                }
                return;
            }
        }
        if (text.matches(RobotConstant.RECITE_PATTERN)) { // 匹配诗歌背诵
            String[] recites = text.split(RobotConstant.RECITE_PART);
            if (recites.length < 2) {
                return;
            }
            Log.d(TAG, "content="+recites[1]);
            if (isEnd) { // 已经结束识别
                if (mPoemPos == -1) {
                    mPoemPos = RobotConstant.searchPoemPos(recites[1], mPoemPos, mPoemList);
                }
                if (mPoemPos != -1) {
                    readPoem(mPoemList.get(mPoemPos)); // // 朗读诗歌
                }
                return;
            }
            // 查找该诗在列表中的位置
            int poemPos = RobotConstant.searchPoemPos(recites[1], mPoemPos, mPoemList);
            if (poemPos > mPoemPos) {
                mTimerTask.cancel(); // 取消计时任务
                if (mPoemPos == -1) {
                    mHandler.postDelayed(mHasFindPoem, 2000);
                }
                mPoemPos = poemPos;
            }
        }
    }

    private int mPoemPos = -1; // 诗歌在列表中的位置
    private Runnable mHasFindPoem = () -> {
        new Thread(() -> mRecognizeTask.cancel()).start(); // 启动取消识别语音的线程
    };

    // 朗读诗歌
    private void readPoem(PoemInfo poem) {
        mTimerTask.cancel(); // 取消计时任务
        mPoemPos = -1;
        String poemContent = poem.getContent().replace("\\n", "\n");
        String showText = String.format("%s\n%s\n%s", poem.getTitle(), poem.getAuthor(), poemContent);
        String answer = String.format("%s,%s。%s", poem.getTitle(), poem.getAuthor(), poemContent);
        Log.d(TAG, "answerCustomQuestion answer=" + answer);
        new Thread(() -> mRecognizeTask.cancel()).start(); // 启动取消识别语音的线程
        runOnUiThread(() -> tv_answer.setText(showText));
        // 启动在线合成语音的线程
        new Thread(() -> onlineCompose(poem.getAuthor()+"_"+poem.getTitle(), -1, answer, "")).start();
    }

    // 回答系统问题
    private void answerSystemQuestion(String question, QuestionInfo questionInfo) {
        mTimerTask.cancel(); // 取消计时任务
        String answer = questionInfo.getAnswer();
        Log.d(TAG, "answerSystemQuestion answer="+answer);
        new Thread(() -> mRecognizeTask.cancel()).start(); // 启动取消识别语音的线程
        Object[] resultArray = RobotConstant.judgeAnswerResult(question, answer, mCityInfo);
        int voiceSeq = (int) resultArray[0];
        String tempText = (String) resultArray[1];
        String answerText = TextUtils.isEmpty(tempText) ? RobotConstant.SYSTEM_ANSWERS[voiceSeq] : tempText;
        runOnUiThread(() -> tv_answer.setText("我的回答是:"+answerText));
        String voicePath = voiceSeq==-1 ? "" : RobotConstant.SAMPLE_PATHS[voiceSeq];
        Log.d(TAG, "voiceSeq="+voiceSeq+",answerText="+answerText+",voicePath="+voicePath);
        // 启动在线合成语音的线程
        new Thread(() -> onlineCompose(answer, voiceSeq, answerText, voicePath)).start();
    }

    // 回答用户问题
    private void answerCustomQuestion(QuestionInfo questionInfo) {
        mTimerTask.cancel(); // 取消计时任务
        String answer = questionInfo.getAnswer();
        Log.d(TAG, "answerCustomQuestion answer=" + answer);
        new Thread(() -> mRecognizeTask.cancel()).start(); // 启动取消识别语音的线程
        runOnUiThread(() -> tv_answer.setText("我的回答是:"+answer));
        // 启动在线合成语音的线程
        new Thread(() -> onlineCompose(""+questionInfo.getId(), -1, answer, "")).start();
    }

    // 在线合成语音
    private void onlineCompose(String function, int seq, String text, String path) {
        Log.d(TAG, "onlineCompose function="+function+",text="+text);
        String voicePath = !TextUtils.isEmpty(path) ? path : String.format("%s/robot/%s.mp3",
                getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString(), function);
        File file = new File(voicePath);
        if (file.exists() && seq==-1) { // 不是样本音频,如果已经存在语音文件,就要删除文件,这样才能重新生成新的语音文件
            file.delete();
        }
        if (file.exists()) {
            playVoice(voicePath); // 播放语音
        } else if (!isComposing) {
            isComposing = true;
            // 创建语音合成任务,并指定语音监听器
            TtsClientEndpoint task = new TtsClientEndpoint(this, voicePath, text, arg -> {
                if (Boolean.TRUE.equals(arg[0])) {
                    playVoice(voicePath); // 播放语音
                    isComposing = false;
                }
            });
            SoundUtil.startSoundTask(SoundConstant.URL_TTS, task); // 启动语音合成任务
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (mCityInfo == null) {
            initLocation(); // 初始化定位服务
        }
    }

    // 初始化定位服务
    private void initLocation() {
        Log.d(TAG, "initLocation");
        // 从系统服务中获取定位管理器
        mLocationMgr = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
        Criteria criteria = new Criteria(); // 创建一个定位准则对象
        // 设置定位精确度。Criteria.ACCURACY_COARSE表示粗略,Criteria.ACCURACY_FIN表示精细
        criteria.setAccuracy(Criteria.ACCURACY_FINE);
        criteria.setAltitudeRequired(true); // 设置是否需要海拔信息
        criteria.setBearingRequired(true); // 设置是否需要方位信息
        criteria.setCostAllowed(true); // 设置是否允许运营商收费
        criteria.setPowerRequirement(Criteria.POWER_LOW); // 设置对电源的需求
        // 获取定位管理器的最佳定位提供者
        String bestProvider = mLocationMgr.getBestProvider(criteria, true);
        if (mLocationMgr.isProviderEnabled(bestProvider)) { // 定位提供者当前可用
            beginLocation(bestProvider); // 开始定位
        }
        Log.d(TAG, "isProviderEnabled="+mLocationMgr.isProviderEnabled(bestProvider));
    }

    // 开始定位
    private void beginLocation(String method) {
        // 检查当前设备是否已经开启了定位功能
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
                != PackageManager.PERMISSION_GRANTED) {
            Toast.makeText(this, "请授予定位权限并开启定位功能", Toast.LENGTH_SHORT).show();
            return;
        }
        // 设置定位管理器的位置变更监听器
        mLocationMgr.requestLocationUpdates(method, 300, 0, mLocationListener);
        // 获取最后一次成功定位的位置信息
        Location location = mLocationMgr.getLastKnownLocation(method);
        getAddress(location); // 获取详细地址
    }

    // 获取详细地址
    private void getAddress(Location location) {
        if (location != null) {
            // 创建一个根据经纬度查询详细地址的任务
            GetAddressTask task = new GetAddressTask(this, location, cityInfo -> getCityCode(cityInfo));
            task.start(); // 启动地址查询任务
        }
    }

    // 获取城市代码
    private void getCityCode(CityInfo cityInfo) {
        isLocated = true;
        mCityInfo = cityInfo;
        // 创建一个查询城市编码的任务
        GetCityCodeTask task = new GetCityCodeTask(this, cityInfo, cityCode -> getWeather(cityCode));
        task.start(); // 启动城市编码查询任务
    }

    // 获取天气信息
    private void getWeather(String city_code) {
        mCityInfo.city_code = city_code;
        // 创建一个查询城市天气的任务
        GetWeatherTask task = new GetWeatherTask(this, city_code, weatherInfo -> {
            mCityInfo.weather_info = weatherInfo;
        });
        task.start(); // 启动城市天气查询任务
    }

    // 定义一个位置变更监听器
    private LocationListener mLocationListener = new LocationListener() {
        @Override
        public void onLocationChanged(Location location) {
            if (!isLocated) {
                getAddress(location); // 获取详细地址
            }
        }

        @Override
        public void onProviderDisabled(String arg0) {}

        @Override
        public void onProviderEnabled(String arg0) {}

        @Override
        public void onStatusChanged(String arg0, int arg1, Bundle arg2) {}
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mLocationMgr != null) {
            mLocationMgr.removeUpdates(mLocationListener); // 移除定位管理器的位置变更监听器
        }
        releaseRobot(); // 释放机器人资源
        mMediaPlayer.release(); // 释放媒体播放器
    }

}

2:问题类

package com.example.voice;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.os.Environment;
import android.text.TextUtils;
import android.view.View;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;

import com.example.voice.dao.QuestionDao;
import com.example.voice.entity.QuestionInfo;

import java.io.File;

public class QuestionEditActivity extends AppCompatActivity {
    private final static String TAG = "QuestionEditActivity";
    private TextView tv_option; // 声明一个文本视图对象
    private EditText et_question; // 声明一个编辑框对象
    private EditText et_answer; // 声明一个编辑框对象
    private LinearLayout ll_view; // 声明一个线性视图对象
    private TextView tv_question; // 声明一个文本视图对象
    private TextView tv_answer; // 声明一个文本视图对象
    private int mQuestionId; // 问答编号
    private boolean isEditing = false; // 是否为编辑状态
    private QuestionDao questionDao; // 声明一个问答的持久化对象
    private QuestionInfo mQuestionInfo; // 问答信息

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_question_edit);
        mQuestionId = getIntent().getIntExtra("id", -1);
        findViewById(R.id.iv_back).setOnClickListener(v -> finish());
        TextView tv_title = findViewById(R.id.tv_title);
        tv_option = findViewById(R.id.tv_option);
        et_question = findViewById(R.id.et_question);
        et_answer = findViewById(R.id.et_answer);
        ll_view = findViewById(R.id.ll_view);
        tv_question = findViewById(R.id.tv_question);
        tv_answer = findViewById(R.id.tv_answer);
        tv_option.setOnClickListener(v -> {
            if (mQuestionId==-1 || isEditing) { // 添加或者修改
                saveQuestion(); // 保存问答信息
            } else { // 查看详情
                tv_option.setText("保存");
                ll_view.setVisibility(View.GONE);
            }
            isEditing = !isEditing;
        });
        // 从App实例中获取唯一的问答持久化对象
        questionDao = MainApplication.getInstance().getQuestionDB().questionDao();
        if (mQuestionId == -1) { // 添加新问答
            tv_title.setText("添加问答");
            tv_option.setText("保存");
        } else { // 查看问答详情
            tv_title.setText("问答详情");
            tv_option.setText("编辑");
            showQuestion(); // 显示问答详情
        }
    }

    // 显示问答详情
    private void showQuestion() {
        // 根据编号加载问答信息
        mQuestionInfo = questionDao.queryQuestionById(mQuestionId);
        et_question.setText(mQuestionInfo.getQuestion());
        et_answer.setText(mQuestionInfo.getAnswer());
        tv_question.setText(mQuestionInfo.getQuestion());
        tv_answer.setText(mQuestionInfo.getAnswer());
        ll_view.setVisibility(View.VISIBLE);
    }

    // 保存问答信息
    private void saveQuestion() {
        String question = et_question.getText().toString();
        String answer = et_answer.getText().toString();
        if (TextUtils.isEmpty(question)) {
            Toast.makeText(this, "请先输入问题描述", Toast.LENGTH_SHORT).show();
            return;
        }
        if (TextUtils.isEmpty(answer)) {
            Toast.makeText(this, "请先输入回答内容", Toast.LENGTH_SHORT).show();
            return;
        }
        if (mQuestionId == -1) { // 添加来源
            mQuestionInfo = new QuestionInfo(question, answer, 1);
            questionDao.insertOneQuestion(mQuestionInfo); // 插入一条问答信息
        } else { // 查看来源
            mQuestionInfo.setQuestion(question);
            mQuestionInfo.setAnswer(answer);
            questionDao.updateQuestion(mQuestionInfo); // 更新问答信息
            String voicePath = String.format("%s/robot/%s.mp3",
                    getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString(),
                    mQuestionInfo.getId()+"");
            File voiceFile = new File(voicePath);
            if (voiceFile.exists()) { // 如果已经存在该问答的语音文件,就要删除原文件,这样下次才会重新合成新的语音文件
                voiceFile.delete();
            }
        }
        Toast.makeText(this, "成功保存问答信息", Toast.LENGTH_SHORT).show();
        finish(); // 关闭当前页面
    }
}

创作不易  觉得有帮助请点赞关注收藏~~~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/42087.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

智能家居—— 树莓派摄像头捕捉人脸并识别

文章目录下载安装mjpg-streamer树莓派安装libcurl库树莓派安装openssl库语音控制开启摄像头线程拍照代码及步骤语音控制摄像头拍照camera.ccontrolDevice.h下载安装mjpg-streamer 参考博文&#xff1a;智能家居 —— 树莓派下载安装mjpg-streamer&#xff08;完成拍照录像监控…

市面上真正的全光谱灯品牌有哪些?全光谱护眼照明灯的作用很明显

众所周知&#xff0c;人眼感知任何事物都离不开光线的照射&#xff0c;但很多人可能不知道&#xff0c;光线不仅可以使我们“看得见”&#xff0c;还可以决定我们是否看得“真实”&#xff0c;这是怎么回事呢&#xff1f;其实这就是光线的色谱丰富度的问题。 人眼感知最舒适的光…

堆、堆排序、堆应用

一、概述 “堆”&#xff08;Heap&#xff09;&#xff0c;原地排序、时间复杂度O(nlogn)的排序算法。 堆是一个完全二叉树&#xff1b;堆中每一个节点的值都必须大于等于&#xff08;或者小于等于&#xff09;其子树中每个节点的值&#xff1b; 二、如何实现一个堆 使用数…

第2-4-7章 docker安装WorkBench-规则引擎Drools-业务规则管理系统-组件化-中台

文章目录8. WorkBench8.1 WorkBench简介8.2 安装方式8.2.1 传统方式安装8.2.2 docker安装drools workbench8.3 使用方式8.3.1 创建空间、项目8.3.2 创建数据对象8.3.3 创建DRL规则文件8.3.4 创建测试场景8.3.5 设置KieBase和KieSession8.3.6 编译、构建、部署8.3.7 在项目中使用…

Intel PAUSE 指令变化如何影响 MySQL 的性能

导读 x86、arm指令都很多&#xff0c;无论是应用程序员还是数据库内核研发大多时候都不需要对这些指令深入理解&#xff0c;但是 Pause 指令和数据库操作太紧密了&#xff0c;本文通过一次非常有趣的性能优化来引入对 Pause 指令的理解&#xff0c;期望可以事半功倍地搞清楚 C…

微服务线上问题排查困难?不知道问题出在哪一环?那是你还不会分布式链路追踪

咱们以前单体应用里面有很多的应用和功能&#xff0c;依赖各个功能之间相互调用&#xff0c;使用公共的代码包等等&#xff0c;排查问题&#xff0c;使用类似于 gdb/dlv 工具或者直接查看代码日志&#xff0c;进行定位和分析 但是现在我们基本上都是微服务架构了&#xff0c;将…

Node.js 入门教程 20 查看 npm 包安装的版本 21 安装 npm 包的旧版本

Node.js 入门教程 Node.js官方入门教程 Node.js中文网 本文仅用于学习记录&#xff0c;不存在任何商业用途&#xff0c;如侵删 文章目录Node.js 入门教程20 查看 npm 包安装的版本21 安装 npm 包的旧版本20 查看 npm 包安装的版本 若要查看所有已安装的 npm 软件包&#xff08…

JetpackCompose Navigation导航快速上手

Navigation 快速上手 下面案例简要展示使用 Compose 版本的 Navigation 库来实现两个页面之间的跳转 这是完整的结构&#xff08;忽略掉红线划过的那个包&#xff09; 编写欲跳转的两个页面 编写 Demo1 页面 子页面使用多个 composable 组件相组合的方法一一装配起来 Demo1m…

【博客543】golang pprof性能调试:寻找cpu瓶颈

golang pprof性能调试&#xff1a;寻找cpu瓶颈 1、引入pprof进行性能调试 在代码中加入&#xff1a; import _ "net/http/pprof"go func() {http.ListenAndServe("0.0.0.0:8899", nil) }()示例&#xff1a;为冒泡排序加入pprof debug package mainimpo…

月薪2万的大数据职位,为什么必须学习Python?

前言 马云说&#xff1a;“未来最大的资源就是数据&#xff0c;不参与大数据十年后一定会后悔。”毕竟出自wuli马大大之口&#xff0c;今年二月份我开始了学习大数据的道路&#xff0c;直到现在对大数据的学习脉络和方法也渐渐清晰。 我们先来看一下数据分析相关职位现在的薪…

Cys(Npys)-(Arg)₉,H2N-C(Npys)-RRRRRRRRR-OH

可渗透细胞的非精氨酸酰胺&#xff0c;可以很容易地偶联到负载分子上&#xff0c;例如通过马来酰亚胺-硫醇偶联。 编号: 126721中文名称: Cys(Npys)-(Arg)₉英文名: Cys(Npys)-(Arg)₉单字母: H2N-C(Npys)-RRRRRRRRR-OH三字母: H2N-Cys(Npys)-Arg-Arg-Arg-Arg-Arg-Arg-Arg-Arg-…

多肽标签TC tag,H2N-CCPGCC-OH

编号: 168488中文名称: 多肽标签TC tag英文名: TC tag单字母: H2N-CCPGCC-OH三字母: H2N-Cys-Cys-Pro-Gly-Cys-Cys-COOH氨基酸个数: 6分子式: C19H32N6O7S4平均分子量: 584.75精确分子量: 584.12等电点(PI): 9.13pH7.0时的净电荷数: 3.85平均亲水性: -1疏水性值: 1.33来源: 人工…

Vision Transformer

Vision Transformer 将Transformer应用于CV领域。 不了解Transformer的先去看下&#xff1a;一文看懂Transformer 对比Transformer&#xff0c;ViT的特殊之处不多。因为作者说了他们想要做“尽量少的改动”将Transformer直接应用于图像领域 论文下载地址&#xff1a;https:…

留学Paper写作需要怎么正确引用?

1.MLA文献引用格式的基本描述&#xff1a;文学类Paper通常使用MLA&#xff08;Modern Language Association&#xff09;格式。作者在正文中用括号夹注的形式注明参考文献的简要出处&#xff0c;即&#xff08;作者姓氏页码&#xff09;。 Ancient writers attributed the inve…

小学生python游戏编程arcade----坦克换色

小学生python游戏编程arcade----坦克换色前言坦克换色1、RGB颜色1.1 RGB1.2 PIL 模块中的image1.3 效果图![在这里插入图片描述](https://img-blog.csdnimg.cn/f533a0bed98f4b49a462a2f056c35786.png)#### 1.4 代码实现1.5 总结2、RGB转换为HSV2.1 RGB2.2 HSV2.3 python RGB 转…

《前端框架开发技术》HTML+CSS+JavaScript 制作个人简历模板

&#x1f389;精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

23、Mybatis查询功能4(查询结果为一个map集合(多条数据))

Mybatis查询功能4&#xff08;查询结果为一个map集合&#xff08;多条数据&#xff09;&#xff09; 可以用Lsit集合接收可以用MapKey注解设置键&#xff08;用一个唯一标识&#xff09;第一步&#xff1a; 第二步&#xff1a; 第三步&#xff1a;

我的第一个Servlet程序,并理解浏览器和代码如何关联在一起

目录 Servlet 第一个Servlet程序 1.创建项目 maven是什么 如何创建maven项目 2.引入Servlet依赖 找到库中maven代码 3.创建目录结构 4.编写代码 理解注解Webserlet 5.打包程序 生成war包 6.部署程序 7.验证程序 二.理解浏览器发的请求是怎么和自己写的代码建立联…

景联文智能标注平台将数据处理效率提升十倍以上!数据精准度最高可达99%

目前主流的机器学习方式是以有监督的深度学习方式为主&#xff0c;这对标注数据有着强较依赖性需求&#xff0c;未经标注处理过的原始数据多以非结构化数据为主&#xff0c;这些数据难以被机器识别和学习。这就需要标注员借助数据标注工具对数据进行标注。 使用高效率的标注工具…

如何知道你的Linux内核占用的内存大小?

如何知道你的Linux内核占用的内存大小&#xff1f;1、代码段等2、kernel heap2.1、kmalloc2.2、vmalloc3、进程的页表4、内核占用内存大小总和1、代码段等 内核所需的代码段、bss段&#xff0c;内核栈等。 / # dmesg | grep Memory Memory policy: Data cache writealloc M…