将语音搜索集成到Google Now中

原文标题:Use Voice Search to integrate with Google Now

原文链接:http://antonioleiva.com/voice_search_google_now/

原文作者:Antonio Leiva(http://antonioleiva.com/about/

原文发布:2015-10-14

 

 

Android最棒的能力之一就是可以将我们的APP以不同的方式集成到它的生态链中。APP相互之间可以进行“交流”,这给予我们极大的灵活性来创建独特的应用体验。

 

集成Google应用就是一个典型的例子。有很多不同的特性可以帮助我们提升APP的知名度,如APP索引或一组强大的语音功能

 

语音搜索

 

尽管,在APP中实现语音搜索的过程与任何其他语音功能类似,但是,在这篇文章中,我还是会聚焦在怎样在APP中实现语音搜索功能。你也可以在Play Music中尝试这个例子:

 

  • 好,GooglePlay Music中,搜索Beatles

 

这句指令将在Play Music APP中开启搜索Beatles。

 

怎样实现语音搜索

 

当语音搜索启动后,我们的APP将收到一个查询文字的Intent,我们必须捕捉和分析这段文字。所以,这第一部分是指定哪个Activity接收这条Intent:

1 <activity android:name=".MainActivity" android:launchMode="singleTask" >
2     <intent-filter>
3         <action android:name="com.google.android.gms.actions.SEARCH_ACTION"/>
4         <category android:name="android.intent.category.DEFAULT"/>
5     </intent-filter>
6 </activity>

 

这个动作(action)称为com.google.android.gms.actions.SEARCH_ACTION,所以我们说MainActivity是处理这类Intent。另外,我用singleTask启动模式,这样MainActivity就仅仅创建一次。否则,每次收到这条Intent就要创建一个新的Activity实例。

 

接下来的一步是要在MainActivity内进行处理它。当我们用singleTask模式时,在Activity的两个不同位置上可以接收Intent:首先是用getIntent()创建Activity,接着是onNewIntent方法中。这样就要创建一个处理方法在需要它的时候调用它:

1     private static final String ACTION_VOICE_SEARCH = "com.google.android.gms.actions.SEARCH_ACTION";
2   ...
3     private void handleVoiceSearch(Intent intent) {
4         if (intent != null && ACTION_VOICE_SEARCH.equals(intent.getAction())) {
5             String query = intent.getStringExtra(SearchManager.QUERY);
6             setSearchViewVisible(true);
7             searchView.setQuery(query, true);
8         }
9     }

 

这个方法检查Intent是否为空(null)或者是否是在收到查询文字前要检测的动作,它是Intent内部的额外动作。查询的额外动作关键字是SearchManager.QUERY。

 

searchView后,设置查询,提交执行查询。其方法是onNewIntent:

1 @Override protected void onNewIntent(Intent intent) {
2     super.onNewIntent(intent);
3     handleVoiceSearch(intent);
4 }

 

UI也准备好(在我们的例子中,当菜单弹出时,我们访问SearchView),你稍后将看到。

 

在我的例子中,UI是基于工具栏(Toolbar)内部的SearchView。你可以在前面的文章中看到怎样实现SearchView,不过我还是稍作解释怎样做。首先,产生菜单动作(menu action):

1 <menu xmlns:android="http://schemas.android.com/apk/res/android"
2       xmlns:app="http://schemas.android.com/apk/res-auto">
3     <item
4         android:id="@+id/action_search"
5         android:title="@string/action_search"
6         android:icon="@drawable/ic_search"
7         app:actionViewClass="android.support.v7.widget.SearchView"
8         app:showAsAction="ifRoom" />
9 </menu>

 

然后,在菜单弹出时,你请求SearchView:

 1 @Override public boolean onCreateOptionsMenu(Menu menu) {
 2     getMenuInflater().inflate(R.menu.main, menu);
 3  
 4     MenuItem searchItem = menu.findItem(R.id.action_search);
 5     searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
 6  
 7     searchView.setOnSearchClickListener(new View.OnClickListener() {
 8         @Override public void onClick(View v) {
 9             setSearchViewVisible(true);
10         }
11     });
12  
13     searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
14         @Override public boolean onQueryTextSubmit(String query) {
15             Toast.makeText(MainActivity.this, query, Toast.LENGTH_LONG).show();
16             searchView.clearFocus();
17             return true;
18         }
19  
20         @Override public boolean onQueryTextChange(String newText) {
21             return false;
22         }
23     });
24  
25     handleVoiceSearch(getIntent());
26  
27     return true;
28 }
29  
30 private void setSearchViewVisible(boolean visible) {
31  
32     if (searchView.isIconified() == visible) {
33         searchView.setIconified(!visible);
34     }
35  
36     if (getSupportActionBar() != null) {
37         getSupportActionBar().setDisplayHomeAsUpEnabled(visible);
38     }
39 }

 

