根据http://developer.android.com/guide/components/loaders.html,关于加载器的一个好处是,它能够在配置更改期间保留其数据。
当它们自动重新连接到最后一个加载器的光标 配置更改后重新创建。因此,他们不需要 重新查询他们的数据。
但是,它并不适用于所有情况。
我采用以下简单示例。它是FragmentActivity
,托管Fragment
。 Fragment
本身拥有AsyncTaskLoader
。
以下3个方案效果很好。
创建了1个加载器,并且loadInBackground
执行一次。
没有创建新的加载器,并且没有触发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/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
我想知道,这个错误有什么好的解决方法吗?
答案 0 :(得分:6)
我的回答实际上非常直截了当。不要使用AsyncTaskLoaders。因为有关AsyncTaskLoaders的一些错误,你现在就知道了。
一个好的组合是带有AsyncTask的可保留(onActivityCreated()中的setRetainInstance(true))片段。以同样的方式工作。只需稍微重构一下代码。
虽然作者没有提供任何代码示例,但这是最接近的可行解决方案。我不使用作者提出的解决方案。相反,我仍然依赖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();
}
答案 1 :(得分: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);
}
}
答案 2 :(得分:0)
如果您正在使用FragmentManager的替换片段技术,则会出现此问题。
当您更换/删除片段时,片段与活动分离,并且由于加载器附加到活动,因此将在方向更改期间重新创建加载器。
尝试使用FragmentManager的隐藏/显示技术。可能这会对你有帮助。
答案 3 :(得分:-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();
}
}