Что вызывает этот экземпляр Exception: "java.lang.IllegalArgumentException: наблюдатель имеет значение null". и как этого можно избежать?
Я получаю это исключение, возвращаясь к исходному ListActivity
после открытия нового действия с содержимым элемента, выбранного пользователем. Это происходит только на сэндвич с мороженым.
Это трассировка:
java.lang.IllegalArgumentException: The observer is null.
at android.database.Observable.unregisterObserver(Observable.java:59)
at android.widget.BaseAdapter.unregisterDataSetObserver(BaseAdapter.java:42)
at android.widget.AbsListView.onDetachedFromWindow(AbsListView.java:2373)
at android.view.View.dispatchDetachedFromWindow(View.java:9756)
at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:2274)
at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:2272)
at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:2272)
at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:2272)
at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:2272)
at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:2272)
at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:2272)
at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:2272)
at android.view.ViewRootImpl.dispatchDetachedFromWindow(ViewRootImpl.java:2227)
at android.view.ViewRootImpl.doDie(ViewRootImpl.java:3679)
at android.view.ViewRootImpl.die(ViewRootImpl.java:3667)
at android.view.WindowManagerImpl.removeViewImmediate(WindowManagerImpl.java:320)
at android.view.WindowManagerImpl$CompatModeWrapper.removeViewImmediate(WindowManagerImpl.java:139)
at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:3144)
at android.app.ActivityThread.access$1200(ActivityThread.java:122)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1179)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4340)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
at dalvik.system.NativeStart.main(Native Method)
Ни один из моих кодов не упоминается в стеке, поэтому он меня озадачивает, и я не могу совершить грязный захват:/
Я регистрирую анонимный DataSetObserver
только при создании действия. Также активность встроена внутри ActivityGroup (которая устарела в 4.0, но все равно должна ее поддерживать, я надеялся).
У кого-нибудь была эта проблема с новой ОС?
Спасибо заранее.
Update:
Хорошо, думаю, я нашел источник проблемы, хотя не знаю, как его решить.
Внутри AbsListView.onDetachedFromWindow()
У нас есть следующее:
if (mAdapter != null) { // Android code added on ICS
mAdapter.unregisterDataSetObserver(mDataSetObserver);
mDataSetObserver = null;
}
Что, как только наблюдатель будет незарегистрирован, он будет аннулирован. Проблема заключается в том, что по какой-то причине в ICS она вызывается дважды. Мне кажется, что глупо проверять нулевые параметры внутри операции удаления, как в классе Observable
:
public void unregisterObserver(T observer) { // Android code
if (observer == null) {
throw new IllegalArgumentException("The observer is null.");
}
synchronized(mObservers) {
int index = mObservers.indexOf(observer);
if (index == -1) {
throw new IllegalStateException("Observer " + observer + " was not registered.");
}
mObservers.remove(index);
}
}
Почему бы просто не игнорировать его? Они могли бы просто сделать это и работать (или лучше):
public void unregisterObserver(T observer) { // Android code
synchronized(mObservers) {
mObservers.remove(observer);
}
}
Ответы
Ответ 1
Эта проблема была введена в Android 4.0.3, а класс Observable был изменен, чтобы генерировать исключение, когда наблюдатель был выпущен более одного раза. Об этом сообщается в качестве ошибки и читайте здесь: http://code.google.com/p/android/issues/detail?id=22946.
Самый простой способ обойти эту проблему - обернуть базовый адаптер и избежать нескольких выпусков.
@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
if (observer != null) {
super.unregisterDataSetObserver(observer);
}
}
Но это не будет работать во всех случаях, например. В ExpandableListView есть внутренний адаптер, к которому невозможно получить доступ. Альтернативное решение здесь - обернуть ExpandableListView и уловить исключение. Это решение сработало для меня, и я пока не обнаружил никаких побочных эффектов.
public class PatchedExpandableListView extends ExpandableListView {
public PatchedExpandableListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onDetachedFromWindow() {
try {
super.onDetachedFromWindow();
} catch(IllegalArgumentException iae) {
// Workaround for http://code.google.com/p/android/issues/detail?id=22751
}
}
}
Ответ 2
Я сделал глупую ошибку, думая, что ни один из моих классов не упомянут в следе, но LoadingDataView является одним из них. Он не отображается в исходной трассе, но другой, который был связан.
Внутри этого класса есть анонимный ArrayAdapter, в котором происходил инцидент, поэтому я добавил это как работу:
@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
if (observer != null) {
super.unregisterDataSetObserver(observer);
}
}
И теперь это работает, хотя я до сих пор не знаю, почему этот метод вызывается дважды.
Хотя, на данный момент я собираюсь использовать Фрагменты как можно больше;)