【问题标题】:LoaderManager behavior when Orientation Changes方向改变时的 LoaderManager 行为
【发布时间】:2014-04-25 18:59:59
【问题描述】:

这是我的应用程序的层次结构:

ViewPager 内的 3 个片段包含一个 FrameLayout:

  • 加载微调器,在搜索数据以填充 ListView 时可见。
  • 显示消息的 LinearLayout,在未找到数据时可见。
  • 还有一个 ListView。

第一个和第二个片段使用 CursorLoader 从 ContentProvider 获取数据。并且一切正常,除非出现以下情况:

  1. 在横向模式下按主页按钮停止应用。
  2. 更改为纵向模式,然后重新启动应用程序。(实际上,如果我保持横向,错误仍然存​​在,因为当我恢复应用程序时,Android 会重新创建纵向活动,然后旋转它。但由于我是将向您显示 LOG,让我们避免记录一个额外的生命周期)。

当前面的情况发生时。第一个和第二个片段留在 loadingSpinner 中,从不显示列表视图。让我们看看 Fragment1 的代码(第二个 Fragment 几乎相同):

public class FragmentOne extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> {

private LinearLayout emptyMsgContainer;
private ListView listView;
private ProgressBar loadingSpinner;
private Details mActivity;

FragmentOneListAdapter mAdapter;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    Log.d("onCreateView()", "FragmentOne");
    View view = inflater.inflate(R.layout.fragment_one_list_fragment, container, false);
    emptyMsgContainer = (LinearLayout)view.findViewById(R.id.empty_message_container_1);
    listView = (ListView)view.findViewById(R.id.listView_1);
    loadingSpinner = (ProgressBar)view.findViewById(R.id.loading_spinner_1);

    return view;
}

@Override
public void onActivityCreated(Bundle icicle) {
    super.onActivityCreated(icicle);
    Log.d("onActivityCreated()", "FragmentOne");

    mAdapter = new FragmentOneListAdapter(getActivity(), null, 0);

    listView.setAdapter(mAdapter);
    listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
    listView.setOnItemClickListener(mListListener);

    // executes initLoader and logs at the same time
    Log.d("onActivityCreated()", getActivity().getSupportLoaderManager().initLoader(1, null, this).toString());
}

@Override
public void onResume() {
    super.onResume();
    Log.d("onResume()", "FragmentOne");
    // used to communicate direclty with the MainActivity
    MainFragment parentFragment = (MainFragment)
            getActivity().getSupportFragmentManager().findFragmentByTag("MAIN_FRAGMENT");
    mActivity = (Details)parentFragment.getDetailsListener();
}

@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        Uri uri = ...;  
        String selection = "...";
        String[] selectionArgs = new String[] { ... };
        CursorLoader loader =  new CursorLoader(getActivity(), uri, null, selection, selectionArgs, null);
        Log.d("onCreateLoader()", loader.toString());
        return loader;
}

@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
            Log.d("onLoadFinished()", loader.toString());
    if(data.getCount() == 0) {
        loadingSpinner.setVisibility(View.GONE);
        listView.setVisibility(View.GONE);
        emptyMsgContainer.setVisibility(View.VISIBLE);  
    } else {
        mAdapter.swapCursor(data);
        myCursor = data;
        loadingSpinner.setVisibility(View.GONE);
        listView.setVisibility(View.VISIBLE);
        emptyMsgContainer.setVisibility(View.GONE);
    }
}

@Override
public void onLoaderReset(Loader<Cursor> loader) {
    mAdapter.swapCursor(null);
    myCursor = null;
}

private OnItemClickListener mListListener = new OnItemClickListener() {

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        mActivity.ShowStoredDetails(position, 1);
    }

};

}

我记录了 Activity 和 Fragment 的生命周期回调、onCreateLoader 和 onLoadFinished 以试图弄清楚发生了什么。首先让我们纵向打开应用:

