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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11631408/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 08:18:04  来源:igfitidea点击:

Android. Fragment getActivity() sometimes returns null

androidandroid-fragmentsandroid-activityandroid-asynctasknullpointerexception

提问by Georgy Gobozov

In developer console error reports sometimes I see reports with NPE issue. I do not understand what is wrong with my code. On emulator and my device application works good without forcecloses, however some users get NullPointerException in fragment class when the getActivity() method is called.

在开发者控制台错误报告中,有时我会看到有关 NPE 问题的报告。我不明白我的代码有什么问题。在模拟器上,我的设备应用程序在没有 forcecloses 的情况下运行良好,但是当调用 getActivity() 方法时,一些用户在片段类中得到 NullPointerException。

Activity

活动

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 class

异步任务类

public class FirstTask extends AsyncTask{

    private TaskListener taskListener;

    ...

    @Override
    protected void onPostExecute(T result) {
        ... 
        taskListener.onTaskComplete(result);
    }   
}

Fragment class

片段类

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

Maybe this error happens when applications resumed from background. In this case how I should handle this situation properly?

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

采纳答案by Georgy Gobozov

It seems that I found a solution to my problem. Very good explanations are given hereand here. Here is my example:

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

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

The main idea in this code is that, while running your application normally, you create new fragments and pass them to the adapter. When you are resuming your application fragment manager already has this fragment's instance and you need to get it from fragment manager and pass it to the adapter.

这段代码的主要思想是,在正常运行应用程序的同时,创建新的片段并将它们传递给适配器。当您恢复应用程序片段管理器时,您的应用程序片段管理器已经拥有该片段的实例,您需要从片段管理器中获取它并将其传递给适配器。

UPDATE

更新

Also, it is a good practice when using fragments to check isAdded before getActivity() is called. This helps avoid a null pointer exception when the fragment is detached from the activity. For example, an activity could contain a fragment that pushes an async task. When the task is finished, the onTaskComplete listener is called.

此外,在调用 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();
    }

}

If we open the fragment, push a task, and then quickly press back to return to a previous activity, when the task is finished, it will try to access the activity in onPostExecute() by calling the getActivity() method. If the activity is already detached and this check is not there:

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

if (isAdded()) 

then the application crashes.

然后应用程序崩溃。

回答by Paul Freez

Ok, I know that this question is actually solved but I decided to share my solution for this. I've created abstract parent class for my Fragment:

好的,我知道这个问题实际上已经解决了,但我决定为此分享我的解决方案。我为我创建了抽象父类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;
        }
    }
}

As you can see, I've added a listener so, whenever I'll need to get FragmentsActivityinstead of standard getActivity(), I'll need to call

如您所见,我添加了一个侦听器,因此,每当我需要 getFragmentsActivity而不是 standard 时getActivity(),我都需要调用

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

回答by Pawan Maheshwari

The best to get rid of this is to keep activity reference when onAttachis called and use the activity reference wherever needed, for e.g.

摆脱这种情况的最好方法是在onAttach调用时保留活动引用,并在需要的地方使用活动引用,例如

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

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

Edited, since onAttach(Activity)is depreciated & now onAttach(Context)is being used

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

回答by bvmobileapps

Don't call methods within the Fragment that require getActivity() until onStart in the parent Activity.

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

回答by Mapsy

I've been battling this kind of problemfor a while, and I think I've come up with a reliable solution.

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

It's pretty difficult to know for sure that this.getActivity()isn't going to return nullfor a Fragment, especially if you're dealing with any kind of network behaviour which gives your code ample time to withdraw Activityreferences.

很难确定athis.getActivity()不会返回,特别是如果您正在处理任何类型的网络行为,这使您的代码有足够的时间撤回引用。nullFragmentActivity

In the solution below, I declare a small management class called the ActivityBuffer. Essentially, this classdeals with maintaining a reliable reference to an owning Activity, and promising to execute Runnables within a valid Activitycontext whenever there's a valid reference available. The Runnables are scheduled for execution on the UI Thread immediately if the Contextis available, otherwise execution is deferred until that Contextis ready.

在下面的解决方案中,我声明了一个名为ActivityBuffer. 本质上,这class涉及维护对 owning 的可靠引用Activity,并承诺只要有有效引用可用就Runnable在有效Activity上下文中执行s 。如果RunnablesContext可用,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;
    }

}

In terms of its implementation, we must take care to apply the life cyclemethods to coincide with the behaviour described above by Pawan M:

就其实现而言,我们必须注意应用生命周期方法以与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;
    }

}

Finally, in any areas within your Fragmentthat extends BaseFragmentthat you're untrustworthy about a call to getActivity(), simply make a call to this.getActivityBuffer().safely(...)and declare an ActivityBuffer.IRunnablefor the task!

最后,在您Fragment扩展BaseFragment到您对调用 不可信的任何区域中getActivity(),只需调用this.getActivityBuffer().safely(...)ActivityBuffer.IRunnable为任务声明!

The contents of your void run(final Activity pActivity)are then guaranteed to execute along the UI Thread.

void run(final Activity pActivity)然后保证您的内容沿着 UI 线程执行。

The ActivityBuffercan then be used as follows:

ActivityBuffer则可以使用如下:

this.getActivityBuffer().safely(
  new ActivityBuffer.IRunnable() {
    @Override public final void run(final Activity pActivity) {
       // Do something with guaranteed Context.
    }
  }
);

回答by Mohanraj Balasubramaniam

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

回答by Feuby

I know this is a old question but i think i must provide my answer to it because my problem was not solved by others.

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

first of all : i was dynamically adding fragments using fragmentTransactions. Second: my fragments were modified using AsyncTasks (DB queries on a server). Third: my fragment was not instantiated at activity start Fourth: i used a custom fragment instantiation "create or load it" in order to get the fragment variable. Fourth: activity was recreated because of orientation change

首先:我正在使用 fragmentTransactions 动态添加片段。第二:我的片段是使用 AsyncTasks(服务器上的数据库查询)修改的。第三:我的片段没有在活动开始时实例化第四:我使用自定义片段实例化“创建或加载它”来获取片段变量。第四:由于方向改变而重新创建活动

The problem was that i wanted to "remove" the fragment because of the query answer, but the fragment was incorrectly created just before. I don't know why, probably because of the "commit" be done later, the fragment was not added yet when it was time to remove it. Therefore getActivity() was returning null.

问题是我想“删除”由于查询答案而导致的片段,但该片段之前被错误地创建。我不知道为什么,可能是因为稍后完成了“提交”,该片段尚未添加到删除它的时间。因此 getActivity() 返回 null。

Solution : 1)I had to check that i was correctly trying to find the first instance of the fragment before creating a new one 2)I had to put serRetainInstance(true) on that fragment in order to keep it through orientation change (no backstack needed therefore no problem) 3)Instead of "recreating or getting old fragment" just before "remove it", I directly put the fragment at activity start. Instantiating it at activity start instead of "loading" (or instantiating) the fragment variable before removing it prevented getActivity problems.

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