【问题标题】:How to avoid recreating view on onCreate on Android?如何避免在 Android 上的 onCreate 上重新创建视图?
【发布时间】:2013-12-04 20:35:16
【问题描述】:

我有一个显示联系人列表的FragmentActivity

这是我的onCreate 方法:

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

    if (findViewById(R.id.human_detail_container) != null) {
        // The detail container view will be present only in the
        // large-screen layouts (res/values-large and
        // res/values-sw600dp). If this view is present, then the
        // activity should be in two-pane mode.
        mTwoPane = true;

        // In two-pane mode, list items should be given the
        // 'activated' state when touched.
        ((HumanListFragment) getSupportFragmentManager()
                .findFragmentById(R.id.human_list))
                .setActivateOnItemClick(true);
    }

    if (savedInstanceState == null || !savedInstanceState.getBoolean("displayed_contacts"))
        displayContacts();
}

我的onSaveInstanceState

@Override
protected void onSaveInstanceState(Bundle savedInstanceState) {
    super.onSaveInstanceState(savedInstanceState);
    savedInstanceState.putBoolean("displayed_contacts", true);
}

我不确定这是否相关,但这是我的displayContacts 以防万一:

private void displayContacts() {

    // Init variables
    String[] SelectColumns = new String[] { Contacts._ID, Contacts.DISPLAY_NAME_PRIMARY, Contacts.PHOTO_URI };
    String rawContactID, displayName, phoneNumber;
    InputStream thumbnailPhoto;
    Cursor c, infoC;

    // Outer cursor (fetches all contact IDs)
    c = getContentResolver().query(
            Contacts.CONTENT_URI,
            SelectColumns,
            Contacts.HAS_PHONE_NUMBER + " = 1 ",
            null,
            Contacts.DISPLAY_NAME_PRIMARY);

    Log.v(getPackageName(), "Found " + (c != null ? c.getCount() : "0") + " contacts");
    try {
        if (c.moveToFirst()) {
            do {
                // Columns
                rawContactID    = c.getString(c.getColumnIndex(SelectColumns[0]));
                displayName     = c.getString(c.getColumnIndex(SelectColumns[1]));
                String[] selectPhone = {CommonDataKinds.Phone.NUMBER};

                thumbnailPhoto = openThumbnail(Long.valueOf(rawContactID));

                infoC = getContentResolver().query(
                        CommonDataKinds.Phone.CONTENT_URI,
                        selectPhone,
                        CommonDataKinds.Phone.CONTACT_ID + " = ?",
                        new String[] {rawContactID},
                        null
                    );
                infoC.moveToFirst();
                phoneNumber = infoC.getString(0);

                // Adds items to ListView
                HumanContent.addItem(new HumanContent.HumanItem(rawContactID, displayName, phoneNumber != "n/a" ? phoneNumber : "", thumbnailPhoto));
                Log.v(getPackageName(), "Cursor position: " + c.getPosition() + ", contact ID: " + rawContactID);
                infoC.close();
            } while (c.moveToNext());
            c.close();
        }
        displayed_contacts = true;
    } catch (Exception e) {
        Log.e(getPackageName(), e.getMessage());
    }
}

事情是这样的:

当我使用返回键退出应用程序,然后通过图标再次打开它时;该列表会重新创建自己,即使它保存在内存中:所以我在同一个视图上得到了一个双重联系人列表。

savedInstanceState 在这种情况下为 null,因此达到了 if 条件,但实际上视图已经有我以前的联系人列表。是什么赋予了?如何避免重新创建列表?我已经尝试过使用实例变量,但无济于事。

我还想避免 100% 地重新创建列表 - 如果我可以重用现有视图,那就太棒了。

【问题讨论】:

  • 使用返回键退出应用程序实际上会破坏应用程序 - 它不会暂停它。
  • 是的,但随后视图状态以某种方式恢复 - 我再次打开应用程序,联系人列表已存在其中,然后我继续重新创建,因为 savedInstanceState 为空。
  • 不能保证@ninetwozero
  • 你能检查一下列表的内容吗?如果存在联系人,请不要打扰加载等。
  • 那么,我怎样才能访问该列表?我正在用适配器和片段加载它

