Chinatalk是一个提供给汉语学习者的安卓app, 主要提供多样化的答题功能,它通过集成了语音交互促进用户口语锻炼。
您可以在此下载体验的apk包: http://download.wzc520pyf.cn/chinatalk
目前安卓端主要使用的技术栈有:Jetpack(ViewModel、Navigation)、用于动态申请权限的RxPermissions、异步框架RxJava2、UI库QMUI、代码简化butterknife、网络请求库retrofit、动画框架lottie、文件下载框架Aria、图片加载框架Glide、Bmob云短信服务、讯飞语音听写、讯飞语音合成、讯飞语音评测等。
服务端采用Nest进行开发,使用mysql数据库。
以下是已开发的部分界面截图:
项目封装了讯飞语音听写、合成、评测三个功能,调用方式简洁,以语音听写为例:
-
实现接口RecognizeListener,接口提供三个回调函数用于接收听写结果。
public class HomeFragment extends Fragment implements RecognizeListener { @Override public void onNewResult(String result) { homeViewModel.setRecognizeText(homeViewModel.getRecognizeText().getValue() + "最新听写结果:" + result + "\n"); } @Override public void onTotalResult(String result, boolean isLast) { homeViewModel.setRecognizeText(homeViewModel.getRecognizeText().getValue() + "所有听写结果:" + result + "\n"); } @Override public void onError(SpeechError speechError) { Toast.makeText(mContext, "出错了 " + speechError, Toast.LENGTH_SHORT).show(); } }
-
在createView中初始化讯飞音频读写管理类
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { //初始化讯飞音频读写管理类 RecognizeSpeechManager.instance().init(mContext); RecognizeSpeechManager.instance().setRecognizeListener(this); }
-
在任意位置调用音频读写管理类的开始/暂时函数
public void onClick(View v) { switch (v.getId()){ case R.id.btStart: RecognizeSpeechManager.instance().startRecognize(); break; case R.id.btCancel: RecognizeSpeechManager.instance().cancelRecognize(); break; case R.id.btStop: RecognizeSpeechManager.instance().stopRecognize(); break; } }
使用retrofit作为网络请求框架, 以exam接口为例:
-
接口返回的数据格式:
{ "code": 200, "message": "查询成功", "data": { "papers":... } }
-
用ServiceResponse类来作为retrofit通用处理类
Observable<ServiceResponse<GettedExam>>
-
在api目录下编写Service接口
/** * 试卷相关接口 */ public interface ExamPaperService { /** * 查询所有模拟试卷 */ @GET("papers/hskmocks/with-user-score/all") Observable<ServiceResponse<GettedExam>> getAllHskMocksWithUserScoreAll(); }
此处采用RxJava书写风格(Observable)
-
在bean目录下编写实体和捕获类
-
实体存放实体类,是一个业务体的具形抽象,比如试卷实体,用户实体
-
捕获类是用于接收接口返回数据的类,与接口返回的data相对应。
public class GettedExam { private ArrayList<Exam> papers; // 与接口一致 public ArrayList<Exam> getPapers() { return papers; } public void setPapers(ArrayList<Exam> papers) { this.papers = papers; } @Override public String toString() { return "GettedExam{" + "papers=" + papers + '}'; } }
-
-
在exam/ExamViewModel中:
这里RxJava风格的写法, 只需关注相关的部分即可
// ... private MutableLiveData<ExamResult> examResult; // ... Observable<ServiceResponse<GettedExam>> observable = new RetrofitClient.Builder().build().create(ExamPaperService.class).getAllHskMocksWithUserScoreAll(); observable.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer<ServiceResponse<GettedExam>>() { @Override public void onSubscribe(Disposable d) { } @Override public void onNext(ServiceResponse<GettedExam> examServiceResponse) { Result<GettedExam> gettedExam = new Result.Success<>(examServiceResponse.getData()); if (gettedExam instanceof Result.Success) { GettedExam data = ((Result.Success<GettedExam>) gettedExam).getData(); examResult.setValue(new ExamResult(new GettedInExamView(data.getPapers()))); } else { examResult.setValue(new ExamResult(R.string.failed)); } } @Override public void onError(Throwable e) { } @Override public void onComplete() { } });
- 在Next回调中可拿到接口的返回值
- 用Result包裹一次返回接口可判断数据获取是否成功
- 如果成功, 则将返回对象GettedExam从Result中取出,
- 接着可对GettedExam做相应的二次处理
- 处理结束后, 将值告知GettedInExamView类(向UI公开数据)
-
在ExamFragment中, 通过监听examResult来做出相应的UI变化
examViewModel.getExamResult().observe(getViewLifecycleOwner(), new Observer<ExamResult>() { @Override public void onChanged(ExamResult examResult) { if (examResult.getError() != null) { // 展示出错信息 Toast.makeText(mContext, examResult.getError(), Toast.LENGTH_LONG).show(); } if (examResult.getSuccess() != null) { // 登录成功,将数据公开给UI,UI拿到数据执行一些操作 Collections.shuffle(examResult.getSuccess().getExams()); mAdapter.setData(examResult.getSuccess().getExams()); } } });