Что вызывает этот экземпляр исключения: 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, но, как я надеялся, должна поддерживать ее).

У кого-нибудь была такая проблема с новой ОС?

Заранее спасибо.


Обновление:

Хорошо, думаю, я нашел источник проблемы, но не знаю, как ее решить.

Внутри 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);
    }
}

person pablisco    schedule 14.12.2011    source источник
comment
Можете ли вы опубликовать свой код активности, и когда вы говорите «вернуться», вы имеете в виду нажатие кнопки «Назад»?   -  person rogermushroom    schedule 14.12.2011
comment
Да, под возвратом я подразумеваю нажатие кнопки «Назад». Вот код действия (pastebin.com/68BzJcc1), но, как я уже упоминал, мой код показан в стеке.   -  person pablisco    schedule 15.12.2011
comment
Я заполнил отчет об ошибке: code.google.com/p/ android/issues/detail?id=22946 Хотя должно быть решение :/   -  person pablisco    schedule 15.12.2011


Ответы (2)


Эта проблема появилась в 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
    }
  }
}
person Martin Kretz    schedule 07.02.2012

Я сделал глупую ошибку, думая, что ни один из моих классов не упоминается в трассировке, но LoadingDataView является одним из них. Это не видно в исходной трассировке, но есть другая, которая была связана.

Внутри этого класса есть анонимный ArrayAdapter, в котором происходит инцидент, поэтому я добавил это как обходной путь:

@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
    if (observer != null) {
        super.unregisterDataSetObserver(observer);
    }
}

И, похоже, теперь это работает, хотя я до сих пор не уверен, почему этот метод был вызван дважды.

Хотя сейчас я буду использовать Фрагменты столько, сколько смогу ;)

person pablisco    schedule 15.12.2011