zoukankan      html  css  js  c++  java
  • 将语音搜索集成到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》一书。

  • 相关阅读:
    [小北De编程手记] : Selenium For C# 教程目录
    [小北De编程手记] [Lesson 02] AutoFramework构建 之 Page Objects
    [小北De编程手记] Lesson 01
    [小北De编程手记] : Lesson 06 玩转 xUnit.Net 之 定义自己的FactAttribute
    [小北De编程手记] : Lesson 05 玩转 xUnit.Net 之 从Assert谈UT框架实践
    [小北De编程手记] : Lesson 04 玩转 xUnit.Net 之 Fixture(下)
    [小北De编程手记] : Lesson 03 玩转 xUnit.Net 之 Fixture(上)
    [小北De编程手记] : Lesson 02 玩转 xUnit.Net 之 基本UnitTest & 数据驱动
    [小北De编程手记] : Lesson 01 玩转 xUnit.Net 之 概述
    [小北De编程手记] : Lesson 08
  • 原文地址:https://www.cnblogs.com/figozhg/p/5037333.html
Copyright © 2011-2022 走看看