Android AsyncTask ProgressDialog меняет разные конфигурации

Мне удалось использовать Asynctask с неопределенным индикатором выполнения во время поворота экрана. Асинтаск запускается только один раз, полоса прогресса восстанавливается по очереди, как я и хотел.

У меня разные макеты для портретной и макетной ориентаций. Макеты включают кнопку и текстовое представление. Размер и цвет текста textview в layout-land отличаются. И ориентация альбомная.

Проблема в том, что когда я поворачиваю экран во время выполнения асинхронной задачи, он не может обновить текстовое представление в методе onPostExecute. Когда я поворачиваюсь, он воссоздает действие с файлом layout-land. Но почему я не могу обновить свой Textview?

layout\activity_main.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" 
    android:orientation="vertical">

    <Button 
        android:text="Start"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="startClicked"
        />
    <TextView
        android:id="@+id/hello"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world"
        tools:context=".MainActivity" />

</LinearLayout>

layout-land\activity_main.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" 
    android:orientation="horizontal">

    <Button 
        android:text="Start"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="startClicked"
        />
    <TextView
        android:textSize="36dp"
        android:textColor="#ff0000"
        android:id="@+id/hello"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world"
        tools:context=".MainActivity" />

</LinearLayout>

MainActivity.java:

package com.example.asynctaskconfig;

import android.app.Activity;
import android.app.ProgressDialog;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

    public class MainActivity extends Activity {
        static String data;
        static ProgressDialog pd;
        MyAsyncTask task;
        TextView tv;

        @Override
        public void onCreate(Bundle icicle) {
            super.onCreate(icicle);
            setContentView(R.layout.activity_main);

            tv = (TextView) findViewById(R.id.hello);

            if (getLastNonConfigurationInstance() != null) {
                task = (MyAsyncTask) getLastNonConfigurationInstance();
                if (task != null) {
                    if (!(task.getStatus().equals(AsyncTask.Status.FINISHED))) {
                        showProgressDialog();
                    }
                }
            }
        }

        @Override
        public Object onRetainNonConfigurationInstance() {
            if (pd != null)
                pd.dismiss();
            if (task != null)
                return (task);
            return super.onRetainNonConfigurationInstance();
        }


        private void showProgressDialog() {
            if (pd == null || !pd.isShowing()) {
                pd = new ProgressDialog(MainActivity.this);
                pd.setIndeterminate(true);
                pd.setTitle("DOING..");
                pd.show();
            }
        }

        private void dismissProgressDialog() {
            if (pd != null && pd.isShowing())
                pd.dismiss();
        }

        public class MyAsyncTask extends AsyncTask<String, Void, Boolean> {
            @Override
            protected void onPreExecute() {
                showProgressDialog();
            }

            @Override
            protected Boolean doInBackground(String... args) {
                try {
                    Thread.sleep(5000);
                    data = "result from ws";
                } catch (Exception e) {
                    return true;
                }
                return true;
            }

            protected void onPostExecute(Boolean result) {
                if (result) {
                    dismissProgressDialog();
                    updateUI();
                }
            }
        }

        private void updateUI() {
            tv.setText(data == null ? "null" : data);
        }

        public void startClicked(View target) {
            task = new MyAsyncTask();
            task.execute("start");
        }
    }

person Timuçin    schedule 12.09.2012    source источник


Ответы (4)


Я сделал следующее:

1- Добавить android:freezesText="true" ко всем моим TextView. Это позволяет TextViews сохранять свои состояния при изменении конфигурации.

2- Сделайте свою AsyncTask static inner class.

3- Измените AsyncTask, чтобы сохранить ссылку на действие, в котором оно находится. Таким образом, AsyncTask может получить доступ к виджетам пользовательского интерфейса действия через эту ссылку.

4- Здесь важно сохранить действительную ссылку на активность во время поворотов экрана. Итак, переопределите метод onDestroy и отвяжите Activity от AsyncTask. Таким образом, задача не сохранит старую (умершую) активность.

5- В onRetainNonConfigurationInstance, если задача все еще выполняется, обновите ссылку на ее действие с помощью текущего действия, чтобы оно было успешно привязано к новому действию.

6- Наконец, в onPostExecuteMethod получите доступ к элементам пользовательского интерфейса действия через ссылку на действие.

Полное рабочее решение:

layout\activity_main.xml :

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" 
    android:orientation="vertical">

    <Button 
        android:text="Start"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="startClicked"
        />
    <TextView
        android:freezesText="true"
        android:id="@+id/hello"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world"
        tools:context=".MainActivity" />
</LinearLayout>

макет-земля\activity_main.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" 
    android:orientation="horizontal">

    <Button 
        android:text="Start"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="startClicked"
        />
    <TextView
        android:freezesText="true"
        android:textSize="36dp"
        android:textColor="#ff0000"
        android:id="@+id/hello"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world"
        tools:context=".MainActivity" />
</LinearLayout>

Основная активность.java:

package com.example.asynctaskconfig;

