【问题标题】:Why is my callback null after being in the background for a while (example app included)为什么我的回调在后台一段时间后为空(包括示例应用程序)
【发布时间】:2012-12-18 03:18:43
【问题描述】:

我有一个FragmentActivity,其中有两个标签是ListFragments。每个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. 为什么会mTab1Callback 垃圾收集而不是在垃圾收集的情况下恢复?我的印象是 Activity 生命周期和 Fragment 生命周期将重新启动,包括 onAttach(...) 如果部分引用被垃圾回收。
  2. 这是如何正确解决的?

示例应用

我创建了一个展示这种行为的示例应用程序。完整的源代码在这里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 并且在旋转或长期在后台运行之后,它唯一为空的地方是 toggleEnabled () 触发回调的函数。

这似乎表明罪魁祸首是 MainActivity 引用了一个不再存在的 Fragment

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

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

所以我挖了TabsAdapter 来关注getItem(...)。这是那里的东西

私有 ArrayList mFragments = new ArrayList(); // 设置标签等的代码 @覆盖 公共片段getItem(int位置){ 片段片段 = mFragments.get(position); if (frag.getActivity() == null) Log.d(TAG, "getItem: Activity 为空");

return mFragments.get(position);

}

发生在旋转之前,getActivity()not null。旋转后,getActivity()null。这似乎表明它并没有重新创建 Fragment 关于正确的 Context 我相信。然而,这就是我对这类事情的了解结束的地方。

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

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

【问题讨论】:

  • 您能否分享一个显示此行为的示例应用程序?
  • 我添加了一个示例应用程序和该应用程序的堆栈跟踪。
  • 我没有看到那个异常。在你的片段中丢失setRetainInstance(true); 调用(你为什么使用它?),看看你是否再次看到这种行为。
  • 我试试看。我使用它的原因是在另一个 Activity / Fragment 组合中,它有许多 UI 元素,我用来保留它们的状态。我想我在这部分应用程序中遇到的Fragments 没有必要
  • 您不需要保留实例,尤其是当您使用 ViewPager 中的片段时,您只有两个片段,因此它们都可用。即使这样,您的代码也应该可以工作,因为即使您保留实例,仍会调用 onAttach 回调。

标签: android android-fragments


【解决方案1】:

有谁知道为什么片段没有正确附加 活动?

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

主要问题是不能在FragmentPagerAdapter 上调用getItem 来获取该职位的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 应设为实例字段而不是静态字段)。

【讨论】:

  • 好吧,解决了它。你确实提到我不应该首先使用getItem。我会查看 StackOverflow 中的其他示例,但是您有什么具体的方法可以使用,或者您提供的添加是否足够正确?
  • @Kirk 如果你不介意黑客的本质,你可以使用这个getSupportFragmentManager().findFragmentByTag("android:switcher:" + R.id.pager + ":0") 来获取位置0 的片段。我的代码应该可以工作,但我现在没有地方可以测试它。
  • 啊,我现在更喜欢你的解决方案。解决这个问题对我来说是一段漫长的旅程,所以这太棒了。如果有人在附近,请告诉他们给你一个高五。
【解决方案2】:

OnAttach 看起来以类似于 onCreate 的方式被调用,即仅在创建时调用一次,因此当您从后台恢复时,此方法将被跳过

【讨论】:

  • 知道如何正确解决这个问题吗?
  • 我可能会将代码从 onAttach 移到片段中的 onResume
  • 我相信应用程序还有其他问题导致了这种情况。据我所知,onAttach 是正确的地方,但有些东西干扰了正确的引用。
  • 感谢您的帮助。我用onResume 进行了测试,结果它崩溃了。
猜你喜欢
  • 2015-02-02
  • 1970-01-01
  • 2021-06-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-23
相关资源
最近更新 更多