Android 垃圾分类APP(三)垃圾分类之语音输入

由于两个方法的目的地都是跳转到其他的Activity,那么这里可以重写一个方法,区别就是目标Activity不同,那么Activity又是什么呢?是class。因此可以写一个这样的方法。

/**

  • 进入Activity

  • @param clazz 目标Activity

*/

private void gotoActivity(Class<?> clazz) {

startActivity(new Intent(MainActivity.this, clazz));

}

这里的当前所在Activity为MainActivity,你也可以用this或者context代替,context是父类BaseActivity中的,子类继承父类可以使用父类中的成员变量。clazz则代表Class,因为class是关键字,因此通常使用clazz或者aClass来表示变量名。至于Class<?>,表示类的通配符,和泛型有相似的地方,也有区别。下面修改jumpTextInput方法

public void jumpTextInput(View view) {

gotoActivity(TextInputActivity.class);

}

我相信你能看懂,直接运行一下:

在这里插入图片描述

可以看到大致没有什么问题,但是TextInputActivity的状态栏还是之前的那样,这样的用户体验是不好的,因此需要做修改,修改状态栏的背景色,然后是图标改成深色。那么为了方便使用,我打算写在BaseActivity中,这样其他的Activity只要继承了BaseActivity就可以直接调用了的。那么首先打开mvplibrary模块,在里面的utils包下新增一个StatusBarUtil类,位置如下图所示:

在这里插入图片描述

代码如下:

package com.llw.mvplibrary.network.utils;

import android.annotation.TargetApi;

import android.app.Activity;

import android.graphics.Color;

import android.os.Build;

import android.view.View;

import android.view.Window;

import android.view.WindowManager;

import androidx.appcompat.app.AppCompatActivity;

import java.lang.reflect.Field;

import java.lang.reflect.Method;

/**

  • 状态栏工具类

*/

public class StatusBarUtil {

/**

  • 修改状态栏为全透明

  • @param activity

*/

@TargetApi(19)

public static void transparencyBar(Activity activity) {

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {

Window window = activity.getWindow();

window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);

window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN

| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);

window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);

window.setStatusBarColor(Color.TRANSPARENT);

} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {

Window window = activity.getWindow();

window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,

WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);

}

}

/**

  • 状态栏亮色模式,设置状态栏黑色文字、图标,

  • 适配4.4以上版本MIUIV、Flyme和6.0以上版本其他Android

  • @param activity

  • @return 1:MIUUI 2:Flyme 3:android6.0

*/

public static int StatusBarLightMode(Activity activity) {

int result = 0;

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {

if (MIUISetStatusBarLightMode(activity, true)) {

result = 1;

} else if (FlymeSetStatusBarLightMode(activity.getWindow(), true)) {

result = 2;

} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);

result = 3;

}

}

return result;

}

/**

  • 修改状态栏颜色,支持4.4以上版本

  • @param activity

  • @param colorId

*/

public static void setStatusBarColor(AppCompatActivity activity, int colorId) {

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {

Window window = activity.getWindow();

window.setStatusBarColor(activity.getResources().getColor(colorId));

}

}

/**

  • 已知系统类型时,设置状态栏黑色文字、图标。

  • 适配4.4以上版本MIUIV、Flyme和6.0以上版本其他Android

  • @param activity

  • @param type 1:MIUUI 2:Flyme 3:android6.0

*/

public static void StatusBarLightMode(Activity activity, int type) {

if (type == 1) {

MIUISetStatusBarLightMode(activity, true);

} else if (type == 2) {

FlymeSetStatusBarLightMode(activity.getWindow(), true);

} else if (type == 3) {

activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);

}

}

/**

  • 状态栏暗色模式,清除MIUI、flyme或6.0以上版本状态栏黑色文字、图标

*/

public static void StatusBarDarkMode(Activity activity, int type) {

if (type == 1) {

MIUISetStatusBarLightMode(activity, false);

} else if (type == 2) {

FlymeSetStatusBarLightMode(activity.getWindow(), false);

} else if (type == 3) {

activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);

}

}

/**

  • 设置状态栏图标为深色和魅族特定的文字风格

  • 可以用来判断是否为Flyme用户

  • @param window 需要设置的窗口

  • @param dark 是否把状态栏文字及图标颜色设置为深色

  • @return boolean 成功执行返回true

*/

