Android系统。 片段getActivity()有时返回null
在开发者控制台错误报告中,有时我会看到有NPE问题的报告。 我不明白我的代码有什么问题。 在模拟器上,我的设备应用程序没有强制closures,但是有些用户在调用getActivity()方法的时候会在fragment类中得到NullPointerException。
活动
pulic class MyActivity extends FragmentActivity{ private ViewPager pager; private TitlePageIndicator indicator; private TabsAdapter adapter; @Override public void onCreate(Bundle savedInstanceState) { pager = (ViewPager) findViewById(R.id.pager); indicator = (TitlePageIndicator) findViewById(R.id.indicator); adapter = new TabsAdapter(getSupportFragmentManager(), false); adapter.addFragment(new FirstFragment()); adapter.addFragment(new SecondFragment()); indicator.notifyDataSetChanged(); adapter.notifyDataSetChanged(); // push first task FirstTask firstTask = new FirstTask(MyActivity.this); // set first fragment as listener firstTask.setTaskListener((TaskListener) adapter.getItem(0)); firstTask.execute(); } indicator.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageSelected(int position) { Fragment currentFragment = adapter.getItem(position); ((Taskable) currentFragment).executeTask(); } @Override public void onPageScrolled(int i, float v, int i1) {} @Override public void onPageScrollStateChanged(int i) {} }); }
AsyncTask类
public class FirstTask extends AsyncTask{ private TaskListener taskListener; ... @Override protected void onPostExecute(T result) { ... taskListener.onTaskComplete(result); } }
片段类
public class FirstFragment extends Fragment immplements Taskable, TaskListener{ public FirstFragment() { } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.first_view, container, false); } @Override public void executeTask() { FirstTask firstTask = new FirstTask(MyActivity.this); firstTask.setTaskListener(this); firstTask.execute(); } @Override public void onTaskComplete(T result) { // NPE is here Resources res = getActivity().getResources(); ... } }
也许这个错误发生在应用程序从后台恢复时。 在这种情况下,我应该如何正确处理这种情况?
似乎我find了解决我的问题。 这里和这里给出了非常好的解释。 这是我的例子:
pulic class MyActivity extends FragmentActivity{ private ViewPager pager; private TitlePageIndicator indicator; private TabsAdapter adapter; private Bundle savedInstanceState; @Override public void onCreate(Bundle savedInstanceState) { .... this.savedInstanceState = savedInstanceState; pager = (ViewPager) findViewById(R.id.pager);; indicator = (TitlePageIndicator) findViewById(R.id.indicator); adapter = new TabsAdapter(getSupportFragmentManager(), false); if (savedInstanceState == null){ adapter.addFragment(new FirstFragment()); adapter.addFragment(new SecondFragment()); }else{ Integer count = savedInstanceState.getInt("tabsCount"); String[] titles = savedInstanceState.getStringArray("titles"); for (int i = 0; i < count; i++){ adapter.addFragment(getFragment(i), titles[i]); } } indicator.notifyDataSetChanged(); adapter.notifyDataSetChanged(); // push first task FirstTask firstTask = new FirstTask(MyActivity.this); // set first fragment as listener firstTask.setTaskListener((TaskListener) getFragment(0)); firstTask.execute(); } private Fragment getFragment(int position){ return savedInstanceState == null ? adapter.getItem(position) : getSupportFragmentManager().findFragmentByTag(getFragmentTag(position)); } private String getFragmentTag(int position) { return "android:switcher:" + R.id.pager + ":" + position; } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt("tabsCount", adapter.getCount()); outState.putStringArray("titles", adapter.getTitles().toArray(new String[0])); } indicator.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageSelected(int position) { Fragment currentFragment = adapter.getItem(position); ((Taskable) currentFragment).executeTask(); } @Override public void onPageScrolled(int i, float v, int i1) {} @Override public void onPageScrollStateChanged(int i) {} });
这段代码的主要思想是,在正常运行应用程序的同时,创build新的碎片并将它们传递给适配器。 当你恢复你的应用程序片段pipe理器已经有这个片段的实例,你需要从片段pipe理器获取它,并将其传递给适配器。
UPDATE
另外,在调用getActivity()之前使用片段来检查isAdded是一个很好的做法。 这有助于避免片段与活动分离时出现空指针exception。 例如,一个活动可能包含推送asynchronous任务的片段。 任务完成后,将调用onTaskComplete侦听器。
@Override public void onTaskComplete(List<Feed> result) { progress.setVisibility(View.GONE); progress.setIndeterminate(false); list.setVisibility(View.VISIBLE); if (isAdded()) { adapter = new FeedAdapter(getActivity(), R.layout.feed_item, result); list.setAdapter(adapter); adapter.notifyDataSetChanged(); } }
如果我们打开这个片段,按下一个任务,然后快速返回到前一个活动,当任务完成时,它将通过调用getActivity()方法尝试访问onPostExecute()中的活动。 如果活动已经分离,并且该检查不存在:
if (isAdded())
那么应用程序崩溃。
好吧,我知道这个问题实际上已经解决了,但我决定分享我的解决scheme。 我为我的Fragment
创build了抽象父类:
public abstract class ABaseFragment extends Fragment{ protected IActivityEnabledListener aeListener; protected interface IActivityEnabledListener{ void onActivityEnabled(FragmentActivity activity); } protected void getAvailableActivity(IActivityEnabledListener listener){ if (getActivity() == null){ aeListener = listener; } else { listener.onActivityEnabled(getActivity()); } } @Override public void onAttach(Activity activity) { super.onAttach(activity); if (aeListener != null){ aeListener.onActivityEnabled((FragmentActivity) activity); aeListener = null; } } @Override public void onAttach(Context context) { super.onAttach(context); if (aeListener != null){ aeListener.onActivityEnabled((FragmentActivity) context); aeListener = null; } } }
正如你所看到的,我添加了一个监听器,所以每当我需要得到Fragments
Activity
而不是标准的getActivity()
,我需要调用
getAvailableActivity(new IActivityEnabledListener() { @Override public void onActivityEnabled(FragmentActivity activity) { // Do manipulations with your activity } });
不要在Fragment中调用需要getActivity()的方法,直到父Activity中的onStart。
private MyFragment myFragment; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); myFragment = new MyFragment(); ft.add(android.R.id.content, youtubeListFragment).commit(); //Other init calls //... } @Override public void onStart() { super.onStart(); //Call your Fragment functions that uses getActivity() myFragment.onPageSelected(); }
最好摆脱这个是在onAttach被调用时保持活动引用,并在需要的地方使用活动引用,例如
@Override public void onAttach(Activity activity) { super.onAttach(activity); mActivity = activity; } @Override public void onDetach() { super.onDetach(); mActivity = null; }
@Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // run the code making use of getActivity() from here }
我知道这是一个古老的问题,但我认为我必须提供我的答案,因为我的问题没有被别人解决。
首先:我使用fragmentTransactionsdynamic添加片段。 其次:使用AsyncTasks(服务器上的数据库查询)修改了我的片段。 第三:我的片段没有在活动开始时实例化第四:我使用了一个自定义的片段实例“create or load it”来获得片段variables。 第四:由于方向改变,活动被重新创build
问题是,我想“删除”的片段,因为查询答案,但片段被错误地创build之前。 我不知道为什么,可能是因为稍后做了“提交”,这个片段在删除的时候还没有被添加。 因此,getActivity()返回null。
解决scheme:1)我必须检查,我正确地试图find片段的第一个实例之前创build一个新的2)我必须把serRetainInstance(true)在该片段,以保持方向改变(没有后台因此没有任何问题)3)在“删除它”之前,我直接将这个片段放在活动开始处,而不是“重新创build或获取旧片段”。 在活动启动时将其实例化,而不是在加载(或实例化)片段variables之前删除它阻止了getActivity问题。
我一直在和这类问题作斗争 ,我想我已经提出了一个可靠的解决scheme。
确实知道this.getActivity()
不会为Fragment
返回null
,特别是如果您正在处理任何types的networking行为,这会使您的代码有足够的时间来撤销Activity
引用。
在下面的解决scheme中,我声明了一个名为ActivityBuffer
的小型pipe理类。 本质上,这个class
处理维护对拥有Activity
的可靠引用,并承诺只要有可用的有效引用,就在有效的Activity
上下文中执行Runnable
。 如果Context
是可用的,则Runnable
被安排在UI线程上立即执行,否则执行被推迟到Context
已经准备好。
/** A class which maintains a list of transactions to occur when Context becomes available. */ public final class ActivityBuffer { /** A class which defines operations to execute once there's an available Context. */ public interface IRunnable { /** Executes when there's an available Context. Ideally, will it operate immediately. */ void run(final Activity pActivity); } /* Member Variables. */ private Activity mActivity; private final List<IRunnable> mRunnables; /** Constructor. */ public ActivityBuffer() { // Initialize Member Variables. this.mActivity = null; this.mRunnables = new ArrayList<IRunnable>(); } /** Executes the Runnable if there's an available Context. Otherwise, defers execution until it becomes available. */ public final void safely(final IRunnable pRunnable) { // Synchronize along the current instance. synchronized(this) { // Do we have a context available? if(this.isContextAvailable()) { // Fetch the Activity. final Activity lActivity = this.getActivity(); // Execute the Runnable along the Activity. lActivity.runOnUiThread(new Runnable() { @Override public final void run() { pRunnable.run(lActivity); } }); } else { // Buffer the Runnable so that it's ready to receive a valid reference. this.getRunnables().add(pRunnable); } } } /** Called to inform the ActivityBuffer that there's an available Activity reference. */ public final void onContextGained(final Activity pActivity) { // Synchronize along ourself. synchronized(this) { // Update the Activity reference. this.setActivity(pActivity); // Are there any Runnables awaiting execution? if(!this.getRunnables().isEmpty()) { // Iterate the Runnables. for(final IRunnable lRunnable : this.getRunnables()) { // Execute the Runnable on the UI Thread. pActivity.runOnUiThread(new Runnable() { @Override public final void run() { // Execute the Runnable. lRunnable.run(pActivity); } }); } // Empty the Runnables. this.getRunnables().clear(); } } } /** Called to inform the ActivityBuffer that the Context has been lost. */ public final void onContextLost() { // Synchronize along ourself. synchronized(this) { // Remove the Context reference. this.setActivity(null); } } /** Defines whether there's a safe Context available for the ActivityBuffer. */ public final boolean isContextAvailable() { // Synchronize upon ourself. synchronized(this) { // Return the state of the Activity reference. return (this.getActivity() != null); } } /* Getters and Setters. */ private final void setActivity(final Activity pActivity) { this.mActivity = pActivity; } private final Activity getActivity() { return this.mActivity; } private final List<IRunnable> getRunnables() { return this.mRunnables; } }
在实现方面,我们必须注意将生命周期方法应用到Pawan M所描述的行为上:
public class BaseFragment extends Fragment { /* Member Variables. */ private ActivityBuffer mActivityBuffer; public BaseFragment() { // Implement the Parent. super(); // Allocate the ActivityBuffer. this.mActivityBuffer = new ActivityBuffer(); } @Override public final void onAttach(final Context pContext) { // Handle as usual. super.onAttach(pContext); // Is the Context an Activity? if(pContext instanceof Activity) { // Cast Accordingly. final Activity lActivity = (Activity)pContext; // Inform the ActivityBuffer. this.getActivityBuffer().onContextGained(lActivity); } } @Deprecated @Override public final void onAttach(final Activity pActivity) { // Handle as usual. super.onAttach(pActivity); // Inform the ActivityBuffer. this.getActivityBuffer().onContextGained(pActivity); } @Override public final void onDetach() { // Handle as usual. super.onDetach(); // Inform the ActivityBuffer. this.getActivityBuffer().onContextLost(); } /* Getters. */ public final ActivityBuffer getActivityBuffer() { return this.mActivityBuffer; } }
最后,在扩展BaseFragment
的Fragment
,对于调用getActivity()
,只需调用this.getActivityBuffer().safely(...)
并为任务声明一个ActivityBuffer.IRunnable
!
你的void run(final Activity pActivity)
的内容将保证在UI Thread中执行。