import android.app.Activity;
import android.app.ProgressDialog;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends Activity {
    static ProgressDialog pd;
    MyAsyncTask task;
    TextView tv;

    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        setContentView(R.layout.activity_main);

        tv = (TextView) findViewById(R.id.hello);

        if (getLastNonConfigurationInstance() != null) {
            task = (MyAsyncTask) getLastNonConfigurationInstance();
            if (task != null) {
                task.activity = this;
                if (!(task.getStatus().equals(AsyncTask.Status.FINISHED))) {
                    showProgressDialog();
                }
            }
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (task != null) {
            task.activity = null;
        }
    }

    @Override
    public Object onRetainNonConfigurationInstance() {
        if (pd != null)
            pd.dismiss();
        if (task != null)
            return (task);
        return super.onRetainNonConfigurationInstance();
    }

    private void showProgressDialog() {
        if (pd == null || !pd.isShowing()) {
            pd = new ProgressDialog(MainActivity.this);
            pd.setIndeterminate(true);
            pd.setTitle("DOING..");
            pd.show();
        }
    }

    private void dismissProgressDialog() {
        if (pd != null && pd.isShowing())
            pd.dismiss();
    }

    static class MyAsyncTask extends AsyncTask<String, Void, String> {
        MainActivity activity;

        public MyAsyncTask(MainActivity activity) {
            this.activity = activity;
        }

        @Override
        protected void onPreExecute() {
            activity.showProgressDialog();
        }
        @Override
        protected String doInBackground(String... args) {
            try {
                Thread.sleep(8000);
                return "data from ws";
            } catch (Exception e) {
                return "exception";
            }
        }

        protected void onPostExecute(String result) {
            activity.dismissProgressDialog();
            activity.tv.setText(result == null ? "null" : result);
        }
    }

    public void startClicked(View target) {
        task = new MyAsyncTask(this);
        task.execute("start");
    }
}
person Timuçin    schedule 12.09.2012

Проблема в вашем случае заключается в том, что TextView, который вы пытаетесь изменить, больше не является TextView, видимым на экране. Ротация заставила Android отказаться от старой Activity и создать новую — со всеми представлениями в xml-файле. Таким образом, ваш TextView 'tv' по-прежнему является частью старой активности, изменения просто ничего не сделают.

Теперь самый простой способ получить желаемое поведение — просто снова найти текстовое представление, то есть снова использовать «findViewById» в вашем методе updateUI, и все будет в порядке!

person Matthias Schicker    schedule 12.09.2012
comment
findViewById по-прежнему будет находить TextView старой Activity, потому что его представления завышены в setContentView(). - person ekholm; 12.09.2012

В этом случае вы, вероятно, не хотите, чтобы активность воссоздавалась при изменении ориентации. Вы можете обрабатывать изменения ориентации в своей деятельности, добавив это в свою активность в своем манифесте:

android:configChanges="keyboardHidden|orientation"

Затем переопределите onConfigurationChanged:

@Override
public void onConfigurationChanged(Configuration newConfig) {
  super.onConfigurationChanged(newConfig);
  setContentView(R.layout.activity_main);
  tv = (TextView) findViewById(R.id.hello);
}
person ekholm    schedule 12.09.2012
comment
Нет, я хочу, чтобы он каждый раз создавался заново. Я использую layout-land\activity_main.xml именно для этой цели. - person Timuçin; 12.09.2012
comment
да, но правильное представление будет завышено при вызове setContentView - person ekholm; 12.09.2012
comment
нет? Вы убедились, что активность не воссоздается и что tv получает новое значение? - person ekholm; 12.09.2012
comment
Тогда это проблема. Вы можете найти подсказки в этой ветке: onconfigurationchanged-not-be-call - person ekholm; 12.09.2012
comment
в порядке. это обновляет текстовое представление с текстовым результатом от ws, но когда я поворачиваю его обратно, макет восстанавливается до исходного состояния, в котором содержимое текстового представления является привет, мир. Что делать, когда мы снова переходим от пейзажа к портрету. - person Timuçin; 12.09.2012
comment
Вы можете просто сохранить текущую текстовую строку в качестве поля в действии и устанавливать ее в TextView каждый раз, когда она раздувается. Или, для более надежного решения, используйте SharedPreferences или поставщика контента. - person ekholm; 13.09.2012

Попробуйте это вместо этого:

private void updateUI() {
    final TextView tv = (TextView) findViewById(R.id.hello);
    tv.setText(data == null ? "null" : data);
}

Если это не удается, возможно, это проблема времени? То есть может ли это быть задача, выполненная во время смены ориентации? На всякий случай вы можете изменить свой метод onCreate, добавив вызов updateUI(), если задача завершена:

 @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        setContentView(R.layout.activity_main);

        tv = (TextView) findViewById(R.id.hello);

        if (getLastNonConfigurationInstance() != null) {
            task = (MyAsyncTask) getLastNonConfigurationInstance();
            if (task != null) {
                if (!(task.getStatus().equals(AsyncTask.Status.FINISHED))) {
                    showProgressDialog();
                } else
                    updateUI();
            }
        }
    }
person 323go    schedule 12.09.2012
comment
Это не работает, потому что tv по-прежнему будет TextView старой активности. - person ekholm; 12.09.2012
comment
да. но каково решение? Почему мы не можем получить доступ к новому телевизору? - person Timuçin; 12.09.2012
comment
@ekholm, этого не должно быть, потому что onCreate вызывается при изменении ориентации и должен увеличивать макет, зависящий от ориентации. Тим, не могли бы вы добавить несколько точек останова в onCreate, чтобы убедиться, что он действительно вызывается и TextView разрешается. В целях тестирования переименуйте один из TextViews в tvHelloLand и посмотрите, сможет ли FindViewById его найти. Если это так, то некрасивым решением будет сначала найти tvHello, а если он нулевой, найти tvHelloLand. Теоретически ваше решение должно работать, так как оно находит TextView после раздувания макета. - person 323go; 12.09.2012
comment
все равно не повезло. Textview увеличен в 2 раза, во второй раз, думаю, он получает его из ландшафтной раскладки. но все еще не обновляет свой текст. - person Timuçin; 12.09.2012
comment
Хорошо, теперь мы находимся в режиме отладки ;) Добавьте немного Log.d и создайте дамп содержимого данных при обновлении, чтобы вы знали, что на самом деле что-то меняете... и/или устанавливаете известный измененный текст. (т.е. static int count = 1; tv.setText(String.format(Мой текст: %d, count++));) - person 323go; 12.09.2012