it-roy-ru.com

Почему getContext () во фрагменте иногда возвращает ноль?

Почему 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) {

        }
    });
17
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 () может вернуть ноль, если пользователь покинет фрагмент (возможно, вызван onDetach ()).

Кроме того, у вас также могут возникнуть утечки памяти, если действие будет уничтожено до завершения сетевого запроса. Итак, у вас есть две проблемы.

Мои решения для решения этих проблем:

  1. чтобы избежать исключения нулевого указателя и утечки памяти, вы должны отменить сетевой запрос при вызове onDestroyView () внутри фрагмента (retrofit возвращает объект, который может отменить запрос: link ).

  2. Другой вариант, который предотвратит исключение нулевого указателя, - переместить создание адаптера LastNewsRVAdapter за пределы обратного вызова и сохранить ссылку на него во фрагменте. Затем используйте эту ссылку внутри обратного вызова, чтобы обновить содержимое адаптера: ссылка

13
Leandro Ocampo

Как принятый ответ, вы должны посмотреть, как вы используете и кешировать контекст. Каким-то образом вы пропускаете контекст, который вызывает исключение нулевого указателя.


Ниже ответ в соответствии с первым пересмотром вопроса.

Используйте 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.

13
Pankaj Kumar

Так что getContext() не вызывается в onCreateView(). Он вызывается внутри обратного вызова onResponse(), который может быть вызван в любое время. Если он вызывается, когда ваш фрагмент не привязан к активности, getContext() вернет ноль.

Идеальным решением здесь является отмена запроса, когда действие/фрагмент не активен (например, пользователь нажал кнопку назад или свернул приложение).

Другое решение состоит в том, чтобы просто игнорировать все, что находится в onResponse(), когда ваш фрагмент не привязан к вашей деятельности, например: https://stackoverflow.com/a/10941410/4747587

1
Henry