安卓。片段 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 上下文中执行Runnables。如果Context 可用,Runnables 将立即安排在 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的主要内容,如果未能解决你的问题,请参考以下文章

getActivity() 在片段上返回 null?

Android getActivity() 总是在片段内返回 null

片段 getActivity() 与接口回调?

在片段中调用 getActivity() 以使其不返回 null 的最佳做法是啥? [复制]

方向更改后片段中的 getActivity() 为空

getActivity() 在片段的 AlertDialog 中为 null