怎样尝试

 

由于APP需要发布到Play商店中,Google Now才能检测到APP,所以我们还不能直接从Google Now尝试这个例子。但是,我们可以用ADB来调试它。这条命令是:

 

  • adb shell am start -a com.google.android.gms.actions.SEARCH_ACTION -e query searchquery app_package

 

对于从我的代码库下载的例子,就可以这样做:

 

  • adb shell am start -a com.google.android.gms.actions.SEARCH_ACTION -e query VoiceSearch com.antonioleiva.googlenowsearch

 

在APP完全关闭和启动时,都可以尝试本例了。这样,就可以测试两种可能的途径了。

 

附加说明:Kotlin语言的实现

 

你可能知道,因为我认为Kotlin语言可以非常好替代Java语言,Kotlin可以使我们的代码更简洁、可读性更好,所以这些天讨论了许多Kotlin语言的特性。作为例子,我将简化onCreateOptionsMenu,你可以在同一个代码库中找到完整的代码。

 

实现扩展函数的能力可以帮助我们减少冗长代码。例如,可以为Menu创建一个扩展函数,基于action id找到ActionView,返回层级视图:

1 inline fun <reified T : View?> Menu.findCompatActionView(actionRes: Int): T {
2     val searchItem = findItem(actionRes)
3     return MenuItemCompat.getActionView(searchItem) as T
4 }

 

现在可以这样做:

1 searchView = menu.findCompatActionView(R.id.action_search)

 

另一个扩展函数可以以清晰的方式帮助我们编写OueryTextListener:

 1 fun SearchView.onQueryText(submit: (String) -> Boolean = { false }, textChange: (String) -> Boolean = { false }) {
 2  
 3     this.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
 4  
 5         override fun onQueryTextSubmit(query: String): Boolean = submit(query)
 6  
 7         override fun onQueryTextChange(newText: String): Boolean = textChange(newText)
 8  
 9     })
10 }

 

这个函数接收一对函数,一个用于侦听器中的每个方法,并给出它们的默认值。这样我们仅需要定义我们要用的。如果我们仅仅要第一个函数(对于第二个我们用默认的),现在我就可以这样做:

1 searchView.onQueryText ({
2     longToast(it)
3     searchView.clearFocus()
4     true
5 })

 

最终,这个函数就是这样的:

 1 override fun onCreateOptionsMenu(menu: Menu): Boolean {
 2     menuInflater.inflate(R.menu.main, menu)
 3  
 4     searchView = menu.findCompatActionView(R.id.action_search)
 5     searchView.setOnSearchClickListener { setSearchViewVisible(true) }
 6  
 7     searchView.onQueryText ({
 8         longToast(it)
 9         searchView.clearFocus()
10         true
11     })
12  
13     intent?.let { handleVoiceSearch(it) }
14  
15     return true
16 }

 

如你所见,如果在这个位置上(例如:在一个常规activity创建上),Intent可以为null,就必须在使用它之前检查它是否为null。用let函数,可以避免if条件的创建,在对象调用时不为null,就可只进入对象内部。

如果你对Kotlin有兴趣,可以搜索Kotlin文章,或购买我编写的《Android开发者的Kotlin》一书。

 

posted @ 2015-12-10 21:19  figozhg  阅读(1109)  评论(0编辑  收藏  举报