23:04:00.089: D/onCreate()(8240): <!> ...  21<!> MainActivity
23:04:00.139: D/onAttach()(8240): <!> ...  61<!> MainFragment
23:04:00.139: D/onCreate()(8240): <!> ...  68<!> MainFragment
23:04:00.139: D/onCreateView()(8240): <!> ...  75<!> MainFragment
23:04:00.169: D/onActivityCreated()(8240): <!> ...  95<!> MainFragment
23:04:00.169: D/onStart()(8240): <!> ...  101<!> MainFragment
23:04:00.169: D/onStart()(8240): <!> ...  31<!> MainActivity
23:04:00.169: D/onResume()(8240): <!> ...  41<!> MainActivity
23:04:00.169: D/onResume()(8240): <!> ...  107<!> MainFragment
23:04:00.179: D/onattach()(8240): <!> ...  51<!> FragmentOne
23:04:00.189: D/onCreate()(8240): <!> ...  57<!> FragmentOne
23:04:00.189: D/onCreateView()(8240): <!> ...  62<!> FragmentOne
23:04:00.199: D/onActivityCreated()(8240): <!> ...  74<!> FragmentOne
23:04:00.199: D/onCreateLoader()(8240): <!> ...  146<!> CursorLoader{405c0e50 id=0}
23:04:00.209: D/onActivityCreated()(8240): <!> ...  83<!> CursorLoader{405c0e50 id=1}
23:04:00.209: D/onStart()(8240): <!> ...  90<!> FragmentOne
23:04:00.209: D/onResume()(8240): <!> ...  96<!> FragmentOne
23:04:00.219: D/onattach()(8240): <!> ...  50<!> FragmentTwo
23:04:00.219: D/onCreate()(8240): <!> ...  56<!> FragmentTwo
23:04:00.219: D/onCreateView()(8240): <!> ...  61<!> FragmentTwo
23:04:00.229: D/onActivityCreated()(8240): <!> ...  73<!> FragmentTwo
23:04:00.239: D/onStart()(8240): <!> ...  88<!> FragmentTwo
23:04:00.259: D/onResume()(8240): <!> ...  94<!> FragmentTwo
23:04:00.479: D/onLoadFinished()(8240): <!> ...  153<!> CursorLoader{405c0e50 id=1}

id=1 的加载器不存在,它被创建了。 onCreateLoader() 被调用,然后是 onLoadFinished()。 ListView 已填充并且工作正常。现在让我们旋转到横向:

23:04:26.999: D/onSaveInstanceState()(8240): <!> ...  113<!> MainFragment
23:04:27.009: D/onSaveInstanceState()(8240): <!> ...  105<!> FragmentOne
23:04:27.009: D/onSaveInstanceState()(8240): <!> ...  103<!> FragmentTwo
23:04:27.009: D/onPause()(8240): <!> ...  111<!> FragmentOne
23:04:27.009: D/onPause()(8240): <!> ...  109<!> FragmentTwo
23:04:27.009: D/onPause()(8240): <!> ...  119<!> MainFragment
23:04:27.009: D/onPause()(8240): <!> ...  46<!> MainActivity
23:04:27.019: D/onStop()(8240): <!> ...  117<!> FragmentOne
23:04:27.019: D/onStop()(8240): <!> ...  115<!> FragmentTwo
23:04:27.019: D/onStop()(8240): <!> ...  125<!> MainFragment
23:04:27.019: D/onStop()(8240): <!> ...  51<!> MainActivity
23:04:27.019: D/onDestroyView()(8240): <!> ...  123<!> FragmentOne
23:04:27.019: D/onDestroyView()(8240): <!> ...  121<!> FragmentTwo
23:04:27.029: D/onDestroyView()(8240): <!> ...  131<!> MainFragment
23:04:27.029: D/onDetach()(8240): <!> ...  143<!> MainFragment
23:04:27.039: D/onDestroy()(8240): <!> ...  56<!> MainActivity
23:04:27.069: D/onAttach()(8240): <!> ...  61<!> MainFragment
23:04:27.079: D/onCreate()(8240): <!> ...  21<!> MainActivity
23:04:27.169: D/onCreateView()(8240): <!> ...  75<!> MainFragment
23:04:27.199: D/onActivityCreated()(8240): <!> ...  95<!> MainFragment
23:04:27.199: D/onCreateView()(8240): <!> ...  62<!> FragmentOne
23:04:27.209: D/onActivityCreated()(8240): <!> ...  74<!> FragmentOne
23:04:27.209: D/onActivityCreated()(8240): <!> ...  83<!> CursorLoader{405c0e50 id=1}
23:04:27.219: D/onCreateView()(8240): <!> ...  61<!> FragmentTwo
23:04:27.239: D/onActivityCreated()(8240): <!> ...  73<!> FragmentTwo
23:04:27.239: D/onStart()(8240): <!> ...  101<!> MainFragment
23:04:27.239: D/onStart()(8240): <!> ...  90<!> FragmentOne
23:04:27.249: D/onStart()(8240): <!> ...  88<!> FragmentTwo
23:04:27.249: D/onLoadFinished()(8240): <!> ...  153<!> CursorLoader{405c0e50 id=1}
23:04:27.249: D/onStart()(8240): <!> ...  31<!> MainActivity
23:04:27.259: D/onResume()(8240): <!> ...  41<!> MainActivity
23:04:27.259: D/onResume()(8240): <!> ...  107<!> MainFragment
23:04:27.259: D/onResume()(8240): <!> ...  96<!> FragmentOne
23:04:27.259: D/onResume()(8240): <!> ...  94<!> FragmentTwo

