web-dev-qa-db-ja.com

フラグメント内の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;
}

これは、フラグメント内の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) {

        }
    });
21
Bek

まず、この 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()でコールバックを指定していますが、それはそのコールバック内のコードがその時点で実行されることを意味しません。ネットワーク呼び出しが行われた後に実行され、アダプターが作成されます。それを念頭に置いて、フラグメントのライフサイクルのそのポイントの後にコールバックを実行することができます。つまり、ユーザーはその画面(フラグメント)に入り、他のフラグメントに移動したり、ネットワークリクエストが終了する(そしてコールバックが実行される)前のフラグメントに戻ることができます。その場合、ユーザーがフラグメントを離れるとgetContext()はnullを返す可能性があります(onDetach()が呼び出された可能性があります)。

また、ネットワーク要求が完了する前にアクティビティが破壊された場合、メモリリークが発生する可能性があります。したがって、2つの問題があります。

これらの問題を解決するための私のソリューションは次のとおりです。

  1. nullポインター例外とメモリリークを回避するために、フラグメント内のonDestroyView()が呼び出されているときにネットワークリクエストをキャンセルする必要があります(retrofitはリクエストをキャンセルできるオブジェクトを返します: link ) 。

  2. Nullポインター例外を防ぐもう1つのオプションは、アダプターLastNewsRVAdapterの作成をコールバックの外側に移動し、フラグメントへの参照を保持することです。次に、コールバック内でその参照を使用して、アダプターのコンテンツを更新します。 link

16
Leandro Ocampo

受け入れられた回答として、コンテキストの使用方法とキャッシュ方法を検討する必要があります。どういうわけか、Nullポインタ例外を引き起こしているコンテキストをリークしています。


以下の回答は、質問の最初の改訂版です。

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を返します。

18
Pankaj Kumar

したがって、getContext()onCreateView()で呼び出されません。 onResponse()コールバック内で呼び出され、いつでも呼び出すことができます。フラグメントがアクティビティにアタッチされていないときに呼び出された場合、getContext()はnullを返します。

ここでの理想的な解決策は、アクティビティ/フラグメントがアクティブでないときにリクエストをキャンセルすることです(ユーザーが戻るボタンを押した、アプリを最小化したなど)。

別の解決策は、次のように、フラグメントがアクティビティにアタッチされていない場合、onResponse()にあるものをすべて無視することです。 https://stackoverflow.com/a/10941410/4747587

1
Henry