Android: Выдает ошибку «Только исходный поток, создавший иерархию представлений, может касаться своих представлений»

#android #multithreading #handler #progressdialog #runnable

#Android #многопоточность #обработчик #progressdialog #доступно для выполнения

Вопрос:

Я работал над этой проблемой весь день и готов рвать на себе волосы. Я нашел несколько ответов здесь и в Интернете, в которых говорится, что это вызвано попыткой что-то сделать с представлением внутри потока (а не в потоке пользовательского интерфейса). Но я перепробовал все идеи (обработчик / новый поток), которые я видел, и все еще не могу заставить это работать. Я много лет программировал на C в качестве хобби, а теперь я новичок в Java / Android. Я программирую с Eclipse и платформой Android 2.1. Я хочу, чтобы мое приложение работало с как можно большим количеством телефонов Android, и я думаю, что все функции, которые я использую, совместимы с API 1. Я также видел, что есть что-то под названием AsyncTask, но вызовет ли это проблему у людей со старыми телефонами?

Итак, вот что делает мое приложение. Когда я нажимаю на кнопку, приложение переходит на веб-сайт и загружает xml / rss-канал. Затем он анализирует его и помещает данные в listview, используя пользовательский адаптер, который я создал. Загрузка и синтаксический анализ могут занимать от 1 секунды до 15 секунд, поэтому я хотел добавить диалоговое окно прогресса. После добавления этого, именно здесь я начал получать сообщение об ошибке в заголовке этого поста. Приложение успешно загружается (в моем примере XML-файла в Интернете содержится 8 записей, поэтому он очень маленький), но затем я вижу ошибку перед отображением listview. Итак, я думаю, мне нужно точно знать, какая часть представления вызывает ошибку, а затем как это исправить.

Вот код (я удалил весь свой тестовый код за последние несколько часов, чтобы он был чистым и не сбивал с толку всех вас … и меня):

 @SuppressWarnings("serial")
public class ClubMessageList extends ListActivity implements Serializable
{
private static final String TAG = "DGMS News";
private ArrayList<CMessage> m_messages = null;
private MessageAdapter m_adapter;
private ProgressDialog m_ProgressDialog = null; 
private Runnable downloadMessages;
// Need handler for callbacks to the UI thread
final Handler mHandler = new Handler();

@SuppressWarnings("unchecked")
@Override
public void onCreate(Bundle icicle)
{
    Log.i(TAG, "Starting the ClubMessageList activity");
    super.onCreate(icicle);

    setContentView(R.layout.list);
    setTitle("DGMS News - Clubs");

    try
    {
        // check to see if we already have downloaded messages available in the bundle
        m_messages = (ArrayList<CMessage>) ((icicle == null) ? null : icicle.getSerializable("savedMessages"));

        // if there are no messages in the bundle, download them from the web and then display them
        if (m_messages == null)
        {
            m_messages = new ArrayList<CMessage>();
            this.m_adapter = new MessageAdapter(this, R.layout.row_club, (ArrayList<CMessage>) m_messages);
            setListAdapter(this.m_adapter);

            downloadMessages = new Runnable(){
                public void run() {
                    getMessages();
                }
            };
            Thread thread =  new Thread(null, downloadMessages, "DownloadMessages");
            thread.start();
            m_ProgressDialog = ProgressDialog.show(ClubMessageList.this,    
                  "Please wait...", "Retrieving 2010 Show data ...", true);
        }
        else // messages were already downloaded, so display them in the listview (don't download them again)
        {
            Log.i("DGMS News", "Starting activity again. Data exists so don't retrieve it again.");
            m_adapter = new MessageAdapter(this, R.layout.row_club, (ArrayList<CMessage>) m_messages);
            this.setListAdapter(m_adapter);
        }
    }
    catch (Throwable t)
    {
        Log.e("DGMS News",t.getMessage(),t);
    }
}

private Runnable returnRes = new Runnable()
{
    public void run()
    {
        if(m_messages != null amp;amp; m_messages.size() > 0)
        {
            m_adapter.notifyDataSetChanged();
            for(int i=0;i<m_messages.size();i  )
                m_adapter.add(m_messages.get(i));
        }
        m_ProgressDialog.dismiss();
        m_adapter.notifyDataSetChanged();
    }
};

private void getMessages()
{
    try
    {
        m_messages = new ArrayList<CMessage>();
        ClubFeedParser parser = ClubFeedParserFactory.getParser();
        m_messages = parser.parse();
        for(int i = 0; i < m_messages.size(); i  )
            m_adapter.add(m_messages.get(i));
    }
    catch (Exception e)
    { 
        Log.e("DGMS News", e.getMessage());
    }
    runOnUiThread(returnRes);
}

protected void onSaveInstanceState(Bundle outState)
{
    super.onSaveInstanceState(outState);
    outState.putSerializable("savedMessages", (Serializable) m_messages);
}

@Override
protected void onListItemClick(ListView l, View v, int position, long id)
{
    super.onListItemClick(l, v, position, id);
    Intent intent = new Intent(ClubMessageList.this, ClubDetails.class);
    // Add all info about the selected club to the intent
    intent.putExtra("title", m_messages.get(position).getTitle());
    intent.putExtra("location", m_messages.get(position).getLocation());
    intent.putExtra("website", m_messages.get(position).getLink());
    intent.putExtra("email", m_messages.get(position).getEmail());
    intent.putExtra("city", m_messages.get(position).getCity());
    intent.putExtra("contact", m_messages.get(position).getContact());
    intent.putExtra("phone", m_messages.get(position).getPhone());
    intent.putExtra("description", m_messages.get(position).getDescription());

    startActivity(intent);
}

private class MessageAdapter extends ArrayAdapter<CMessage> implements Serializable
{
    private ArrayList<CMessage> items;

