getActivity () повертає нуль у функції Fragment


192

У мене є фрагмент (F1) з таким публічним методом

public void asd() {
    if (getActivity() == null) {
        Log.d("yes","it is null");
    }
}

і так, коли я називаю це (з діяльності), це недійсне значення ...

FragmentTransaction transaction1 = getSupportFragmentManager().beginTransaction();
F1 f1 = new F1();
transaction1.replace(R.id.upperPart, f1);
transaction1.commit();
f1.asd();

Це я маю щось неправильно, але я не знаю, що це


Я не впевнений, чи була помилка, коли ви вставляли її в цю посаду, але вам потрібні дужки після цього getActivity(). Крім того, як ви створюєте фрагмент? Чи є він у вашому layout.xml?
CaseyB

Де належить другий фрагмент коду? Для oncreate () - метод діяльності? А ви вже називали setContentView ()?
Францискус Карсунке

R.id.upperPar - це елемент у макеті, тому його слід було замінити фрагментом, але це не моя проблема. Я не розумію, чому я отримую нуль, коли я викликаю getActivity () у користувацьких методах фрагментів, скажімо, у методі onActivityCreate getActivity - це фактична активність, а не нуль
Lukap

проблема полягає не в макетах, додаток працює добре, але чому я отримую нуль для getActivity ?, btw всі елементи, включаючи фрагмент, він видається, як це не повинно
виникати

1
Вам слід назвати цей метод: f1.asd (); у методі onActivityCreate, який слід переосмислити у вашому класі фрагментів.
Намрата Багерваль

Відповіді:


164

commit планує транзакцію, тобто вона не відбувається відразу, але планується як робота над основним потоком наступного разу, коли основний потік буде готовий.

Я б запропонував додати

onAttach(Activity activity)

методом до вашого Fragmentі поставити на нього точку перерви і побачити, коли він викликається відносно вашого дзвінка asd(). Ви побачите, що він викликається після методу, коли ви здійснюєте виклик для asd()виходу. onAttachВиклик , де Fragmentприкріплюються до її діяльності і з цією точкою getActivity()буде повертати ненульові (пь є також onDetach()виклик).


5
Я не розумів, як можна вирішити свою проблему. Якщо моя getActivity () не готова, як я можу отримати посилання на об’єкт FragmentActivity?
CeccoCQ

2
@Vivek Я не знаю зовсім того, чого ти хочеш досягти. Якщо вам потрібен фрагмент для відображення діалогового діалогового вікна, тоді він повинен зробити те, що йому потрібно зробити при створенні, наприклад, у його методах onCreateViewчи onActivityCreatedметодах. Я сумніваюся, чому потрібно викликати asd (), коли це робиться у розміщенні питань.
PJL

3
onAtatach застарілий
abbasalim

6
onAttach (активність mActivity), схоже, знецінився. будь-яке рішення для цього
ashish.n

4
API 24 представленийcommitNow()
Ніколя

92

Найкраще, щоб позбутися цього, - це зберігати посилання на активність, коли onAttach викликається, і використовувати посилання на активність, де це потрібно, наприклад,

@Override
public void onAttach(Context context) {
    super.onAttach(activity);
    mContext = context;
}

@Override
public void onDetach() {
    super.onDetach();
    mContext = null;
}

34
Чи слід встановлювати mActivity = null onDetach ()?
Олівер Пірмен

5
@OliverPearmain, якщо ви зробите це в onDetach (), то прибутку не буде. Ви повинні звести нанівець його в onDestory (). Крім того, ви повинні провести його в WeakRefernce.
Кирило Попов

Я зводячи нанівець це як в обох, так onDestroy()і onDetach()тому onDestroy(), що не гарантовано називаються.
Мухаммед Алі

8
Ми просочуємося, Activityякщо не зводимо нанівець це onDestroy()?
Мухаммед Алі

3
За даними developer.android.com/intl/zh-tw/guide/components/… , onAttach () викликається перед викликом onCreateView (). Але я все одно отримую NullPointerException, поки я викликаю getActivity () в onCreateView (). Як це могло статися?
Кімі Чіу

82

Це сталося, коли ви викликаєте getActivity()інший потік, який завершився після видалення фрагмента. Типовим випадком є ​​виклик getActivity()(наприклад, для a Toast) після завершення запиту HTTP ( onResponseнаприклад,).

Щоб цього уникнути, ви можете визначити ім'я поля mActivityта використовувати його замість getActivity(). Це поле може бути ініціалізовано методом фрагмента onAttach () таким чином:

@Override
public void onAttach(Context context) {
    super.onAttach(context);

    if (context instanceof Activity){
        mActivity =(Activity) context;
    }
}

У своїх проектах я зазвичай визначаю базовий клас для всіх моїх фрагментів з цією функцією:

public abstract class BaseFragment extends Fragment {

    protected FragmentActivity mActivity;

    @Override
public void onAttach(Context context) {
    super.onAttach(context);

    if (context instanceof Activity){
        mActivity =(Activity) context;
    }
}
}

Щасливе кодування,


19
встановимо mActivity = null; в onDetach ()?
Бхарат Додеджа

