1、介绍
总览
搜索服务、Network Kit以及机器学习服务是新闻类应用最常用的服务。机器学习服务把用户输入信息传递给搜索服务,搜索服务和Network Kit一起检索相关新闻返回给机器学习服务,机器学习服务再通过语音合成功能播报新闻。
本次codelab描述如何创建Android新闻应用SmartNews,并在应用中集成搜索服务、Network Kit以及机器学习服务。用户可以注册账号,阅读新闻,并选择不同语言听机器学习服务的TTS功能转换的语音新闻。用户可以通过语音或文字搜索信息。
-
Network Kit通过NEWS接口获取新闻。
-
阅读新闻,可以选择6种语言播放新闻。
-
切换语言设置。
-
通过语音或文字搜索新闻。
服务场景描述
本codelab以搜索服务、Network Kit以及机器学习服务为例,描述如何在一个应用中集成多个服务。这种场景在多媒体应用(例如电商应用、娱乐应用、教育应用以及商业应用)中广泛应用。
特性 |
HMS Core服务 |
通过TTS服务听语音新闻 |
机器学习服务 |
听语音新闻时,切换目标语言 |
机器学习服务 |
搜索新闻 |
搜索服务 |
通过接口搜索新闻 |
机器学习服务 |
传递用户输入,用户新闻搜索 |
机器学习服务 |
您将建立什么
在本次Codelab中,您将建立一个示例项目并集成服务。在该项目中,您可以:
-
获取最新的新闻,并在新闻中展示广告。
-
把文本转换为语音。
-
通过语音或文字搜索新闻。
-
TTS转换前切换语言设置。
应用流程
Splash Activity
启动splash页面。
NewsDetails Activity
1. 展示选择的新闻。
2. 听语音新闻。
Login Activity
通过华为账号和调用Login Activity完成登录。
NewsActivity
1. 阅读最新新闻,选择新闻离线阅读。
新闻检索
输入文字或通过语音搜索新闻。
语音设置
1. 查看详情。
2. 切换语言。
新闻刷新
刷新新闻列表,阅读最新新闻。
您需要什么
在本codelab中,你需要学习:
-
如何集成Network Kit。
-
如何集成机器学习服务提供TTS、文本翻译以及ASR功能。
-
如何集成搜索服务。
2、您需要什么
硬件需求
提前准备如下硬件:
-
一台Windows 10台式或笔记本电脑。
-
一部集成HMS Core (APK) 5.0.0.300或以上版本的华为手机。
软件需求
提前准备如下软件:
-
Android Studio 3.0或以上版本
-
JDK 1.8或以上版本
-
安卓SDK平台(API 23或以上版本)
-
Gradle 4.6或以上版本
3、能力接入准备
集成HMS Core服务之前,注册成为开发者并完成如下准备工作:
-
在AppGallery Connect中创建新闻。
-
创建Android Studio项目。
-
生成签名证书。
-
生成签名证书指纹。
-
配置签名证书指纹。
-
添加应用包名并保存配置文件。
-
在项目级build.gradle文件中添加AppGallery Connect插件和Maven仓。
-
在Android Studio中配置签名证书。
4、集成Network Kit
本项目通过Network Kit调用第三方接口获取新闻,通过搜索服务将新闻提供给用户。
-
在Application类中初始化Network Kit。
Java
/** * 异步加载Network Kit对象。 */ public void initNetworkKIT() { NetworkKit.init( getApplicationContext(), new NetworkKit.Callback() { @Override public void onResult(boolean status) { if (status) { Log.d(TAG, getApplication().getResources().getString(R.string.Network_sucess)); } else { Log.d(TAG, getApplication().getResources().getString(R.string.Network_failed)); } } });
Kotlin
/** * 异步加载Network Kit对象。 */ fun initNetworkKit() { NetworkKit.init(this, object : NetworkKit.Callback() { override fun onResult(result: Boolean) { if (result) { Log.i(TAG, "Networkkit init success") } else { Log.i(TAG, "Networkkit init failed") } } }) }
-
实现Network URL请求并创建接口。
Java
/** * 声明一个请求API。 **/ public interface NewsApi { @GET("newsapi.org/v2/top-headlines") Submit<String> getNews(@Query("country") String country, @Query("apiKey") String apiKey);
Kotlin
/** * 声明一个请求API。 **/ public interface NewsApi @GET("newsapi.org/v2/top-headlines") fun getNews(@Query("country") country: String?,@Query("apiKey") apiKey: String?): Submit<String>
-
创建数据模型类。
Java
/** * 新闻资源模型描述资源信息 * 以及新闻文章。 */ public class Newsdata { @SerializedName("status") @Expose private String status; @SerializedName("totalResult") @Expose private int totalResult; @SerializedName("articles") @Expose private List<Article> article; public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public void setTotalResults(int totalresults) { this.totalResult = totalresults; } public List<Article> getArticle() { return article; } public void setArticle(List<Article> article) { this.article = article; } }
Kotlin
/** * 新闻资源模型描述资源信息 * 以及新闻文章。 */ data class NewsSource( @SerializedName("status") var status: String = "", @SerializedName("source") var source: String = "", @SerializedName("sortBy") var sortBy: String = "", @SerializedName("articles") var articles: List<News> = emptyList() )
-
Create a RESTClient from Network Kit.
Java
/** * 提供Network Kit的RestClient。 */ @Singleton @Provides static RestClient provideNetworkService () { return new RestClient.Builder().baseUrl(Constants.BASE_URL).httpClient(getHttpClient()).build(); } private static HttpClient getHttpClient() { return new HttpClient.Builder() .connectTimeout(Constants.CONNECT_TIMEOUT) .readTimeout(Constants.TIMEOUT) .writeTimeout(Constants.WRITE_TIMEOUT) .build(); }
Kotlin
/** * 提供Network Kit的RestClient。 */ @Singleton @Provides fun provideNetworkService(): ApiService { return RestClient .Builder() .baseUrl(Constants.BASE_URL) .httpClient(getHttpClient()) .build() .create(ApiService::class.java) } private fun getHttpClient(): HttpClient? { return HttpClient.Builder() .connectTimeout(Constants.CONNECT_TIMEOUT) .readTimeout(Constants.TIMEOUT) .writeTimeout(Constants.WRITE_TIMEOUT) .enableQuic(false) .build() }
-
创建请求接口对象,发送异步请求,使用NewsActivity类中的RecyclerView展示新闻。
Java
/** * 根据网络连接状态获取新闻。 */ private void getNewsList() { showProgressBar(true); viewModel .getNewsList() .observe( this, new Observer<NewsResource<Newsdata>>() { @Override public void onChanged(NewsResource<Newsdata> newsNewsResource) { switch (newsNewsResource.status) { case SUCCESS: recyclerView.setVisibility(View.VISIBLE); showProgressBar(false); if (newsNewsResource.data != null && newsNewsResource.data.getStatus().equals(Constants.STATUS_OK)) { newsError.setVisibility(View.GONE); if (!articles.isEmpty()) { articles.clear(); } articles = newsNewsResource.data.getArticle(); if (articles.size() != 0) { adapter = new NewsListAdapter( articles, NewsActivity.this, NewsActivity.this); recyclerView.setAdapter(adapter); adapter.notifyDataSetChanged(); } else newsError.setVisibility(View.VISIBLE); } else { recyclerView.setVisibility(View.GONE); newsError.setVisibility(View.VISIBLE); } break; case ERROR: showProgressBar(false); recyclerView.setVisibility(View.GONE); newsError.setVisibility(View.VISIBLE); break; } } });
Kotlin
private fun getNews() { newsArticleViewModel.fetchNewsSearchApi("Today News").observe(this, Observer { latestNews = it if (latestNews.isNotEmpty()){ recyclerView!!.visibility = View.VISIBLE newsError.visibility = View.GONE mAdapter = NewsListAdapter( latestNews, this@NewsActivity, this@NewsActivity ) recyclerView!!.adapter = mAdapter mAdapter!!.notifyDataSetChanged() } })
5、集成机器学习服务
本应用中,机器学习服务把文本转换为语音,实现TTS和ASR,把文本翻译成多种语言,作为搜索服务的输入。
-
在Application类中初始化机器学习服务。
Java
/** * 初始化机器学习服务。 * */ private void initMLkit() { AGConnectServicesConfig config = AGConnectServicesConfig.fromContext(this); MLApplication.getInstance().setApiKey(config.getString(API_KEY)); }
Kotlin
/** * 初始化机器学习服务。 */ private fun initMLKit() { val config = AGConnectServicesConfig.fromContext(this) MLApplication.getInstance().apiKey = config.getString(API_KEY) }
-
自定义MLTtsConfig类,生成对象并传递给MLTtsEngine。
Java
/** * 定制配置类MLTtsConfig,创建文本到语音转换引擎。 **/ public void confingMLkitTTS() { mlTtsConfig = new MLTtsConfig().setSpeed(1.0f).setVolume(1.0f); mlTtsEngine = new MLTtsEngine(mlTtsConfig); mlTtsEngine.updateConfig(mlTtsConfig); }
Kotlin
/** * 定制配置类MLTtsConfig,创建文本到语音转换引擎。 **/ fun confMLkitTTS() { mlTtsConfig = MLTtsConfig() .setSpeed(1.0f) .setVolume(1.0f) mlTtsEngine = MLTtsEngine(mlTtsConfig) mlTtsEngine!!.updateConfig(mlTtsConfig) }
-
翻译文本,提供译文给TTS供用户听语音新闻。
Java
/** * 根据用户选择的语言将文本翻译为语音。 * * @param 新闻文本以及用户选择的语言 */ public void translateText(String text, String lang) { MLRemoteTranslateSetting setting = new MLRemoteTranslateSetting.Factory().setTargetLangCode(lang).create(); MLRemoteTranslator mlRemoteTranslator = MLTranslatorFactory.getInstance().getRemoteTranslator(setting); final Task<String> task = mlRemoteTranslator.asyncTranslate(text); task.addOnSuccessListener( new OnSuccessListener<String>() { @Override public void onSuccess(String text) { mlTtsEngine.speak(text, MLTtsEngine.QUEUE_APPEND); } }) .addOnFailureListener( new OnFailureListener() { @Override public void onFailure(Exception e) { try { MLException mlException = (MLException) e; Log.e(TAG, "failure to convert TTS"); } catch (Exception error) { } } }); }
Kotlin
/** * 根据用户选择的语言将文本翻译为语音。 * * @param 新闻文本以及用户选择的语言 */ fun translateText(text: String?, lang: String?) { val setting = MLRemoteTranslateSetting.Factory() .setTargetLangCode(lang) .create() val mlRemoteTranslator = MLTranslatorFactory.getInstance().getRemoteTranslator(setting) val task = mlRemoteTranslator.asyncTranslate(text) task.addOnSuccessListener { text -> mlTtsEngine!!.speak(text, MLTtsEngine.QUEUE_APPEND) }.addOnFailureListener { e -> try { val mlException = e as MLException } catch (error: Exception) { Log.e(TAG, "failure to convert TTS") } } }
-
使用搜索服务实现ASR功能。在NewsActivity类的onCreate()方法中检查是否有所需的权限。
Java
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { checkPermission(); } /** * 检查SDK所需权限。 */ private void checkPermission() { if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) { ArrayList<String> permissionsList = new ArrayList<>(); for (String perm : getAllPermission()) { if (PackageManager.PERMISSION_GRANTED != ActivityCompat.checkSelfPermission(this, perm)) { permissionsList.add(perm); } } if (!permissionsList.isEmpty()) { ActivityCompat.requestPermissions(this, permissionsList.toArray(new String[0]), REQUEST_MICROPHONE); } } } public static List<String> getAllPermission() { return Collections.unmodifiableList(Arrays.asList(ALL_PERMISSION)); }
Kotlin
if (ActivityCompat.checkSelfPermission( this, Manifest.permission.RECORD_AUDIO ) != PackageManager.PERMISSION_GRANTED ) { this.requestAudioPermission(); } /** * 检查SDK所需权限。 */ private fun requestAudioPermission() { val permissions = arrayOf(Manifest.permission.RECORD_AUDIO) if (!ActivityCompat.shouldShowRequestPermissionRationale( this, Manifest.permission.RECORD_AUDIO ) ) { ActivityCompat.requestPermissions(this, permissions, AUDIO_PERMISSION_CODE) return } } override fun onRequestPermissionsResult( requestCode: Int, permissions: Array<String?>, grantResults: IntArray ) { if (requestCode != AUDIO_PERMISSION_CODE) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) return } }
-
开启搜索框中的ASR按钮点击功能。
Java
/** * 开启ASR功能,使用Intent实现识别设置。 * */ public void startASR() { search.clearFocus(); Intent intent = new Intent(this, MLAsrCaptureActivity.class) .putExtra(MLAsrCaptureConstants.LANGUAGE, "en-US") .putExtra(MLAsrCaptureConstants.FEATURE, MLAsrCaptureConstants.FEATURE_WORDFLUX); startActivityForResult(intent, REQUEST_CODE_ASR); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); String text = ""; if (requestCode == REQUEST_CODE_ASR) { switch (resultCode) { case MLAsrCaptureConstants.ASR_SUCCESS: if (data != null) { Bundle bundle = data.getExtras(); if (bundle.containsKey(MLAsrCaptureConstants.ASR_RESULT)) { text = bundle.getString(MLAsrCaptureConstants.ASR_RESULT); viewModel.initSearch(text); } } break; case MLAsrCaptureConstants.ASR_FAILURE: if (data != null) { Bundle bundle = data.getExtras(); if (bundle.containsKey(MLAsrCaptureConstants.ASR_ERROR_CODE)) { Log.e(TAG, getApplication().getResources().getString(R.string.asr_errorCode)); } } default: break; } } }
Kotlin
/** * 开启ASR功能,使用Intent实现识别设置。 * */ fun startASR() { search!!.clearFocus() val intent = Intent(this, MLAsrCaptureActivity::class.java) .putExtra(MLAsrCaptureConstants.LANGUAGE, "en-US") .putExtra(MLAsrCaptureConstants.FEATURE, MLAsrCaptureConstants.FEATURE_WORDFLUX) startActivityForResult(intent, REQUEST_CODE_ASR) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) var text = "" if (requestCode == REQUEST_CODE_ASR) { when (resultCode) { MLAsrCaptureConstants.ASR_SUCCESS -> if (data != null) { val bundle = data.extras if (bundle!!.containsKey(MLAsrCaptureConstants.ASR_RESULT)) { text = bundle.getString(MLAsrCaptureConstants.ASR_RESULT).toString() if(text!=""){ newsArticleViewModel.initSearch(text) } } } MLAsrCaptureConstants.ASR_FAILURE -> if (data != null) { val bundle = data.extras if (bundle!!.containsKey(MLAsrCaptureConstants.ASR_ERROR_CODE)) { val errorCode = bundle.getInt(MLAsrCaptureConstants.ASR_ERROR_CODE) Log.e("TAG", application.resources.getString(R.string.sigin_failed)) } if (bundle.containsKey(MLAsrCaptureConstants.ASR_ERROR_MESSAGE)) { val errorMsg = bundle.getString(MLAsrCaptureConstants.ASR_ERROR_MESSAGE) } if (bundle.containsKey(MLAsrCaptureConstants.ASR_SUB_ERROR_CODE)) { val subErrorCode = bundle.getInt(MLAsrCaptureConstants.ASR_SUB_ERROR_CODE) } } else -> { } } } } }
6、集成搜索服务
本项目中,搜索服务负责提供搜索功能。用户可以输入文本或通过语音搜索新闻。
-
在NewsActivity类中新建搜索框并在Application类中初始化搜索服务。
Java
/** * Initialize the Search Kit instance. */ private void initSearchKit() { SearchKitInstance.init(this, Constants.CLIENT_ID); }
Kotlin
/** * Initialize the Search Kit instance. */ fun initSearchKit() { SearchKitInstance.init(this, "103207661") }
-
获取access token,用于在服务端验证搜索请求。验证成功后返回搜索结果。
Java
/** * API for obtaining the access token for Search Kit. * */ public interface AccessTokenService { @POST("oauth-login.cloud.huawei.com/oauth2/v3/token") @FormUrlEncoded @Headers("Content-Type:application/x-www-form-urlencoded\", \"charset:UTF-8") Submit<String> createAccessToken( @Field("grant_type") String grant_type, @Field("client_secret") String client_secret, @Field("client_id") String client_id); }
Kotlin
/** * API for obtaining the access token for Search Kit. * */ interface ApiService { @POST("oauth-login.cloud.huawei.com/oauth2/v3/token") @FormUrlEncoded @Headers("Content-Type:application/x-www-form-urlencoded\", \"charset:UTF-8") fun getAccessToken( @Field("grant_type") grant_type: String?, @Field("client_secret") client_secret: String?, @Field("client_id") client_id: String? ): Submit<String>?
-
创建请求接口并发送异步请求。
Java
restClient .create(AccessTokenService.class) .createAccessToken(Constants.GRANT_TYPE, Constants.CLIENT_SECRET, Constants.CLIENT_ID). enqueue(searchCallback); public void searchCallback(String text) { searchCallback = new Callback() { @Override public void onResponse(Submit submit, Response response) processResponse(response) @Override public void onFailure(Submit submit, Throwable throwable) { String errorMsg = "response onFailure : "; Log.e(TAG, errorMsg); } };
Kotlin
apiServices.getAccessToken( Constants.GRANT_TYPE, Constants.CLIENT_SECRET, Constants.CLIENT_ID )?.enqueue(object : Callback<String?>() { override fun onResponse(body: Submit<String?>?, response: Response<String?>?) { Handler(Looper.getMainLooper()).postDelayed({ val gson = Gson() val tokenModel: TokenModel = gson.fromJson(response!!.body, TokenModel::class.java) val mSearchNews = initSearchKit(tokenModel.access_token, context, text) mCountryList.value = mSearchNews mCallback.onNetworkSuccess() }, 1000) } override fun onFailure(p0: Submit<String?>?, p1: Throwable) { mCallback.onFailure("Api Failure") } }) return mCountryList }
-
设置搜索服务实例的access token并把用户文本或语音输入的查询信息传给搜索服务。把新闻列表传给adapter类并通过RecyclerView展示新闻。
Java
/** * Set the acess token. */ SearchKitInstance.getInstance().setInstanceCredential(accessToken); SearchKitInstance.getInstance().getNewsSearcher().setTimeOut(10000); /** * Search for news based on information entered by the user */ private void searchForQuery(String accessToken, String text) { CommonSearchRequest commonSearchRequest = new CommonSearchRequest(); commonSearchRequest.setQ(text); commonSearchRequest.setLang(Language.ENGLISH); commonSearchRequest.setSregion(Region.UNITEDKINGDOM); commonSearchRequest.setPs(10); commonSearchRequest.setPn(1); SearchKitInstance.getInstance().setInstanceCredential(accessToken); SearchKitInstance.getInstance().getNewsSearcher().setTimeOut(10000); BaseSearchResponse<List<NewsItem>> response = SearchKitInstance.getInstance().getNewsSearcher().search(commonSearchRequest); if (response.getData().isEmpty() || response == null || response.equals("") ) { newsStatus.setStatus(Constants.STATUS_FAILED); mNews.postValue(newsStatus); return; } List<NewsItem> newinfo = response.getData(); processbodyforsearch(newinfo); }
Kotlin
/** * Search for news based on information entered by the user. */ private fun searchForQuery( accessToken: String?, context: Context, text: String ): ArrayList<News> { var mSearchNews = ArrayList<News>() val commonSearchRequest = CommonSearchRequest() commonSearchRequest.setQ(text) commonSearchRequest.setLang(Language.ENGLISH) commonSearchRequest.setSregion(Region.UNITEDKINGDOM) commonSearchRequest.setPs(10) commonSearchRequest.setPn(1) SearchKitInstance.init(context, "103207661") SearchKitInstance.getInstance().setInstanceCredential(accessToken) SearchKitInstance.getInstance().newsSearcher.setTimeOut(10000) val response = SearchKitInstance.getInstance().newsSearcher.search(commonSearchRequest) if (response.getData().isEmpty() || !(!response.equals("") || response.getData() != null)) { mSearchNews = arrayListOf() mCallback.onFailure("No data Found") } else { val newsitem = response!!.getData() for (i in newsitem.indices) { mSearchNews.add( News( null, newsitem[i].title, null, newsitem[i].clickUrl, newsitem[i].provider.logo, newsitem[i].publish_time ) ) } mCallback.onNetworkSuccess() } return mSearchNews; } }
7、恭喜您
祝贺您,您已经成功地构建了一个新闻应用并学会:
-
如何集成Network Kit。
-
如何实现ML Kit的TTS、文本翻译以及ASR功能。
-
实现Search Kit的新闻搜索功能。
8、参考
参考如下文档获取更多信息:
点击如下链接下载源码:
声明:本codelab实现多个HMS Core服务在单个项目中的集成,供您参考。您需要验证确保相关开源代码的安全合法合规。
欲了解更多更全技术文章,欢迎访问https://developer.huawei.com/consumer/cn/forum/?ha_source=zzh
标签:ASR,场景,String,解决方案,Kit,新闻,MLAsrCaptureConstants,public From: https://www.cnblogs.com/developer-huawei/p/16845574.html