MainActivity 被销毁,并且还查看 Fragments 的层次结构,但 Fragment 实例保持不变(MainFragment 使用 setRetainInstance(true))。 MainActivity 被重新创建,MainFragment 附加到它,再次创建 FragmentOne 的视图层次结构,ListView 填充相同的 Loader id=1,它已经存在,所以只调用 onLoadFinished()。现在让我们停止按主页按钮的应用:

23:04:59.639: D/onSaveInstanceState()(8240): <!> ...  113<!> MainFragment
23:04:59.649: D/onSaveInstanceState()(8240): <!> ...  105<!> FragmentOne
23:04:59.649: D/onSaveInstanceState()(8240): <!> ...  103<!> FragmentTwo
23:04:59.649: D/onPause()(8240): <!> ...  111<!> FragmentOne
23:04:59.649: D/onPause()(8240): <!> ...  109<!> FragmentTwo
23:04:59.649: D/onPause()(8240): <!> ...  119<!> MainFragment
23:04:59.659: D/onPause()(8240): <!> ...  46<!> MainActivity
23:05:00.059: D/onStop()(8240): <!> ...  117<!> FragmentOne
23:05:00.069: D/onStop()(8240): <!> ...  115<!> FragmentTwo
23:05:00.069: D/onStop()(8240): <!> ...  125<!> MainFragment
23:05:00.069: D/onStop()(8240): <!> ...  51<!> MainActivity

一切都停止了。最后让我们继续应用:

23:05:47.489: D/onDestroyView()(8240): <!> ...  123<!> FragmentOne
23:05:47.489: D/onDestroyView()(8240): <!> ...  121<!> FragmentTwo
23:05:47.499: D/onDestroyView()(8240): <!> ...  131<!> MainFragment
23:05:47.499: D/onDetach()(8240): <!> ...  143<!> MainFragment
23:05:47.499: D/onDestroy()(8240): <!> ...  56<!> MainActivity
23:05:47.509: D/onAttach()(8240): <!> ...  61<!> MainFragment
23:05:47.509: D/onCreate()(8240): <!> ...  21<!> MainActivity
23:05:47.539: D/onCreateView()(8240): <!> ...  75<!> MainFragment
23:05:47.569: D/onActivityCreated()(8240): <!> ...  95<!> MainFragment
23:05:47.569: D/onCreateView()(8240): <!> ...  62<!> FragmentOne
23:05:47.579: D/onActivityCreated()(8240): <!> ...  74<!> FragmentOne
23:05:47.579: D/onCreateLoader()(8240): <!> ...  146<!> CursorLoader{40540548 id=0}
23:05:47.579: D/onActivityCreated()(8240): <!> ...  83<!> CursorLoader{40540548 id=0}
23:05:47.579: D/onCreateView()(8240): <!> ...  61<!> FragmentTwo
23:05:47.589: D/onActivityCreated()(8240): <!> ...  73<!> FragmentTwo
23:05:47.589: D/onStart()(8240): <!> ...  101<!> MainFragment
23:05:47.589: D/onStart()(8240): <!> ...  90<!> FragmentOne
23:05:47.599: D/onStart()(8240): <!> ...  88<!> FragmentTwo
23:05:47.599: D/onStart()(8240): <!> ...  31<!> MainActivity
23:05:47.599: D/onResume()(8240): <!> ...  41<!> MainActivity
23:05:47.599: D/onResume()(8240): <!> ...  107<!> MainFragment
23:05:47.599: D/onResume()(8240): <!> ...  96<!> FragmentOne
23:05:47.599: D/onResume()(8240): <!> ...  94<!> FragmentTwo

