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

Bek*_*Bek 28 android android-fragments fragment-lifecycle

为何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)
Run Code Online (Sandbox Code Playgroud)

这是LastNewsRVAdapter.java构造函数.

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

这是onCreateViewFragment中的代码

@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;
}
Run Code Online (Sandbox Code Playgroud)

这是refreshFragment中的方法

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

        }
    });
Run Code Online (Sandbox Code Playgroud)

Pan*_*mar 26

作为公认的答案,您应该研究您使用和缓存上下文的方式.不知何故,您正在泄漏导致空指针异常的上下文.


以下答案是根据问题的第一个修订版.

使用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;
}
Run Code Online (Sandbox Code Playgroud)

此上下文将在onCreateView中提供,因此您应该使用它.


来自片段文档

注意:如果您需要一个Context物体Fragment,可以打电话getContext().但是,getContext()只有在片段附加到活动时才要小心.当片段尚未连接或在其生命周期结束时被分离时, getContext()将返回null.

  • 谢谢。为我工作。这是最好的答案。 (2认同)
  • 请注意,尽管这听起来不错,但有时这是不可行的,因为我们不是在自己的代码中请求上下文的人。例如:`getString`内部调用`requireContext`,所以无论我们是否记住了`onAttach`中的上下文,我们仍然需要进行`if (getContext() != null)`安全检查 (2认同)

Lea*_*mpo 18

首先,正如你在这个链接上看到的那样,片段生命周期内的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) {

        }
    });
Run Code Online (Sandbox Code Playgroud)

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

此外,如果活动在网络请求完成之前被销毁,您也可能会发生内存泄漏.所以你有两个问题.

我解决这些问题的解决方案是:

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

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