20

http://developer.android.com/guide/components/loaders.htmlによると、ローダーの優れた点の 1 つは、構成の変更中にデータを保持できることです。

構成の変更後に再作成されると、最後のローダーのカーソルに自動的に再接続します。したがって、データを再クエリする必要はありません。

ただし、すべてのシナリオでうまく機能するわけではありません。

次の簡単な例を取り上げます。をFragmentActivityホストしているFragmentです。Fragment自体が を所有していますAsyncTaskLoader

次の 3 つのシナリオは非常にうまく機能します。

初回起動時(OK)

1 つのローダが作成され、1loadInBackground回実行されます。

単純回転中(OK)

新しいローダーは作成されてloadInBackgroundおらず、トリガーされていません。

子アクティビティが起動され、戻るボタンが押されました (OK)

新しいローダーは作成されて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/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;
}

完全なソース コードは、 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

私は疑問に思っていました、このバグの良い回避策はありますか?

4

4 に答える 4

0

変えてみて、

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

}    
于 2013-04-17T09:49:58.830 に答える
-1

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();
    }
}
于 2013-04-23T01:12:27.630 に答える