【问题标题】:Loader restarts on orientation change装载机在方向改变时重新启动
【发布时间】:2017-09-10 00:29:15
【问题描述】:

http://developer.android.com/guide/components/loaders.html 找到的加载器的 Android 文档中,它说加载器的属性之一是:

在配置更改后重新创建它们时,它们会自动重新连接到最后一个加载器的光标。因此,他们不需要重新查询他们的数据。

以下代码似乎没有反映这种行为,创建了一个新的加载器并完成了对 ContentResolver 的查询,然后我旋转屏幕并重新创建了加载器!

public class ReportFragment extends Fragment implements LoaderCallbacks<Cursor> {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getLoaderManager().initLoader(1, null, this);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.fragment_report, container, false);
        return v;
    }

    public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
        Log.d("TEST", "Creating loader");
        return new CursorLoader(getActivity(), ResourcesContract.Reports.CONTENT_URI, null, null, null, null);
    }

    public void onLoadFinished(Loader<Cursor> arg0, Cursor arg1) {
        Log.d("TEST", "Load finished");
    }

    public void onLoaderReset(Loader<Cursor> arg0) {

    }

}

这是我的 logcat 的输出:

08-17 16:49:54.474: D/TEST(1833): Creating loader
08-17 16:49:55.074: D/TEST(1833): Load finished
*Here I rotate the screen*
08-17 16:50:38.115: D/TEST(1833): Creating loader
08-17 16:50:38.353: D/TEST(1833): Load finished

知道我在这里做错了什么吗?

编辑:

我应该注意,我正在构建 Android Google API 版本 8,并使用 v4 支持库。

第二次编辑:

这很可能是由于支持库中的错误,如果您想了解更多信息,请查看此错误提交:

http://code.google.com/p/android/issues/detail?id=20791&can=5&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars

【问题讨论】:

  • 查看这篇文章了解更多信息:androiddesignpatterns.com/2012/08/implementing-loaders.html
  • 另外,您应该在 onActivityCreated 中调用 getLoaderManager()... 如果首次创建 FragmentActivity 仍然是 null,您当前的代码将抛出 IllegalStateException .
  • @AlexLockwood,在您的示例中(干得好!)您使用的是 support-v4:18.0.0。当前版本是 23.0.1,它包含这个错误。我测试的最新工作版本是 22.0.0。
  • 我将它作为错误提交:code.google.com/p/android/issues/detail?id=187391 支持的最新版本是 22.2.1。

标签: android


【解决方案1】:

虽然这是一个老问题,但我遇到了与 OP 相同的问题。使用加载器,我需要在导航到新活动时重新启动它,然后再返回。但同时,我不希望加载器在我旋转手机屏幕时重新启动。

我发现可以在 onRestart() 中实现这一点,如果你在调用它的 super 之前重新启动加载器。

public class MainActivity extends AppCompatActivity implements
LoaderManager.LoaderCallbacks<Cursor> {

    ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ...

        //Initialize the loader.
        getSupportLoaderManager().initLoader(0, null, this);
    }

    @Override
    protected void onRestart() {
        //Restart the loader before calling the super.
        getSupportLoaderManager().restartLoader(LOADER_ID, null, this);

        super.onRestart();
    }

    ...

}

【讨论】:

  • +1 谢谢 :) 这帮助我解决了我的问题 :) 我正在使用 AdapterViewFlipper 并按照您的建议管理我的加载器,翻转器不再受屏幕旋转的影响 :)
【解决方案2】:

在我看来,您误解了文档的内容。文档说,他们不需要重新查询他们的数据,而且它没有这样做。

尝试在ContentProvider#query() 方法中记录/插入断点!该查询将仅在 Activity 启动时调用,而不是在方向更改后调用。

但这不适用于LoaderCallbacks#onCreateLoader() 方法。每次方向改变后都会调用它,但这并不意味着重新查询,它只是调用方法,以便您可以根据需要更改 CursorLoader。

