为什么我的回调在后台暂停一段时间后为null(包括示例应用程序)

时间:2012-12-15 07:11:28

标签: android android-fragments

我有一个FragmentActivity,其中有两个标签ListFragment。每个ListFragment都有一个回调。

注意:如果它被注意到,大部分问题都是从我自己的问题Callback after orientation change becomes null中重新提出的,但这是一个不同的问题,因为这与轮换无关,但是我希望通过回调理解实际问题。)

回调的一个例子

回调在 onAttach(...)方法内部关联,如http://developer.android.com/training/basics/fragments/communicating.html所述

OnTab1Listener mTab1Callback;

public interface OnTab1Listener {
    public void onTab1Update();
}

@Override
public void onAttach(Activity activity) {
    Log.d(TAG, "onAttach");
    super.onAttach(activity);

    try {
        mTab1Callback = (OnTab1Listener)activity;
    } catch (ClassCastException e) {
        throw new ClassCastException(activity.toString() + " must implement OnTab1Listener");
    }
}

稍后,我通过此回调与FragmentActivity进行通信,该回调正常工作。

将应用程序发送到后台一段时间后,我将应用程序带到前面并触发将使用回调的内容,回调是 null 并且我的应用程序崩溃。我相信一旦它在后台并且系统进行垃圾收集就会发生这种情况。

这是一个在正常执行期间正常工作的示例调用。

public void toggleEnabled(View v) {
    Log.d(TAG, "toggleEnabled");

    // null pointer here
    mTab1Callback.onTab1Update();
}

问题

  1. 为什么 mTab​​1Callback 垃圾被收集,如果垃圾被收集,则不会恢复?我的印象是Activity生命周期和Fragment生命周期会重新启动,包括 onAttach(...),如果部分参考文件是垃圾回收的。
  2. 这是如何妥善修复的?
  3. 示例应用

    我创建了一个展示此行为的示例应用程序。完整的源代码在http://www.2shared.com/file/kkgFejUq/broken_example.html

    示例应用堆栈跟踪

    FATAL EXCEPTION: main
    java.lang.IllegalStateException: Could not execute method of the activity
    at android.view.View$1.onClick(View.java:3591)
    at android.view.View.performClick(View.java:4084)
    at android.widget.CompoundButton.performClick(CompoundButton.java:100)
    at android.view.View$PerformClick.run(View.java:16966)
    at android.os.Handler.handleCallback(Handler.java:615)
    at android.os.Handler.dispatchMessage(Handler.java:92)
    at android.os.Looper.loop(Looper.java:137)
    at android.app.ActivityThread.main(ActivityThread.java:4745)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:511)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
    at dalvik.system.NativeStart.main(Native Method)
    Caused by: java.lang.reflect.InvocationTargetException
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:511)
    at android.view.View$1.onClick(View.java:3586)
    ... 12 more
    Caused by: java.lang.NullPointerException
    at com.example.myapp.Tab2.toggleEnabled(Tab1.java:87)
    at com.example.myapp.MainActivity.onClick(MainActivity.java:50)
    ... 15 more
    

    这是 Tab1.java:87

    mTab1Callback.onTab1Update();
    

    如果您让应用程序转到后台,执行其他操作一段时间(如玩游戏,音乐等)并将其返回到前台并单击其中一个复选框,则此指针将成为空指针。

    如果重要,我将应用程序图标放在启动器上并从那里启动它,然后在我运行其他应用程序一段时间后将其带到前台。这是它崩溃的时候。

    此外,我无法让它在模拟器上崩溃,只有真正的硬件。当手机插入电脑时,它似乎不太可能崩溃,而且如果在测试时拔掉插头,则更容易崩溃。

    TabsAdapter 是否与此有关?

    此外,它使用文件中包含的 SherlockActionBar 。它应该用作 MyApp 的库。

    更新

    我开始看到 getActivity() null 的位置,在旋转或长期在后台运行后,唯一的位置是null,位于 toggleEnabled内()触发回调的函数。

    这似乎指出罪魁祸首是MainActivity引用不再存在的Fragment

    结合空间代码,这实际上是我点击CheckBox中的Tab1后我引用 toggleEnabled()函数的方式。 (参见附件中的完整代码示例)

    public void onClick(View v) {
         Tab1 tab = (Tab1)mTabsAdapter.getItem(0);
         tab.toggleEnabled(v);
    }
    

    所以我挖到TabsAdapter跟随 getItem(...)。这是什么

    private ArrayList mFragments = new ArrayList(); //设置标签的代码等 @覆盖 public Fragment getItem(int position){     Fragment frag = mFragments.get(position);     if(frag.getActivity()== null)         Log.d(TAG,“getItem:Activity is null”);

    return mFragments.get(position);
    

    }

    在轮换之前会发生什么, getActivity() not null 。旋转后, getActivity() null 。这似乎表明它没有重新构建Fragment关于我相信的Context。然而,这是我对这类事情的了解的结束。

    我可以将mFragments设为静态且有效,但我觉得这不是一个正确的解决方案。

    有谁知道为什么Fragment没有正确附加Activity

2 个答案:

答案 0 :(得分:1)

OnAttach看起来像onCreate一样被调用,即在创建时只调用一次,所以当你从后台恢复时,这个方法被跳过

答案 1 :(得分:1)

  

有谁知道为什么片段没有正确连接   活动?

这是因为ViewPager的工作方式。当您首次设置ViewPager(以及实例化两个片段)时,它将调用getItem()方法,它将收到正确的Fragment。旋转手机后,Activity及其中的所有Fragments将被销毁并重新创建。此时,ViewPager会尝试在内部重新创建状态,并使Fragments与之前的状态匹配,并构建自己的Fragments。您使用Fragments方法添加的自己addTab将不会被使用,因为在旋转手机后,不会为初始getItem()调用Fragments方法。问题是,使用当前代码,您调用getItem方法获取Fragments的{​​{1}},ViewPager将从列表中检索Fragments。这些片段是简单的实例,与Activity无关,因此onAttach()方法中的回调集为null

主要问题是无法在getItem上调用FragmentPagerAdapter来获取该位置的Fragment。关于如何执行此操作的stackoverflow有很多问题。我编写了下面的方法,在片段列表中始终有正确的片段,无论ViewPager做什么(通过删除你设置的片段)。您当前的代码将保持不变只需添加:

@Override
public Object instantiateItem(ViewGroup container, int position) {
    Fragment created = (Fragment) super
            .instantiateItem(container, position);
    if (position < mFragments.size()) {
        Fragment fromList = mFragments.get(position);           
        if (!created.equals(fromList)) {
            mFragments.set(position, created);
        }
    } else {
        int difference = position - mFragments.size();
        if (difference == 0) {
            mFragments.add(created);                
        } else {
            for (int i = 0; i < difference - 1; i++) {
                mFragments.add(null);
            }
            mFragments.add(created);
        }
    }
    return created;
}

TabsAdapter。它应该删除任何手动创建的Fragments有利于ViewPager自己的片段。看看它是否有效。

此外:

  • 请勿使用setRetainInstance(true)
  • 使用引用Context的静态字段时要小心,以避免内存泄漏(例如,您的MyObjectAdapter应该是实例字段而不是静态字段。)