public static boolean FlymeSetStatusBarLightMode(Window window, boolean dark) {

boolean result = false;

if (window != null) {

try {

WindowManager.LayoutParams lp = window.getAttributes();

Field darkFlag = WindowManager.LayoutParams.class

.getDeclaredField(“MEIZU_FLAG_DARK_STATUS_BAR_ICON”);

Field meizuFlags = WindowManager.LayoutParams.class

.getDeclaredField(“meizuFlags”);

darkFlag.setAccessible(true);

meizuFlags.setAccessible(true);

int bit = darkFlag.getInt(null);

int value = meizuFlags.getInt(lp);

if (dark) {

value |= bit;

} else {

value &= ~bit;

}

meizuFlags.setInt(lp, value);

window.setAttributes(lp);

result = true;

} catch (Exception e) {

}

}

return result;

}

/**

  • 需要MIUIV6以上

  • @param activity

  • @param dark 是否把状态栏文字及图标颜色设置为深色

  • @return boolean 成功执行返回true

*/

public static boolean MIUISetStatusBarLightMode(Activity activity, boolean dark) {

boolean result = false;

Window window = activity.getWindow();

if (window != null) {

Class clazz = window.getClass();

try {

int darkModeFlag = 0;

Class layoutParams = Class.forName(“android.view.MiuiWindowManager$LayoutParams”);

Field field = layoutParams.getField(“EXTRA_FLAG_STATUS_BAR_DARK_MODE”);

darkModeFlag = field.getInt(layoutParams);

Method extraFlagField = clazz.getMethod(“setExtraFlags”, int.class, int.class);

if (dark) {

extraFlagField.invoke(window, darkModeFlag, darkModeFlag);//状态栏透明且黑色字体

} else {

extraFlagField.invoke(window, 0, darkModeFlag);//清除黑色字体

}

result = true;

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

//开发版 7.7.13 及以后版本采用了系统API,旧方法无效但不会报错,所以两个方式都要加上

if (dark) {

activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);

} else {

activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);

}

}

} catch (Exception e) {

}

}

return result;

}

}

这样的代码网络上一大把,都是类似的。下面进入BaseActivity,新增如下方法。

/**

  • 设置页面状态栏

  • @param activity 目标Activity

  • @param bgColorId 状态栏背景颜色

  • @param isDarkTheme 状态栏主题 深色则为黑色图标和文字,浅色则为白色图标和文字

*/

protected void setStatubar(AppCompatActivity activity,int bgColorId,boolean isDarkTheme){

StatusBarUtil.setStatusBarColor(activity,bgColorId);

if(isDarkTheme){

//深色

StatusBarUtil.StatusBarLightMode(this);

}

}

通过这个方法就可以设置状态栏的背景颜色,图标主题样式。

修改还没有完,因为我们的页面toolbar如果需要返回的话,每一个页面去写返回方法明显不方便,因此也可以写在BaseActivity中,方法如下:

/**

  • 页面返回

  • @param toolbar 标题

  • @param isDestroy 是否销毁

*/

protected void back(Toolbar toolbar, boolean isDestroy) {

toolbar.setNavigationOnClickListener(v -> {

if (isDestroy) {

finish();

} else {

onBackPressed();

}

});

}

下面回到TextInputActivity中。

private MaterialToolbar toolbar;//工具栏

然后在initView中,增加如下代码:

setStatubar(this, R.color.white, true);

toolbar = findViewById(R.id.toolbar);

back(toolbar, false);

在这里插入图片描述

然后你再运行就是白色的背景了,

在这里插入图片描述

至此,这个文字输入页面的转移就完成了。

二、新增语音输入页面


在ui下新建一个VoiceInputActivity,布局为activity_voice_input.xml。

布局代码如下:

<?xml version="1.0" encoding="utf-8"?>

<FrameLayout xmlns:android=“http://schemas.android.com/apk/res/android”

xmlns:app=“http://schemas.android.com/apk/res-auto”

android:layout_width=“match_parent”

android:layout_height=“match_parent”

android:fitsSystemWindows=“true”

android:orientation=“vertical”>

<LinearLayout

android:layout_width=“match_parent”

android:layout_height=“match_parent”

android:orientation=“vertical”>

<com.google.android.material.appbar.MaterialToolbar

android:id=“@+id/toolbar”

android:layout_width=“match_parent”

android:layout_height=“?attr/actionBarSize”

android:background=“@color/white”

app:navigationIcon=“@mipmap/icon_back”>

<TextView

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_gravity=“center”

android:text=“语音输入”

android:textColor=“@color/black”

android:textSize=“18sp” />

</com.google.android.material.appbar.MaterialToolbar>

<androidx.recyclerview.widget.RecyclerView

android:id=“@+id/rv_result”

