AsyncTaskLoader onLoadFinished带有待执行任务和配置更改
我试图使用AsyncTaskLoader
在后台加载数据以填充详细视图以响应所选列表项。我已经得到它主要工作,但我仍然有一个问题。如果我在列表中选择第二个项目,然后在第一个选定项目的加载完成之前旋转设备,则onLoadFinished()
调用将报告正在停止的活动而不是新活动。当选择一个项目然后旋转时,这可以正常工作。AsyncTaskLoader onLoadFinished带有待执行任务和配置更改
这是我正在使用的代码。活动时间:
public final class DemoActivity extends Activity
implements NumberListFragment.RowTappedListener,
LoaderManager.LoaderCallbacks<String> {
private static final AtomicInteger activityCounter = new AtomicInteger(0);
private int myActivityId;
private ResultFragment resultFragment;
private Integer selectedNumber;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
myActivityId = activityCounter.incrementAndGet();
Log.d("DemoActivity", "onCreate for " + myActivityId);
setContentView(R.layout.demo);
resultFragment = (ResultFragment) getFragmentManager().findFragmentById(R.id.result_fragment);
getLoaderManager().initLoader(0, null, this);
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d("DemoActivity", "onDestroy for " + myActivityId);
}
@Override
public void onRowTapped(Integer number) {
selectedNumber = number;
resultFragment.setResultText("Fetching details for item " + number + "...");
getLoaderManager().restartLoader(0, null, this);
}
@Override
public Loader<String> onCreateLoader(int id, Bundle args) {
return new ResultLoader(this, selectedNumber);
}
@Override
public void onLoadFinished(Loader<String> loader, String data) {
Log.d("DemoActivity", "onLoadFinished reporting to activity " + myActivityId);
resultFragment.setResultText(data);
}
@Override
public void onLoaderReset(Loader<String> loader) {
}
static final class ResultLoader extends AsyncTaskLoader<String> {
private static final Random random = new Random();
private final Integer number;
private String result;
ResultLoader(Context context, Integer number) {
super(context);
this.number = number;
}
@Override
public String loadInBackground() {
// Simulate expensive Web call
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Item " + number + " - Price: $" + random.nextInt(500) + ".00, Number in stock: " + random.nextInt(10000);
}
@Override
public void deliverResult(String data) {
if (isReset()) {
// An async query came in while the loader is stopped
return;
}
result = data;
if (isStarted()) {
super.deliverResult(data);
}
}
@Override
protected void onStartLoading() {
if (result != null) {
deliverResult(result);
}
// Only do a load if we have a source to load from
if (number != null) {
forceLoad();
}
}
@Override
protected void onStopLoading() {
// Attempt to cancel the current load task if possible.
cancelLoad();
}
@Override
protected void onReset() {
super.onReset();
// Ensure the loader is stopped
onStopLoading();
result = null;
}
}
}
名单片段:
public final class NumberListFragment extends ListFragment {
interface RowTappedListener {
void onRowTapped(Integer number);
}
private RowTappedListener rowTappedListener;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
rowTappedListener = (RowTappedListener) activity;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
ArrayAdapter<Integer> adapter = new ArrayAdapter<Integer>(getActivity(),
R.layout.simple_list_item_1,
Arrays.asList(1, 2, 3, 4, 5, 6));
setListAdapter(adapter);
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
ArrayAdapter<Integer> adapter = (ArrayAdapter<Integer>) getListAdapter();
rowTappedListener.onRowTapped(adapter.getItem(position));
}
}
结果片段:
public final class ResultFragment extends Fragment {
private TextView resultLabel;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.result_fragment, container, false);
resultLabel = (TextView) root.findViewById(R.id.result_label);
if (savedInstanceState != null) {
resultLabel.setText(savedInstanceState.getString("labelText", ""));
}
return root;
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("labelText", resultLabel.getText().toString());
}
void setResultText(String resultText) {
resultLabel.setText(resultText);
}
}
我已经能够得到这个使用普通AsyncTask
s工作,但我想了解更多约为Loader
,因为它们会自动处理配置更改。
编辑:我想我可能已经通过查看源LoaderManager问题追查。在配置更改后调用initLoader
时,LoaderInfo
对象的mCallbacks
字段更新为新的活动,并执行LoaderCallbacks
,如我所料。
public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
if (mCreatingLoader) {
throw new IllegalStateException("Called while creating a loader");
}
LoaderInfo info = mLoaders.get(id);
if (DEBUG) Log.v(TAG, "initLoader in " + this + ": args=" + args);
if (info == null) {
// Loader doesn't already exist; create.
info = createAndInstallLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback);
if (DEBUG) Log.v(TAG, " Created new loader " + info);
} else {
if (DEBUG) Log.v(TAG, " Re-using existing loader " + info);
info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
}
if (info.mHaveData && mStarted) {
// If the loader has already generated its data, report it now.
info.callOnLoadFinished(info.mLoader, info.mData);
}
return (Loader<D>)info.mLoader;
}
然而,当存在未决装载机,主LoaderInfo
对象还具有mPendingLoader
字段与对LoaderCallbacks
的参考,以及,这个目的是从不与在mCallbacks
领域的新的活动更新。我希望看到的代码看起来像这个:
// This line was already there
info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
// This line is not currently there
info.mPendingLoader.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
这似乎是因为这样,未决加载器调用onLoadFinished
在老年活动实例。如果我在此方法中断点并使用调试器使我感觉缺少的调用一切正常,则我所期望的一切都可以正常工作。
新的问题是:我发现了一个错误,或者这是预期的行为?
好吧我试图理解这个原谅我,如果我误解了任何东西,但是当设备旋转时失去对某些东西的引用。
以刺...
会增加
android:configChanges="orientation|keyboardHidden|screenSize"
在您的清单该活动改正错误?或阻止onLoadFinished()
说活动停止?
这可能是一种解决方法,但这很可能是Android中的一个真正的bug。 – 2012-11-13 21:24:54
@StevePomeroy不一定,当设备旋转时;该活动被重新创建,因此重新初始化任何对该对象或其他变量的引用。这会阻止系统通过将活动传递给活动本身来重新创建活动,从而允许它处理/覆盖方向更改。如果设备进入横向模式,某些布局元素可能会重新调整大小或重新定位。 – StrikeForceZero 2012-11-14 05:59:15
@StevePomeroy您的异步加载程序从未被破坏和重建活动 – StrikeForceZero 2012-11-14 06:05:33
在大多数情况下,如果Activity已被销毁,则应忽略此类报告。你
public void onLoadFinished(Loader<String> loader, String data) {
Log.d("DemoActivity", "onLoadFinished reporting to activity " + myActivityId);
if (isDestroyed()) {
Log.i("DemoActivity", "Activity already destroyed, report ignored: " + data);
return;
}
resultFragment.setResultText(data);
}
同时也应该检查插入任何内部类isDestroyed()
。可运行 - 是最常用的情况。
例如:
// UI thread
final Handler handler = new Handler();
Executor someExecutorService = ... ;
someExecutorService.execute(new Runnable() {
public void run() {
// some heavy operations
...
// notification to UI thread
handler.post(new Runnable() {
// this runnable can link to 'dead' activity or any outer instance
if (isDestroyed()) {
return;
}
// we are alive
onSomeHeavyOperationFinished();
});
}
});
但在这样的情况下最好的方法是,以避免传递活性强引用另一个线程(AsynkTask,装载,执行器等)。
最可靠的解决方案是在这里:
// BackgroundExecutor.java
public class BackgroundExecutor {
private static final Executor instance = Executors.newSingleThreadExecutor();
public static void execute(Runnable command) {
instance.execute(command);
}
}
// MyActivity.java
public class MyActivity extends Activity {
// Some callback method from any button you want
public void onSomeButtonClicked() {
// Show toast or progress bar if needed
// Start your heavy operation
BackgroundExecutor.execute(new SomeHeavyOperation(this));
}
public void onSomeHeavyOperationFinished() {
if (isDestroyed()) {
return;
}
// Hide progress bar, update UI
}
}
// SomeHeavyOperation.java
public class SomeHeavyOperation implements Runnable {
private final WeakReference<MyActivity> ref;
public SomeHeavyOperation(MyActivity owner) {
// Unlike inner class we do not store strong reference to Activity here
this.ref = new WeakReference<MyActivity>(owner);
}
public void run() {
// Perform your heavy operation
// ...
// Done!
// It's time to notify Activity
final MyActivity owner = ref.get();
// Already died reference
if (owner == null) return;
// Perform notification in UI thread
owner.runOnUiThread(new Runnable() {
public void run() {
owner.onSomeHeavyOperationFinished();
}
});
}
}
也许不是最好的解决办法,但... 这每一次代码重新启动加载器,这是不好的,但只是解决的作品 - 如果你想二手装载机。
Loader l = getLoaderManager().getLoader(MY_LOADER);
if (l != null) {
getLoaderManager().restartLoader(MY_LOADER, null, this);
} else {
getLoaderManager().initLoader(MY_LOADER, null, this);
}
顺便说一句。我正在使用Cursorloader ...
一个可能的解决方案是在自定义单例对象中启动AsyncTask并从Activity中的单例访问onFinished()结果。每当你旋转你的屏幕,去onPause()或onResume(),最新的结果将被使用/访问。如果您的单身物件中仍然没有结果,则说明它仍然很忙或者您可以重新启动任务。
另一种方法是与像Otto这样的服务公司一起工作,或使用服务工作。
看一看['CursorLoader.java'](http://grepcode.com/file_/repository.grepcode.com/java/ext/com.google.android/android/4.0.1_r1/android/ content/CursorLoader.java /?v = source)源代码。尝试实现'onStartLoading','onStopLoading','onCanceled','onReset'和'deliverResult'类似于源代码的做法......'LoaderManager'假定所有这些方法都正确实现。这可能就是为什么你的实现只能部分处理配置更改。 – 2012-07-17 05:24:10
所以事实证明'onLoadFinished()* *实际上是被调用的 - 它只是报告给旧的活动(配置改变之前的活动),而不是新的活动。问题已编辑,代码已更新。 – 2012-07-17 13:50:52
老实说,我认为这里的问题是这个例子本身的微不足道......在现实生活中,'Loader'不包含实际的数据源(即private final int字段是您的实际数据源,是不是?)。 'Loader's也应该监视他们的数据源并在发生变化时进行报告。在大多数情况下,由于Loader足够聪明以保留其旧数据,在配置更改之后,您的新Activity上不会调用onLoadFinished,只有在它看到对其后备数据所做的更改时才会重新加载制作。 – 2012-07-17 14:05:08