android:textSize=“12dp” />
<ScrollView
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:layout_above=“@+id/btn”
<TextView
android:id=“@+id/showText”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:layout_margin=“10dp”
android:background=“@android:color/darker_gray”
android:minLines=“3”
android:scrollbars=“vertical” />
下面再来看OnlineActivity的代码
2. 编辑代码
package com.llw.speechsynthesis;
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import com.baidu.tts.chainofresponsibility.logger.LoggerProxy;
import com.baidu.tts.client.SpeechSynthesizer;
import com.baidu.tts.client.SpeechSynthesizerListener;
import com.baidu.tts.client.TtsMode;
import com.llw.speechsynthesis.control.InitConfig;
import com.llw.speechsynthesis.listener.UiMessageListener;
import com.llw.speechsynthesis.util.Auth;
import com.llw.speechsynthesis.util.AutoCheck;
import com.llw.speechsynthesis.util.FileUtil;
import com.llw.speechsynthesis.util.IOfflineResourceConst;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
/**
-
除了SDK,该类没有任何依赖,可以直接复制进你的项目
-
-
默认TEMP_DIR = “/sdcard/baiduTTS”; // 重要!请手动将assets目录下的3个dat 文件复制到该目录
-
确保 TEXT_FILENAME 和 MODEL_FILENAME 存在
-
Created by fujiayi on 2017/9/14.
*/
public class OnlineActivity extends AppCompatActivity implements IOfflineResourceConst {
/**
- 要合成的文本,可以自行改动。
*/
private static final String TEXT = “欢迎使用百度语音合成,请在代码中修改合成文本”;
protected String appId;
protected String appKey;
protected String secretKey;
protected String sn; // 纯离线合成SDK授权码;离在线合成SDK没有此参数
//TtsMode.ONLINE 纯在线
private TtsMode ttsMode = TtsMode.ONLINE;
private boolean isOnlineSDK = TtsMode.ONLINE.equals(DEFAULT_SDK_TTS_MODE);
// ================ 纯离线sdk或者选择TtsMode.ONLINE 以下参数无用;
private static final String TEMP_DIR = “/sdcard/baiduTTS”; // 重要!请手动将assets目录下的3个dat 文件复制到该目录
// 请确保该PATH下有这个文件
private static final String TEXT_FILENAME = TEMP_DIR + “/” + TEXT_MODEL;
// 请确保该PATH下有这个文件 ,m15是离线男声
private static final String MODEL_FILENAME = TEMP_DIR + “/” + VOICE_MALE_MODEL;
// ===============初始化参数设置完毕,更多合成参数请至getParams()方法中设置 =================
protected SpeechSynthesizer mSpeechSynthesizer;
// =========== 以下为UI部分 ==================================================
private TextView mShowText;
protected Handler mainHandler;
private String desc; // 说明文件
private static final String TAG = “MiniActivity”;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
appId = Auth.getInstance(this).getAppId();
appKey = Auth.getInstance(this).getAppKey();
secretKey = Auth.getInstance(this).getSecretKey();
sn = Auth.getInstance(this).getSn(); // 纯离线合成必须有此参数;离在线合成SDK没有此参数
desc = FileUtil.getResourceText(this, R.raw.mini_activity_description);
setContentView(R.layout.activity_online);
initView();
initPermission();
initTTs();
}
/**
-
注意此处为了说明流程,故意在UI线程中调用。
-
实际集成中,该方法一定在新线程中调用,并且该线程不能结束。具体可以参考NonBlockSyntherizer的写法
*/
private void initTTs() {
LoggerProxy.printable(true); // 日志打印在logcat中
boolean isSuccess;
if (!isOnlineSDK) {
// 检查2个离线资源是否可读
isSuccess = checkOfflineResources();
if (!isSuccess) {
return;
} else {
print(“离线资源存在并且可读, 目录:” + TEMP_DIR);
}
}
// 日志更新在UI中,可以换成MessageListener,在logcat中查看日志
SpeechSynthesizerListener listener = new UiMessageListener(mainHandler);
// 1. 获取实例
mSpeechSynthesizer = SpeechSynthesizer.getInstance();
mSpeechSynthesizer.setContext(this);
// 2. 设置listener
mSpeechSynthesizer.setSpeechSynthesizerListener(listener);
// 3. 设置appId,appKey.secretKey
int result = mSpeechSynthesizer.setAppId(appId);
checkResult(result, “setAppId”);
result = mSpeechSynthesizer.setApiKey(appKey, secretKey);
checkResult(result, “setApiKey”);
// 4. 如果是纯离线SDK需要离线功能的话
if (!isOnlineSDK) {
// 文本模型文件路径 (离线引擎使用), 注意TEXT_FILENAME必须存在并且可读
mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_TTS_TEXT_MODEL_FILE, TEXT_FILENAME);
// 声学模型文件路径 (离线引擎使用), 注意TEXT_FILENAME必须存在并且可读
mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_TTS_SPEECH_MODEL_FILE, MODEL_FILENAME);
mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_MIX_MODE, SpeechSynthesizer.MIX_MODE_DEFAULT);
// 该参数设置为TtsMode.MIX生效。
// MIX_MODE_DEFAULT 默认 ,wifi状态下使用在线,非wifi离线。在线状态下,请求超时6s自动转离线
// MIX_MODE_HIGH_SPEED_SYNTHESIZE_WIFI wifi状态下使用在线,非wifi离线。在线状态下, 请求超时1.2s自动转离线
// MIX_MODE_HIGH_SPEED_NETWORK , 3G 4G wifi状态下使用在线,其它状态离线。在线状态下,请求超时1.2s自动转离线
// MIX_MODE_HIGH_SPEED_SYNTHESIZE, 2G 3G 4G wifi状态下使用在线,其它状态离线。在线状态下,请求超时1.2s自动转离线
}
// 5. 以下setParam 参数选填。不填写则默认值生效
// 设置在线发声音人: 0 普通女声(默认) 1 普通男声 3 情感男声<度逍遥> 4 情感儿童声<度丫丫>
mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_SPEAKER, “0”);
// 设置合成的音量,0-15 ,默认 5
mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_VOLUME, “9”);
// 设置合成的语速,0-15 ,默认 5
mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_SPEED, “5”);
// 设置合成的语调,0-15 ,默认 5
mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_PITCH, “5”);
// mSpeechSynthesizer.setAudioStreamType(AudioManager.MODE_IN_CALL); // 调整音频输出
if (sn != null) {
// 纯离线sdk这个参数必填;离在线sdk没有此参数
mSpeechSynthesizer.setParam(PARAM_SN_NAME, sn);
}
// x. 额外 : 自动so文件是否复制正确及上面设置的参数
Map<String, String> params = new HashMap<>();
// 复制下上面的 mSpeechSynthesizer.setParam参数
// 上线时请删除AutoCheck的调用
if (!isOnlineSDK) {
params.put(SpeechSynthesizer.PARAM_TTS_TEXT_MODEL_FILE, TEXT_FILENAME);
params.put(SpeechSynthesizer.PARAM_TTS_SPEECH_MODEL_FILE, MODEL_FILENAME);
}
// 检测参数,通过一次后可以去除,出问题再打开debug
InitConfig initConfig = new InitConfig(appId, appKey, secretKey, ttsMode, params, listener);
AutoCheck.getInstance(getApplicationContext()).check(initConfig, new Handler() {
@Override
/**
- 开新线程检查,成功后回调
*/
public void handleMessage(Message msg) {
if (msg.what == 100) {
AutoCheck autoCheck = (AutoCheck) msg.obj;
synchronized (autoCheck) {
String message = autoCheck.obtainDebugMessage();
print(message); // 可以用下面一行替代,在logcat中查看代码
// Log.w(“AutoCheckMessage”, message);
}
}
}
});
// 6. 初始化
result = mSpeechSynthesizer.initTts(ttsMode);
checkResult(result, “initTts”);
}
/**
-
在线SDK不需要调用,纯离线SDK会检查资源文件
-
检查 TEXT_FILENAME, MODEL_FILENAME 这2个文件是否存在,不存在请自行从assets目录里手动复制
-
@return 检测是否成功
*/
private boolean checkOfflineResources() {
String[] filenames = {TEXT_FILENAME, MODEL_FILENAME};
for (String path : filenames) {
File f = new File(path);
if (!f.canRead()) {
print(“[ERROR] 文件不存在或者不可读取,请从demo的assets目录复制同名文件到:”
- f.getAbsolutePath());
print(“[ERROR] 初始化失败!!!”);
return false;
}
}
return true;
}
private void speak() {
/* 以下参数每次合成时都可以修改
-
mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_SPEAKER, “0”);
-
设置在线发声音人: 0 普通女声(默认) 1 普通男声 3 情感男声<度逍遥> 4 情感儿童声<度丫丫>
-
mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_VOLUME, “5”); 设置合成的音量,0-15 ,默认 5
-
mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_SPEED, “5”); 设置合成的语速,0-15 ,默认 5
-
mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_PITCH, “5”); 设置合成的语调,0-15 ,默认 5
*/
if (mSpeechSynthesizer == null) {
print(“[ERROR], 初始化失败”);
return;
}
int result = mSpeechSynthesizer.speak(TEXT);
mShowText.setText(“”);
print(“合成并播放 按钮已经点击”);
checkResult(result, “speak”);
}
private void stop() {
print(“停止合成引擎 按钮已经点击”);
int result = mSpeechSynthesizer.stop();
checkResult(result, “stop”);
}
// 下面是UI部分
private void initView() {
Button mSpeak = this.findViewById(R.id.speak);
Button mStop = this.findViewById(R.id.stop);
mShowText = this.findViewById(R.id.showText);
mShowText.setText(desc);
View.OnClickListener listener = new View.OnClickListener() {
@Override
public void onClick(View v) {
int id = v.getId();
switch (id) {
case R.id.speak:
speak();
break;
case R.id.stop:
stop();
break;
default:
break;
}
}
};
mSpeak.setOnClickListener(listener);
mStop.setOnClickListener(listener);
mainHandler = new Handler() {
/*
- @param msg
*/
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.obj != null) {
print(msg.obj.toString());
}
}
};
}
private void print(String message) {
Log.i(TAG, message);
mShowText.append(message + “\n”);
}
@Override
protected void onDestroy() {
if (mSpeechSynthesizer != null) {
mSpeechSynthesizer.stop();
mSpeechSynthesizer.release();
mSpeechSynthesizer = null;
print(“释放资源成功”);
}
super.onDestroy();
}
private void checkResult(int result, String method) {
if (result != 0) {
print(“error code :” + result + " method:" + method);
}
}
// 下面是android 6.0以上的动态授权
/**
- android 6.0 以上需要动态申请权限
*/
private void initPermission() {
String[] permissions = {
Manifest.permission.INTERNET,
Manifest.permission.ACCESS_NETWORK_STATE,
Manifest.permission.MODIFY_AUDIO_SETTINGS,
Manifest.permission.WRITE_SETTINGS,
Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.CHANGE_WIFI_STATE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
ArrayList toApplyList = new ArrayList<>();
for (String perm : permissions) {
if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(this, perm)) {
toApplyList.add(perm);
// 进入到这里代表没有权限.
}
}
String[] tmpList = new String[toApplyList.size()];
if (!toApplyList.isEmpty()) {
ActivityCompat.requestPermissions(this, toApplyList.toArray(tmpList), 123);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
// 此处为android 6.0以上动态授权的回调,用户自行实现。
}
}
这里的代码其实都是这个SDK中的,直接就可以使用了。我只改动了一点点。
3. 配置
然后修改AndroidManifest.xml
然后在activity_main.xml中增加一个按钮。
<Button
android:text=“在线SDK合成”
android:onClick=“onlineSDK”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”/>
在MainActivity中增加方法。
/**
-
在线SDK合成
-
@param view
*/
public void onlineSDK(View view) {
startActivity(new Intent(this, OnlineActivity.class));
}
4. 运行
下面运行:
可以看到在线SDK合成,没有网络时是合成不了的,有网络才行,这里的声音是女声。
使用API方式就稍稍有一些麻烦,因为这个设计到网络的请求,而且不是一次请求,首先进行鉴权,拿到token,然后通过Token去请求合成,下载MP3文件,首先要构建网络模块,当然我也只是简单的写一下而已。
1. 鉴权返回实体
在com.llw.imagediscerndemo下新建一个model包,包下新建一个GetTokenResponse类,代码如下:
package com.llw.speechsynthesis.model;
/**
-
获取鉴权认证Token响应实体
-
@author llw
-
@date 2021/5/7 16:16
*/
public class GetTokenResponse {
/**
-
refresh_token : 25.0141c302b0f460cd0500827fa31f22ce.315360000.1935736936.282335-24113250
-
expires_in : 2592000
-
session_key : 9mzdCS6a/7/wIFWLR8zFoYs2koSri++RGhSecVXM/vY93At4kxYRajL/xMV17MoxcTAJfadRVaSBxokIqFeQoxsZ8e3NPQ==
-
access_token : 24.2830c05696b214cf07bfbdf764599b39.2592000.1622968936.282335-24113250
-
scope : audio_voice_assistant_get brain_enhanced_asr audio_tts_post brain_speech_realtime public brain_all_scope picchain_test_picchain_api_scope brain_asr_async wise_adapt lebo_resource_base lightservice_public hetu_basic lightcms_map_poi kaidian_kaidian ApsMisTest_Test权限 vis-classify_flower lpq_开放 cop_helloScope ApsMis_fangdi_permission smartapp_snsapi_base smartapp_mapp_dev_manage iop_autocar oauth_tp_app smartapp_smart_game_openapi oauth_sessionkey smartapp_swanid_verify smartapp_opensource_openapi smartapp_opensource_recapi fake_face_detect_开放Scope vis-ocr_虚拟人物助理 idl-video_虚拟人物助理 smartapp_component smartapp_search_plugin avatar_video_test
-
session_secret : 2cdde5fd8f3fd4394c1b090e2ffa2d1c
*/
private String refresh_token;
private int expires_in;
private String session_key;
private String access_token;
private String scope;
private String session_secret;
public String getRefresh_token() {
return refresh_token;
}
public void setRefresh_token(String refresh_token) {
this.refresh_token = refresh_token;
}
public int getExpires_in() {
return expires_in;
}
public void setExpires_in(int expires_in) {
this.expires_in = expires_in;
}
public String getSession_key() {
return session_key;
}
public void setSession_key(String session_key) {
this.session_key = session_key;
}
public String getAccess_token() {
return access_token;
}
public void setAccess_token(String access_token) {
this.access_token = access_token;
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
public String getSession_secret() {
return session_secret;
}
public void setSession_secret(String session_secret) {
this.session_secret = session_secret;
}
}
下面简单的写一个网络请求框架。
2. 添加框架依赖
打开你的app的build.gradle,在dependencise{}闭包下添加如下依赖:
//retrofit2
implementation ‘com.squareup.retrofit2:retrofit:2.4.0’
implementation ‘com.squareup.retrofit2:converter-gson:2.4.0’
implementation ‘com.squareup.okhttp3:logging-interceptor:3.4.1’
//权限请求框架
implementation ‘com.tbruyelle.rxpermissions2:rxpermissions:0.9.4@aar’
implementation ‘io.reactivex.rxjava2:rxandroid:2.0.2’
implementation “io.reactivex.rxjava2:rxjava:2.0.0”
然后在android{}闭包下添加JDK1.8的支持
compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
记得要Sync Now,这里的依赖一个是网络,一个是权限请求,后面都会用到的。
3. 搭建网络请求框架
在com.llw.speechsynthesis下新建一个network包,在这个包下新建一个NetCallBack抽象类。里面的代码如下:
package com.llw.speechsynthesis.network;
import android.util.Log;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
/**
-
网络请求回调
-
@param
*/
public abstract class NetCallBack implements Callback {//这里实现了retrofit2.Callback
//访问成功回调
@Override
public void onResponse(Call call, Response response) {//数据返回
if (response != null && response.body() != null && response.isSuccessful()) {
onSuccess(call, response);
} else {
onFailed(response.raw().toString());
}
}
//访问失败回调
@Override
public void onFailure(Call call, Throwable t) {
Log.d(“data str”, t.toString());
onFailed(t.toString());
}
//数据返回
public abstract void onSuccess(Call call, Response response);
//失败异常
public abstract void onFailed(String errorStr);
}
然后在network包下新增一个ServiceGenerator类,里面的代码如下:
package com.llw.speechsynthesis.network;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
/**
-
接口地址管理
-
@author llw
*/
public class ServiceGenerator {
public static String BASE_URL = null;
public static String getBaseUrl(int type) {
switch (type) {
case 0://鉴权地址
BASE_URL = “https://openapi.baidu.com”;
break;
case 1://合成地址
BASE_URL = “https://tsn.baidu.com”;
break;
default:
break;
}
return BASE_URL;
}
/**
-
创建服务 参数就是API服务
-
@param serviceClass 服务接口
-
@param 泛型规范
-
@return api接口服务
*/
public static T createService(Class serviceClass, int type) {
//创建OkHttpClient构建器对象
OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder();
//设置请求超时的时间,这里是10秒
okHttpClientBuilder.connectTimeout(20000, TimeUnit.MILLISECONDS);
//消息拦截器 因为有时候接口不同在排错的时候 需要先从接口的响应中做分析。利用了消息拦截器可以清楚的看到接口返回的所有内容
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
//setlevel用来设置日志打印的级别,共包括了四个级别:NONE,BASIC,HEADER,BODY
//BASEIC:请求/响应行
//HEADER:请求/响应行 + 头
//BODY:请求/响应航 + 头 + 体
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
//为OkHttp添加消息拦截器
okHttpClientBuilder.addInterceptor(httpLoggingInterceptor);
//在Retrofit中设置httpclient
//设置地址 就是上面的固定地址,如果你是本地访问的话,可以拼接上端口号 例如 +“:8080”
Retrofit retrofit = new Retrofit.Builder().baseUrl(getBaseUrl(type))
//用Gson把服务端返回的json数据解析成实体
.addConverterFactory(GsonConverterFactory.create())
//放入OKHttp,之前说过retrofit是对OkHttp的进一步封装
.client(okHttpClientBuilder.build())
.build();
//返回这个创建好的API服务
return retrofit.create(serviceClass);
}
}
下面写接口,在network包下新增ApiService接口,代码如下:
package com.llw.speechsynthesis.network;
import com.llw.speechsynthesis.model.GetTokenResponse;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.POST;
import retrofit2.http.Streaming;
/**
-
API服务
-
@author llw
-
@date 2021/5/8 10:48
*/
public interface ApiService {
/**
-
获取鉴权认证Token
-
@param grant_type 类型
-
@param client_id API Key
-
@param client_secret Secret Key
-
@return GetTokenResponse
*/
@FormUrlEncoded
@POST(“/oauth/2.0/token”)
Call getToken(@Field(“grant_type”) String grant_type,
@Field(“client_id”) String client_id,
@Field(“client_secret”) String client_secret);
/**
-
在线API音频合成
-
@param tok 鉴权token
-
@param ctp 客户端类型选择,web端填写固定值1
-
@param cuid 用户唯一标识,用来计算UV值。建议填写能区分用户的机器 MAC 地址或 IMEI 码,长度为60字符以内
-
@param lan 固定值zh。语言选择,目前只有中英文混合模式,填写固定值zh
-
@param tex 合成的文本,使用UTF-8编码。小于2048个中文字或者英文数字,文本在百度服务器内转换为GBK后,长度必须小于4096字节(5003、5118发音人需小于512个中文字或者英文数字)
-
@return 正常合成之后返回一个音频文件
*/
@Streaming
@FormUrlEncoded
@POST(“/text2audio”)
Call synthesis(@Field(“tok”) String tok,
@Field(“ctp”) String ctp,
@Field(“cuid”) String cuid,
@Field(“lan”) String lan,
@Field(“tex”) String tex);
}
里面有两个接口,一个是用来获取鉴权Token的,另一个是用来将文字合成音频文件的。这里会比较的麻烦一些。到此为止这个简单的网络框架就写好了。
4. 编辑布局和页面
在com.llw.speechsynthesis下新建一个OnlineAPIActivity,对应的布局是activity_online_api.xml,里面的代码如下:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:app=“http://schemas.android.com/apk/res-auto”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:orientation=“vertical”
tools:context=“.OnlineAPIActivity”>
<EditText
android:layout_margin=“12dp”
android:background=“#FFF”
android:padding=“12dp”
android:gravity=“top”
android:textColor=“#000”
android:id=“@+id/et_text”
android:hint=“请输入要合成的文本”
android:layout_width=“match_parent”
android:layout_height=“100dp”/>
<Button
android:id=“@+id/btn_synth_api”
android:text=“在线API合成”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”/>
<Button
android:id=“@+id/btn_play”
android:text=“播放合成的音频”
android:visibility=“gone”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”/>
下面先到AndroidManifest.xml中去配置Title。
下面回到OnlineAPIActivity看原始的代码是什么样子。
package com.llw.speechsynthesis;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
/**
-
在线API合成
-
@author llw
*/
public class OnlineAPIActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_online_api);
}
}
先来完成页面的初始化。现在布局的控件有三个
声明变量
private static final String TAG = “OnlineAPIActivity”;
/**
- 输入框
*/
private EditText etText;
/**
- 页面按钮
*/
private Button btnSynthApi, btnPlay;
写一个初始化页面的方法
/**
- 初始化
*/
private void initView() {
etText = findViewById(R.id.et_text);
btnSynthApi = findViewById(R.id.btn_synth_api);
btnPlay = findViewById(R.id.btn_play);
btnSynthApi.setOnClickListener(this);
btnPlay.setOnClickListener(this);
}
这里我给两个按钮添加了点击的监听,那么你需要给Activity实现控件的点击监听。
然后重写onClick方法
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_synth_api://在线API合成
break;
case R.id.btn_play://播放音频
break;
default:
break;
}
}
然后要在onCreate方法中调用initView()方法。
5. 获取鉴权Token
声明变量
/**
- Api服务
*/
private ApiService service;
/**
- 鉴权Toeken
*/
private String accessToken;
然后新增一个requestApiGetToken方法,代码如下:
/**
- 访问API获取接口
*/
private void requestApiGetToken() {
String grantType = “client_credentials”;
String apiKey = “sKWlGNoBrNyaKaAycoiKFzdT”;
String apiSecret = “OwEPWPiSnMNxCF5GFZlORKzP01KwgC1Z”;
service = ServiceGenerator.createService(ApiService.class, 0);
service.getToken(grantType, apiKey, apiSecret)
.enqueue(new NetCallBack() {
@Override
public void onSuccess(Call call, Response response) {
if (response.body() != null) {
//鉴权Token
accessToken = response.body().getAccess_token();
Log.d(TAG, accessToken);
}
}
@Override
public void onFailed(String errorStr) {
Log.e(TAG, “获取Token失败,失败原因:” + errorStr);
accessToken = null;
}
});
}
这里的apiKey、apiSecret 的值改成自己平台创建应用时产生,你要是用我的,除了问题又问我为什么,我就只能。。。了。当然也要在onCreate中调用,这样我们已经入页面就会请求接口拿到鉴权Token。
下面我们运行一下,不过要先在MainActivity中写一个入口才行,在activity_main.xml中增加一个按钮。
<Button
android:text=“在线API合成”
android:onClick=“onlineAPI”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”/>
然后在MainActivity中增加方法
/**
-
在线API合成
-
@param view
*/
public void onlineAPI(View view) {
startActivity(new Intent(this,OnlineAPIActivity.class));
}
那么现在你就可以运行了。
看起来好像什么都没有做是吧。你过你看看控制台的打印。
这里的鉴权Token就拿到了,这种方式用户就是无感知的。其实这个鉴权Token还有优化的空间,至于怎么做,我在其他的文章写过了,你也可以自己实践。
6. 动态权限请求
因为接口请求之后会下载一个文件到手机本地,因此你需要文件读写权限、
声明变量
/**
- 权限请求框架
*/
private RxPermissions rxPermissions;
然后在initView中实例化。
然后新怎一个方法
/**
- android 6.0 以上需要动态申请权限
*/
@SuppressLint(“CheckResult”)
private void requestPermission() {
rxPermissions.request(Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE)
.subscribe(grant -> {
if (grant) {
//获得权限
} else {
Toast.makeText(OnlineAPIActivity.this,“未获取到权限”,Toast.LENGTH_SHORT).show();
}
});
}
这里也是很简单的代码,当点击在线合成API按钮时,先调用requestPermission方法进行权限的检查。
7. Api语音合成
这里合成是读取页面中的文本,如果输入框的内容为空则使用默认文字进行语音合成,因此需要一个默认的文本。
声明变量
/**
- 默认文本,当输入框未输入使用,
*/
private String defaultText = “你好!百度。”;
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后
这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司2021年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。
相信它会给大家带来很多收获:
当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。
- 无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,这四个字就是我的建议!!!
- 我希望每一个努力生活的IT工程师,都会得到自己想要的,因为我们很辛苦,我们应得的。
当我们在抱怨环境,抱怨怀才不遇的时候,没有别的原因,一定是你做的还不够好!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
nifest.permission.READ_EXTERNAL_STORAGE)
.subscribe(grant -> {
if (grant) {
//获得权限
} else {
Toast.makeText(OnlineAPIActivity.this,“未获取到权限”,Toast.LENGTH_SHORT).show();
}
});
}
这里也是很简单的代码,当点击在线合成API按钮时,先调用requestPermission方法进行权限的检查。
7. Api语音合成
这里合成是读取页面中的文本,如果输入框的内容为空则使用默认文字进行语音合成,因此需要一个默认的文本。
声明变量
/**
- 默认文本,当输入框未输入使用,
*/
private String defaultText = “你好!百度。”;
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-w5VH847r-1713151792494)]
[外链图片转存中…(img-WgUjI0Ch-1713151792495)]
[外链图片转存中…(img-ox5myKg9-1713151792495)]
[外链图片转存中…(img-fMkyfjTk-1713151792495)]
[外链图片转存中…(img-fSRGB6hu-1713151792495)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后
这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司2021年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。
相信它会给大家带来很多收获:
[外链图片转存中…(img-WZ4JkR1T-1713151792496)]
当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。
- 无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,这四个字就是我的建议!!!
- 我希望每一个努力生活的IT工程师,都会得到自己想要的,因为我们很辛苦,我们应得的。
当我们在抱怨环境,抱怨怀才不遇的时候,没有别的原因,一定是你做的还不够好!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!