android:layout_width=“match_parent”

android:layout_height=“match_parent”

android:overScrollMode=“never”

android:scrollbars=“none” />

<com.google.android.material.floatingactionbutton.FloatingActionButton

android:layout_width=“wrap_content”

android:layout_height=“wrap_content”

android:layout_gravity=“bottom|end”

android:layout_margin=“@dimen/dp_20”

android:onClick=“voiceInput”

android:src=“@mipmap/icon_voice”

app:backgroundTint=“@color/white”

app:backgroundTintMode=“screen”

app:fabSize=“auto”

app:hoveredFocusedTranslationZ=“@dimen/dp_18”

app:pressedTranslationZ=“@dimen/dp_18” />

然后继承BaseActivity,代码如下:

package com.llw.goodtrash.ui;

import android.os.Bundle;

import android.view.View;

import com.google.android.material.appbar.MaterialToolbar;

import com.llw.goodtrash.R;

import com.llw.mvplibrary.base.BaseActivity;

/**

  • 语音输入物品进行垃圾分类

*/

public class VoiceInputActivity extends BaseActivity {

private MaterialToolbar toolbar;

@Override

public void initData(Bundle savedInstanceState) {

initView();

}

private void initView() {

setStatubar(this, R.color.white, true);

toolbar = findViewById(R.id.toolbar);

back(toolbar, false);

}

@Override

public int getLayoutId() {

return R.layout.activity_voice_input;

}

public void voiceInput(View view) {

showMsg(“语音”);

}

}

三、导入讯飞SDK


这个语音页面主要的核心还是在语音上,这里我使用讯飞语音识别SDK,首先把讯飞SDK引入进来。首先登陆讯飞开放平台

在这里插入图片描述

可以看到我这里已经创建了三个应用,都是在对应的文章中提到过,下面创建新应用。

在这里插入图片描述

填写好资料后,提交。

在这里插入图片描述

然后下载SDK

在这里插入图片描述

下载后解压,里面的内容如下所示:

在这里插入图片描述

首先把libs下的文件复制到app模块下的libs中。

在这里插入图片描述

你可以看到现在Msc.jar是无法展开的,这说明还没有引入。

然后打开app的build.gradle在android{}闭包配置如下代码:

//修改APP加载资源目录

sourceSets {

main {

jniLibs.srcDir ‘libs’

jni.srcDirs = [] //disable automatic ndk-build

}

}

然后Sync Now 这时你的jar可以展开,则说明你的SDK引入成功,如下图所示:

在这里插入图片描述

在main下新建一个assets文件夹,将文件中的assets下的文件粘贴到刚才创建的这个文件夹下,如下图所示:

在这里插入图片描述

然后在TrashApplication的onCreate方法中进行SDK的初始化,这里需要用到APPID,就是在平台上创建应用时生成的。

在这里插入图片描述

这里请使用自己创建应用时生成的APPID,我的你拿去也没有用。

四、请求权限


语音输入需要使用麦克风,这个是需要权限的,因此要动态申请,这个权限申请有两种方式,第一个是在应用安装运行时申请,通常是在引导页或者欢迎页,第二个就是在用户需要使用这个功能时才去请求权限,乍一看好像用到时再申请合理一些,不过这也和APP的权限数量有关系,现在普遍的都是在启动页面一次申请APP用到的所有权限,让用户一次性通过,后面就不管了这个了,这种做法是居多的。而我们目前只有一个麦克风权限所以就直接在用的地方申请好了。

这里的权限我会用到第三方依赖库,而这个依赖也是放到mvplibrary下进行统一的管理,打开mvplibrary模块下的build.gradle,在dependencies{}闭包下添加如下依赖:

//权限请求框架

api ‘com.tbruyelle.rxpermissions2:rxpermissions:0.9.4@aar’

在这里插入图片描述

然后Sync Now。

下面回到iVoiceInputActivity,先完成权限的请求,添加 requestPermission() 方法,代码如下:

/**

  • 请求权限

*/

@SuppressLint(“CheckResult”)

private void requestPermission() {

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

//Android6.0及以上版本

rxPermissions.request(Manifest.permission.RECORD_AUDIO).subscribe(granted -> {

if (granted) {//权限通过

//开始语音监听

} else {//权限未通过

showMsg(“权限未通过,你不能使用该功能”);

}

});

} else {

//Android6.0以下无须动态请求

//开始语音监听

}

}

很简单的一个方法,这里还需要在点击浮动按钮时调用requestPermission()方法。

/**

  • 悬浮按钮点击

*/

