我有一个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();
}
题
为什么mTab1Callback垃圾会被收集,而如果事情被垃圾收集,则不会还原?我的印象是,如果部分引用被垃圾回收,则
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(...)。这是那里私有ArrayList mFragments = new ArrayList();
//设置标签等的代码
@Override
public Fragment getItem(int position){
片段碎片= mFragments.get(position);
如果(frag.getActivity()== null)
Log.d(TAG,“ getItem:活动为空”);
return mFragments.get(position);
}
发生在轮换之前,getActivity()不为null。旋转后,getActivity()为null。这似乎表明它没有针对我认为正确的
Fragment
重新创建Context
。但是,这就是我对这类事情的了解的结尾。我可以将
mFragments
设为静态,并且可以工作,但是我觉得那不是正确的解决方案。有谁知道为什么
Fragment
没有正确连接的Activity
? 最佳答案
有谁知道为什么碎片没有正确连接
活动?
这是因为ViewPager
的工作方式。首次设置ViewPager
(以及实例化两个片段)时,它将调用getItem()
方法,并且它将收到正确的Fragment
。旋转手机后,Activity
以及其中的所有Fragments
将被销毁并重新创建。此时,ViewPager
将尝试在内部重新创建其状态,并使Fragments
匹配先前的状态,并构造自己的Fragments
。通过Fragments
方法添加的您自己的addTab
将不会使用,因为旋转手机后,初始getItem()
不会调用Fragments
方法。问题在于,使用您当前的代码,您调用getItem
方法以获取Fragments
的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
。它应该删除任何有利于ViewPager自身片段的手动创建的Fragments
。看看是否有效。也:
不要使用
setRetainInstance(true)
使用引用了
Context
的静态字段时要小心,以避免内存泄漏(例如,应将MyObjectAdapter
设置为实例字段而不是静态字段)。