thucnguyen, а як же оголосити статичність mActivity для додатка однієї активності?
iamthevoid

Я взагалі не бачу причин для доступу Activityз іншої теми. Ви нічого не можете з цим зробити, навіть не показувати тост. Тож вам слід спочатку перенести роботу в основний потік, або взагалі не використовувати Activity.
Дмитро Лазерка

4
@BharatDodeja ми повинні встановити mActivity = null onDetach? Ви дізналися?
SLearner

5
Це буде протікати, не знімаючи активності.
Дарек Деонізіак

30

Інші відповіді, які пропонують зберегти посилання на активність в OnAttach, - це лише підказка до реальної проблеми. Коли getActivity повертається до нуля, це означає, що фрагмент не приєднаний до активності. Найчастіше це відбувається, коли активність відійшла через обертання або активність закінчується, але фрагмент все ще має зареєстрований слухач зворотного виклику. Коли слухач зателефонує, якщо вам потрібно щось робити з Активністю, але Активність відсутня, ви не можете багато чого зробити. У своєму коді слід просто перевіритиgetActivity() != nullа якщо його там немає, то не робіть нічого. Якщо ви зберігаєте посилання на активність, яку не було, ви забороняєте збирати сміття. Користувач не побачить будь-які речі інтерфейсу користувача, які ви можете спробувати. Я можу уявити деякі ситуації, коли у слухача зворотного дзвінка ви можете мати Контекст для чогось, що не пов’язане з інтерфейсом користувача, у тих випадках, мабуть, має сенс отримати контекст програми. Зауважте, що єдиною причиною того, що onAttachтрюк не є великим витоком пам’яті, є те, що зазвичай після того, як слухач виконує зворотний виклик, він більше не знадобиться, і його можна збирати сміття разом з фрагментом, усіма його переглядами та контекстом діяльності. Якщо тиsetRetainInstance(true) більша ймовірність витоку пам’яті, оскільки поле «Активність» також буде збережене, але після обертання це може бути попередня активність, а не поточна.


1
Це саме моя проблема. У мене є фрагмент, який виконує процес -> тоді показується оголошення ->, а потім проас продовжується. У деяких пристроях після повернення з оголошення (через слухача до рекламних подій) getActivity () є нульовим. Але мені потрібно продовжувати виконувати іншу частину роботи, щоб закінчити роботу. Ви маєте на увазі, що для цього немає рішення?
Нотбад

Це саме те, з чим я стикаюся. У мене є фрагмент діяльності у фрагменті, де я виконую деякі рахунки. Після оплати я хочу використовувати інтерфейс, щоб зробити щось, але інтерфейс був нульовим.
Фредді

Це, здається, є правильною загальною відповіддю на 100 запитань ТА щодо цієї теми.
Мануель

Найкраща відповідь. Є так багато бандаїдних рішень для Android на SO.
maxbeaudoin

Отже, якщо я хочу виконати якусь операцію, як я можу виконати після того, як доступ до getActivity () буде доступний (якщо він взагалі є).
Sreekanth Karumanaghat

17

Починаючи з Android API рівня 23, функція onAttach (активність) застаріла. Вам потрібно використовувати onAttach (контекст контексту). http://developer.android.com/reference/android/app/Fragment.html#onAttach(android.app.Activity)

Діяльність - це контекст, тому, якщо ви можете просто перевірити, що це контекст, це діяльність та при необхідності подати її.

@Override
public void onAttach(Context context) {
    super.onAttach(context);

    Activity a;

    if (context instanceof Activity){
        a=(Activity) context;
    }

}

Як це використовувати
ARR.s

10

PJL має рацію. Я використав його пропозицію, і ось що я зробив:

  1. визначені глобальні змінні для фрагмента:

    private final Object attachingActivityLock = new Object();

    private boolean syncVariable = false;

  2. здійснено

@Override
public void onAttach(Activity activity) {
  super.onAttach(activity);
  synchronized (attachingActivityLock) {
      syncVariable = true;
      attachingActivityLock.notifyAll();
  }
}

3. Я завершив свою функцію, куди мені потрібно викликати getActivity (), в потоці, тому що якщо вона буде працювати на головній нитці, я б заблокував потік кроком 4. а onAttach () ніколи не називався б.

    Thread processImage = new Thread(new Runnable() {

        @Override
        public void run() {
            processImage();
        }
    });
    processImage.start();