public void voiceInput(View view) {

//请求权限

requestPermission();

}

五、语音配置


这里的语音配置就用我之前写的一个工具类好了,首先在app模块下的utils包下新建一个JsonParser类,里面的代码如下:

package com.llw.goodtrash.utils;

import org.json.JSONArray;

import org.json.JSONObject;

import org.json.JSONTokener;

/**

  • Json结果解析类

  • @author llw

  • @date 2021/4/1 15:44

*/

public class JsonParser {

public static String parseIatResult(String json) {

StringBuffer ret = new StringBuffer();

try {

JSONTokener tokener = new JSONTokener(json);

JSONObject joResult = new JSONObject(tokener);

JSONArray words = joResult.getJSONArray(“ws”);

for (int i = 0; i < words.length(); i++) {

// 转写结果词,默认使用第一个结果

JSONArray items = words.getJSONObject(i).getJSONArray(“cw”);

JSONObject obj = items.getJSONObject(0);

ret.append(obj.getString(“w”));

// 如果需要多候选结果,解析数组其他字段

// for(int j = 0; j < items.length(); j++)

// {

// JSONObject obj = items.getJSONObject(j);

// ret.append(obj.getString(“w”));

// }

}

} catch (Exception e) {

e.printStackTrace();

}

return ret.toString();

}

public static String parseGrammarResult(String json) {

StringBuffer ret = new StringBuffer();

try {

JSONTokener tokener = new JSONTokener(json);

JSONObject joResult = new JSONObject(tokener);

JSONArray words = joResult.getJSONArray(“ws”);

for (int i = 0; i < words.length(); i++) {

JSONArray items = words.getJSONObject(i).getJSONArray(“cw”);

for(int j = 0; j < items.length(); j++)

{

JSONObject obj = items.getJSONObject(j);

if(obj.getString(“w”).contains(“nomatch”))

{

ret.append(“没有匹配结果.”);

return ret.toString();

}

ret.append(“【结果】” + obj.getString(“w”));

ret.append(“【置信度】” + obj.getInt(“sc”));

ret.append(“\n”);

}

}

} catch (Exception e) {

e.printStackTrace();

ret.append(“没有匹配结果.”);

}

return ret.toString();

}

public static String parseLocalGrammarResult(String json) {

StringBuffer ret = new StringBuffer();

try {

JSONTokener tokener = new JSONTokener(json);

JSONObject joResult = new JSONObject(tokener);

JSONArray words = joResult.getJSONArray(“ws”);

for (int i = 0; i < words.length(); i++) {

JSONArray items = words.getJSONObject(i).getJSONArray(“cw”);

for(int j = 0; j < items.length(); j++)

{

JSONObject obj = items.getJSONObject(j);

if(obj.getString(“w”).contains(“nomatch”))

{

ret.append(“没有匹配结果.”);

return ret.toString();

}

ret.append(“【结果】” + obj.getString(“w”));

ret.append(“\n”);

}

}

ret.append(“【置信度】” + joResult.optInt(“sc”));

} catch (Exception e) {

e.printStackTrace();

ret.append(“没有匹配结果.”);

}

return ret.toString();

}

public static String parseTransResult(String json, String key) {

StringBuffer ret = new StringBuffer();

try {

JSONTokener tokener = new JSONTokener(json);

JSONObject joResult = new JSONObject(tokener);

String errorCode = joResult.optString(“ret”);

if(!errorCode.equals(“0”)) {

return joResult.optString(“errmsg”);

}

JSONObject transResult = joResult.optJSONObject(“trans_result”);

ret.append(transResult.optString(key));

/*JSONArray words = joResult.getJSONArray(“results”);

for (int i = 0; i < words.length(); i++) {

JSONObject obj = words.getJSONObject(i);

ret.append(obj.getString(key));

}*/

} catch (Exception e) {

e.printStackTrace();

}

return ret.toString();

}

}

这个类用来解析语音识别到的结果。下面同样在这个包下新建一个SpeechUtil类,里面的代码如下:

package com.llw.goodtrash.utils;

import android.app.Activity;

import android.content.Context;

import android.content.SharedPreferences;

import android.graphics.Paint;

import android.os.Bundle;

import android.os.Environment;

import android.os.MemoryFile;

import android.util.Log;

import android.widget.TextView;

import android.widget.Toast;

import com.iflytek.cloud.ErrorCode;

import com.iflytek.cloud.InitListener;

import com.iflytek.cloud.RecognizerResult;

import com.iflytek.cloud.SpeechConstant;

import com.iflytek.cloud.SpeechError;

