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

Pau*_*ing 24 android asynctaskloader android-loadermanager android-loader

我正在尝试使用an 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;
        }

    }

}
Run Code Online (Sandbox Code Playgroud)

列表片段:

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

}
Run Code Online (Sandbox Code Playgroud)

结果片段:

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

}
Run Code Online (Sandbox Code Playgroud)

我已经能够使用plain AsyncTasks 工作了,但我正在尝试更多地了解Loaders,因为它们会自动处理配置更改.


编辑:我想我可能通过查看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;
}
Run Code Online (Sandbox Code Playgroud)

但是,当有一个挂起的加载器时,主LoaderInfo对象也有一个mPendingLoader带有对a的引用的字段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;
Run Code Online (Sandbox Code Playgroud)

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

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

Str*_*ero -2

好吧,我正在尝试理解这个,如果我误解了什么,请原谅,但是当设备旋转时,您会丢失对某些内容的引用。

刺一下...

会添加

android:configChanges="orientation|keyboardHidden|screenSize"
Run Code Online (Sandbox Code Playgroud)

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