通过 Google Voice Actions 搜索操作过滤列表时 Android 应用程序崩溃
Posted
技术标签:
【中文标题】通过 Google Voice Actions 搜索操作过滤列表时 Android 应用程序崩溃【英文标题】:Android app crashes when filtering list via Google Voice Actions search action 【发布时间】:2016-05-20 12:58:51 【问题描述】:我在使用基于 this tutorial 通过 Volley 解析的 JSON 数据过滤列表时遇到问题,当该列表通过 Google 应用程序中的 Google 系统语音操作中的 Search in App 意图进行过滤时。
遇到的具体问题如下:
应用程序最初根本没有运行(如果应用程序正在运行或在后台,搜索将完美运行)。
通过 adb 触发 Intent:
cd C:...\android-sdk\platform-tools
adb shell am start -a "com.google.android.gms.actions.SEARCH_ACTION" --es query "[query keyword]" -n "com.testapp/.MainActivity"
正确的应用程序打开,但列表为空,即没有过滤结果。
然后应用程序崩溃。
下面是堆栈跟踪:
02-15 17:54:03.331: D/AndroidRuntime(31982): Shutting down VM
02-15 17:54:03.341: E/AndroidRuntime(31982): FATAL EXCEPTION: main
02-15 17:54:03.341: E/AndroidRuntime(31982): Process: com.test.app, PID: 31982
02-15 17:54:03.341: E/AndroidRuntime(31982): java.lang.IndexOutOfBoundsException: Invalid index 0, size is 0
02-15 17:54:03.341: E/AndroidRuntime(31982): at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:255)
02-15 17:54:03.341: E/AndroidRuntime(31982): at java.util.ArrayList.get(ArrayList.java:308)
02-15 17:54:03.341: E/AndroidRuntime(31982): at com.test.app.adapter.CustomListAdapter.getView(CustomListAdapter.java:92)
02-15 17:54:03.341: E/AndroidRuntime(31982): at android.database.DataSetObservable.notifyChanged(DataSetObservable.java:37)
02-15 17:54:03.341: E/AndroidRuntime(31982): at android.widget.BaseAdapter.notifyDataSetChanged(BaseAdapter.java:50)
02-15 17:54:03.341: E/AndroidRuntime(31982): at com.test.app$1.onResponse(MainActivity.java:152)
02-15 17:54:03.341: E/AndroidRuntime(31982): at com.test.app$1.onResponse(MainActivity.java:1)
02-15 17:54:03.341: E/AndroidRuntime(31982): at com.android.volley.toolbox.JsonRequest.deliverResponse(JsonRequest.java:65)
02-15 17:54:03.341: E/AndroidRuntime(31982): at com.android.volley.ExecutorDelivery$ResponseDeliveryRunnable.run(ExecutorDelivery.java:99)
02-15 17:54:03.341: E/AndroidRuntime(31982): at android.os.Handler.handleCallback(Handler.java:739)
02-15 17:54:03.341: E/AndroidRuntime(31982): at android.os.Handler.dispatchMessage(Handler.java:95)
02-15 17:54:03.341: E/AndroidRuntime(31982): at android.os.Looper.loop(Looper.java:145)
02-15 17:54:03.341: E/AndroidRuntime(31982): at android.app.ActivityThread.main(ActivityThread.java:6843)
02-15 17:54:03.341: E/AndroidRuntime(31982): at java.lang.reflect.Method.invoke(Native Method)
02-15 17:54:03.341: E/AndroidRuntime(31982): at java.lang.reflect.Method.invoke(Method.java:372)
02-15 17:54:03.341: E/AndroidRuntime(31982): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1404)
02-15 17:54:03.341: E/AndroidRuntime(31982): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1199)
我的应用包含一个 MainActivity,它是显示列表的可搜索 Activity、一个模型类、一个适配器类和一个用于列表项的控制器类(类似于现有的 Android ListView 教程)。
以下是代码(部分代码省略):
主活动
//[…]
public class MainActivity extends AppCompatActivity
//[…]
private List<Item> itemList = new ArrayList<Item>();
public static ListView listView;
private CustomListAdapter adapter;
private static final String GMS_SEARCH_ACTION = "com.google.android.gms.actions.SEARCH_ACTION";
private String qq;
@Override
protected void onCreate(Bundle savedInstanceState)
//[…]
onNewIntent(getIntent());
public void onNewIntent(Intent intent)
super.onNewIntent(intent);
String action = intent.getAction();
if (action!= null && (action.equals(Intent.ACTION_SEARCH)||action.equals(GMS_SEARCH_ACTION)))
qq = intent.getStringExtra(SearchManager.QUERY);
doSearch(qq);
/* FIXME - Current problem: From adb command, ListView can only be filtered successfully only if app is still running in background (i.e. onPause()). ListView filtering works ok if searching within the app. If app is not running, sending the search command causes the app to open but nothing is filtered, and then crashes shortly after.*/
private void doSearch(String qq)
CharSequence query = qq.toUpperCase(Locale.getDefault());
MainActivity.this.adapter.getFilter().filter(query);
@Override
public boolean onCreateOptionsMenu(Menu menu)
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu, menu);
// […]
MenuItem searchItem = menu.findItem(R.id.menu_search);
SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
SearchableInfo searchableInfo = searchManager.getSearchableInfo(getComponentName());
searchView.setSearchableInfo(searchableInfo);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener()
@Override
public boolean onQueryTextSubmit(String qq)
doSearch(qq);
return false;
@Override
public boolean onQueryTextChange(String qq)
doSearch(qq);
return false;
);
return super.onCreateOptionsMenu(menu);
适配器类
// […]
public class CustomListAdapter extends BaseAdapter
private Activity activity;
private LayoutInflater inflater;
private List<Item> items, default_items;
private Filter myFilter;
ImageLoader imageLoader = AppController.getInstance().getImageLoader();
public CustomListAdapter(Activity activity, List<Item> items)
this.activity = activity;
this.items = items;
this.default_items = items;
//[…]
@Override
public View getView(int position, View convertView, ViewGroup parent)
//…
public Filter getFilter()
if (myFilter == null) myFilter = new MYFilter();
return myFilter;
private class MYFilter extends Filter
@Override
protected FilterResults performFiltering(CharSequence query)
FilterResults results = new FilterResults();
// If no filter implemented, return the whole list
if (query == null || query.length() == 0)
results.values = default_ items;
results.count = default_ items.size();
else
List<Item> nitems = new ArrayList<Item>();
items = default_nitems;
for (Item t : items)
// Filter logic implemented here, items are added to nitems if it meets conditions
results.values = nitems;
results.count = nitems.size();
return results;
@Override
protected void publishResults(CharSequence query, FilterResults results)
if (results.count == 0)
items = (List<Item>) results.values;
MainActivity.emptyView.setVisibility(View.VISIBLE);
MainActivity.listView.setVisibility(View.GONE);
else
items = (List< Item >) results.values;
MainActivity.listView.setVisibility(View.VISIBLE);
MainActivity.emptyView.setVisibility(View.GONE);
notifyDataSetChanged();
清单
<?xml version="1.0" encoding="utf-8"?>
<manifest ... >
<application
android:name=".app.AppController"
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" android:allowTaskReparenting="true">
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:screenOrientation="portrait"
android:exported="true"
android:launchMode="singleTop" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable" />
<intent-filter>
<action android:name="com.google.android.gms.actions.SEARCH_ACTION" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
<meta-data
android:name="android.app.default_searchable"
android:value=".MainActivity" />
</activity>
<!-- ... -->
</application>
</manifest>
感谢您提供解决此问题的任何帮助。
【问题讨论】:
项目结构基于this tutorial。java.lang.IndexOutOfBoundsException: Invalid index 0, size is 0
在CustomListAdapter.java
第 92 行
【参考方案1】:
如果将来有人在使用基于另一个线程或 AsyncTask 中填充的数据集的 ListView 时遇到类似的问题(例如,通过 Volley in this tutorial 解析 JSON),则导致此问题是因为 JSON 解析已完成与onNewIntent(getIntent());
方法同时使用。
因此,在过滤操作MYFilter
期间可能只有一个空的ArrayList 需要过滤,这会导致IndexOutOfBoundsException
。
在这种特定情况下(使用 Volley),解决方案是在 onResponse 方法的末尾调用onNewIntent(getIntent());
(有关更多信息,请参阅this SO question)。对我来说,onCreate()
内的 Volley Request 之后没有更多代码,所以我很安全。
我意识到这是个问题,因为自从发布了这个问题后,我为该应用程序创建了一个“离线模式”,它在主线程中进行简单的 JSON 解析,并且通过 Google Voice Actions 进行过滤非常有效.另外,我碰巧在多台设备上测试了这段代码,其中一些设备在收到应用内搜索 (com.google.android.gms.actions.SEARCH_ACTION
) 意图之前完成了 JSON 数据的解析,而其他设备则没有。
【讨论】:
以上是关于通过 Google Voice Actions 搜索操作过滤列表时 Android 应用程序崩溃的主要内容,如果未能解决你的问题,请参考以下文章