AsyncTaskLoader onLoadFinished与一个挂起的任务和configuration改变

我试图使用AsyncTaskLoader在后台加载数据来填充一个详细视图,以响应select的列表项。 我已经得到它主要工作,但我仍然有一个问题。 如果我在列表中select第二个项目,然后在第一个选定项目的加载完成之前旋转设备,则onLoadFinished()调用将报告被停止的活动,而不是新活动。 这个工作正常,当select一个单一的项目,然后旋转。

这是我正在使用的代码。 活动:

 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得到这个工作,但我想了解更多关于Loader的,因为他们自动处理configuration更改。


编辑 :我想我可能已经通过查看LoaderManager的来源追查问题。 在configuration更改之后调用initLoaderLoaderInfo对象的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 。 如果我在这个方法断点,并使用debugging器使我觉得失去了调用,一切正常,我期望。

新的问题是:我发现了一个错误,或者这是预期的行为?

Solutions Collecting From Web of "AsyncTaskLoader onLoadFinished与一个挂起的任务和configuration改变"

在大多数情况下,如果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(); }); } }); 

但在这种情况下,最好的方法是避免将强大的Activity引用传递给另一个线程 (AsynkTask,Loader,Executor等)。

最可靠的解决scheme是:

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

也许不是最好的解决scheme,但是…这个代码重新启动加载程序每一次,这是坏的,但只有在周围的工作 – 如果你想使用装载机。

 Loader l = getLoaderManager().getLoader(MY_LOADER); if (l != null) { getLoaderManager().restartLoader(MY_LOADER, null, this); } else { getLoaderManager().initLoader(MY_LOADER, null, this); } 

BTW。 我正在使用Cursorloader …

一个可能的解决scheme是在一个自定义单例对象中启动AsyncTask,并从Activity中的单例访问onFinished()结果。 每当你旋转你的屏幕,去onPause()或onResume(),最新的结果将被使用/访问。 如果你的单例对象中还没有结果,那么你知道它仍然很忙,或者你可以重新启动这个任务。

另一种方法是与像Otto这样的服务公司一起工作,或与一个服务工作。

好的,我试图理解这个借口,如果我误解了任何东西,但是当设备旋转时,你正在失去对某些东西的引用。

采取刺…

会join

 android:configChanges="orientation|keyboardHidden|screenSize" 

在你的清单中,解决你的错误? 或阻止onLoadFinished()说活动停止?