我有一个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();
}
问题
Activity
生命周期和Fragment
生命周期会重新启动,包括 onAttach(...),如果部分参考文件是垃圾回收的。示例应用
我创建了一个展示此行为的示例应用程序。完整的源代码在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
?
答案 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
应该是实例字段而不是静态字段。)