标签: android


【解决方案1】:

首先,您的 savedInstanceState 为 null 的原因 - 系统仅保存由于系统限制而被破坏的活动的状态。如果您退出 Activity,它会被永久销毁,并且不会保存任何状态。

相关文档: http://developer.android.com/training/basics/activity-lifecycle/recreating.html

当您的活动因用户按返回或 Activity 自己完成,系统对该Activity 的概念 实例永远消失了,因为行为表明活动 不再需要。但是,如果系统破坏了活动由于 到系统约束(而不是正常的应用程序行为),然后虽然 实际的 Activity 实例消失了,系统会记住它 存在这样,如果用户导航回它,系统会创建 使用一组已保存数据的活动的新实例 描述活动被销毁时的状态。

因此,您的特殊问题似乎是,当您的 Activity 消失时,您的静态 HumanContact 类仍在内存中,并且您的新 Activity 正在加载您的联系人的另一个副本。

有几种方法可以解决这个问题。首先,您可以在 HumanContent 上实现一个方法来清除其所有项目,并在您启动 Activity 的新实例时调用它。这将有利于确保您的数据是最新的,但意味着您必须重新加载您的联系人。

其次,如果您想真正避免重新加载数据,我建议您为独立于 Activity 的联系人创建某种缓存。您应该将您的 Activity 视为相当短暂的,它可以并且将被频繁地销毁和重新创建,而缓存可以持续存在。

在某种程度上,HumanContent 似乎已经在履行这一职责。您正在使用它来存储您的数据,并且它会在您的 Activity 生命周期之外持续存在。您可以另外添加一个方法来检查它是否加载了联系人,如果没有加载它们本身。这样它就可以完全控制加载和缓存数据,并且活动可以单独负责显示这些数据。请注意这种类型的解决方案,您不会在内存中存储太多数据,只要您希望数据发生更改就重新加载缓存,并注意在某些情况下您的进程可能会被系统重新启动,因此您的缓存必须准备好重新加载数据,以防数据被破坏。

至于保留您的视图,如果用户退出您的 Activity,则将调用 finish(),并且您的 Activity 将被销毁。请记住,在这种情况下,系统不再有任何 Activity 的概念,因此将无法保留这些视图以供重用。

【讨论】:

  • 感谢您的回复。那么我将如何进行缓存呢?还有可能,由于HumanContent 是自己保存的,如果我在那里放置一个类变量来检查是否已加载联系人,这可能意味着虽然数据本身被保留,但我的变量也会被保留,所以我可以使用它对我有利?
  • 您的缓存可能采用数组的形式,其中包含存储在 HumenContent 中的 HumenItem 对象。我会在 HumenContent 上创建一个方法,当调用该方法时,会检查其 HumenItems 数组以查看它是否为空,以及是否加载联系人,否则什么也不做。然后我会在每次启动时从我的 Activity 调用这个方法。这样,您的联系人将保留在 HumenContent 中,可供 Activity 使用,但不会重新创建。
  • 要回答您的第二个问题,是的,您可以在 HumanContent 上创建一个布尔变量来存储联系人是否已加载。
【解决方案2】:

很简单。在您的活动中按照这些步骤操作

  • 在 Activity 类中声明您的 listview 对象listview_adapter 对象
  • OnCreate() 方法中初始化您的 listview 对象listview_adapter 对象,并在 displayContacts() 中添加内容。

那么你肯定不会得到内容复制。

【讨论】:

  • 我会注意到,由于在您的特定情况下,Activity 以及 Fragment 都被销毁,因此在 Activity 上缓存您的视图和适配器不会解决您的问题,除非它们是静态存储的。在 Activity 上缓存它们相当于在片段上调用 ​​setRetainInstance(true) - 它仅在重新创建 Activity 时有效,而不是在从头开始创建新实例时有效。
