在 TextWatcher 中过滤适配器时出现 IllegalStateException
我有一个由自定义适配器(扩展 BaseAdapter
)支持的自定义 ListActivity
。 受此启发,该布局还包含一个 EditText
和一个 TextWatcher
。适配器实现了Filterable
。正如 Hamy 在另一个问题中建议的那样,TextWatcher.onTextChanged(...)
只需调用 adapter.getFilter().filter(string);
即可。
问题:当设备方向改变时,Activity 会被销毁,并与所有对象一起重新创建。发生这种情况时,会引发 IllegalStateException
。研究揭示了这个问题:适配器内容不应该从后台线程修改,而只能从 UI 线程修改。 另请参阅此处
在我看来,TextWatcher 是在后台线程,同时重新创建布局,过滤适配器内容并导致异常。
我该如何解决这个问题?
编辑:
我正在针对 Android 1.6 (Donut) - API Level 4 进行构建,并使用 Samsung Galaxy S2、Android 2.3.3 进行测试。
活动:
public class MyActivity extends ListActivity {
MyAdapter adapter;
EditText searchBox;
public boolean activeSearch = false;
public ArrayList<MyItem> filteredItems = new ArrayList<MyItem>();
/*
* Activity overrides
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mylayout);
getListView().setTextFilterEnabled(true);
adapter = new MyAdapter(this, R.layout.myitemlayout);
setListAdapter(adapter);
searchBox = (EditText)findViewById(R.id.mySearchBox);
searchBox.addTextChangedListener(searchBoxTextWatcher); // defined below
}
@Override
protected void onDestroy() {
super.onDestroy();
searchBox.removeTextChangedListener(searchBoxTextWatcher);
}
/*
* Search box text watcher
*/
TextWatcher searchBoxTextWatcher = new TextWatcher() {
@Override
public void onTextChanged(CharSequence searchText, int start, int before, int count) {
adapter.getFilter().filter(searchText);
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void afterTextChanged(Editable s) {
}
};
}
适配器:
public class MyAdapter extends BaseAdapter implements Filterable {
MyActivity activity;
int layoutID;
Filter filter;
/*
* Constructor
*/
public MyAdapter(MyActivity activity, int layoutID) {
super();
this.activity = activity;
this.layoutID = layoutID;
}
/*
* BaseAdapter overrides
*/
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
MyItem item = activity.activeSearch ? activity.filteredItems.get(position) : allItems.get(position);
// Create/prepare view
// ...
return view;
}
@Override
public int getCount() {
return activity.activeSearch ? activity.filteredItems.size() : allItems.size();
}
@Override
public MyItem getItem(int position) {
return activity.activeSearch ? activity.filteredItems.get(position) : allItems.get(position);
}
@Override
public long getItemId(int position) {
return getItem(position).itemID;
}
/*
* Filterable overrides
*/
@Override
public Filter getFilter() {
if (filter == null) {
filter = new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
String searchText = constraint.toString();
FilterResults results = new FilterResults();
if (searchText == null || searchText.length() == 0) {
synchronized (this) {
activity.activeSearch = false;
results.values = allItems;
results.count = allItems.size();
}
} else {
synchronized (this) {
activity.activeSearch = true;
ArrayList<MyItem> filteredItems = new ArrayList<MyItem>();
ArrayList<MyItem> tmpItems = new ArrayList<MyItem>();
tmpItems.addAll(allItems);
for (MyItem item : tmpItems) {
boolean matched = false;
// Compare various properties of item against searchText
// and set matched = true if it matched.
// ...
if (matched) {
filteredItems.add(item);
}
}
results.values = filteredItems;
results.count = filteredItems.size();
}
}
return results;
}
@SuppressWarnings("unchecked")
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
activity.filteredItems = (ArrayList<MyItem>)results.values;
notifyDataSetChanged();
}
};
}
return filter;
}
}
布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
>
<EditText
android:id="@+id/mySearchBox"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="Enter something :)"
android:inputType="number"
android:maxLines="1"
></EditText>
<ListView
android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="@android:color/white"
></ListView>
</LinearLayout>
Stacktrace:
Thread [<1> main] (Suspended (exception IllegalStateException))
ListView.layoutChildren() line: 1558
ListView(AbsListView).onLayout(boolean, int, int, int, int) line: 1384
ListView(View).layout(int, int, int, int) line: 7228
LinearLayout.setChildFrame(View, int, int, int, int) line: 1254
LinearLayout.layoutVertical() line: 1130
LinearLayout.onLayout(boolean, int, int, int, int) line: 1047
LinearLayout(View).layout(int, int, int, int) line: 7228
FrameLayout.onLayout(boolean, int, int, int, int) line: 338
FrameLayout(View).layout(int, int, int, int) line: 7228
PhoneWindow$DecorView(FrameLayout).onLayout(boolean, int, int, int, int) line: 338
PhoneWindow$DecorView(View).layout(int, int, int, int) line: 7228
ViewRoot.performTraversals() line: 1148
ViewRoot.handleMessage(Message) line: 1868
ViewRoot(Handler).dispatchMessage(Message) line: 99
Looper.loop() line: 123
ActivityThread.main(String[]) line: 3691
Method.invokeNative(Object, Object[], Class, Class[], Class, int, boolean) line: not available [native method]
Method.invoke(Object, Object...) line: 507
ZygoteInit$MethodAndArgsCaller.run() line: 847
ZygoteInit.main(String[]) line: 605
NativeStart.main(String[]) line: not available [native method]
LogCat 显示没有 Stacktrace,只有这个(不知道是否相关):
09-15 09:23:59.575: WARN/WindowManager(2696): Window freeze timeout expired.
09-15 09:23:59.575: WARN/WindowManager(2696): Force clearing orientation change: Window{40a82ba8 InputMethod paused=false}
09-15 09:23:59.575: WARN/WindowManager(2696): Force clearing orientation change: Window{40bff2f8 my.package/my.package.Activities.MyActivity paused=false}
09-15 09:23:59.875: WARN/InputConnectionWrapper.ICC(2820): Timed out waiting on IInputContextCallback
09-15 09:24:20.330: WARN/InputConnectionWrapper.ICC(2820): Timed out waiting on IInputContextCallback
09-15 09:24:20.330: ERROR/InputMethodService(2820): Unexpected null in startExtractingText : mExtractedText = null, input connection = com.android.internal.view.InputConnectionWrapper@406a82c0
09-15 09:24:22.340: WARN/InputConnectionWrapper.ICC(2820): Timed out waiting on IInputContextCallback
09-15 09:24:22.340: ERROR/InputMethodService(2820): Unexpected null in startExtractingText : mExtractedText = null, input connection = com.android.internal.view.InputConnectionWrapper@406a82c0
I have a custom ListActivity
backed by a custom adapter (extending BaseAdapter
). Inspired by this, the layout also contains an EditText
with a TextWatcher
. The Adapter implements Filterable
. The TextWatcher.onTextChanged(...)
simply calls adapter.getFilter().filter(string);
just as Hamy suggested in the other question.
The problem: When device orientation changes, the Activity is destroyed and recreated along with all the objects. When that happens, an IllegalStateException
is thrown. Research revealed the problem: the adapter content should not be modified from a background thread, but only from the UI thread. see also here
Seems to me the TextWatcher is called on a background thread while recreating the layout, filtering the adapter contents and causing the exception.
How can I work around that?
EDIT:
I'm building against Android 1.6 (Donut) - API Level 4 and testing with a Samsung Galaxy S2, Android 2.3.3.
The activity:
public class MyActivity extends ListActivity {
MyAdapter adapter;
EditText searchBox;
public boolean activeSearch = false;
public ArrayList<MyItem> filteredItems = new ArrayList<MyItem>();
/*
* Activity overrides
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mylayout);
getListView().setTextFilterEnabled(true);
adapter = new MyAdapter(this, R.layout.myitemlayout);
setListAdapter(adapter);
searchBox = (EditText)findViewById(R.id.mySearchBox);
searchBox.addTextChangedListener(searchBoxTextWatcher); // defined below
}
@Override
protected void onDestroy() {
super.onDestroy();
searchBox.removeTextChangedListener(searchBoxTextWatcher);
}
/*
* Search box text watcher
*/
TextWatcher searchBoxTextWatcher = new TextWatcher() {
@Override
public void onTextChanged(CharSequence searchText, int start, int before, int count) {
adapter.getFilter().filter(searchText);
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void afterTextChanged(Editable s) {
}
};
}
The adapter:
public class MyAdapter extends BaseAdapter implements Filterable {
MyActivity activity;
int layoutID;
Filter filter;
/*
* Constructor
*/
public MyAdapter(MyActivity activity, int layoutID) {
super();
this.activity = activity;
this.layoutID = layoutID;
}
/*
* BaseAdapter overrides
*/
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
MyItem item = activity.activeSearch ? activity.filteredItems.get(position) : allItems.get(position);
// Create/prepare view
// ...
return view;
}
@Override
public int getCount() {
return activity.activeSearch ? activity.filteredItems.size() : allItems.size();
}
@Override
public MyItem getItem(int position) {
return activity.activeSearch ? activity.filteredItems.get(position) : allItems.get(position);
}
@Override
public long getItemId(int position) {
return getItem(position).itemID;
}
/*
* Filterable overrides
*/
@Override
public Filter getFilter() {
if (filter == null) {
filter = new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
String searchText = constraint.toString();
FilterResults results = new FilterResults();
if (searchText == null || searchText.length() == 0) {
synchronized (this) {
activity.activeSearch = false;
results.values = allItems;
results.count = allItems.size();
}
} else {
synchronized (this) {
activity.activeSearch = true;
ArrayList<MyItem> filteredItems = new ArrayList<MyItem>();
ArrayList<MyItem> tmpItems = new ArrayList<MyItem>();
tmpItems.addAll(allItems);
for (MyItem item : tmpItems) {
boolean matched = false;
// Compare various properties of item against searchText
// and set matched = true if it matched.
// ...
if (matched) {
filteredItems.add(item);
}
}
results.values = filteredItems;
results.count = filteredItems.size();
}
}
return results;
}
@SuppressWarnings("unchecked")
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
activity.filteredItems = (ArrayList<MyItem>)results.values;
notifyDataSetChanged();
}
};
}
return filter;
}
}
The layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
>
<EditText
android:id="@+id/mySearchBox"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="Enter something :)"
android:inputType="number"
android:maxLines="1"
></EditText>
<ListView
android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="@android:color/white"
></ListView>
</LinearLayout>
Stacktrace:
Thread [<1> main] (Suspended (exception IllegalStateException))
ListView.layoutChildren() line: 1558
ListView(AbsListView).onLayout(boolean, int, int, int, int) line: 1384
ListView(View).layout(int, int, int, int) line: 7228
LinearLayout.setChildFrame(View, int, int, int, int) line: 1254
LinearLayout.layoutVertical() line: 1130
LinearLayout.onLayout(boolean, int, int, int, int) line: 1047
LinearLayout(View).layout(int, int, int, int) line: 7228
FrameLayout.onLayout(boolean, int, int, int, int) line: 338
FrameLayout(View).layout(int, int, int, int) line: 7228
PhoneWindow$DecorView(FrameLayout).onLayout(boolean, int, int, int, int) line: 338
PhoneWindow$DecorView(View).layout(int, int, int, int) line: 7228
ViewRoot.performTraversals() line: 1148
ViewRoot.handleMessage(Message) line: 1868
ViewRoot(Handler).dispatchMessage(Message) line: 99
Looper.loop() line: 123
ActivityThread.main(String[]) line: 3691
Method.invokeNative(Object, Object[], Class, Class[], Class, int, boolean) line: not available [native method]
Method.invoke(Object, Object...) line: 507
ZygoteInit$MethodAndArgsCaller.run() line: 847
ZygoteInit.main(String[]) line: 605
NativeStart.main(String[]) line: not available [native method]
LogCat shows no Stacktrace, only this (don't know if it's related):
09-15 09:23:59.575: WARN/WindowManager(2696): Window freeze timeout expired.
09-15 09:23:59.575: WARN/WindowManager(2696): Force clearing orientation change: Window{40a82ba8 InputMethod paused=false}
09-15 09:23:59.575: WARN/WindowManager(2696): Force clearing orientation change: Window{40bff2f8 my.package/my.package.Activities.MyActivity paused=false}
09-15 09:23:59.875: WARN/InputConnectionWrapper.ICC(2820): Timed out waiting on IInputContextCallback
09-15 09:24:20.330: WARN/InputConnectionWrapper.ICC(2820): Timed out waiting on IInputContextCallback
09-15 09:24:20.330: ERROR/InputMethodService(2820): Unexpected null in startExtractingText : mExtractedText = null, input connection = com.android.internal.view.InputConnectionWrapper@406a82c0
09-15 09:24:22.340: WARN/InputConnectionWrapper.ICC(2820): Timed out waiting on IInputContextCallback
09-15 09:24:22.340: ERROR/InputMethodService(2820): Unexpected null in startExtractingText : mExtractedText = null, input connection = com.android.internal.view.InputConnectionWrapper@406a82c0
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论