Why getContext() in fragment sometimes returns null?
为什么
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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) |
这是
1 2 3 4 5 6 7 | public LastNewsRVAdapter(Context context, List<LatestNewsData> latestNewsDataList, FirstPageSideBanner sideBanner) { this.context = context; this.latestNewsDataList = latestNewsDataList; inflater = LayoutInflater.from(context); this.sideBanner = sideBanner; } |
这是片段内的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | @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中的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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) { } }); |
作为公认的答案,您应该调查使用和缓存上下文的方式。不知何故,您正在泄漏导致Null指针异常的上下文。
Below Answer is as per first revision of question.
使用
1 2 3 4 5 6 7 8 9 | // 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中可用,因此您应该使用它。
从片段文档
Caution: If you need a
Context object within yourFragment , you can callgetContext() . However, be careful to callgetContext() only
when the fragment is attached to an activity. When the fragment is not
yet attached, or was detached during the end of its lifecycle,
getContext() will returnnull .
首先,正如您在此链接上看到的那样,片段生命周期内的onCreateView()方法位于onAttach()之后,因此此时您应该已经有一个上下文。您可能想知道,为什么getContext()然后返回null?问题出在您创建适配器的位置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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())。
此外,如果活动在您的网络请求完成之前被破坏,您也可能会发生内存泄漏。因此,您有两个问题。
我解决这些问题的解决方案是:
为了避免空指针异常和内存泄漏,应在调用片段内的onDestroyView()时取消网络请求(retrofit返回可以取消请求的对象:链接)。
防止空指针异常的另一个选项是将适配器LastNewsRVAdapter的创建移到回调之外,并在片段中保留对其的引用。然后,在回调中使用该引用来更新适配器的内容:链接
因此,在
理想的解决方案是在活动/片段未处于活动状态时(例如用户按下后退按钮或最小化应用程序)取消请求。
另一个解决方案是,当片段未附加到活动时,只需忽略