【解决方案3】:

始终建议使用缓存来保留数据。正如 Groucho 所提到的,您可以使用存储选项 详情请查看以下链接。

http://developer.android.com/guide/topics/data/data-storage.html

【讨论】:

    【解决方案4】:

    我曾经遇到过类似的问题。我不确定这是多么合法,但我使用的代码如下,它修复了很多:

    private static ViewGroup view1;
    @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            mCurrentActivity = getActivity();
            if (view1 == null) {
                settleFragment(inflater);
            } else {
                if (view1.getParent() != null && view1.getParent() instanceof ViewGroup) {
                    ((ViewGroup) view1.getParent()).removeView(view1);
                }
            }
            return view1;
        }
    

    您可以使用您的方法调整settleFragment(inflater); 方法。 神奇之处在于名为view1 的静态变量。一旦片段被初始化,它就会被数据填充。 Fragments 的其他实例将简单地将 view1 从其前父级撕开并将其粘贴到自己身上。

    由于对象保存在内存中,因此文件存储缓存非常快。如果操作系统垃圾收集对象,静态变量将再次为空,并且在重新创建视图时不会发生重复。

    【讨论】:

      【解决方案5】:

      您无法阻止 onCreate 函数的执行。但是,您可以应用解决方法来避免重复列表,您可以从列表视图中删除所有项目,然后让它重新填充列表视图。

      【讨论】:

        【解决方案6】:
        @Override
        public void onPause ()
        {
            super.onPause();
            //...........
        }
        
        @Override
        public void onStop ()
        {
            super.onPause();
            //............
        }
        

        检查是否可以在片段中使用这些方法?

        【讨论】:

          【解决方案7】:

          实现此目的最简单的方法是执行 Nullcheck

          if(yourcomponent==null) {
              initializecomponent();
          }
          


          这可能会阻止您的视图重新创建。

          【讨论】:

            【解决方案8】:

            当您在 displayContacts() 中加载完数据后,您可以在共享首选项中保存一个标志 (displayed_contacts)。在 onCreate() 检查首选项值,并相应地调用您的 displayContacts()。

            【讨论】:

            • 但是当活动完全终止时,该值将保持为真。
            • 你可以实现 ondestroy() 方法,在那里你可以重置标志。
            【解决方案9】:

            我在使用 Fragments 时遇到了同样的问题。

            如上所述,我们有现有的方法。如果您发现自己在使用它时遇到困难,那么您可以尝试如下所述:-

            • 用 false 初始化一个布尔值。
            • 在获取数据后将该布尔值设置为 true。
            • 现在在重新启动 OnCreate() 时检查该布尔值是真还是假。如果为假,那么您需要再次提取该数据,否则您不需要获取该数据。设置相同的条件。

            【讨论】:

              【解决方案10】:

              您将为此检查控制器。

              而不是尝试检查您存储的值。 首先取一个变量作为Bundle。 保存在 onCreate 方法中。

              Bundle bdlSaveInstant;   // Declare at top before onCreate() method.
              /* Initialise in onCreate() method. After 
              
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_human_list);
              bdlSaveInstant = savedInstanceState;
              */
              
              @override
              onBackPressed()
              {
                bdlSaveInstant.putBoolean("displayed_contacts", true);
              }
              
              @override
              onDestroy()
              {
                bdlSaveInstant.putBoolean("displayed_contacts", true);
              }
              

              试试这个就行了。

              【讨论】:

                【解决方案11】:

                在您的 on create 方法中,只需清除您的列表。因为每次它都会转到 on create 方法并清除列表(哪个)是您之前的数据加载。

                检查您的列表 if(list.size==null) 然后在 else 块 (list.setAdapter) 中调用您的 API。

                希望它会起作用。

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2019-07-16
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2021-09-06
                  • 2010-10-22
                  • 1970-01-01
                  • 2020-07-14
                  相关资源
                  最近更新 更多