Activity 和 Fragments 生命周期完成。 Activity 被重新创建,MainFragment 附加到它。 FragmentOne 的视图层次结构被创建。但这一次,LoaderManager 不再包含 id=1 的 Loader。执行 initLoader 时,会调用 onCreateLoader()(它接收到的“id”参数为 1),但不会调用 onLoadFinished(),loadingSpinner 保持可见。

从日志中可以对比应用第一次执行的时间(Loader id=1 不存在)...

onActivityCreated()(8240): <!> ...  83<!> CursorLoader{405c0e50 id=1}
onCreateLoader()(8240): <!> ...  146<!> CursorLoader{405c0e50 id=0}
onLoadFinished()(8240): <!> ...  153<!> CursorLoader{405c0e50 id=1}

...第二次加载器 id=1 不存在:

onActivityCreated()(8240): <!> ...  83<!> CursorLoader{40540548 id=0}
onCreateLoader()(8240): <!> ...  146<!> CursorLoader{40540548 id=0}

第一次onActivityCreated(initLoader)返回一个id=1的Loader,第二次返回id=0。我只能假设这就是第二次没有调用 onLoadFinished() 的原因。据我所知,当方向改变时,LoaderManager 应该保持其状态。对这里发生的事情有任何想法吗?

编辑

我应该提到我正在使用支持库:

import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;

