当加载器被重新使用并包含数据时,LoaderCallbacks.onLoadFinished 不会被调用

发布于 2024-11-29 12:28:07 字数 4396 浏览 0 评论 0原文

我有 1 个活动和 2 个片段。两个片段都使用自定义 AsyncTaskLoader 从 Web 服务获取一些数据,并且当我使用 Loader 时,它应该在活动和片段重新创建之间保留数据。两个片段都会重写 onActivityCreated 方法并调用 getLoaderManager().initLoader(0, null, this) ,这会创建一个新的加载器或重用现有的加载器。

首次创建 Activity 时,它默认在 FrameLayout 中添加 Fragment #1,加载数据,内部调用 LoaderCallbacks.onLoadFinished() 方法并显示结果。我有一个按钮,单击时将片段 #1 替换为片段 #2,并且片段 #1 被推送到片段后台堆栈。当用户按下 BACK 键时,它会切换回 Fragment #1。

onActivityCreated 在 Fragment #1 上再次被调用,然后显然再次调用 iniLoader() 。这次数据已经存在于加载器中,我希望它再次自动调用 LoaderCallbacks.onLoadFinished 方法,因为它已经有可用的数据,如下所述: http://goo.gl/EGFJk

确保加载程序已初始化并处于活动状态。如果加载程序尚不存在,则会创建一个加载程序并(如果活动/片段当前已启动)启动加载程序。否则,将重新使用最后创建的加载程序。 在任何一种情况下,给定的回调都与加载器相关联,并且将在加载器状态更改时被调用。 如果在调用时调用者处于启动状态,并且请求的加载器已经存在并已生成其数据,则回调 onLoadFinished(Loader, D) 将立即被调用(在此函数内部),因此您必须为此做好准备。

但是,即使加载程序存在并且已生成准备交付的数据,也永远不会调用该方法。


编辑#1 从用户角度来看的问题:

  • 用户启动 Activity 并看到带有一些数据的fragment1
  • 用户单击某些内容,将第一个片段更改为另一个片段,其中包含不同的数据 data
  • 用户按下 BACK 键
  • 用户现在再次查看fragment1,但没有数据。 (这意味着我需要再次从网络服务获取它 - 如果可能的话,我想避免这种情况)

这是我的活动:

public class FragmentTestsActivity extends Activity implements OnClickListener {

    private Button btn1;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        btn1 = (Button) findViewById(R.id.btn1);
        btn1.setOnClickListener(this);

        Fragment newFragment = new Fragment1();
        FragmentTransaction ft = getFragmentManager().beginTransaction();
        ft.add(R.id.fragmentContainer, newFragment).commit();
    }

    @Override
    public void onClick(View view) {
        int id = view.getId();
        if (id == R.id.btn1) {
            showNewFragment();
        }
    }

    public void showNewFragment() {
        // Instantiate a new fragment.
        Fragment2 newFragment = new Fragment2();

        // Add the fragment to the activity, pushing this transaction
        // on to the back stack.
        FragmentTransaction ft = getFragmentManager().beginTransaction();
        ft.replace(R.id.fragmentContainer, newFragment);
        ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
        ft.addToBackStack(null);
        ft.commit();
    }
}

我的片段#1:

public class Fragment1 extends Fragment implements LoaderCallbacks<String> {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment1, container, false);
    }

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

        LoaderManager.enableDebugLogging(true);        
        getLoaderManager().initLoader(0, null, this);
    }

    private static class TestLoader extends AsyncTaskLoader<String> {

        String result;

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

        @Override
        public String loadInBackground() {
            // Some long-running call to a webservice - replaced with a simple string to test with            
            return "FirstTimeData";
        }

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

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

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

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

    @Override
    public Loader<String> onCreateLoader(int id, Bundle args) {
        return new TestLoader(getActivity());
    }

    @Override
    public void onLoadFinished(Loader<String> loader, String result) {
        Log.d("Fragment1", "onLoadFinished: " + result);
    }

    @Override
    public void onLoaderReset(Loader<String> loader) {
        // TODO Auto-generated method stub
    }
}

