你的位置:首页 > 操作系统

[操作系统]将语音搜索集成到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 ="http://schemas.android.com/apk/res/android"2    ="http://schemas.android.com/apk/res-auto">3   <item4     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 T4 }

 

现在可以这样做:

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   true5 })

 

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

 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     true11   })12 13   intent?.let { handleVoiceSearch(it) }14 15   return true16 }

 

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

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