import com.iflytek.cloud.SpeechEvent;

import com.iflytek.cloud.SpeechRecognizer;

import com.iflytek.cloud.SpeechSynthesizer;

import com.iflytek.cloud.SynthesizerListener;

import com.iflytek.cloud.msc.util.FileUtil;

import com.iflytek.cloud.msc.util.log.DebugLog;

import com.iflytek.cloud.ui.RecognizerDialog;

import com.iflytek.cloud.ui.RecognizerDialogListener;

import org.json.JSONException;

import org.json.JSONObject;

import java.util.HashMap;

import java.util.LinkedHashMap;

import java.util.Objects;

import java.util.Vector;

/**

  • 语音工具类

  • @author llw

*/

public class SpeechUtil {

private static final String TAG = “SpeechUtil”;

private static Context mContext;

// 语音合成对象

public static SpeechSynthesizer mTts;

//播放的文字

private static String defaultText = “富强、明主、文明、和谐、自由、平等、公正、法制、爱国、敬业、诚信、友善。”;

// 引擎类型

private static String mEngineType = SpeechConstant.TYPE_CLOUD;

private static Vector<byte[]> container = new Vector<>();

//内存文件

private static MemoryFile memoryFile;

//总大小

public static volatile long mTotalSize = 0;

// 默认发音人

private static String voicer = “xiaoyan”;

//语速

private static String speedValue = “50”;

//音调

private static String pitchValue = “50”;

//音量

private static String volumeValue = “50”;

private static TextView tvState;

/语音识别****/

private static SpeechRecognizer mIat;// 语音听写对象

private static RecognizerDialog mIatDialog;// 语音听写UI

// 用HashMap存储听写结果

private static HashMap<String, String> mIatResults = new LinkedHashMap<String, String>();

private static SharedPreferences mSharedPreferences;//缓存

private static String language = “zh_cn”;//识别语言

private static String resultType = “json”;//结果内容数据格式

private static String dictationResults;//听写结果

/**

  • 初始化监听。

*/

private static InitListener mTtsInitListener = code -> {

Log.i(TAG, "InitListener init() code = " + code);

if (code != ErrorCode.SUCCESS) {

Log.i(TAG, “初始化失败,错误码:” + code);

} else {

Log.i(TAG, “初始化成功”);

}

};

/**

  • 初始化

  • @param context

*/

public static void init(Context context) {

mContext = context;

/*************** 语音合成 *****************/

// 初始化合成对象

mTts = SpeechSynthesizer.createSynthesizer(mContext, mTtsInitListener);

/*************** 语音听写 *****************/

// 使用SpeechRecognizer对象,可根据回调消息自定义界面;

mIat = SpeechRecognizer.createRecognizer(mContext, mInitListener);

// 使用UI听写功能,请根据sdk文件目录下的notice.txt,放置布局文件和图片资源

mIatDialog = new RecognizerDialog(mContext, mInitListener);

mSharedPreferences = mContext.getSharedPreferences(“ASR”,

Activity.MODE_PRIVATE);

}

/**

  • 合成回调监听。

*/

private static SynthesizerListener mTtsListener = new SynthesizerListener() {

//开始播放

@Override

public void onSpeakBegin() {

Log.i(TAG, “开始播放”);

}

//暂停播放

@Override

public void onSpeakPaused() {

Log.i(TAG, “暂停播放”);

}

//继续播放

@Override

public void onSpeakResumed() {

Log.i(TAG, “继续播放”);

}

//合成进度

@Override

public void onBufferProgress(int percent, int beginPos, int endPos, String info) {

Log.i(TAG, “合成进度:” + percent + “%”);

}

//播放进度

@Override

public void onSpeakProgress(int percent, int beginPos, int endPos) {

// 播放进度

Log.i(TAG, “播放进度:” + percent + “%”);

tvState.setText(“播报中”);

}

//播放完成

@Override

public void onCompleted(SpeechError error) {

tvState.setText(“播报完成”);

if (error == null) {

Log.i(TAG, “播放完成,” + container.size());

DebugLog.LogD(“播放完成,” + container.size());

for (int i = 0; i < container.size(); i++) {

//写入文件

writeToFile(container.get(i));

}

//保存文件

FileUtil.saveFile(memoryFile, mTotalSize, mContext.getExternalFilesDir(null) + “/1.pcm”);

} else {

//异常信息

showTip(error.getPlainDescription(true));

}

tvState.setText(“”);

}

//事件

@Override

public void onEvent(int eventType, int arg1, int arg2, Bundle obj) {

// 以下代码用于获取与云端的会话id,当业务出错时将会话id提供给技术支持人员,可用于查询会话日志,定位出错原因

// 若使用本地能力,会话id为null

if (SpeechEvent.EVENT_SESSION_ID == eventType) {

String sid = obj.getString(SpeechEvent.KEY_EVENT_SESSION_ID);

Log.i(TAG, “session id =” + sid);

}

//当设置SpeechConstant.TTS_DATA_NOTIFY为1时,抛出buf数据

if (SpeechEvent.EVENT_TTS_BUFFER == eventType) {

byte[] buf = obj.getByteArray(SpeechEvent.KEY_EVENT_TTS_BUFFER);

Log.i(TAG, “bufis =” + buf.length);

container.add(buf);

}

}

};

/**

  • 写入文件

*/

private static void writeToFile(byte[] data) {

if (data == null || data.length == 0) {

return;

}

try {

if (memoryFile == null) {

Log.i(TAG, “memoryFile is null”);

String mFilepath = mContext.getExternalFilesDir(null) + “/1.pcm”;

memoryFile = new MemoryFile(mFilepath, 1920000);

memoryFile.allowPurging(false);

}

memoryFile.writeBytes(data, 0, (int) mTotalSize, data.length);

mTotalSize += data.length;

} catch (Exception e) {

e.printStackTrace();

}

}

/**

  • 参数设置

  • @return

*/

private static void setParam() {

// 清空参数

mTts.setParameter(SpeechConstant.PARAMS, null);

// 根据合成引擎设置相应参数

if (mEngineType.equals(SpeechConstant.TYPE_CLOUD)) {

mTts.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_CLOUD);

//支持实时音频返回,仅在synthesizeToUri条件下支持

mTts.setParameter(SpeechConstant.TTS_DATA_NOTIFY, “1”);

// 设置在线合成发音人

mTts.setParameter(SpeechConstant.VOICE_NAME, voicer);

//设置合成语速

mTts.setParameter(SpeechConstant.SPEED, speedValue);

//设置合成音调

mTts.setParameter(SpeechConstant.PITCH, pitchValue);

//设置合成音量

mTts.setParameter(SpeechConstant.VOLUME, volumeValue);

} else {

mTts.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_LOCAL);

mTts.setParameter(SpeechConstant.VOICE_NAME, “”);

}

