【问题标题】:Why getContext() in fragment sometimes returns null?为什么片段中的 getContext() 有时会返回 null?
【发布时间】:2017-12-27 06:49:33
【问题描述】:

为什么getContext() 有时会返回null?我将上下文作为参数传递给LastNewsRVAdapter.java。但是LayoutInflater.from(context) 有时会崩溃。我在游戏控制台上收到了一些崩溃报告。以下是崩溃报告。

java.lang.NullPointerException
com.example.adapters.LastNewsRVAdapter.<init>

java.lang.NullPointerException: 
at android.view.LayoutInflater.from (LayoutInflater.java:211)
at com.example.adapters.LastNewsRVAdapter.<init> (LastNewsRVAdapter.java)
at com.example.fragments.MainFragment$2.onFailure (MainFragment.java)
or                     .onResponse (MainFragment.java)
at retrofit2.ExecutorCallAdapterFactory$ExecutorCallbackCall$1$1.run 
(ExecutorCallAdapterFactory.java)
at android.os.Handler.handleCallback (Handler.java:808)
at android.os.Handler.dispatchMessage (Handler.java:103)
at android.os.Looper.loop (Looper.java:193)
at android.app.ActivityThread.main (ActivityThread.java:5299)
at java.lang.reflect.Method.invokeNative (Method.java)
at java.lang.reflect.Method.invoke (Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run 
(ZygoteInit.java:825)
at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:641)
at dalvik.system.NativeStart.main (NativeStart.java)

这是LastNewsRVAdapter.java 构造函数。

public LastNewsRVAdapter(Context context, List<LatestNewsData> 
    latestNewsDataList, FirstPageSideBanner sideBanner) {
    this.context = context;
    this.latestNewsDataList = latestNewsDataList;
    inflater = LayoutInflater.from(context);
    this.sideBanner = sideBanner;
}

这是片段内的代码onCreateView

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    // Inflate the layout for this fragment

    final View view = inflater.inflate(R.layout.fragment_main_sonku_kabar, container, false);
    tvSonkuKabar = view.findViewById(R.id.textview_sonku_kabar_main);
    tvNegizgiKabar = view.findViewById(R.id.textview_negizgi_kabar_main);
    refresh(view);

    final SwipeRefreshLayout swipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.mainRefreshSonkuKabar);
    swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
        @Override
        public void onRefresh() {
            refresh(view);
            swipeRefreshLayout.setRefreshing(false);
        }
    });
    setHasOptionsMenu(true);
    return view;
}

这是 Fragment 中的 refresh 方法

private void refresh(final View view) {
    sideBanner = new FirstPageSideBanner();

    final RecyclerView rvLatestNews = (RecyclerView) view.findViewById(R.id.recViewLastNews);
    rvLatestNews.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false));
    rvLatestNews.setNestedScrollingEnabled(false);
    App.getApiService().getLatestNews().enqueue(new Callback<LatestNews>() {
        @Override
        public void onResponse(Call<LatestNews> call, Response<LatestNews> response) {
            if (response.isSuccessful() && response.body().isSuccessfull()){
                adapter = new LastNewsRVAdapter(getContext(), response.body().getData(), sideBanner);
                rvLatestNews.setAdapter(adapter);
                tvSonkuKabar.setVisibility(View.VISIBLE);
            }
        }

        @Override
        public void onFailure(Call<LatestNews> call, Throwable t) {

        }
    });

【问题讨论】:

  • 你尝试过 onCreateView() 中的 getActivity() 吗?
  • 没有。如果我遇到更多崩溃,那么我会尝试。
  • 我建议使用 getActivity。当我们在 Activity 中使用它时,我们在 Fragment 的 onCreateView() 中使用 getActivity。
  • 使用 getActivity()(它将返回活动的上下文)而不是 getContext()。
  • 发布你的片段代码

标签: android android-fragments fragment-lifecycle


【解决方案1】:

正如公认的答案所说,您必须研究使用和缓存上下文的方式。不知何故,您正在泄漏导致空指针异常的上下文。


下面的答案是根据问题的第一次修订。

使用onAttach() 获取上下文。 大多数情况下,您不需要此解决方案,因此只有在任何其他解决方案都不起作用时才使用此解决方案。而且你可能会创造一个活动泄漏的机会,所以在使用它时要小心。从片段离开时,您可能需要再次将上下文设为空

// Declare Context variable at class level in Fragment
private Context mContext;

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

这个上下文将在 onCreateView 中可用,所以你应该使用它。


来自Fragment Documentation

注意:如果您需要在您的Fragment 中添加一个Context 对象,您可以调用getContext()。但是,请注意仅致电getContext() 当片段附加到活动时。当片段不是 尚未连接,或在其生命周期结束时分离, getContext() 将返回 null

【讨论】:

  • 我们应该重写 onAttach() 吗?或者我们应该使用 getActivity ?我有一点困惑。我总是使用 getActivity 来获取参考。请清除它
  • getActivity() 有时也可以返回 null (阅读developer.android.com/reference/android/support/v4/app/…)。所以重写 onAttach() 是获取上下文对象的最佳选择。
  • 谢谢。为我工作。这是最好的答案。
  • 太漂亮了
  • 注意,尽管这听起来不错,但有时这是不可行的,因为我们不是在自己的代码中请求上下文的人。例如:getString 内部调用了requireContext,所以无论我们是否记得来自onAttach 的上下文,我们仍然需要进行if (getContext() != null) 安全检查
