notifyDataSetChanged() в ListView не работает с runOnUiThread (новый Runnable())

У меня есть ListView, который использует BaseAdapter, а ListView расположен в ViewGroup. Когда я вызываю notifyDataSetChanged() через runOnUiThread() из другого потока, ListView не обновляется. Только когда я касаюсь и перетаскиваю, другими словами, пытаюсь прокрутить, ListView обновляется.

Я понимаю, что у многих людей возникают проблемы, когда они вызывают notifyDataSetChanged() из другого потока, кроме потока пользовательского интерфейса, но я этого не делаю, и у меня все еще есть проблема.

Приведенный ниже код кажется минимальным для воспроизведения проблемы.

public class MyMainActivity extends Activity
{
    public MyStringViewGroup StringViewGroup = null;

    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        StringViewGroup = new MyStringViewGroup(this);
        setContentView(StringViewGroup);

        // This is the thread I'll call UpdateStringList() from
        new OtherThread(this).start();

        //UpdateStringList(); // If I call it from here, everything works
    }

    // This should post the runnable on the UI-thread so StringViewGroup.UpdateStringList() is called from the right thread
    public void UpdateStringList() {runOnUiThread(new Runnable() {public void run()
    {
        StringViewGroup.UpdateStringList();
    }});}
}

class OtherThread extends Thread
{
    MyMainActivity Main = null;

    OtherThread(MyMainActivity main)
    {
        Main = main;
    }
    @Override public void run()
    {
        // Wait 2 seconds
        try {sleep(2000);} catch (InterruptedException e) {}

        // Call the funktion that add the three rows
        Main.UpdateStringList(); // If i call it from here, It's not working
    }
}

class MyStringViewGroup extends ViewGroup
{
    ListView StreamListView = null;
    MyStringListAdapter StringListAdapter = null;
    ArrayList<String> StringArray = new ArrayList<String>();

    public MyStringViewGroup(MyMainActivity context)
    {
        super(context);

        StreamListView = new ListView(context);
        StreamListView.setId(android.R.id.list);
        addView(StreamListView);

        StringListAdapter = new MyStringListAdapter(StringArray);
        StreamListView.setAdapter(StringListAdapter);
    }

    public void UpdateStringList()
    {
        StringArray.add("Row 1");
        StringArray.add("Row 2");
        StringArray.add("Row 3");

        StringListAdapter.notifyDataSetChanged();
    }

    @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom)
    {
        StreamListView.layout(left, top, right, bottom);
    }

    // An ordinary Adapter, nothing special here (I hope)
    public class MyStringListAdapter extends BaseAdapter
    {
        ArrayList<String> StringArray = null;

        public MyStringListAdapter(ArrayList<String> stringArray) {StringArray = stringArray;}
        @Override public int getCount() {return StringArray.size();}
        @Override public String getItem(int position) {return StringArray.get(position);}
        @Override public long getItemId(int position) {return position;}
        @Override public View getView(int position, View convertView, ViewGroup parent)
        {
            final TextView textView;
            if (convertView == null)
            {
                textView = new TextView(MyStringViewGroup.this.getContext());
                textView.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
            }
            else
                textView = (TextView)convertView;

            textView.setText(StringArray.get(position));
            return textView;
        }
    }
}

Что я заметил, так это то, что getView() в адаптере вызывается, затем я пытаюсь прокрутить, а не раньше, и появляются три строки, поэтому, очевидно, адаптер обновлен.

Итак, чем отличается вызов через runOnUiThread()?

Если я перейду на:

public void UpdateStringList() {StringViewGroup.post(new Runnable() {public void run()
{
    StringViewGroup.UpdateStringList();
}});}

и попробуйте позвонить:

UpdateStringList();

из основного потока я получаю ту же проблему. Я даже пытался вызвать invalidate() и requestLayout() в списке, но безрезультатно (вам в любом случае не нужно этого делать, а просто убедиться)

Может быть, мне нужно что-то сделать в ViewGroup, что я пропустил?


person Floaf    schedule 31.07.2014    source источник
comment
ваше использование имен переменных с заглавными буквами затрудняет различие между классом и переменной. В любом случае, используйте AsyncTask, с ним гораздо проще делать такие вещи.   -  person tyczj    schedule 31.07.2014
comment
Я знаю, мы все кодируем по-разному. Они должны раскрасить источник здесь. В любом случае, это был просто урезанный пример для воссоздания проблемы, обновления поступают из службы, и проблема существует даже без потоков вообще, если я публикую исполняемый файл для запуска обновления.   -  person Floaf    schedule 31.07.2014


Ответы (1)


Хорошо, я только что понял, что вы обязательно должны вызвать метод Measure() для ListView в вызове onLayout, например:

@Override protected void onLayout(boolean changed, int left, int top, int right, int bottom)
{
    StreamListView.measure(...);
    StreamListView.layout(left, top, right, bottom);
}

Все еще очень странно, что ошибка возникает только тогда, когда notifyDataSetChanged() вызывается из другого потока через runOnUiThread(new Runnable()), но я думаю, что Measure() является обязательным.

person Floaf    schedule 20.08.2014