Пользовательский загрузчик Android, LoaderManagerImpl.LoaderInfo.callOnLoadFinished вызывается только один раз

Я пытаюсь реализовать свой собственный Loader для Android, чтобы иметь возможность использовать преимущества LoaderManager в своем приложении (отделение загрузки данных от жизненного цикла моих Activities). strong> и Фрагменты).

Сначала я подумал о создании подклассов от AsyncLoader, но на самом деле мне не нужно, чтобы загрузка данных помещалась в AsyncTask (это то, что AsyncLoader делает под капотом). Базовые данные в моем случае - это данные/образцы, поступающие из собственной библиотеки. Эти образцы кэшируются в библиотеке, которая полностью асинхронна с моими приложениями, поэтому нет необходимости перебирать этот собственный кеш в отдельном потоке.

Вот примерно так выглядит мой пользовательский Loader:

public class TestSampleListLoader extends Loader<List<TestSample>> {
    private static final String TAG = "TestSampleListLoader";

    private NativeLibFactory mNativeLib = null;
    private SampleReader<TestSample> mTestSampleReader;
    private TestSampleListener mTestSampleSampleListener;
    private List<TestSample> mTestSampleList;

    public TestSampleListLoader(Context context) {
        super(context);
        Log.i(TAG, "TestSampleListLoader constructor!!!");
    }

    @Override
    public void deliverResult(List<TestSample> testSamples) {
        Log.i(TAG, "deliverResult(data) " + testSamples.size());
        super.deliverResult(testSamples);
    }

    @Override
    public boolean isStarted() {
        Log.i(TAG, "isStarted()");
        return super.isStarted();
    }

    @Override
    protected void onStartLoading() {
        Log.i(TAG, "onStartLoading()");
        super.onStartLoading();

        mTestSampleList = new ArrayList<TestSample>();

        if (null == mNativeLib) {
            initNativeLib();
        }
    }

    @Override
    public void forceLoad() {
        Log.i(TAG, "forceLoad()");
        super.forceLoad();
    }

    @Override
    protected void onForceLoad() {
        Log.i(TAG, "onForceLoad()");
        super.onForceLoad();

        mTestSampleList.clear();

        for (TestSample testSample : mTestSampleReader) {
            mTestSampleList.add(testSample);
        }

        Log.i(TAG, "forceLoad(deliverResult) " + mTestSampleList.size());
        deliverResult(mTestSampleList);
    }

    @Override
    protected void onReset() {
        Log.i(TAG, "onReset()");

        mTestSampleList.clear();

        if (null != mTestSampleReader) {
            mTestSampleReader.close();
            mTestSampleReader = null;
        }
        if (null != mNativeLib) {
            mNativeLib.close();
            mNativeLib = null;
        }

        super.onReset();
    }

    @Override
    public void onContentChanged() {
        Log.i(TAG, "onContentChanged()");
        super.onContentChanged();
    }

    private void initNativeLib() {
        Log.i(TAG, "initNativeLib()");
        NativeLibAndroid.initNativeLib(getContext().getApplicationContext(), new NativeLibConnectionListener() {
            @Override
            public void onNativeLibReady(NativeLibFactory NativeLib) {
                Log.d(TAG, "onNativeLibReady!!!");
                mNativeLib = NativeLib;

                mTestSampleSampleListener = new TestSampleListener();
                mTestSampleReader = mNativeLib.createSampleReader(TestSample.class, mTestSampleSampleListener);
            }
        });
    }

    public class TestSampleListener implements SampleReaderListener {
        @Override
        public void onUpdate() {
            Log.i(TAG, "TestSampleListener.onUpdate() => onContentChanged");
            TestSampleListLoader.this.onContentChanged();
        }
    }
}

Я использую Fragment для отображения собственных образцов даты с помощью ArrayAdapter:

public class TestSampleListFragment extends ListFragment implements LoaderManager.LoaderCallbacks<List<TestSample>> {
    private static final String TAG = "TestSampleListFragment";
    private static final boolean DEBUG = true;

    // The Loader's id (this id is specific to the ListFragment's LoaderManager)
    private static final int LOADER_ID = 1;

    // We use a custom ArrayAdapter to bind application info to the ListView.
    private TestSampleListAdapter mTestReaderAdapter;

    @Override
    public void onActivityCreated(Bundle savedInstanceSample) {
        super.onActivityCreated(savedInstanceSample);
        Log.i(TAG, "onActivityCreated()");

        mTestReaderAdapter = new TestSampleListAdapter(getActivity());
        setEmptyText("No testSamples");
        setListAdapter(mTestReaderAdapter);
        setListShown(false);

        if (DEBUG) {
            Log.i(TAG, "Calling initLoader()!");
            if (getLoaderManager().getLoader(LOADER_ID) == null) {
                Log.i(TAG, "Initializing the new Loader...");
            } else {
                Log.i(TAG, "Reconnecting with existing Loader (id '1')...");
            }
        }

        // Initialize a Loader with id '1'. If the Loader with this id already
        // exists, then the LoaderManager will reuse the existing Loader.
        getLoaderManager().initLoader(LOADER_ID, null, this);
    }

    /**********************/
    /** LOADER CALLBACKS **/
    /**********************/

    @Override
    public Loader<List<TestSample>> onCreateLoader(int id, Bundle args) {
        Log.i(TAG, "onCreateLoader(id) " + id);
        // return new TestSampleListLoader(getActivity());
        TestSampleListLoaderBis testSampleListLoaderBis = new TestSampleListLoaderBis(getActivity());
        return testSampleListLoaderBis;
    }

