46

为什么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;
}

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

        }
    });
4

5 回答 5

46

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


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

用于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.

于 2017-12-27T06:54:13.163 回答
32

首先,正如您在此链接上看到的,片段生命周期中的 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

于 2017-12-27T07:46:30.717 回答
3

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

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

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

于 2017-12-27T13:14:47.630 回答
2

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

requireContext()

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

然后你应该通过if clouse(或某些东西)检查是否为空,或者确保如果程序到达这一行,它不为空。

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

于 2020-02-08T15:47:21.470 回答
0

编写一个通用方法,以确保您永远不会得到 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;
    }
于 2021-04-11T17:02:38.557 回答