// 设置播放合成音频打断音乐播放,默认为true

mTts.setParameter(SpeechConstant.KEY_REQUEST_FOCUS, “false”);

// 设置音频保存路径,保存音频格式支持pcm、wav

mTts.setParameter(SpeechConstant.AUDIO_FORMAT, “pcm”);

mTts.setParameter(SpeechConstant.TTS_AUDIO_PATH, mContext.getExternalFilesDir(null) + “/msc/tts.pcm”);

}

/**

  • 开始语音预报

*/

public static void startVoiceBroadcast(String text, TextView textView) {

tvState = textView;

if (mTts == null) {

showTip(“创建对象失败,请确认 libmsc.so 放置正确,且有调用 createUtility 进行初始化”);

return;

}

if (text == null || text.isEmpty()) {

text = defaultText;

}

//设置参数

setParam();

//开始合成播放

int code = mTts.startSpeaking(text, mTtsListener);

if (code != ErrorCode.SUCCESS) {

showTip("语音合成失败,错误码: " + code);

}

}

/**

  • 初始化语音听写监听器

*/

private static InitListener mInitListener = code -> {

Log.d(TAG, "SpeechRecognizer init() code = " + code);

if (code != ErrorCode.SUCCESS) {

showTip(“初始化失败,错误码:” + code + “,请点击网址https://www.xfyun.cn/document/error-code查询解决方案”);

}

};

/**

  • 听写UI监听器

*/

private static RecognizerDialogListener mRecognizerDialogListener = new RecognizerDialogListener() {

/**

  • 识别结果

*/

@Override

public void onResult(RecognizerResult results, boolean isLast) {

parsingResult(results);//结果数据解析

}

/**

  • 识别回调错误

*/

@Override

public void onError(SpeechError error) {

showTip(error.getPlainDescription(true));

}

};

/**

  • 语音识别结果数据解析

  • @param results

*/

private static void parsingResult(RecognizerResult results) {

//获取解析结果

String text = JsonParser.parseIatResult(results.getResultString());

String sn = null;

// 读取json结果中的sn字段

try {

JSONObject resultJson = new JSONObject(results.getResultString());

sn = resultJson.optString(“sn”);

} catch (JSONException e) {

e.printStackTrace();

}

mIatResults.put(sn, text);

StringBuffer resultBuffer = new StringBuffer();

for (String key : mIatResults.keySet()) {

resultBuffer.append(mIatResults.get(key));

}

dictationResults = resultBuffer.toString();//听写结果显示

//回调

mSpeechCallback.dictationResults(dictationResults);

Log.d(TAG,dictationResults);

}

/**

  • 听写参数设置

  • @return

*/

public static void setDictationParam() {

// 清空参数

mIat.setParameter(SpeechConstant.PARAMS, null);

// 设置听写引擎

mIat.setParameter(SpeechConstant.ENGINE_TYPE, mEngineType);

// 设置返回结果格式

mIat.setParameter(SpeechConstant.RESULT_TYPE, resultType);

if (language.equals(“zh_cn”)) {

String lag = mSharedPreferences.getString(“iat_language_preference”,

“mandarin”);

Log.e(TAG, “language:” + language);// 设置语言

mIat.setParameter(SpeechConstant.LANGUAGE, “zh_cn”);

// 设置语言区域

mIat.setParameter(SpeechConstant.ACCENT, lag);

} else {

mIat.setParameter(SpeechConstant.LANGUAGE, language);

}

Log.e(TAG, “last language:” + mIat.getParameter(SpeechConstant.LANGUAGE));

//此处用于设置dialog中不显示错误码信息

//mIat.setParameter(“view_tips_plain”,“false”);

// 设置语音前端点:静音超时时间,即用户多长时间不说话则当做超时处理

mIat.setParameter(SpeechConstant.VAD_BOS, mSharedPreferences.getString(“iat_vadbos_preference”, “4000”));

// 设置语音后端点:后端点静音检测时间,即用户停止说话多长时间内即认为不再输入, 自动停止录音

mIat.setParameter(SpeechConstant.VAD_EOS, mSharedPreferences.getString(“iat_vadeos_preference”, “1000”));

// 设置标点符号,设置为"0"返回结果无标点,设置为"1"返回结果有标点

mIat.setParameter(SpeechConstant.ASR_PTT, mSharedPreferences.getString(“iat_punc_preference”, “1”));

// 设置音频保存路径,保存音频格式支持pcm、wav,设置路径为sd卡请注意WRITE_EXTERNAL_STORAGE权限

mIat.setParameter(SpeechConstant.AUDIO_FORMAT, “wav”);

mIat.setParameter(SpeechConstant.ASR_AUDIO_PATH, Environment.getExternalStorageDirectory() + “/msc/iat.wav”);

}

/**

  • 开始听写

*/

public static void startDictation(SpeechCallback speechCallback){

mSpeechCallback = speechCallback;

if( null == mIat ){

// 创建单例失败,与 21001 错误为同样原因,参考 http://bbs.xfyun.cn/forum.php?mod=viewthread&tid=9688

showTip( “创建对象失败,请确认 libmsc.so 放置正确,且有调用 createUtility 进行初始化” );

return;

}

mIatResults.clear();//清除数据

setDictationParam(); // 设置参数

mIatDialog.setListener(mRecognizerDialogListener);//设置监听

mIatDialog.show();// 显示对话框

//获取字体所在的控件

TextView tvLink = Objects.requireNonNull(mIatDialog.getWindow()).getDecorView().findViewWithTag(“textlink”);

tvLink.setText(" ");

tvLink.getPaint().setFlags(Paint.SUBPIXEL_TEXT_FLAG);//取消下划线

tvLink.setEnabled(false);//禁用点击

}

/**

  • Toast提示

  • @param msg

*/

private static void showTip(String msg) {

Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();

}

//语音回调

private static SpeechCallback mSpeechCallback;

/**

  • 语音回调接口

*/

public interface SpeechCallback {

/**

  • 听写结果

*/

void dictationResults(String goodsName);

}

}