【问题讨论】:

    标签: android fragment android-loadermanager android-cursorloader orientation-changes


    【解决方案1】:

    好的,我找到了类似的案例:Loader restarts on orientation change

    这似乎是 SupportLibrary 中的一个错误,与我实现嵌套片段有关。

    为了使它工作,我不得不将LoaderCallbacks 接口和initLoader() 的位置从FragmentOne 和FragmentTwo 更改为MainActivity。这有点乱,因为我必须创建一些接口,但它确实有效。

    如果有人遇到这种情况,我会解释:

    首先,我创建了两个接口:

    ListenerFragments 接口,在 MainActivity 中实现,并从 FragmentOne 和 FragmentTwo 用于在 MainActivity 中将自己注册为将要使用加载器的片段:

    public interface ListenerFragments {
        public void setFragmentOne(FragmentsUICallbacks callbacks);
        public void setFragmentTwo(FragmentsUICallbacks callbacks);
        public void prepareLoader(int id);
    }
    

    第二个接口,在 FragmentOne 和 FragmentTwo 中实现。并包含将更改 Fragment 的 UI、交换光标并使 FrameLayout 子代(ListViewLoadingSpinner...)可见或不可见的方法。另外,这是我们要传递给 MainActivity 的setFragmentOne()setFragmentTwo() 的接口,所以它可以在调用onLoadFinished()onLoaderReset() 时修改UI:

    public interface FragmentsUICallbacks {
            public void emptyCursor();
            public void assignCursor(Cursor data);
        public void clearCursorReferences();
    }
    

    MainActivity 正在实现ListenerFragmentsLoaderCallbacks&lt;Cursor&gt; 接口:

    public class MainActivity extends ActionBarActivity implements LoaderManager.LoaderCallbacks<Cursor>, ListenerFragments {
        private FragmentsUICallbacks fragmentOneCallbacks;
        private FragmentsUICallbacks fragmentTwoCallbacks;
    
        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
            Uri uri;
            String selection;
            String[] selectionArgs;
            switch(id) {
                case 1:
                    uri = ...;  
                    selection = "...";
                    selectionArgs = new String[] { ... };
                    return new CursorLoader(this, uri, null, selection, selectionArgs, null);
                case 2:
                    ...
            }
            return null;
        }
    
        @Override
        public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
            switch(loader.getId()) {
                case 1:
                    if(data.getCount() == 0) {
                        fragmentOneCallbacks.emptyCursor(); 
                    } else {
                        fragmentOneCallbacks.assignCursor(data);
                    }
                    break;
                case 2:
                    ...
                }
        }
    
        @Override
        public void onLoaderReset(Loader<Cursor> loader) {
            switch(loader.getId()) {
                case 1:
                    fragmentOneCallbacks.clearCursorReferences();
                    break;
                case 2:
                    ...
            }
        }
    
        @Override
        public void setFragmentOne(FragmentsUICallbacks callbacks) {
            if(callbacks != null)
                this.fragmentOneCallbacks = callbacks;
        }
    
        @Override
        public void setFragmentTwo(FragmentsUICallbacks callbacks) {
            if(callbacks != null)
                this.fragmentTwoCallbacks = callbacks;
        }
    
        @Override
        public void prepareLoader(int id) {
            getSupportLoaderManager().initLoader(id, null, this);
        }
    }
    

    代码非常简单。棘手的部分来自 FragmentOne 的onResume()

    public class FragmentOne extends Fragment implements FragmentsUICallbacks {
    
        ...
    
        @Override
        public void onResume() {
            super.onResume();
            MainFragment parentFragment = (MainFragment)
                getActivity().getSupportFragmentManager().findFragmentByTag("MAIN_FRAGMENT");
    
            ListenerFragments listenerFragments = (ListenerFragments)parentFragment.getListenerFragments();
            listenerFragments.setFragmentOne(this);
            listenerFragments.prepareLoader(1);
        }
    
        public void emptyCursor() {
            loadingSpinner.setVisibility(View.GONE);
            listView.setVisibility(View.GONE);
            emptyMsgContainer.setVisibility(View.VISIBLE);  
        }
    
        public void assignCursor(Cursor data) {
            mAdapter.swapCursor(data);
            myCursor = data;
            loadingSpinner.setVisibility(View.GONE);
            listView.setVisibility(View.VISIBLE);
            emptyMsgContainer.setVisibility(View.GONE);
        }
    
        public void clearCursorReferences() {
            mAdapter.swapCursor(null);
            myCursor = null;
        }
    
    }
    

    我们需要获取 MainActivity 正在实现的ListenerFragment 接口方法的引用,以便通知它 FragmentOne 将启动一个加载器。我们通过 MainFragment 得到那个引用,为什么?因为我们不能直接从FragmentOne.onAttach(Activity activity) 获取它,因为它只在应用程序第一次启动时被调用,并且片段既不被破坏也不分离,当方向改变时片段从onDestroyView() 变为onCreateView()onAttach() 未被调用。

    另一方面,MainFragment 也没有被销毁 (setRetainInstance(true)),但它与旧的 MainActivity 分离,并在方向更改完成时再次附加到新的 MainActivity。我们使用onAttach() 来保存引用并创建一个getter 方法,以便ViewPager 中的片段可以获取该引用:

    public class MainFragment extends Fragment implements OnClickListener {
    
        private ListenerFragments listenerFragments;
    
        @Override
        public void onAttach(Activity myActivity) {
            super.onAttach(myActivity);
            this.listenerFragments = (ListenerFragments)myActivity;
        }
    
        public ListenerFragments getListenerFragments() {
                return listenerFragments;
        }
    
    }
    

    知道了这一点,我们可以回到FragmentOne.onResume(),在那里我们得到一个对 MainFragment 的引用:

    MainFragment parentFragment = (MainFragment)
        getActivity().getSupportFragmentManager().findFragmentByTag("MAIN_FRAGMENT");
    

    我们使用我们创建的 MainFragment getter 方法来获取对 MainActivity 方法的 get 访问:

        ListenerFragments listenerFragments = (ListenerFragments)parentFragment.getListenerFragments();
        listenerFragments.setFragmentOne(this);
        listenerFragments.prepareLoader(1);
    

    基本上就是这样。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-03-20
      • 2023-03-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-06
      • 1970-01-01
      相关资源
      最近更新 更多