Подтвердить что ты не робот

Погрузчик не может сохранить себя во время определенного изменения конфигурации

Согласно http://developer.android.com/guide/components/loaders.html, одна из приятных вещей о загрузчике заключается в том, что она может сохранять свои данные во время изменения конфигурации.

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

Однако во всех сценариях он не работает.

Я делаю следующий простой пример. Это FragmentActivity, в котором размещается Fragment. Fragment сам принадлежит AsyncTaskLoader.

Следующие 3 сценария работают очень хорошо.

Во время первого запуска (OK)

Создается

1 загрузчик, а loadInBackground выполняется один раз.

Во время простого вращения (OK)

Никакой новый загрузчик не создается и loadInBackground не запускается.

Запускается дочерняя активность и нажата кнопка возврата (ОК)

Никакой новый загрузчик не создается и loadInBackground не запускается.

Однако в следующем сценарии.

Запущена дочерняя активность → Вращение → нажата кнопка "Назад" (Неверно)

В это время вызывается старый загрузчик onReset. Старый загрузчик будет уничтожен. Будет создан новый загрузчик, и новый загрузчик loadInBackground будет запущен снова.

Правильное поведение, которое я ожидаю, не будет создан новый загрузчик.

Код, связанный с загрузчиком, следующий. Я запускаю код под эмулятором Android 4.1.

package com.example.bug;

import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class MainFragment extends Fragment implements LoaderManager.LoaderCallbacks<Integer> {
    private static class IntegerArrayLoader extends AsyncTaskLoader<Integer> {

        private Integer result = null;

        public IntegerArrayLoader(Context context) {
            super(context);
            Log.i("CHEOK", "IntegerArrayLoader created!");
        }

        @Override
        public Integer loadInBackground() {
            Log.i("CHEOK", "Time consuming loadInBackground!");
            this.result = 123456;
            return result;
        }

        /**
         * Handles a request to cancel a load.
         */
        @Override 
        public void onCanceled(Integer integer) {
            super.onCanceled(integer);
        }

        /**
         * Handles a request to stop the Loader.
         * Automatically called by LoaderManager via stopLoading.
         */
        @Override 
        protected void onStopLoading() {
            // Attempt to cancel the current load task if possible.
            cancelLoad();
        }

        /**
         * Handles a request to start the Loader.
         * Automatically called by LoaderManager via startLoading.
         */
        @Override        
        protected void onStartLoading() {
            if (this.result != null) {
                deliverResult(this.result);
            }

            if (takeContentChanged() || this.result == null) {
                forceLoad();
            }
        }

        /**
         * Handles a request to completely reset the Loader.
         * Automatically called by LoaderManager via reset.
         */
        @Override 
        protected void onReset() {
            super.onReset();

            // Ensure the loader is stopped
            onStopLoading();

            // At this point we can release the resources associated with 'apps'
            // if needed.
            this.result = null;
        }        
    }

    @Override
    public Loader<Integer> onCreateLoader(int arg0, Bundle arg1) {
        Log.i("CHEOK", "onCreateLoader being called");
        return new IntegerArrayLoader(this.getActivity());
    }

    @Override
    public void onLoadFinished(Loader<Integer> arg0, Integer arg1) {
        result = arg1;

    }

    @Override
    public void onLoaderReset(Loader<Integer> arg0) {
        // TODO Auto-generated method stub

    }

    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.fragment_main, container, false);
        return v;
    }

    // http://stackoverflow.com/info/11293441/android-loadercallbacks-onloadfinished-called-twice
    @Override
    public void onResume()
    {
        super.onResume();

        if (result == null) {
            // Prepare the loader.  Either re-connect with an existing one,
            // or start a new one.
            getLoaderManager().initLoader(0, null, this);
        } else {
            // Restore from previous state. Perhaps through long pressed home
            // button.
        }
    }    

    private Integer result;
}

Полный исходный код можно загрузить с https://www.dropbox.com/s/n2jee3v7cpwvedv/loader_bug.zip

