安卓。片段 getActivity() 有时返回 null
Posted
技术标签:
【中文标题】安卓。片段 getActivity() 有时返回 null【英文标题】:Android. Fragment getActivity() sometimes returns null 【发布时间】:2012-07-22 19:16:29 【问题描述】:在开发者控制台错误报告中,有时我会看到带有 NPE 问题的报告。我不明白我的代码有什么问题。在模拟器上,我的设备应用程序在没有强制关闭的情况下运行良好,但是当调用 getActivity() 方法时,一些用户在片段类中得到 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();
...
当应用程序从后台恢复时,可能会发生此错误。在这种情况下我应该如何正确处理这种情况?
【问题讨论】:
我发现了一个问题,但不是解决方案。我不知道为什么,但片段恢复了早期的活动。这只发生在我的应用程序在最近应用程序列表中的最后位置时,系统似乎破坏了我的应用程序。 当我从后台 Fragmetn onCreate 恢复我的应用程序时,在活动 onCreate/onResume 方法之前调用了一个 onResume。似乎一些分离的片段还活着并试图恢复。 在这个字符串中 firstTask.setTaskListener((TaskListener) adapter.getItem(0)); adapter.getItem(0) 返回旧片段,适配器不正确删除片段 顺便说一句很棒的活动 :) 提出问题,留下 cmets 并给出答案 - 全部由一个人完成!为这些 +1。 将 Context(getActivity()) 保存在 onCreateView() 中,因为在后台情况下重新创建视图时会调用此方法。 【参考方案1】:看来我找到了解决问题的方法。 here 和 here 给出了很好的解释。 这是我的例子:
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)
);
这段代码的主要思想是,在正常运行您的应用程序时,您会创建新的片段并将它们传递给适配器。当您恢复应用程序片段管理器时,您已经拥有该片段的实例,您需要从片段管理器获取它并将其传递给适配器。
更新
此外,在调用 getActivity() 之前使用片段检查 isAdded 是一个好习惯。当片段与活动分离时,这有助于避免空指针异常。例如,一个活动可能包含一个推送异步任务的片段。任务完成后,会调用 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();
如果我们打开fragment,推送一个任务,然后快速按回返回上一个activity,当任务完成后,它会通过调用getActivity()方法尝试访问onPostExecute()中的activity。如果 Activity 已分离且此检查不存在:
if (isAdded())
然后应用程序崩溃。
【讨论】:
这很烦人,每次访问之前都必须调用isAdded()
... 让代码变得丑陋。
if(isAdded())
或 if(getActivity() != null)
之间似乎没有太大区别【参考方案2】:
好的,我知道这个问题实际上已经解决了,但我决定分享我的解决方案。我为Fragment
创建了抽象父类:
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
);
【讨论】:
很好的答案!应该被标记为正确的,因为它解决了真正的问题:在我的情况下,检查 getActivity() 不为空是不够的,因为无论如何我都必须完成我的任务。我正在使用它,它工作得很好。【参考方案3】:最好的办法是在调用onAttach
时保留活动引用,并在需要时使用活动引用,例如
@Override
public void onAttach(Context context)
super.onAttach(context);
mContext = context;
@Override
public void onDetach()
super.onDetach();
mContext = null;
已编辑,因为 onAttach(Activity)
已贬值,现在正在使用 onAttach(Context)
【讨论】:
Fragments 始终保留其父 Activity 的引用,并通过 getActivity() 方法使您可用,这里我们保留相同的引用。 如果您需要片段与活动共享事件,Google 实际上建议您这样做。 developer.android.com/guide/components/fragments.html(查找“为活动创建事件回调”) 您可能想要添加 onDetach 方法,该方法会使活动引用无效 是的,在 onDetach 方法上初始化 mActivity=null 以使该活动引用无效。 永远不要那样做。您正在泄漏您的完整活动(以及整个布局树,以及可绘制对象等)。如果getActivity()
返回 null,那是因为您不再处于活动中。这是一个肮脏的解决方法。【参考方案4】:
在父 Activity 中的 onStart 之前,不要调用 Fragment 中需要 getActivity() 的方法。
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();
【讨论】:
其实我遇到了类似的问题,因为我是在片段构造函数中启动任务的。非常感谢。【参考方案5】:我已经battling this kind of problem 有一段时间了,我想我已经想出了一个可靠的解决方案。
很难确定this.getActivity()
不会为Fragment
返回null
,特别是如果您正在处理任何类型的网络行为,这会给您的代码足够的时间来撤回@ 987654326@ 参考资料。
在下面的解决方案中,我声明了一个名为ActivityBuffer
的小型管理类。本质上,这个class
处理维护对拥有Activity
的可靠引用,并承诺只要有可用的有效引用,就会在有效的Activity
上下文中执行Runnable
s。如果Context
可用,Runnable
s 将立即安排在 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;
最后,在您的Fragment
中扩展BaseFragment
的任何区域中,您对getActivity()
的调用是不可信的,只需调用this.getActivityBuffer().safely(...)
并为该任务声明一个ActivityBuffer.IRunnable
!
您的void run(final Activity pActivity)
的内容随后保证沿着 UI 线程执行。
ActivityBuffer
可以按如下方式使用:
this.getActivityBuffer().safely(
new ActivityBuffer.IRunnable()
@Override public final void run(final Activity pActivity)
// Do something with guaranteed Context.
);
【讨论】:
能否添加一个使用this.getActivityBuffer().safely(...)方法的例子。【参考方案6】:@Override
public void onActivityCreated(Bundle savedInstanceState)
super.onActivityCreated(savedInstanceState);
// run the code making use of getActivity() from here
【讨论】:
能否请您详细说明您的答案,添加更多关于您提供的解决方案的描述?【参考方案7】:我知道这是一个老问题,但我认为我必须提供我的答案,因为我的问题没有被其他人解决。
首先:我正在使用 fragmentTransactions 动态添加片段。 第二:我的片段是使用 AsyncTasks(服务器上的数据库查询)修改的。 第三:我的片段在活动开始时没有被实例化 第四:我使用自定义片段实例化“创建或加载它”来获取片段变量。 第四:由于方向改变而重新创建活动
问题是由于查询答案,我想“删除”片段,但之前错误地创建了片段。我不知道为什么,可能是因为稍后完成了“提交”,当需要删除片段时还没有添加片段。因此 getActivity() 返回 null。
解决方案: 1)在创建新片段之前,我必须检查我是否正确地尝试找到片段的第一个实例 2)我必须把 serRetainInstance(true) 放在那个片段上,以保持它通过方向变化(不需要回栈,因此没问题) 3)我没有在“删除它”之前“重新创建或获取旧片段”,而是直接将片段放在活动开始处。 在活动开始时实例化它而不是在删除之前“加载”(或实例化)片段变量可以防止 getActivity 问题。
【讨论】:
【参考方案8】:在 Kotlin 中,您可以尝试这种方式来处理 getActivity() 空条件。
activity?.let // activity == getActivity() in java
//your code here
它将检查活动是否为空,如果不为空则执行内部代码。
【讨论】:
以上是关于安卓。片段 getActivity() 有时返回 null的主要内容,如果未能解决你的问题,请参考以下文章
Android getActivity() 总是在片段内返回 null