    public MessageAdapter(Context context, int textViewResourceId, ArrayList<CMessage> items)
    {
        super(context, textViewResourceId, items);
        this.items = items;
    }

    public View getView(int position, View convertView, ViewGroup parent)
    {
        View v = convertView;
        if (v == null)
        {
            LayoutInflater vi = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            v = vi.inflate(R.layout.row_club, null);

            CMessage m = items.get(position);
            if (m != null)
            {
                TextView ltt = (TextView) v.findViewById(R.id.ltoptext);
                TextView rtt = (TextView) v.findViewById(R.id.rtoptext);
                TextView lbt = (TextView) v.findViewById(R.id.lbottext);

                if (ltt != null)
                    ltt.setText(m.getTitle());

                if (rtt != null)
                    rtt.setText(m.getLocation());

                if (lbt != null)
                    lbt.setText(m.getCity()   ", CO");

                //if (rbt != null)
                    ; // not used in this list row
            }
        }
        return v;
    }
}
}
  

Как я уже сказал, весь этот код работал нормально, пока я не добавил содержимое диалогового окна прогресса, которое я нашел на другом веб-сайте несколько дней назад.

Я ценю любую помощь, хотя я уже заходил на веб-сайт разработчиков Android, чтобы посмотреть на потоки, обработчики и т.д., И это все больше и больше запутывало меня. Фактические изменения кода были бы потрясающими. 🙂 У меня болит голова после просмотра стольких веб-сайтов сегодня.

Спасибо!

Боб

Ответ №1:

               runOnUiThread(new Runnable() {
            public void run() {
                  m_adapter.notifyDataSetChanged();

                  m_adapter.add(m_messages.get(i));

                 m_ProgressDialog.dismiss();
                 m_adapter.notifyDataSetChanged();


            }
        });
  

Весь ваш код пользовательского интерфейса должен выполняться в runOnUiThread. Ошибка, которую вы получаете, заключается в том, что вы пытаетесь обновить пользовательский интерфейс из другого потока, отличного от потока пользовательского интерфейса activity.

этот поток в вашем коде вызывает проблему.

                private Runnable returnRes = new Runnable()
             {
                public void run()
                {
              if(m_messages != null amp;amp; m_messages.size() > 0)
             {
                m_adapter.notifyDataSetChanged();
                for(int i=0;i<m_messages.size();i  )
                m_adapter.add(m_messages.get(i));
              }
           m_ProgressDialog.dismiss();
           m_adapter.notifyDataSetChanged();
         }
          };
  

Комментарии:

1. @user593424: Спасибо! Это исправлено. И на самом деле я дважды загружал список в своем коде, поэтому я исправил и это. Очень признателен!

Ответ №2:

Используйте AsyncTask. На данный момент почти ни один телефон не использует Android 1.0 / 1.1 (единственным телефоном, который когда-либо поставлялся с Android < 1.5, был HTC G1, и большинство из них были обновлены OTA много лет назад). Если вам действительно нужно поддерживать эти устройства, вы можете использовать идентичные UserTask . Смотрите эту статью для получения дополнительной информации. И посмотрите около дюжины вопросов SO о том, чтобы убедиться, что вы обновили пользовательский интерфейс из основного потока.

Комментарии:

1. Привет, Йони! Спасибо за ваш ответ. Я попробую ваше предложение, как только смогу. Если я смогу заставить это работать, я заменю им существующий код. 🙂