为什么 fragment 中的 getContext() 有时会返回 null?

新手上路,请多包涵

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

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

        }
    });

原文由 Bek 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.3k
2 个回答

首先,正如您在此 链接 中看到的,片段生命周期内的方法 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 的创建移到回调之外,并在片段中保留对它的引用。然后,在回调中使用该引用来更新适配器的内容: 链接

原文由 Leandro Ocampo 发布,翻译遵循 CC BY-SA 4.0 许可协议

正如公认的答案所说,您必须研究您使用和缓存上下文的方式。您以某种方式泄漏了导致空指针异常的上下文。


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

使用 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 中可用,因此您应该使用它。


来自 片段文档

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

原文由 Pankaj Kumar 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题