Урок 91. AsyncTask. Поворот экрана

Обсуждение уроков
Аватара пользователя
Fry
Сообщения: 183
Зарегистрирован: 07 дек 2013, 22:07

Re: Урок 91. AsyncTask. Поворот экрана

Сообщение Fry » 16 янв 2015, 13:00

Mikhail_dev писал(а): А собственно как нотификейшн связан с UI, кроме того, что он может открыть какую-то активность?
Это я привел довод KamiSempai - доступ к UI из сервиса нужен в том числе для доступа к нотификейшн.
UI поток = обычный поток. Его просто назвали UI потоком. Он ничем не отличается от других. Я так понимаю там жестко прописали мол если с рутового потока запускать сетевую задачу, то писать что "NetworkOnMainThreadException". Так что да, сервисы имеют разные потоки. А GUI потоков не существует.
А Exception возникает только в случае обращения к сети, или вообще при любой задаче, которая надолго держит UI?

Например, TimeUnit.SECONDS.sleep(1000) в UI у меня исключение не вызывает.
Да. Используйте IntentService, он запускает задачи в отдельном потоке (не процессе).
Спасибо за наводку! Сейчас читаю, похоже то что нужно.
Это не совсем прозрачная тема. Первое - это то, что памяти выделяется больше (столько же, сколько и для нового приложения).
А на жизненный цикл сервиса его выделение в отдельный процесс как-то влияет?
Arbeit macht Fry

Аватара пользователя
Elek
Сообщения: 55
Зарегистрирован: 29 май 2012, 09:57

Re: Урок 91. AsyncTask. Поворот экрана

Сообщение Elek » 14 мар 2015, 21:01

Привет, как поддерживать поворот экрана, когда asyncTask был вызван из фрагмента.
Таск тянет данные из инета и передает их во фрагмент.

rePlay
Сообщения: 7
Зарегистрирован: 11 июл 2015, 17:54

Re: Урок 91. AsyncTask. Поворот экрана

Сообщение rePlay » 01 авг 2015, 10:45

Подскажите, зачем нужен unlink? Мы же все равно поменяем ссылку переменной activity(класс MyTask) в новом активити, и она уже не будет ссылаться на старый активити.
И ещё интересный момент: после уничтожения активити (OnDestroy) таск может ещё работать с активити и при этом не выпадает никаких исключений. Почему?
И по-моему, неправильно передавать старый таск в новое активити. Лучше просто сохранить состояние в тот же preference и в OnCreate нового активити считывать.
Тут ещё после закрытия приложения этот таск будет продолжать работать, поэтому MyTask нужно объявить булеву переменную для управления отменой задачи и еще несколько строчек кода в методах onRetainNonConfigurationInstance, OnDestroy, link.

Аватара пользователя
Kirill
Сообщения: 19
Зарегистрирован: 09 сен 2015, 13:53

Re: Урок 91. AsyncTask. Поворот экрана

Сообщение Kirill » 18 сен 2015, 21:16

Урок 91 последний в группе уроков про AsyncTask и я тут накидал небольшой пример с комментариями в коде в котором находятся все задачи которые мы прошли, может кому пригодится, или кто-то посмотрит как можно реализровать тот или иной функционал. В общем все то, что мы прошли, ничего нового:

1. AsyncTask выполняется в отдельном потоке и эмулирует "тяжелый процесс", объект MyTask сохраняется и передается в новый Актвити при уничтожении старого. То есть отдельный поток MyTask не теряется. Используется новый метод onRetainCustomNonConfigurationInstance() вместо устаревшого onRetainNonConfigurationInstance()
2. MyTask класс наследует AsyncTask и объявлен как вложенный статический, чтобы избежать внутренней ссылки на внешний класс (в отличии от внутреннего).
3. в MyTask есть методы link() & unlink() чтобы обнулять ссылку на объект Актвити перед уничтожением старого активити (в случае смены ориентации экрана) и подключать ее после создания нового активити.
3. класс MyTask выполняет onPreExecute() и onPostExecute(), а так же onProgressUpdate(), который постонно передает в UI обновление хода процесса.*
4. * - из-за того что doInBackground() бежит в отдельном потоке и постоянно обращается к UI с помощью метода publishProgress() то иногда при повороте экрана происходит ситуация когда unlink уже обнулил ссылку на активити, а link еще не присвоил ее и в этот момент publishProgress() обращается к activity который равен null и соответственно падает с NullPointerException. Для этого сделал небольшой "буфер" в который складываются данные пока activity не будет назначен.
5. Реализована кнопка Cancel с досрочной остановкой потока MyTask
6. Кнопка Start неактивна пока процесс работает.
7. Результат который постоянно передается в TextView не обнуляется при поворотах экрана и выглядит все красиво. Используется onSaveInstanceState() и onRestoreInstanceState()

в коде подробные комментарии. За указания на ошибки или предложения лучшего решения буду на самом деле очень благодарен

layout/main.xml
[syntax=xml]
<RelativeLayout 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:padding="10dp"
tools:context=".MainActivity">
<Button
android:id="@+id/btnCancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/btnCancel" />
<Button
android:layout_toEndOf="@id/btnCancel"
android:id="@+id/btnStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/btnStart" />

<ProgressBar
android:id="@+id/pb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="@id/btnStart"
/>

<TextView
android:id="@+id/tvStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/btnStart"
android:text="Status: " />

<TextView
android:id="@+id/tvResult"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/tvStatus"
android:text="..."
android:textSize="25sp" />

</RelativeLayout>
[/syntax]


MainActivity.java:
[syntax=java5]
package com.mypackage.sa091_asynctask_5_retainactivityandmytask;

import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;

import java.util.concurrent.TimeUnit;

public class MainActivity extends AppCompatActivity {
private static String LOG_TAG = "myLogs";

private TextView tvResult, tvStatus;
private Button btnStart, btnCancel;
private ProgressBar pb;

private MyTask task;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

tvResult = (TextView) findViewById(R.id.tvResult);
tvStatus = (TextView) findViewById(R.id.tvStatus);
btnStart = (Button) findViewById(R.id.btnStart);
btnCancel = (Button) findViewById(R.id.btnCancel);

pb = (ProgressBar) findViewById(R.id.pb);
pb.setVisibility(View.GONE);

/*
Восстанавливаем сохраненный объект MyTask при создании нового Активити:
*/
task = (MyTask) getLastCustomNonConfigurationInstance();
/*
Если Активити создается первый раз значит создадим MyTask конструктором:
*/
if (task == null) {
task = new MyTask();
}
/* Присваиваем текущий объект Активити в поле объекта MyTask чтобы там был доступ к View элементам:
*/
task.link(this);

/*
Определяем с каким статусом отрисовывать кнопку Start, если AsyncTask поток выполняется
тогда она должна быть сразу неактивна при появлении.
*/
btnStart.setEnabled(isStartable());

btnStart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
/* Делаем кнопку неактивной */
btnStart.setEnabled(false);
tvResult.setText("");

/* в кнопке Start еще раз создаем объект MyTask и добавляем в него текущий активити.
Делается это чтобы повторно не стартовать уже отработавший объект MyTask (иначе Exception)
*/
task = new MyTask();
task.link(MainActivity.this);

Log.d(LOG_TAG, "---btnStart: MainActivity hash: " + MainActivity.this.hashCode() + " MyTask hash: " + task.hashCode());

task.execute("android developer 80 level ");
}
});

btnCancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (task != null) task.cancel(false);
}
});
}

/*
Готов к старту: если объект MyTask не равен null
и при этом он или Canceled или не RUNNING (значит PENDING или FINISHED)
*/
private boolean isStartable() {
if (task != null && (task.isCancelled() || !task.getStatus().equals(AsyncTask.Status.RUNNING))) {
return true;
} else {
return false;
}
}

