AsyncTask работает на каждой странице ViewPager
У меня есть 3 вкладки, как в учебное пособие по разработке Android
Теперь то, что я хочу сделать, очень просто. Я использую фрагменты на каждой странице. Я хочу показать различный контент из RSS-ленты на каждой странице.
Проблема заключается в том, когда я перехожу на следующую вкладку, в которой запускается AsyncTask (которая находится в onCreateView) предыдущего фрагмента.
Итак, вы начинаете на странице 1, чтобы загрузить контент. Затем, когда вы переходите на страницу 2, снова запускается onCreateView фрагмента страницы. И, очевидно, дает NullException. Дело в том, что он вообще не должен запускать AsyncTask на странице 1.
Я не думаю, что есть какой-то примерный код, если это так, скажите мне, какую часть вам нужно увидеть. Затем я отредактирую свой вопрос.
AsyncTask внутри объекта ListFragment:
public class MyAsyncTask extends AsyncTask<List<String>, Void, List<String>>
{
// List of messages of the rss feed
private List<Message> messages;
private volatile boolean running = true;
@SuppressWarnings("unused")
private WeakReference<NieuwsSectionFragment> fragmentWeakRef;
private MyAsyncTask(NieuwsSectionFragment fragment)
{
this.fragmentWeakRef = new WeakReference<NieuwsSectionFragment>(fragment);
}
@Override
protected void onPreExecute()
{
super.onPreExecute();
mProgress.show();
// progress.setVisibility(View.VISIBLE); //<< set here
}
@Override
protected void onCancelled()
{
Log.w("onCancelled", "now cancelled");
running = false;
}
@Override
protected List<String> doInBackground(List<String>... urls)
{
FeedParser parser = FeedParserFactory.getParser();
messages = parser.parse();
List<String> titles = new ArrayList<String>(messages.size());
for (Message msg : messages)
{
titles.add(msg.getTitle());
// Log.w("doInBackground", msg.getTitle());
}
return titles;
}
@Override
protected void onPostExecute(List<String> result)
{
super.onPostExecute(result);
mProgress.dismiss();
if (result != null)
{
PostData data = null;
listData = new PostData[result.size()];
for (int i = 0; i < result.size(); i++)
{
data = new PostData();
data.postTitle = result.get(i);
data.postThumbUrl = "http://igo.nl/foto/app_thumb/28991-Taxi-vast-na-poging-tot-nemen-van-sluiproute.jpg";
listData[i] = data;
Log.w("onPostExecute", "" + listData[i].postTitle);
}
adapter = new PostItemAdapter (getActivity(), android.R.layout.simple_list_item_1, listData);
setListAdapter(adapter);
adapter.notifyDataSetChanged();
}
}
}
Он вызван внутри метода и этот метод выполняется внутри onCreateView в ListFragment:
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
startNewAsyncTask();
View rootView = inflater.inflate(R.layout.fragment_section_nieuws, container, false);
return rootView;
}
@SuppressWarnings("unchecked")
public void startNewAsyncTask()
{
MyAsyncTask asyncTask = new MyAsyncTask(this);
this.asyncTaskWeakRef = new WeakReference<MyAsyncTask>(asyncTask);
asyncTask.execute();
}
LogCat:
![logcat]()
Ответы
Ответ 1
Попробуйте использовать isAdded()
до onPostExecute()
. isAdded()
возвращает true, если фрагмент в настоящее время добавлен к его активности.
http://developer.android.com/reference/android/app/Fragment.html#isAdded()
@Override
protected void postExecute(){
if(isAdded()){
//perform Display Changes
}
}
Ответ 2
Переместите
startNewAsyncTask();
to onActivityCreated()
Ответ 3
Я предполагаю, что вы используете FragmentPagerAdapter
со своим ViewPager
.
Чтобы включить плавные анимации, ViewPager
по умолчанию сохраняет текущий фрагмент и два соседа в возобновленном состоянии. Так что onCreateView
не лучшее место для запуска AsyncTask
.
Вместо этого вам нужно создать пользовательский интерфейс прослушивателя. fragments
в ViewPager
должен реализовать его и вызвать новый интерфейс из ViewPager's
OnPageChangeListener
.
Посмотрите мой ответ на этот вопрос или вы можете прочитать весь учебник здесь.
Ответ 4
Трудность здесь заключается в том, что после перехода к следующему фрагменту (Fragment 2
), AsyncTask
из Fragment 1
все еще работает. Вы можете подумать: "Хорошо, в таком случае я остановил свой AsyncTask
на onDestroyView
, например", но другой сложной точкой является тот факт, что AsyncTask
не прекратится сразу после вызова . cancel. Итак, проблема: на onPostExecute
вы ссылаетесь на ресурсы, которые не существуют после переключения на Fragment 2
. Давайте посмотрим на ArrayAdapter: 310:
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
Ясно, что context
есть null
. Давайте углубимся и рассмотрим метод getActivity - он просто возвращает значение поля mActivity
.
В таком случае, почему он null
? Поскольку после удаления Fragment
FragmentManager
вызывает initState, который явно удаляет ссылку на объект Activity
:
void initState() {
...
mActivity = null;
...
}
Как защитить от NPE
? Всегда проверяйте результат getActivity
на onPostExecute
для null
.
Ответ 5
Вы получаете это исключение, потому что вы вызываете getActivity()
слишком рано. Вы должны сделать это после onActivityCreated()
(см. эту диаграмму)
Выполнение onCreateView()
в фоновом режиме является прекрасным и фактически является поведением по умолчанию. Дело в том, что ViewPager
оптимизирован для загрузки содержимого соседних невидимых страниц в фоновом режиме для улучшения UX. Вы можете сделать это:
mViewPager.setOffscreenPageLimit(2);
(значение по умолчанию равно 1) для загрузки всех трех страниц одновременно (1 загружается как видимый в настоящее время, а другой 2 - как оптимизация). Или установите его на 0, чтобы отключить это поведение, но это не лучшая идея.
В общем, вы должны обналичить свои загруженные данные и не загружать их снова, сделав методы жизненного цикла фрагмента настолько легкими, насколько это возможно. Предел страницы 2 - штраф на 3 страницы, но если у вас будет, например, 10 страниц, ограничение на 9 слишком велико.
Ответ 6
Если я правильно понял ваш вопрос, я думаю, что вам нужен уникальный контент с каждым фрагментом?
Попробуйте использовать переменные аргумента метода execute. Например:
yourTask.execute(<some-unique-URL>, parameter, one-more-parameter);
Таким образом, вы можете передать уникальный URL-адрес для фрагмента, который вы можете получить.
Я чувствую, что у вас уже есть это. Метод doInBackground имеет список URL-адресов. Вам просто нужно передать эту информацию в методе выполнения и использовать ее в doInBackground.
Надеюсь, это поможет!
Ответ 7
Это нормально, что он запускает AsyncTask
из соседнего Fragments
, так как комбо ViewPager
+ PagerAdapter
работает, загружая текущий, предыдущий и следующий Fragment
.
Вы должны сфокусировать проблему, чтобы не остановить выполнение AsyncTask
, но позволить ей запускать без бросания NullPointerException
.
Внутри onCreateView()
должно быть вызвано следующее:
adapter = new PostItemAdapter (getActivity(), android.R.layout.simple_list_item_1, myList);
setListAdapter(adapter);
И затем onPostExecute()
myList.clear();
myList.addAll(listData);
adapter.notifyDataSetChanged();
Ответ 8
ViewPager создаст представления для фрагментов на соседних страницах и уничтожит представления для фрагментов, которые не смежны с текущей страницей. Таким образом, onCreateView
на стр. 1 вызывается, если вы переходите со страницы1- > page2- > page3- > page2. Вы можете заставить просмотрщик хранить больше страниц в памяти, используя ViewPager.setOffscreenPageLimit
.
fragmentPagerAdapter сохраняет объекты фрагмента. Только взгляды уничтожены. Таким образом, когда viewpage воссоздает представление page1, объект фрагмента тот же. Следовательно, все поля в фрагменте будут сохранены.
Как и в большинстве приложений, где нет данных в реальном времени, не требуется загружать данные каждый раз при создании представления фрагмента, вы можете сохранить данные в объекте фрагмента после загрузки. Затем перед запуском AsyncTask в onCreateView/onActivityCreated проверьте, были ли данные ранее загружены или нет.
class PageFragment {
private List<String> mData;
...
void onActivityCreated() {
if (data == null) { // OR if the data is expired
startAsyncTask();
} else {
updateViews();
}
}
void updateViews() {
// Display mData in views
}
class LoadDataTask extends AsyncTask<List<String>, ..., ...> {
...
void onPostExecute(List<String> result) {
PageFragment.this.mData = result;
PageFragment.this.updateViews();
}
}
Я рекомендую использовать загрузчики для загрузки данных для фрагмента. Для вашей цели вы можете настроить загрузчик для загрузки данных только один раз.
Это отличный учебник по загрузчикам.
http://www.androiddesignpatterns.com/2012/07/loaders-and-loadermanager-background.html
В учебнике загрузчик настроен на немедленное возвращение предыдущих данных, если он доступен, и затем извлекать данные в фоновом режиме и возвращать их после завершения набора. Таким образом, пользовательский интерфейс будет обновляться после загрузки свежих данных, но в то же время он сначала отображает предыдущие данные во время загрузки.
Ответ 9
Вы можете использовать другое действие - это действие будет выполняться как асинхронное, а затем перейти к активности, связанной с фрагментом. Таким образом, он должен звонить только один раз.
Если вам нужно обновить интерфейс Fragment с помощью этой AsyncTask, используйте статический метод для вызова AsyncTask.