有人知道这个问题的解决方案或者我在这里做错了什么吗?非常感谢任何帮助。

I have 1 activity and 2 fragments. Both fragments use a custom AsyncTaskLoader to get some data from a webservice and as i'm using a Loader it should keep the data across activity and fragment re-creations. Both fragments override the onActivityCreated method and calls getLoaderManager().initLoader(0, null, this) which either creates a new or reuses an existing loader.

When the activity is first created, it adds Fragment #1 in a FrameLayout by default, loads the data, internally calls LoaderCallbacks.onLoadFinished() method and displays the result. I have a button which replaces Fragment #1 with Fragment #2 on click and Fragment #1 is pushed to the fragment-backstack. When the user hits the BACK key, it switches back to Fragment #1.

onActivityCreated gets called again on Fragment #1 and then obviously calls iniLoader() again. This time the data already exists in the loader and i expect it to automatically call the LoaderCallbacks.onLoadFinished method again, because it already has data available, as described here: http://goo.gl/EGFJk

Ensures a loader is initialized and active. If the loader doesn't already exist, one is created and (if the activity/fragment is currently started) starts the loader. Otherwise the last created loader is re-used.
In either case, the given callback is associated with the loader, and will be called as the loader state changes. If at the point of call the caller is in its started state, and the requested loader already exists and has generated its data, then callback onLoadFinished(Loader, D) will be called immediately (inside of this function), so you must be prepared for this to happen.

But the method is never called even if the loader exists and has generated data ready to deliver.


Edit #1
The problem from a users perspective:

  • User starts activity and sees fragment1 with some data
  • User clicks something which changes the first fragment to another, with different
    data
  • User hits the BACK key
  • User is now looking at fragment1 again, but there's no data. (which means i need to get it from the webservice again - and i'd like to avoid that if possible)

Here is my activity:

public class FragmentTestsActivity extends Activity implements OnClickListener {

    private Button btn1;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        btn1 = (Button) findViewById(R.id.btn1);
        btn1.setOnClickListener(this);

        Fragment newFragment = new Fragment1();
        FragmentTransaction ft = getFragmentManager().beginTransaction();
        ft.add(R.id.fragmentContainer, newFragment).commit();
    }

    @Override
    public void onClick(View view) {
        int id = view.getId();
        if (id == R.id.btn1) {
            showNewFragment();
        }
    }

    public void showNewFragment() {
        // Instantiate a new fragment.
        Fragment2 newFragment = new Fragment2();

        // Add the fragment to the activity, pushing this transaction
        // on to the back stack.
        FragmentTransaction ft = getFragmentManager().beginTransaction();
        ft.replace(R.id.fragmentContainer, newFragment);
        ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
        ft.addToBackStack(null);
        ft.commit();
    }
}

My Fragment #1:

public class Fragment1 extends Fragment implements LoaderCallbacks<String> {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment1, container, false);
    }

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

        LoaderManager.enableDebugLogging(true);        
        getLoaderManager().initLoader(0, null, this);
    }

    private static class TestLoader extends AsyncTaskLoader<String> {

        String result;

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

        @Override
        public String loadInBackground() {
            // Some long-running call to a webservice - replaced with a simple string to test with            
            return "FirstTimeData";
        }

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

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

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

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

    @Override
    public Loader<String> onCreateLoader(int id, Bundle args) {
        return new TestLoader(getActivity());
    }

    @Override
    public void onLoadFinished(Loader<String> loader, String result) {
        Log.d("Fragment1", "onLoadFinished: " + result);
    }

    @Override
    public void onLoaderReset(Loader<String> loader) {
        // TODO Auto-generated method stub
    }
}

Anyone know a solution to this or what i'm doing wrong here? Any help is greatly appreciated.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

月朦胧 2024-12-06 12:28:07

至少对我来说,正确的答案是将整个 Loader 初始化从 onViewCreatedonActivityCreated 移至 onStart

