Android. Fragment的getActivity()有时会返回null。

199
在开发者控制台的错误报告中,有时我会看到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) {}
    });
}

异步任务类

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

也许应用从后台恢复时会出现这种错误。在这种情况下,我应该如何正确处理这种情况?

1
我找到了一个问题,但没有解决方案。我不知道为什么片段会恢复早期的活动。而且只有当我的应用程序在最近的应用程序列表中处于最后位置时才会发生这种情况,似乎系统销毁了我的应用程序。 - Georgy Gobozov
1
当我从后台恢复我的应用程序时,片段onCreate和onResume方法之前会调用onResume。似乎一些已分离的片段仍然存在并尝试恢复。 - Georgy Gobozov
1
在这个字符串中,firstTask.setTaskListener((TaskListener) adapter.getItem(0)); adapter.getItem(0) 返回旧的 fragment,adapter 没有正确地移除 fragments。 - Georgy Gobozov
9
顺便说一下,这真是太棒了 :) 提问、留言和回答都由同一个人完成!对此我给予+1的评价。 - Prizoff
在onCreateView()中保存Context(getActivity()),因为当视图在后台重新创建时会调用该方法。 - sha
8个回答

125

看起来我找到了解决我的问题的方法。 这里有非常好的解释:这里这里。 这是我的示例:

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) {}
 });
在这段代码中的主要思想是,当应用程序正常运行时,您会创建新的Fragment并将其传递给Adapter。当您恢复应用程序时,Fragment管理器已经拥有此Fragment实例,您需要从Fragment管理器获取该实例并将其传递给Adapter。
更新:
另外,使用Fragment时一个好的做法是在调用getActivity()之前检查isAdded。这有助于避免在fragment与activity分离后出现空指针异常。例如,一个activity可能包含一个Fragment来推送异步任务,在任务完成时会调用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()) 

那么应用程序就会崩溃。


58
每次访问之前都要调用“isAdded()”真的很烦人...让代码变丑了。 - User
27
使用 if(isAdded())if(getActivity() != null) 看起来没有太大差别。 - StackOverflowed

19

好的,我知道这个问题已经解决了,但我决定分享我的解决方案。我为我的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() 不为空是不够的,因为我必须无论如何都要完成我的任务。我正在使用这个方法,它完美地解决了我的问题。 - Hadas Kaminsky

19

最好的方法是在调用 onAttach 时保留活动引用,并在需要时使用该活动引用,例如:

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

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

因为onAttach(Activity)已被弃用,现在使用的是onAttach(Context),所以进行了编辑


9
Fragments始终保留其父Activity的引用,并通过getActivity()方法向您提供访问权限,这里我们将保留相同的引用。 - Pawan Maheshwari
8
如果您需要片段与活动共享事件,Google实际上建议这样做。请参阅http://developer.android.com/guide/components/fragments.html(查找“创建到活动的事件回调”)。 - Vering
6
你可能想要添加一个onDetach方法,它会将活动(activity)引用设为null,以此来完善内容。 - midnight
2
在onDetach方法中初始化mActivity为null,以清空该活动的引用。 - Pawan Maheshwari
20
千万不要这样做。你正在泄露你的全部活动(包括整个布局树、可绘制对象等)。如果getActivity()返回null,那是因为你已经不在一个活动中了。这是一种不良的解决方法。 - njzk2
显示剩余10条评论

10

在父 Activity 的 onStart 中调用依赖 getActivity() 的 Fragment 方法。

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

其实,我遇到了类似的问题,因为我是在片段构造函数中启动任务。非常感谢。 - Supreme Dolphin

5

我已经为这种问题奋斗了一段时间,我认为我已经想出了一个可靠的解决方案。

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

在下面的解决方案中,我声明了一个小型管理类,称为ActivityBuffer。本质上,这个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;
    }

}

最后,在您的扩展了BaseFragmentFragment中,如果您对调用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(...) 方法的示例? - fahad_sust

3
@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    // run the code making use of getActivity() from here
}

您能否详细阐述一下您的答案,并对您提供的解决方案进行更多描述? - abarisone

1

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

首先:我正在使用fragmentTransactions动态添加片段。 其次:我的片段是使用AsyncTasks(在服务器上进行的DB查询)修改的。 第三:我的片段在活动启动时没有被实例化。 第四:我使用了自定义片段实例化“创建或加载它”,以便获取片段变量。 第五:由于方向更改,活动被重新创建

问题是,由于查询答案,我想“删除”片段,但片段在之前不正确地创建了。我不知道为什么,可能是因为稍后才执行“提交”,当需要删除它时,片段尚未添加。因此,getActivity()返回null。

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


0
在 Kotlin 中,您可以尝试使用以下方式来处理 getActivity() 的 null 条件。
   activity?.let { // activity == getActivity() in java

        //your code here

   }

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接