【解决方案2】:

首先,正如您在link 中看到的那样,片段生命周期中的 onCreateView() 方法位于 onAttach() 之后,因此此时您应该已经有了上下文。您可能想知道,为什么 getContext() 会返回 null 呢?问题在于您在哪里创建适配器:

App.getApiService().getLatestNews().enqueue(new Callback<LatestNews>() {
        @Override
        public void onResponse(Call<LatestNews> call, Response<LatestNews> response) {
            if (response.isSuccessful() && response.body().isSuccessfull()){
                adapter = new LastNewsRVAdapter(getContext(), response.body().getData(), sideBanner);
                rvLatestNews.setAdapter(adapter);
                tvSonkuKabar.setVisibility(View.VISIBLE);
            }
        }

        @Override
        public void onFailure(Call<LatestNews> call, Throwable t) {

        }
    });

虽然您在 onCreateView() 中指定了回调,但这并不意味着该回调中的代码将在此时运行。网络调用完成后,它将运行并创建适配器。考虑到这一点,您的回调可能会在片段生命周期中的该点之后运行。我的意思是用户可以在网络请求完成(并且回调运行)之前进入该屏幕(片段)并转到另一个片段或返回到前一个片段。如果发生这种情况,如果用户离开片段,getContext() 可能会返回 null(可能已调用 onDetach())。

此外,您也可能会出现内存泄漏,以防活动在您的网络请求完成之前被破坏。所以你有两个问题。

我解决这些问题的建议是:

  1. 为了避免空指针异常和内存泄漏,你应该在fragment内部的onDestroyView()被调用时取消网络请求(retrofit返回一个可以取消请求的对象:link )。

  2. 另一个防止空指针异常的选项是将适配器 LastNewsRVAdapter 的创建移到回调之外,并在片段中保留对它的引用。然后,在回调中使用该引用来更新适配器的内容:link

【讨论】:

  • 当然,异步操作可以在片段已经分离后访问上下文。在这种情况下,我们应该使用isAdded() 方法检查它是否存在。在请求开始后留下片段时,我多次捕获异常。
【解决方案3】:

所以getContext() 不会在onCreateView() 中调用。它在onResponse() 回调中调用,可以随时调用。如果在您的片段未附加到活动时调用它,getContext() 将返回 null。

这里的理想解决方案是在活动/片段不活动时取消请求(例如用户按下后退按钮或最小化应用程序)。

另一种解决方案是在片段未附加到活动时简单地忽略 onResponse() 中的任何内容,如下所示:https://stackoverflow.com/a/10941410/4747587

【讨论】:

    【解决方案4】:

    当您确定片段已附加到其主机(onResume、onViewCreated 等)时,请使用它而不是 getContext()

    requireContext()
    

    它不会被 lint 检查,但如果上下文分离,它会抛出异常!

    那么你应该通过 if clouse(或其他东西)来检查 nullity,或者确保如果程序到达这一行,它不是 null。

    在改造调用或改造中(可能其他人以同样的方式),它将返回一个必须在 onDestroy 之前清除或处置的 disposable

    【讨论】:

      【解决方案5】:

      编写一个通用方法,确保您永远不会得到 null Context

      public class BaseFragment extends Fragment {
          private Context contextNullSafe;
      
           @Override
          public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
              super.onViewCreated(view, savedInstanceState);
               /*View creation related to this fragment is finished here. So in case if contextNullSafe is null
               * then we can populate it here.In some discussion in - https://stackoverflow.com/questions/6215239/getactivity-returns-null-in-fragment-function
               * and https://stackoverflow.com/questions/47987649/why-getcontext-in-fragment-sometimes-returns-null,
               * there are some recommendations to call getContext() or getActivity() after onCreateView() and
               * onViewCreated() is called after the onCreateView() call every time */
              if (contextNullSafe == null) getContextNullSafety();
          }
      
      
         @Override
          public void onAttach(@NonNull Context context) {
              super.onAttach(context);
              contextNullSafe = context;
          }
      
          /**CALL THIS IF YOU NEED CONTEXT*/
          public Context getContextNullSafety() {
                      if (getContext() != null) return getContext();
                      if (getActivity() != null) return getActivity();
                      if (contextNullSafe != null) return contextNullSafe;
                      if (getView() != null && getView().getContext() != null) return getView().getContext();
                      if (requireContext() != null) return requireContext();
                      if (requireActivity() != null) return requireActivity();
                      if (requireView() != null && requireView().getContext() != null)
                          return requireView().getContext();
                      
                      return null;
                  
              }
      
          /**CALL THIS IF YOU NEED ACTIVITY*/
          public FragmentActivity getActivityNullSafety() {
              if (getContextNullSafety() != null && getContextNullSafety() instanceof FragmentActivity) {
                  /*It is observed that if context it not null then it will be
                   * the related host/container activity always*/
                  return (FragmentActivity) getContextNullSafety();
              }
              return null;
          }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-02-20
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多