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

47 人关注

为什么 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)

This is LastNewsRVAdapter.java constructor.

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

这就是Fragment里面的代码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方法中的Fragment

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) {
    
5 个评论
你是否在onCreateView()中尝试了getActivity()?
Bek
不,如果我得到更多的崩溃,那么我就会去尝试。
我建议使用getActivity。因为我们在活动中使用这个,就像我们在Fragment的onCreateView()中使用getActivity。
使用getActivity()(它将返回活动的上下文)而不是getContext()。
发布你的片段的代码
android
android-fragments
fragment-lifecycle
Bek
Bek
发布于 2017-12-27
5 个回答
Pankaj Kumar
Pankaj Kumar
发布于 2021-04-12
已采纳
0 人赞同

正如公认的答案所说,你必须研究一下你使用和缓存上下文的方式。你在某种程度上泄露了上下文,导致Null Pointer Exception。

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

Use onAttach() to get Context. 大多数情况下,你不需要这个解决方案,所以只有在其他解决方案不起作用时才使用这个解决方案。而且你可能会造成活动泄漏的机会,所以在使用时要谨慎。当你离开片段时,你可能需要再次使上下文为空。

// 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中可用,所以你应该使用它。

From 片段文档

注意事项。如果你在你的Fragment中需要一个Context对象,你可以调用getContext()。然而,要注意的是,只有在getContext()被附加到一个活动上时,才可以调用【替换代码】。 当片段被连接到一个活动时。当片段还没有连接到 还没有连接,或者在其生命周期结束时被分离。 getContext()将返回null

我们应该覆盖onAttach()吗?或者我们应该使用getActivity?我有一点困惑。我总是使用getActivity来获取引用。请清除它
getActivity()有时也会返回null(请阅读 developer.android.com/reference/android/support/v4/app/... ).因此,重写onAttach()是获取上下文对象的最佳选择。
Bek
@Pankaj 好的,我会尝试一下,更新我的应用程序并发布结果。
真的吗?你应该像这样存储上下文吗?这难道不是因为在回调发生之前,视图已经消失了吗?
注意,尽管这一切听起来很好,但有时这并不可行,因为我们不是在自己的代码中请求上下文的人。比如说。替换代码0】内部调用了 requireContext ,所以无论我们是否从 onAttach 中记住了一个上下文,我们仍然需要进行 if (getContext() != null) 的安全检查
Leandro Ocampo
Leandro Ocampo
发布于 2021-04-12
0 人赞同

首先,正如你所看到的,在这个 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()中指定了一个回调,但这并不意味着该回调中的代码会在此时运行。它将在网络调用完成后运行并创建适配器。考虑到这一点,你的回调可能会在片段生命周期的那个点之后运行。我的意思是,用户可以进入那个屏幕(片段),并在网络请求结束(和回调运行)之前进入另一个片段或返回到前一个片段。如果发生这种情况,那么如果用户离开这个片段(onDetach()可能已经被调用),getContext()可能会返回null。

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

我对解决这些问题的建议是。

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

  • 另一个可以防止空指针异常的方法是把创建适配器LastNewsRVAdapter的过程移到回调之外,在片段中保留对它的引用。然后,在回调中使用这个引用来更新适配器的内容。link

  • 当然,一个异步操作可以在一个片段已经被分离后访问上下文。在这种情况下,我们应该用 isAdded() 方法检查它是否存在。当一个请求开始后留下一个片段时,我曾多次遇到异常。
    Henry
    Henry
    发布于 2021-04-12
    0 人赞同

    所以 getContext() 没有在 onCreateView() 中被调用。它是在 onResponse() 的回调中被调用的,它可以随时被调用。如果它在你的片段没有连接到活动时被调用, getContext() 将返回空。

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

    另一个解决方案是,当你的片段没有连接到你的活动时,简单地忽略 onResponse() 中的任何内容,像这样。 https://stackoverflow.com/a/10941410/4747587

    Mahdi Moqadasi
    Mahdi Moqadasi
    发布于 2021-04-12
    0 人赞同

    当你确定片段被连接到它的主机上时(onResume, onViewCreated,等等),用这个代替 getContext() :

    requireContext()
    

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

    Then you should check nullity by if clouse(或一些东西),或者确定如果程序到达这一行,它不是空的。

    在改造调用或改造(可能还有其他同样的方式),它将返回一个一次性的前必须清除或处理的onDestroy.

    Gk Mohammad Emon
    Gk Mohammad Emon
    发布于 2021-04-12
    0 人赞同

    写一个普通的方法,确保你永远不会得到空 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*/