    @Override
    public void onLoadFinished(Loader<List<TestSample>> loader, List<TestSample> testSampleList) {
        Log.i(TAG, "onLoadFinished(): " + testSampleList.size());
        setListShown(false);
        mTestReaderAdapter.setData(testSampleList);

        if (isResumed()) {
            Log.i(TAG, "onLoadFinished(isResumed)");
            setListShown(true);
        } else {
            Log.i(TAG, "onLoadFinished(isNotResumed)");
            setListShownNoAnimation(true);
        }
    }

    @Override
    public void onLoaderReset(Loader<List<TestSample>> arg0) {
        Log.i(TAG, "onLoaderReset()");
        mTestReaderAdapter.setData(null);
    }
}

Следы ADB Logcat:

D/TestSampleListLoader(31166): onQeoReady!!!
I/TestSampleListLoader(31166): initQeo(mTestSampleReader): org.qeo.internal.SampleReaderImpl@41d29e68

I/TestSampleListLoader(31166): TestSampleListener.onUpdate() => onContentChanged
I/TestSampleListLoader(31166): onContentChanged()
I/TestSampleListLoader(31166): forceLoad()
I/TestSampleListLoader(31166): onForceLoad()
I/TestSampleListLoader(31166): forceLoad(deliverResult) 5
I/TestSampleListLoader(31166): deliverResult(data) 5
I/TestSampleListFragment(31166): onLoadFinished(): 5
I/TestSampleListAdapter(31166): setData(): 5
I/TestSampleListAdapter(31166): setData() for testSample: Test Sample #1
I/TestSampleListAdapter(31166): setData() for testSample: Test Sample #2
I/TestSampleListAdapter(31166): setData() for testSample: Test Sample #3 UPDATED
I/TestSampleListAdapter(31166): setData() for testSample: Test Sample #4
I/TestSampleListAdapter(31166): setData() for testSample: Test Sample #6 UPDATED
I/TestSampleListFragment(31166): onLoadFinished(isResumed)

I/TestSampleListLoader(31166): TestSampleListener.onUpdate() => onContentChanged
I/TestSampleListLoader(31166): onContentChanged()
I/TestSampleListLoader(31166): forceLoad()
I/TestSampleListLoader(31166): onForceLoad()
I/TestSampleListLoader(31166): forceLoad(deliverResult) 5
I/TestSampleListLoader(31166): deliverResult(data) 5

I/TestSampleListLoader(31166): TestSampleListener.onUpdate() => onContentChanged
I/TestSampleListLoader(31166): onContentChanged()
I/TestSampleListLoader(31166): forceLoad()
I/TestSampleListLoader(31166): onForceLoad()
I/TestSampleListLoader(31166): forceLoad(deliverResult) 6
I/TestSampleListLoader(31166): deliverResult(data) 6

Проблема в том, что теперь мой Loader правильно информируется об изменениях данных, но только в первый раз, когда они доставляются правильно в направлении LoaderManager.LoaderCallbacks onLoadFinished() обратный вызов. После изменения ориентации та же история, первый раз результат правильно поступает в onLoadFinished(), но последующие обновления, поступающие из собственного слоя, не достигают фрагмента.

Я использовал функцию отладки eclipse, чтобы отследить проблему, и нашел ее в исходниках LoaderManager (строки 447-453: этот код запускается из Loader.deliverResult => onLoadComplete [=> callOnLoadFinished => обновление фрагмента ОК]):

// Notify of the new data so the app can switch out the old data before
// we try to destroy it.
if (mData != data || !mHaveData) {
    mData = data;
    mHaveData = true;
    if (mStarted) {
        callOnLoadFinished(loader, data);
    }
}

Кажется, что только в самый первый раз mData != data (поскольку в этом случае mData == null). При последующих попаданиях этого условия mData == data всегда (и объект/массив данных правильно растет с моим собственным вводом), что очень странно, потому что я не могу узнать, кто устанавливает/обновляет это Объект mData в классе LoaderInfo в LoaderManagerImpl.

Эта проблема блокирует меня, потому что только если это условие истинно, выполняется необходимый вызов callOnLoadFinished, который будет правильно информировать мой фрагмент и arrayAdapter о базовых изменениях.

Кто-нибудь знает, что я делаю неправильно в своем пользовательском загрузчике, или пользовательский загрузчик не очень хорошее решение, когда базовый набор данных все время меняется?

Заранее спасибо! Барт

Хороший справочник по пользовательским загрузчикам Android: http://www.androiddesignpatterns.com/2012/08/implementing-loaders.html


person Boeboe    schedule 27.10.2013    source источник
comment
Спасибо Вам за Ваш вопрос! Полдня мучился с этим! Не знал, что он сравнивает экземпляры объектов под капотом.   -  person Andrey Novikov    schedule 12.02.2016


Ответы (1)


если вы хотите, чтобы данные. изменить, я бы удалить mTestSampleList.clear() и заменить его на mTestSampleList = новый ArrayList();

person pskink    schedule 27.10.2013
comment
pskink, это действительно решило проблему! Я предполагаю, что clear очищает только ссылки в ArrayList на отдельные объекты TestSample. Следовательно, с точки зрения ArrayList (и, следовательно, LoaderManager) массив на самом деле не изменяется (размер тот же, только ссылки обнуляются). Большой! Еще раз спасибо за решение! - person Boeboe; 27.10.2013