Это может быть связано с 1 неизвестной ошибкой Android: https://code.google.com/p/android/issues/detail?id=20791&can=5&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars

https://groups.google.com/forum/?fromgroups=#!topic/android-developers/DbKL6PVyhLI

Мне было интересно, есть ли хорошие способы обхода этой ошибки?

4b9b3361

Ответ 1

Мой ответ довольно прямолинейный. Не используйте AsyncTaskLoaders. Из-за нескольких ошибок в отношении AsyncTaskLoaders вы уже знали это.

Хорошая комбинация будет сохраняемой (setRetainInstance (true) в onActivityCreated()) фрагменте с AsyncTask. Работает точно так же. Просто нужно немного перестроить код.


Сообщение от OP

Хотя автор не предоставляет какой-либо пример кода, это наиболее подходящее решение. Я не использую предложенное автором решение. Вместо этого я все еще полагаюсь на AsyncTaskLoader для всей необходимой задачи загрузки. Обходной путь заключается в том, что я буду полагаться на дополнительный сохранившийся фрагмент, чтобы определить, следует ли повторно подключать/создавать загрузчик. Это скелетный код на всей идее. Хорошо работает, насколько я могу судить.

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    ...

    dataRetainedFragment = (DataRetainedFragment)fm.findFragmentByTag(DATE_RETAINED_FRAGMENT);
    // dataRetainedFragment can be null still...
}

@Override
public void onResume() {
    ...
    if (this.data == null) {
        if (dataRetainedFragment != null) {
            // Re-use!
            onLoadFinished(null, dataRetainedFragment);
        } else {
            // Prepare the loader.  Either re-connect with an existing one,
            // or start a new one.
            getLoaderManager().initLoader(0, null, this);
        }
    } else {
    }
}

@Override
public void onLoadFinished(Loader<Data> arg0, Data data) {
    this.data = data;

    if (this.dataRetainedFragment == null) {
        this.dataRetainedFragment = DataRetainedFragment.newInstance(this.data);
        FragmentManager fm = getFragmentManager();
        fm.beginTransaction().add(this.dataRetainedFragment, DATE_RETAINED_FRAGMENT).commitAllowingStateLoss();            
    }

Ответ 2

Попробуйте изменить,

 @Override
public void onResume()
{
    super.onResume();

    if (result == null) {
        // Prepare the loader.  Either re-connect with an existing one,
        // or start a new one.
        getLoaderManager().initLoader(0, null, this);
    } else {
        // Restore from previous state. Perhaps through long pressed home
        // button.
    }
}    

к

 @Override
public void onResume()
{
    super.onResume();

Loader loader = getLoaderManager().getLoader(0); 
if ( loader != null && loader.isReset() ) { 
    getLoaderManager().restartLoader(0, getArguments(), this); 
} else { 
    getLoaderManager().initLoader(0, getArguments(), this); 
} 

}    

Ответ 3

Если вы используете FragmentManager, замените метод фрагмента, эта проблема будет выполнена.

Когда вы заменяете/удаляете фрагмент, фрагмент отсоединяется от действия и, поскольку загрузчики привязаны к активности, загрузчики будут воссозданы во время изменения ориентации.

Попробуйте использовать технику скрыть/показать FragmentManager. Может быть, это поможет вам.

Ответ 4

У меня был успех подклассификации AsyncTaskLoader и внесение нескольких настроек в его методы.

public class FixedAsyncTaskLoader<D> extends AsyncTaskLoader<D> {

    private D result;

    public FixedAsyncTaskLoader(Context context) {
        super(context);
    }

    @Override
    protected void onStartLoading() {
        if (result != null) {
            deliverResult(result);
        } else {
            forceLoad();
        }
    }

    @Override
    public void deliverResult(T data) {
        result = data;

        if (isStarted()) {
            super.deliverResult(result);
        }
    }

    @Override
    protected void onReset() {
        super.onReset();
        onStopLoading();

        result = null;
    }

    @Override
    protected void onStopLoading() {
        cancelLoad();
    }
}