AsyncTaskLoader onLoadFinished有待处理的任务和配置更改

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

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

 public final class DemoActivity extends Activity implements NumberListFragment.RowTappedListener, LoaderManager.LoaderCallbacks { 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 onCreateLoader(int id, Bundle args) { return new ResultLoader(this, selectedNumber); } @Override public void onLoadFinished(Loader loader, String data) { Log.d("DemoActivity", "onLoadFinished reporting to activity " + myActivityId); resultFragment.setResultText(data); } @Override public void onLoaderReset(Loader loader) { } static final class ResultLoader extends AsyncTaskLoader { 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 adapter = new ArrayAdapter(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 adapter = (ArrayAdapter) 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的更多信息,因为它们会自动处理配置更改。


编辑 :我想我可能通过查看LoaderManager的源代码来跟踪问题。 在配置更改后调用initLoaderLoaderInfo对象的mCallbacks字段更新为新活动,作为LoaderCallbacks的实现,正如我所料。

 public  Loader initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks 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)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)callback; } if (info.mHaveData && mStarted) { // If the loader has already generated its data, report it now. info.callOnLoadFinished(info.mLoader, info.mData); } return (Loader)info.mLoader; } 

但是,当有一个挂起的加载器时,主LoaderInfo对象也有一个mPendingLoader字段,该字段也引用了LoaderCallbacks ,并且该对象永远不会使用mCallbacks字段中的新活动进行更新。 我希望看到代码看起来像这样:

 // This line was already there info.mCallbacks = (LoaderManager.LoaderCallbacks)callback; // This line is not currently there info.mPendingLoader.mCallbacks = (LoaderManager.LoaderCallbacks)callback; 

似乎是因为这个挂起的加载器在旧活动实例上调用onLoadFinished 。 如果我在这个方法中断点并使用调试器进行我觉得缺少的调用,那么一切都按预期工作。

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

Solutions Collecting From Web of "AsyncTaskLoader onLoadFinished有待处理的任务和配置更改"

在大多数情况下,如果Activity已被销毁,您应该忽略此类报告。

 public void onLoadFinished(Loader 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() 。 Runnable – 是最常用的案例。

例如:

 // 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等)。

最可靠的解决方案是:

 // 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 ref; public SomeHeavyOperation(MyActivity owner) { // Unlike inner class we do not store strong reference to Activity here this.ref = new WeakReference(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); } 

BTW。 我正在使用Cursorloader …

一种可能的解决方案是在自定义单例对象中启动AsyncTask,并从Activity中的单例访问onFinished()结果。 每次旋转屏幕时,转到onPause()或onResume(),将使用/访问最新结果。 如果您的单件对象中仍然没有结果,则表示它仍然处于忙碌状态,或者您可以重新启动该任务。

另一种方法是使用像Otto这样的服务总线,或者使用服务。

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

采取刺…

会补充

 android:configChanges="orientation|keyboardHidden|screenSize" 

在您的活动清单中修复您的错误? 或阻止onLoadFinished()说活动停止了?