安卓 片段getActivity()有时会返回null


194

在开发人员控制台错误报告中,有时我会看到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();
        ...
    }
}

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


我发现了一个问题,但没有解决。我不知道为什么,但是片段恢复了先前的活动。而且只有当我的应用程序在最近列出的应用程序中的最后位置时,才会发生这种情况,似乎系统破坏了我的应用程序。
乔治·戈博佐夫

1
当我从后台fragmetn onCreate应用恢复活动onCreate / onResume方法之前调用onResume时。似乎有一些分离的片段仍然存在,并试图恢复。
乔治·戈博佐夫

1
在此字符串中firstTask.setTaskListener((TaskListener)adapter.getItem(0)); adapter.getItem(0)返回旧片段,适配器不能正确删除片段
Georgy Gobozov

9
顺便说一句,很棒的活动:)问题,评论和给出的答案-所有这些都由一个人完成!+1。
2013年

将Context(getActivity())保存在onCreateView()中,因为在后台情况下重新创建视图时会调用此方法。

Answers:


123

看来我找到了解决问题的方法。这里这里都给出很好的解释。这是我的示例:

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()之前使用片段检查iwego时,这也是一个好习惯。当片段与活动分离时,这有助于避免空指针异常。例如,一个活动可能包含一个推送异步任务的片段。任务完成后,将调用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()) 

然后应用程序崩溃。


56
但是,这很烦人,isAdded()每次访问之前都必须调用...使代码难看。
Ixx

25
拥有if(isAdded())if(getActivity() != null)
StackOverflowed 2015年

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而不是standard getActivity(),我都需要调用

 getAvailableActivity(new IActivityEnabledListener() {
        @Override
        public void onActivityEnabled(FragmentActivity activity) {
            // Do manipulations with your activity
        }
    });

好答案!应该将其标记为正确的代码,因为它可以解决实际的问题:就我而言,仅检查getActivity()不为null是不够的,因为无论如何我都必须完成任务。我正在使用它,它运行完美。
哈达斯·卡明斯基

18

最好的解决方法是在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始终保留其父活动的引用,并使您可以使用getActivity()方法访问,这里我们保持相同的引用。
Pawan Maheshwari

8
如果您需要您的片段来与活动共享事件,那么Google实际上会建议这样做。 developer.android.com/guide/components/fragments.html(查找“为活动创建事件回调”)
Vering

6
您可能要添加onDetach方法,该方法将取消活动引用
午夜

2
是的,在onDetach方法上初始化mActivity = null可以使该活动引用无效。
Pawan Maheshwari 2014年

19
从来没有那样做。您正在泄漏整个活动(以及整个布局树,可绘制对象等)。如果getActivity()返回null,那是因为您不再处于活动状态。这是一个肮脏的解决方法。
njzk2 2014年

10

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

实际上,我有一个类似的问题,因为我是在片段构造函数中启动任务的。非常感谢。
至尊海豚

4

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

要确定肯定this.getActivity()不会返回是非常困难nullFragment,尤其是当您要处理任何形式的网络行为时,这会使您的代码有足够的时间撤回Activity引用。

在下面的解决方案中,我声明了一个名为的小型管理类ActivityBuffer。从本质上讲,这class涉及维护对owner的可靠引用Activity,并承诺在有有效引用可用时Runnable在有效Activity上下文中执行。如果Runnables Context可用,则计划将其立即在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(...)方法的示例。
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(服务器上的数据库查询)修改了我的片段。第三:我的片段未在活动开始时实例化。第四:我使用自定义片段实例“创建或加载”以获取片段变量。第四:活动因方向变化而重新创建

问题是由于查询答案,我想“删除”该片段,但是该片段是在之前错误地创建的。我不知道为什么,可能是因为稍后要进行“提交”,所以在删除该片段时还没有添加该片段。因此,getActivity()返回null。

解决方案:1)在创建新片段之前,我必须检查自己是否在尝试找到该片段的第一个实例2)我必须在该片段上放置serRetainInstance(true),以通过方向更改来保持它(无后置堆栈)因此不需要。)3)我将片段直接放在活动开始时,而不是在“删除”之前“重新创建或获取旧片段”。在活动开始时实例化它,而不是在删除它之前先“加载”(或实例化)片段变量,这避免了getActivity问题。


0

在Kotlin中,您可以尝试这种方法来处理getActivity()空条件。

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

        //your code here

   }

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.