4. у своїй функції, де мені потрібно викликати getActivity (), я використовую це (перед викликом getActivity ())

    synchronized (attachingActivityLock) {
        while(!syncVariable){
            try {
                attachingActivityLock.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

Якщо у вас є кілька оновлень інтерфейсу користувача, не забудьте запустити їх у потоці інтерфейсу користувача. Мені потрібно оновити ImgeView, щоб я зробив:

image.post(new Runnable() {

    @Override
    public void run() {
        image.setImageBitmap(imageToShow);
    }
});

7

Порядок виклику зворотних викликів після фиксации ():

  1. Який би метод ви не викликали вручну відразу після фіксації ()
  2. onAttach ()
  3. onCreateView ()
  4. onActivityCreate ()

Мені потрібно було виконати деяку роботу, яка передбачала деякі погляди, тому onAttach () не працював для мене; воно розбилося. Тому я перемістив частину свого коду, яка встановлювала параметри всередині методу, який називається відразу після commit () (1.), потім іншу частину коду, яка обробляла перегляд всередині onCreateView () (3.).


3

Я використовую OkHttp, і я щойно стикався з цією проблемою.


З першої частини @thucnguyen був на правильному шляху .

Це сталося, коли ви викликаєте getActivity () в іншому потоці, який закінчився після видалення фрагмента. Типовим випадком є ​​виклик getActivity () (наприклад, для Toast) після завершення запиту HTTP (наприклад, у OnResponse).

Деякі дзвінки HTTP виконувались навіть після того, як активність була закрита (оскільки завершення запиту HTTP може зайняти деякий час). Я потім, HttpCallbackспробувавши оновити деякі фрагменти поля і отримав nullвиняток при спробі getActivity().

http.newCall(request).enqueue(new Callback(...
  onResponse(Call call, Response response) {
    ...
    getActivity().runOnUiThread(...) // <-- getActivity() was null when it had been destroyed already

Рішення IMO полягає в тому, щоб запобігти виникненню зворотних викликів, коли фрагмент вже не живий (і це не тільки для Okhttp).

Виправлення: профілактика.

Якщо ви подивитесь на життєвий цикл фрагмента (більше інформації тут ), ви помітите, що є onAttach(Context context)і onDetach()методи. Вони називаються після того, як Фрагмент належить до діяльності і безпосередньо перед тим, як перестати бути таким, відповідно.

Це означає, що ми можемо не допустити зворотного виклику, контролюючи його в onDetachметоді.

@Override
public void onAttach(Context context) {
    super.onAttach(context);

    // Initialize HTTP we're going to use later.
    http = new OkHttpClient.Builder().build();
}

@Override
public void onDetach() {
    super.onDetach();

    // We don't want to receive any more information about the current HTTP calls after this point.
    // With Okhttp we can simply cancel the on-going ones (credits to https://github.com/square/okhttp/issues/2205#issuecomment-169363942).
    for (Call call : http.dispatcher().queuedCalls()) {
        call.cancel();
    }
    for (Call call : http.dispatcher().runningCalls()) {
        call.cancel();
    }
}

2

Де ви називаєте цю функцію? Якщо ви зателефонуєте йому в конструктор Fragment, він повернеться null.

Просто зателефонуйте, getActivity()коли метод onCreateView()виконаний.


1

Виконайте наступне. Я думаю, що вам це буде корисно.

private boolean isVisibleToUser = false;
private boolean isExecutedOnce = false;


@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View root = inflater.inflate(R.layout.fragment_my, container, false);
    if (isVisibleToUser && !isExecutedOnce) {
        executeWithActivity(getActivity());
    }
    return root;
}

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    this.isVisibleToUser = isVisibleToUser;
    if (isVisibleToUser && getActivity()!=null) {
        isExecutedOnce =true;
        executeWithActivity(getActivity());
    }
}


private void executeWithActivity(Activity activity){
    //Do what you have to do when page is loaded with activity

}

1

Тим, хто все ще має проблему з onAttach (Activity Activity), його щойно змінили на Context -

    @Override
public void onAttach(Context context) {
    super.onAttach(context);
    this.context = context;
}

У більшості випадків збереження контексту вам буде достатньо - наприклад, якщо ви хочете зробити getResources (), ви можете це зробити прямо з контексту. Якщо вам все-таки потрібно внести контекст у свою діяльність, зробіть це -

 @Override
public void onAttach(Context context) {
    super.onAttach(context);
    mActivity a; //Your activity class - will probably be a global var.
    if (context instanceof mActivity){
        a=(mActivity) context;
    }
}

За пропозицією користувача1868713.


0

Ви можете використовувати onAttach або якщо ви не хочете ставити на AtAtach скрізь, ви можете поставити метод, який повертає ApplicationContext в основний клас програми:

public class App {
    ...  
    private static Context context;

    @Override
    public void onCreate() {
        super.onCreate();
        context = this;
    }

    public static Context getContext() {
        return context;
    }
    ...
}

Після цього ви можете повторно використовувати його скрізь у всьому проекті, наприклад:

App.getContext().getString(id)

Будь ласка, дайте мені знати, якщо це не працює для вас.


0

Ще одним хорошим рішенням буде використання LiveData Android з архітектурою MVVM. Ви б визначили об’єкт LiveData всередині свого ViewModel і спостерігали за ним у своєму фрагменті, і коли значення LiveData буде змінено, воно сповістить вашого спостерігача (фрагмент у цьому випадку) лише про те, що ваш фрагмент знаходиться в активному стані, тож буде гарантовано, що ви зробить ваш інтерфейс користувача активним і отримає доступ до активності лише тоді, коли ваш фрагмент знаходиться в активному стані. Це одна з переваг, яка поставляється з LiveData

Звичайно, коли вперше було задано це питання, LiveData не було. Я залишаю цю відповідь тут, тому що, як я бачу, все ще існує ця проблема, і вона могла б комусь допомогти.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.