/*
Сохраняем текст из tvResult и tvStatus для переноса в новое активити
*/
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("tvResult", tvResult.getText().toString());
outState.putString("tvStatus", tvStatus.getText().toString());
}

/*
Вставляем сохраненный текст из tvResult и tvStatus в новое активити из старого
*/
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
tvResult.setText(savedInstanceState.getString("tvResult", ""));
tvStatus.setText(savedInstanceState.getString("tvStatus", ""));
}

/*
Перед уничтожением Активити вызывается этот метод, который сохраняет объект MyTask с уже работающим потоком.
Чтобы он не держал старое Активити - убираем у него ссылку на объект Активити.
*/
@Override
public Object onRetainCustomNonConfigurationInstance() {
if (task != null) {
task.unlink();
}
return task;
}

/*
Вложенный класс MyTask сделали статическим чтобы он не содержал скрытую ссылку на внешний класс Активити
*/
static class MyTask extends AsyncTask<String, Character, String> {

MainActivity activity;
StringBuffer sb = new StringBuffer();

public void link(MainActivity activity) {
this.activity = activity;
}


public void unlink() {
activity = null;
}


@Override
public void onPreExecute() {
activity.tvStatus.setText("Status: Begin");
activity.pb.setVisibility(View.VISIBLE);
}

public String doInBackground(String[] strings) {
int i = 0;
for (char c : strings[0].toCharArray()) {
try {
i++;
if (isCancelled()) return "";
TimeUnit.MILLISECONDS.sleep(200);
publishProgress(c);

} catch (InterruptedException e) {
e.printStackTrace();
}
}
return i + " chars successfully printed";
}

/*
Если новый (AsynkTask) поток продолжает работать и в этот момент пересоздастся Активити, то в
какой-то короткий момент объект класса MyTask (поток AsynkTask) не будет иметь ссылку на Активити.
Это происходит в момент когда unlink уже отработал, но link еще не сработал (В Main потоке),
а в AsynkTask потоке произошло обращение к эелменту в активити (например вызовом publishProgress()
который вызывает onProgressUpdate()) - тогда сработает исключение NullPointerException.
Для этого в методе onProgressUpdate() делаем буфер куда будет складываться промежуточная
информация в случае activity == null.
Когда активити вновь будет присвоен - этот буффер обновит элементы в актвити и будет очищен.
*/
@Override
public void onProgressUpdate(Character[] characters) {
if (activity == null) {
sb.append(characters[0]);
} else {

if (sb.length() > 0) {
activity.tvResult.append(sb.toString());
sb.setLength(0);
}
activity.tvResult.append(characters[0].toString());

Log.d(LOG_TAG, "---doInBackground: " + characters[0] + " MainActivity hash: " + activity.hashCode() +
" MyTask hash: " + this.hashCode());
}


}

/*
onPostExecute() выполняется когда doInBackground закончил работу.
Если к этому моменту активити так и не был назначен - вываливаем буфер здесь.
*/
@Override
public void onPostExecute(String result) {
if (activity == null) return;

activity.tvStatus.setText("Status: End. " + result);
activity.pb.setVisibility(View.GONE);
if (sb.length() > 0) {
activity.tvResult.append(sb.toString());
}
/* Делаем кнопку активной */
activity.btnStart.setEnabled(true);
}

/*
если был вызван метод cancel() тогда onCancelled() выполняется вместо onPostExecute()
поэтому здесь делаем то же что и в onPostExecute - вываливаем буфер во View элементы
и делаем кнопку Start вновь активной.
*/
@Override
protected void onCancelled() {
super.onCancelled();
if (activity == null) return;

activity.tvStatus.setText("Status: Canceled");
activity.pb.setVisibility(View.GONE);
if (sb.length() > 0) {
activity.tvResult.append(sb.toString());
}

/* Делаем кнопку активной */
activity.btnStart.setEnabled(true);
}
}
}