【讨论】:

  • 是的。每次旋转都会创建一个新的 CursorLoader 实例(一个相当轻量级的操作),但不会创建一个新的 Cursor 实例(这可能需要一个重量级的数据库查询)。对光标的引用保持不变,新的加载程序“重新连接”到它。
【解决方案3】:

到目前为止,我发现保留片段 Fragment.setRetainInstance(true) 可以防止使用支持库在方向更改时重新创建加载器。加载程序的最后结果很好地在onLoadFinished() 中提供。它至少在活动管理单个片段并且使用FragmentTransaction 将片段添加到活动时有效。

【讨论】:

    【解决方案4】:

    虽然这是一个有点老的问题,但我想在这里发表我的看法。

    不需要在 onSaveInstanceState 中存储额外的信息

    在配置更改后重新创建时,框架会自动重新连接到最后一个加载器的光标。因此,他们不需要重新查询他们的数据。

    这意味着在 onCreate 函数中,只有当 savedInstanceState 为 null 时才需要调用 loaderManager

    例如:

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if(savedInstanceState == null) {
            getLoaderManager().initLoader(1, null, this);
        }
    }
    

    【讨论】:

      【解决方案5】:

      您可以简单地检查加载程序是否已经存在 onCreate。然后您可以初始化或重新启动。

      @Override
      public void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          if (getLoaderManager().getLoader(LOADER_ID) == null) {
              getLoaderManager().initLoader(LOADER_ID, null, this);
          } else {
              getLoaderManager().restartLoader(LOADER_ID);
          }
      }
      

      您通常会将 ID 传递给加载程序,以便稍后通过加载程序管理器引用它。

      希望这会有所帮助!

      【讨论】:

      • 嗨尼尔森,如果已经存在加载程序,这将重新启动加载程序,这违背了它的目的。即使您要切换两个语句(init 和 restart),它也会导致与首先调用 initLoader 相同的行为!
      • getLoaderManager().restartLoader(LOADER_ID);与整个“if() {} else {}”语句完全相同,因为如果 Loader 尚不存在,restartLoader 的作用完全相同。
      【解决方案6】:

      onCreate() 在屏幕方向更改期间被调用,因为活动被销毁并重新创建。

      除非您要加载大量数据,否则这样做并没有什么坏处,但是如果您愿意,可以尝试以下方法(我还没有测试过,但理论上我认为它会起作用)。

      在类的顶部全局声明一个静态布尔值。我认为你还需要一个静态光标来引用

      private static boolean dataDownloaded = false;
      private static Cursor oldCursor;
      

      然后onLoadFinished设置dataDownloaded = true

      重写 onSaveInstanceState 以保存布尔值

      @Override
      protected void onSaveInstanceState(Bundle outSave) {
          outSave.putBoolen("datadownloaded", dataDownloaded);
          oldCursor = adapter.swapCursor(null);
      }
      

      和 onCreate 添加以下内容

      if (savedInstanceState != null) {
          this.dataDownloaded = savedInstanceState.getBoolean("datadownloaded", false);
      }
      

      调整你的 onCreateLoader

      public Loader<Cursor> onCreateLoader(int id, Bundle args) {
          CursorLoader cursorLoader;
          if (dataDownloaded) {
              cursorLoader = new CursorLoader(getActivity(),
                  null, projection, null, null, null);
              cursorLoader.deliverResult(oldCursor);
          } else {
              CursorLoader cursorLoader = new CursorLoader(getActivity(),
                  URI_PATH, projection, null, null, null);
          }
      
          return cursorLoader;
      }
      

      【讨论】:

      • 这不会以任何方式查询 ContentResolver(不管 dataDownloaded 是真还是假,仍然会创建一个新的 CursorLoader)?无论如何,我认为这实际上是由于支持库的一个错误而发生的,有关更多信息,请参阅此帖子:groups.google.com/forum/?fromgroups#!topic/android-developers/…
      • 我调整了它..如果数据已经下载,它应该为 URI 路径为“null”
      • 现在说得通了。好吧,据我所知,这是一个解决方案,因此我将其标记为一个。谢谢你的回答。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多