我建议你先实现功能,然后再去了解里面做了什么,如果你时间不够的话。那么这个工具类自然要在VoiceInputActivity中进行配置使用的,下面进入VoiceInputActivity,下面先声明一些变量

private static final String TAG = “VoiceInputActivity”;

private MaterialToolbar toolbar;//工具栏

private RxPermissions rxPermissions;//异步权限请求框架

private List<TrashResponse.NewslistBean> newslistBeanList = new ArrayList<>();//数据列表

private SearchGoodsAdapter searchGoodsAdapter;//结果列表适配器

private RecyclerView rvResult;//结果列表

然后写一个初始化的initView()方法,在initData中调用。initView()方法代码如下:

private void initView() {

toolbar = findViewById(R.id.toolbar);

rvResult = findViewById(R.id.rv_result);

//设置页面状态栏

setStatubar(this, R.color.white, true);

//设置返回工具栏图标点击事件

back(toolbar, false);

//实例化

rxPermissions = new RxPermissions(this);

//初始化语音

SpeechUtil.init(context);

//配置适配器 设置布局和数据源

searchGoodsAdapter = new SearchGoodsAdapter(R.layout.item_search_rv, newslistBeanList);

//设置列表的布局管理器

rvResult.setLayoutManager(new LinearLayoutManager(this));

//列表item点击事件

searchGoodsAdapter.setOnItemChildClickListener((adapter, view, position) -> {

showMsg(“点击了” + newslistBeanList.get(position).getName());

});

//设置列表适配器

rvResult.setAdapter(searchGoodsAdapter);

}