[/syntax]

Аватара пользователя
Mikhail_dev
Сообщения: 2386
Зарегистрирован: 09 янв 2012, 14:45
Откуда: Самара

Re: Урок 91. AsyncTask. Поворот экрана

Сообщение Mikhail_dev » 18 сен 2015, 23:26

Стесняюсь спросить, но знаете ли вы про Loaders? И если да, то что вы сделали кардинально нового, в отличии от стандартных лодеров (к примеру AsyncTaskLoader, название которого как бы намекает...) ?

Аватара пользователя
doter.ua
Сообщения: 1106
Зарегистрирован: 23 ноя 2013, 16:08
Откуда: Ukraine

Re: Урок 91. AsyncTask. Поворот экрана

Сообщение doter.ua » 19 сен 2015, 02:32

Mikhail_dev писал(а):Стесняюсь спросить, но знаете ли вы про Loaders? И если да, то что вы сделали кардинально нового, в отличии от стандартных лодеров (к примеру AsyncTaskLoader, название которого как бы намекает...) ?
Поймал рофляночку с тигра.
Семь раз отмерь - поставь студию.
Эклипс не студия, ошибка вылетит - не исправишь.
Скажи мне кто твой друг, и оба поставили студию.
Студия - свет, а эклипс - тьма.

Аватара пользователя
Kirill
Сообщения: 19
Зарегистрирован: 09 сен 2015, 13:53

Re: Урок 91. AsyncTask. Поворот экрана

Сообщение Kirill » 19 сен 2015, 11:05

Mikhail_dev писал(а):Стесняюсь спросить, но знаете ли вы про Loaders?
пока нет, я иду с первого урока. Loader будет на 135
Mikhail_dev писал(а):И если да, то что вы сделали кардинально нового, в отличии от стандартных лодеров (к примеру AsyncTaskLoader, название которого как бы намекает...) ?
Kirill писал(а): накидал небольшой пример с комментариями в коде в котором находятся все задачи которые мы прошли, может кому пригодится, или кто-то посмотрит как можно реализровать тот или иной функционал. В общем все то, что мы прошли, ничего нового:

alex1
Сообщения: 12
Зарегистрирован: 22 июл 2015, 13:45

Re: Урок 91. AsyncTask. Поворот экрана

Сообщение alex1 » 27 окт 2015, 12:16

Есть Activity с ExpandableListView.
Хочу организовать загрузку данных в этот ExpandableListView из базы данных не в UI.
Изпользую AsyncTask, как в уроке.
Но есть проблема.
Кратко:
Имеется объект класса для загрузки данных:

Код: Выделить всё

public class DataBaseHelper extends SQLitOpenHelper {
...
 public DataBaseHelper(Context context)
 {
  super(context,...);
 }
...
}
Далее в doInBackground
используются объекты:

Код: Выделить всё

  DataBaseHelper dbh = new DataBaseHelper(activity);
  SQLiteDatabase db;

...
  db = dbh.getReadableDatabase();
Как только переворачиваю эмулятор - ес-но вываливается ошибка на вышеприведённой строчке.
Т.к. активность уничтожена, соот-но её контекст уже того.
Вопрос - как быть?
1. Сделать загрузку в сервисе - но чё-то кажется что ради загрузки данных в ExpanableListView это сильно жирно.
2. Смотреть в сторону AsyncTaskLoader (ещё не читал, но как понял из обсуждений, если передернуть экран - то этот лоадер может задачу убить и запустить заново).
Но тоже сомнения - если будут экран туда сюда дергать - оно так и не загрузится. Хотя, может, что-то в этом есть.
3. Какой-то иной вариант?
В идеале бы хотелось, чтобы AsyncTask загрузил все данные независимо от наличия/отсутствия активности, а потом, как активность будет доступна - получила бы сама данные методом get и всё (так и сделал в другой активности, где идёт загрузка текстового файла, но тут для базы данных нужен контекст, будь он неладен :) )

