Pertanyaan Loader tidak dapat mempertahankan dirinya sendiri selama perubahan konfigurasi tertentu


Menurut http://developer.android.com/guide/components/loaders.html, salah satu hal yang menyenangkan tentang loader adalah, ia mampu mempertahankan datanya selama perubahan konfigurasi.

Mereka secara otomatis terhubung kembali ke kursor pemuat terakhir saat berada   diciptakan kembali setelah perubahan konfigurasi. Jadi, mereka tidak perlu melakukannya   re-query data mereka.

Namun, itu tidak berfungsi dengan baik di semua skenario.

Saya mengambil contoh sederhana berikut ini. Ini adalah sebuah FragmentActivity, yang menjadi hosting Fragment. Itu Fragment itu sendiri memiliki AsyncTaskLoader.

3 skenario berikut ini bekerja dengan cukup baik.

Saat pertama diluncurkan (OK)

1 loader dibuat, dan loadInBackground dijalankan sekali.

Selama rotasi sederhana (OK)

Tidak ada loader baru yang dibuat dan loadInBackground tidak dipicu.

Aktivitas anak diluncurkan, dan tombol kembali ditekan (OK)

Tidak ada loader baru yang dibuat dan loadInBackground tidak dipicu.

Namun, dalam skenario berikut.

Aktivitas anak diluncurkan -> Rotasi -> Tombol kembali ditekan (Salah)

Pada waktu itu, loader lama onReset disebut. Loader lama akan dihancurkan. Loader baru akan dibuat dan loader baru loadInBackground akan dipicu lagi.

Perilaku yang benar yang saya harapkan adalah, tidak ada loader baru yang akan dibuat.

Kode yang terkait dengan loader adalah sebagai berikut. Saya menjalankan kode di bawah emulator 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/questions/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;
}

Kode sumber lengkap dapat diunduh https://www.dropbox.com/s/n2jee3v7cpwvedv/loader_bug.zip

Ini mungkin terkait dengan 1 bug Android yang belum terpecahkan: 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

Saya bertanya-tanya, apakah ada solusi yang baik untuk bug ini?


20
2018-04-09 08:56


asal


Jawaban:


Jawaban saya cukup lurus ke depan sebenarnya. Jangan gunakan AsyncTaskLoaders. Karena beberapa bug mengenai AsyncTaskLoaders, Anda sudah mengetahuinya sekarang.

Kombinasi yang baik akan dapat dipertahankan (setRetainInstance (true) di onActivityCreated ()) fragment dengan AsyncTask. Bekerja dengan cara yang sama. Hanya perlu merestrukturisasi kode sedikit.


Pesan dari OP

Meskipun penulis tidak memberikan contoh kode apa pun, ini adalah solusi yang bisa diterapkan paling dekat. Saya tidak menggunakan solusi yang diajukan penulis. Sebaliknya, saya masih mengandalkan AsyncTaskLoader untuk semua tugas pemuatan yang diperlukan. Solusinya adalah, saya akan mengandalkan fragmen dipertahankan tambahan, untuk menentukan apakah saya harus menghubungkan kembali / membuat loader. Adalah kode kerangka pada seluruh ide. Bekerja cukup baik sejauh yang saya tahu.

@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();            
    }

6
2018-04-17 09:26



Cobalah untuk berubah,

 @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.
    }
}    

untuk

 @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); 
} 

}    

0
2018-04-17 09:49



Jika Anda menggunakan teknik fragmen FragmentManager, masalah ini akan terjadi.

Saat Anda mengganti / menghapus Fragmen, fragmen dipisahkan dari aktivitas dan karena loader dilekatkan ke aktivitas, loader akan dibuat ulang selama perubahan orientasi.

Coba gunakan teknik hide / show FragmentManager. Mungkin ini akan membantu Anda.


0
2018-04-23 14:29



Saya telah berhasil melakukan subkelas AsyncTaskLoader dan membuat beberapa penyesuaian pada metodenya.

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();
    }
}

-1
2018-04-23 01:12