之后就可以正常工作了!

The correct answer, for me at least, was to move the entire Loader initialisation from onViewCreated or onActivityCreated to onStart.

After that it works fine!

握住我的手 2024-12-06 12:28:07

从我的角度来看, onLoadFinished 只会在第一次被调用,因为加载已经完成,两个片段只完成一次。您可以做的是将结果存储在活动的属性中,并在第二个片段创建时检查它。

From my point of view the onLoadFinished will only be called the first time cause the load has already finished, it finishes just once for both fragments. What you could do is to store the result in a property in the activity and check for it in the second fragment creation.

折戟 2024-12-06 12:28:07

更新:经过进一步调查,我发现我原来的答案是错误的。 restartLoader 也会销毁加载器(如果它已经存在)。

尽管如此,我还是解决了我自己的问题。我在 onCreateLoader 中创建了一个标准 CursorLoader,并在 onLoadFinished 中交换了 CursorAdapter 的光标。我需要在初始化 CursorAdapter 之后调用 initLoader。现在,当片段从返回堆栈返回时,数据仍然存在。

原始答案:我发现了两种可能的方法来解决这个问题。

显然,当Fragment从backstack返回后,在onActivityCreated(Bundle savingInstanceState)中第二次调用initLoader(int id, Bundle args, LoaderCallbackscallback)时,方法 onLoadFinished(Loaderloader, String result) 永远不会被调用(如您所描述的)。

但是,在 initLoader 之后或代替 initLoader 调用 restartLoader(int id, Bundle args, LoaderCallbackscallback)最终将导致 onLoadFinished 被调用。为了提高性能,我使用布尔参数来确定 Fragment 是否是新实例。然后,仅当 Fragment 从 backstack 返回时,我才调用 restartLoader

据我所知,旧数据仍然存在于加载器中并且不会重新加载,但我不确定。第二种可能性(特别是当不使用 backstack 而是在中创建一个新的 Fragment 实例时)事务)是在 Fragment 消失之前调用 destroyLoader(int id) (例如在 onPause 中)。

Update: After further investigation I found my original answer to be wrong. restartLoader will also destroy the loader if it already exists.

Nevertheless I solved my own problem. I create a standard CursorLoader in onCreateLoader and swap the cursor of my CursorAdapter in onLoadFinished. I needed to call initLoader after initializing the CursorAdapter. Now the data is still there when the fragment is returned from the backstack.

Original answer: I found two possible ways to solve this issue.

Apparently when initLoader(int id, Bundle args, LoaderCallbacks<D> callback) is called for the second time in onActivityCreated(Bundle savedInstanceState) after the Fragment is returned from the backstack, the method onLoadFinished(Loader<String> loader, String result) is never called (as you described).

However, a call to restartLoader(int id, Bundle args, LoaderCallbacks<D> callback) after or instead of initLoader will finally cause onLoadFinished to be called. To improve performance, I use a boolean argument to determine whether the Fragment is a new instance or not. Then I call restartLoader only if the Fragment is returned from the backstack.

As far as I can tell, old data persists in the loader and is not reloaded, but I'm not sure. The second possibility (especially when not using the backstack but instead creating a new Fragment instance in a transaction) is to call destroyLoader(int id) before the Fragment goes away (e.g in onPause).

橘香 2024-12-06 12:28:07

我已经遇到了这个问题,我无法真正解释为什么会出现这个错误,但我知道那一行:

getLoaderManager().initLoader(0, null, this);

在我的代码中不起作用,所以你可以为此更改它:

LoaderManager lm = getLoaderManager();
lm.initLoader(LOADER_ID, null, this);

代码启动方法:

onCreateLoader

你可以尝试...

I already had that issue, I can't really explain why that bug but I know that line :

getLoaderManager().initLoader(0, null, this);

don't work in my code, So you can changed it for that :

LoaderManager lm = getLoaderManager();
lm.initLoader(LOADER_ID, null, this);

The code start the methods :

onCreateLoader

you can try...

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文