alex1
Сообщения: 12
Зарегистрирован: 22 июл 2015, 13:45

Re: Урок 91. AsyncTask. Поворот экрана

Сообщение alex1 » 27 окт 2015, 14:20

Вопрос отменяется!
Всё решилось вызовом getApplicationContext.

IgorYanovich
Сообщения: 1
Зарегистрирован: 30 дек 2015, 20:06

Re: Урок 91. AsyncTask. Поворот экрана

Сообщение IgorYanovich » 30 дек 2015, 20:16

Напишу здесь свой вариант решения. В методе onProgressUpdate мы ставим проверку activity == null. Если activity - не null, то без проблем меняем textView. Если же activity - null, то текст, который мы хотели прописать в TextView, мы сохраняем в какую-нить свою переменную класса MyTask. А новое Activity, когда получает MyTask, достает данные из этой переменной и сама помещает их в TextView.
А что будет если мы проверили ссылку на activity она не равна null.
Затем мы начинаем что-то делать с визуальными объектами activity и вот тут пропадёт ссылка на сам activity.
Или в момент времени когда мы проверяли activity на null в нём ещё находилась ссылка, а потом мы поворачиваем экран ссылка пропадает, а мы начнём что-то менять и тут вылетит ошибка, что нет указателя или ссылки (не суть) на activity.
Вероятность возникновения этого всего конечно очень маленькая, но по моему она есть :(

AIRIZZIO
Сообщения: 4
Зарегистрирован: 09 янв 2016, 10:10

Re: Урок 91. AsyncTask. Поворот экрана

Сообщение AIRIZZIO » 09 янв 2016, 10:12

Такой вопрос, а зачем вообще было всё это городить, если можно просто в манифесте прописать в активити configChanges="orientation|screenSize" и активность не будет убиваться при повороте экрана? Так ведь намного проще и эффективнее.

Segen
Сообщения: 2
Зарегистрирован: 20 фев 2016, 11:54

Re: Урок 91. AsyncTask. Поворот экрана

Сообщение Segen » 20 фев 2016, 12:04

AIRIZZIO писал(а):Такой вопрос, а зачем вообще было всё это городить, если можно просто в манифесте прописать в активити configChanges="orientation|screenSize" и активность не будет убиваться при повороте экрана? Так ведь намного проще и эффективнее.
Вот здесь - http://stackoverflow.com/questions/7818 ... rientation не рекомендуют без особой на то необходимости использовать configChanges="orientation|screenSize", т.к. активность может уничтожаться не только при смене ориентации, но и, например, при смене языка и в ситуации когда заканчивается память в устройстве.

sharks_hockey
Сообщения: 6
Зарегистрирован: 07 июн 2016, 18:07

Re: Урок 91. AsyncTask. Поворот экрана

Сообщение sharks_hockey » 26 июн 2016, 01:18

Mikhail_dev писал(а):Стесняюсь спросить, но знаете ли вы про Loaders? И если да, то что вы сделали кардинально нового, в отличии от стандартных лодеров (к примеру AsyncTaskLoader, название которого как бы намекает...) ?
Как в этом AsyncTaskLoader сделать так, чтобы новый таск не запускался каждый раз при повороте экрана?

almalchikov
Сообщения: 1
Зарегистрирован: 18 апр 2018, 07:07

Re: Урок 91. AsyncTask. Поворот экрана

Сообщение almalchikov » 18 апр 2018, 07:08

Скопировал все как в уроке, в итоге:

Error:(32, 19) error: onRetainNonConfigurationInstance() in MainActivity cannot override onRetainNonConfigurationInstance() in FragmentActivity
overridden method is final
Error:Execution failed for task ':app:compileDebugJavaWithJavac'.
> Compilation failed; see the compiler error output for details.

Ответить