方向更改时的LoaderManager行为

时间:2014-04-25 18:59:59

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

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

hierarchy

ViewPager中的3个片段包含FrameLayout:

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

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

  1. 在横向模式下停止应用程序,按主页按钮。
  2. 更改为纵向模式,然后再次启动应用程序。(实际上,如果我留在横向中,错误仍然存​​在,因为当我恢复应用程序时,Android会以纵向方式重新创建活动,然后旋转它。但是因为我&#39 ;我将向您展示LOG,让我们避免记录一个额外的生命周期。)
  3. 当前一种情况发生时。第一个和第二个片段保留在loadingSpinner中,从不显示列表视图。让我们看看Fragment1的代码(第二个片段几乎相同):

    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的Loader不存在,它被创建。 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的层次结构,但片段实例保持不变(MainFragment使用setRetainInstance(true))。重新创建MainActivity,附加MainFragment,再次创建FragmentOne的视图层次结构,并使用相同的Loader id = 1填充ListView,它已经存在,因此只调用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
    

    活动和片段生命周期完成。重新创建活动,MainFragment附加到它。 FragmentOne的视图层次结构已创建。但这一次,LoaderManager不再包含id = 1的Loader。执行initLoader时,调用onCreateLoader()(它接收的&#34; id&#34;参数为1),但不调用onLoadFinished()并且loadSpinner保持可见。

    从日志中可以比较第一次执行应用程序时(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(ini​​tLoader)第一次返回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;
    

1 个答案:

答案 0 :(得分:3)

好的,我发现了类似的案例:Loader restarts on orientation change

它似乎是SupportLibrary中的一个错误,与我对嵌套Fragments的实现有关。

为了使它工作,我不得不将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<Cursor>接口:

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方法的访问权限:

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

基本上就是这样。