initData中调用

@Override

public void initData(Bundle savedInstanceState) {

//页面初始化

initView();

}

然后你可以使用TextContract,或者重写一个VoiceContract,这两个类目前来说内容一致,因此你可以用一个来解决。不过我这里还是写了一个VoiceContract类,放在contract包下,里面的代码如下:

package com.llw.goodtrash.contract;

import android.annotation.SuppressLint;

import com.llw.goodtrash.api.ApiService;

import com.llw.goodtrash.model.TrashResponse;

import com.llw.mvplibrary.base.BasePresenter;

import com.llw.mvplibrary.base.BaseView;

import com.llw.mvplibrary.network.NetworkApi;

import com.llw.mvplibrary.network.observer.BaseObserver;

/**

  • 语音文字输入页面访问网络

  • @author llw

  • @date 2021/4/1 16:03

*/

public class VoiceContract {

public static class VoicePresenter extends BasePresenter {

/**

  • 搜索物品

  • @param word 物品名

*/

@SuppressLint(“CheckResult”)

public void searchGoods(String word) {

ApiService service = NetworkApi.createService(ApiService.class);

service.searchGoods(word).compose(NetworkApi.applySchedulers(new BaseObserver() {

@Override

public void onSuccess(TrashResponse groupResponse) {

if (getView() != null) {

getView().getSearchResponse(groupResponse);

}

}

@Override

public void onFailure(Throwable e) {

if (getView() != null) {

getView().getSearchResponseFailed(e);

}

}

}));

}

}

public interface VoiceView extends BaseView {

/**

  • 搜索物品返回

  • @param response

*/

void getSearchResponse(TrashResponse response);

/**

  • 搜索物品异常返回

  • @param throwable

*/

void getSearchResponseFailed(Throwable throwable);

}

}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

文末

今天关于面试的分享就到这里,还是那句话,有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。

最后在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司2021年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

【Android核心高级技术PDF文档,BAT大厂面试真题解析】

【算法合集】

【延伸Android必备知识点】

【Android部分高级架构视频学习资源】

**Android精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

= NetworkApi.createService(ApiService.class);

service.searchGoods(word).compose(NetworkApi.applySchedulers(new BaseObserver() {

@Override

public void onSuccess(TrashResponse groupResponse) {

if (getView() != null) {

getView().getSearchResponse(groupResponse);

}

}

@Override

public void onFailure(Throwable e) {

if (getView() != null) {

getView().getSearchResponseFailed(e);

}

}

}));

}

}

public interface VoiceView extends BaseView {

/**

  • 搜索物品返回

  • @param response

*/

void getSearchResponse(TrashResponse response);

/**

  • 搜索物品异常返回

  • @param throwable

*/

void getSearchResponseFailed(Throwable throwable);

}

}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-69iiRE4t-1713750466790)]

[外链图片转存中…(img-Gw2NXrdB-1713750466791)]

[外链图片转存中…(img-mUCh0yI3-1713750466792)]

[外链图片转存中…(img-vzLTh0fV-1713750466793)]

[外链图片转存中…(img-jhyNmUGQ-1713750466794)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

[外链图片转存中…(img-STSQHGH5-1713750466796)]

文末

今天关于面试的分享就到这里,还是那句话,有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。

最后在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司2021年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

【Android核心高级技术PDF文档,BAT大厂面试真题解析】

[外链图片转存中…(img-8SAVdlSA-1713750466796)]

【算法合集】

[外链图片转存中…(img-5UjrcSEp-1713750466797)]

【延伸Android必备知识点】

[外链图片转存中…(img-oY7Q7PWI-1713750466798)]

【Android部分高级架构视频学习资源】

**Android精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 11
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值