Android.Fragment getActivity() 有时返回 null

在开发人员控制台错误报告中,有时我看到报告中有 NPE 问题。我不明白我的代码有什么问题。在模拟器和我的设备应用程序工作得很好,没有强制,然而,一些用户得到NullPointerException片段类时,getActivity()方法被调用。

活动

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();
...
}
}

当应用程序从后台恢复时,可能会发生此错误。在这种情况下,我应该如何妥善处理这种情况?

150509 次浏览
看来我找到了解决问题的办法。 在这里在这里给出了很好的解释。 下面是我的例子:

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();
}


}

如果我们打开片段,推送一个任务,然后快速返回到前一个活动,当任务完成时,它将尝试通过调用getActivity()方法访问onPostExecute()中的活动。如果活动已经被分离并且这个检查不存在:

if (isAdded())

然后应用程序崩溃。

最好的解决方法是在onAttach被调用时保留活动引用,并在任何需要的地方使用活动引用。

@Override
public void onAttach(Context context) {
super.onAttach(context);
mContext = context;
}


@Override
public void onDetach() {
super.onDetach();
mContext = null;
}

已编辑,因为onAttach(Activity)被折旧&现在正在使用onAttach(Context)

不要在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();
}

好吧,我知道这个问题实际上已经解决了,但我决定分享我的解决方案。我已经为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
}
});
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// run the code making use of getActivity() from here
}

我知道这是一个老问题,但我想我必须提供我的答案,因为我的问题没有被其他人解决。

首先:我使用fragmentTransactions动态添加片段。 第二:我的片段是使用AsyncTasks(服务器上的DB查询)修改的。 第三:我的片段在活动开始时没有实例化 第四:我使用了一个自定义片段实例化“创建或加载它”,以获得片段变量。 第四:活动被重新创建,因为方向改变

问题是,我想“删除”的片段,因为查询的答案,但片段是错误地创建之前。我不知道为什么,可能是因为“提交”是稍后完成的,当需要删除它时,这个片段还没有添加。因此getActivity()返回null。

< p >解决方案: 1)我必须检查我是正确地试图找到片段的第一个实例,然后再创建一个新的 2)我不得不把serRetainInstance(true)上的片段,以保持它通过方向变化(没有backstack需要因此没有问题) 3)在“删除它”之前,我没有“重新创建或获取旧的片段”,而是直接将片段放在活动开始时。 在活动开始时实例化它,而不是在删除它之前“加载”(或实例化)片段变量,以防止getActivity问题

我已经与这种问题作斗争一段时间了,我想我已经提出了一个可靠的解决方案。

很难确定this.getActivity()不会为Fragment返回null,特别是如果你正在处理任何类型的网络行为,这些行为会给你的代码足够的时间来撤回Activity引用。

在下面的解决方案中,我声明了一个名为ActivityBuffer的小型管理类。本质上,这个class处理维护对拥有的Activity的可靠引用,并承诺在有效的Activity上下文中执行Runnables,只要有有效的引用可用。如果Context可用,__abc3将被安排立即在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;
}


}

在它的实现方面,我们必须注意应用生命周期方法,以符合上面一生米所描述的行为:

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.
}
}
);

在Kotlin中,您可以尝试用这种方式处理getActivity() null条件。

   activity?.let { // activity == getActivity() in java


//your code here


}

它将检查活动是否为空,如果不为空,则执行内部代码。