GetActivity() возвращает значение null в функции Фрагмент
У меня есть фрагмент (F1) с общедоступным методом, подобным этому
public void asd() {
if (getActivity() == null) {
Log.d("yes","it is null");
}
}
и да, когда я его вызываю (из Activity), он равен null...
FragmentTransaction transaction1 = getSupportFragmentManager().beginTransaction();
F1 f1 = new F1();
transaction1.replace(R.id.upperPart, f1);
transaction1.commit();
f1.asd();
Это должно быть то, что я делаю очень неправильно, но я не знаю, что это такое
Ответы
Ответ 1
commit
планирует транзакцию, т.е. это происходит не сразу, а запланировано как работа над основным потоком в следующий раз, когда основной поток будет готов.
Я бы предложил добавить
onAttach(Activity activity)
к вашему Fragment
и помещая точку останова на него и видя, когда он вызван относительно вашего вызова на asd()
. Вы увидите, что он вызывается после метода, по которому вы вызываете вызов asd()
. Вызов onAttach
- это то, где Fragment
привязан к его активности, и с этой точки getActivity()
будет возвращать ненулевое значение (nb также есть вызов onDetach()
).
Ответ 2
Лучше всего избавиться от этого - сохранить ссылку на активность при вызове onAttach и использовать ссылку на активность, где это необходимо, например
@Override
public void onAttach(Context context) {
super.onAttach(activity);
mContext = context;
}
@Override
public void onDetach() {
super.onDetach();
mContext = null;
}
Ответ 3
Это произошло, когда вы вызываете getActivity()
в другом потоке, завершившемся после удаления фрагмента. Типичным случаем является вызов getActivity()
(например, для Toast
), когда HTTP-запрос завершен (например, в onResponse
).
Чтобы избежать этого, вы можете определить имя поля mActivity
и использовать его вместо getActivity()
. Это поле может быть инициализировано в методе Fragment 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;
}
}
}
Удачного кодирования,
Ответ 4
Так как уровень API Android 23, onAttach (активность активности) устарел. Вам нужно использовать onAttach (контекст контекста). http://developer.android.com/reference/android/app/Fragment.html#onAttach(android.app.Activity)
Активность - это контекст, поэтому, если вы можете просто проверить, что контекст - это Activity, и при необходимости его приложите.
@Override
public void onAttach(Context context) {
super.onAttach(context);
Activity a;
if (context instanceof Activity){
a=(Activity) context;
}
}
Ответ 5
Другие ответы, которые предлагают сохранить ссылку на активность в onAttach, просто предполагают бандит к реальной проблеме. Когда getActivity возвращает ноль, это означает, что фрагмент не привязан к действию. Чаще всего это происходит, когда действие исчезает из-за ротации или завершения действия, но во фрагменте все еще зарегистрирован какой-либо прослушиватель обратного вызова. Когда вызывается слушатель, если вам нужно что-то сделать с Активом, но Активность пропала, вы ничего не можете сделать. В вашем коде вы должны просто проверить getActivity() != null
и, если его там нет, ничего не делать. Если вы сохраняете ссылку на пропавшую активность, вы запрещаете сборку мусора. Любой пользовательский интерфейс не сможет увидеть пользовательский интерфейс. Я могу представить некоторые ситуации, когда в прослушивателе обратного вызова вы можете захотеть иметь контекст для чего-то, не связанного с пользовательским интерфейсом, в этих случаях, вероятно, имеет больше смысла получить контекст приложения. Обратите внимание, что единственная причина, по onAttach
трюк onAttach
не является большой утечкой памяти, заключается в том, что обычно после того, как слушатель обратного вызова исполняется, он больше не нужен и может быть собран мусором вместе с Fragment, всем его View и контекстом Activity. Если вы setRetainInstance(true)
существует большая вероятность утечки памяти, поскольку поле Activity также будет сохранено, но после поворота это может быть предыдущее Activity, а не текущее.
Ответ 6
PJL является правильным.
Я использовал его предложение, и это то, что я сделал:
-
определены глобальные переменные для фрагмента:
private final Object attachingActivityLock = new Object();
private boolean syncVariable = false;
-
реализованы
@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
Порядок, в котором вызовы вызываются после commit():
- Независимо от метода, который вы вызываете вручную сразу после commit()
- onAttach()
- onCreateView()
- onActivityCreated()
Мне нужно было сделать некоторую работу, которая включала некоторые виды, поэтому onAttach() не работал у меня; оно сломалось. Поэтому я переместил часть своего кода, который устанавливал некоторые параметры внутри метода, называемого сразу после commit() (1.), а затем в другой части кода, который обрабатывал представление внутри onCreateView() (3.).
Ответ 8
Я использую 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();
}
}
Ответ 9
Сделайте следующее. Я думаю, это будет полезно для вас.
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
}
Ответ 10
Где вы называете эту функцию? Если вы вызовете его в конструкторе Fragment
, он вернет null
.
Просто вызовите getActivity()
, когда выполняется метод onCreateView()
.
Ответ 11
Те, у кого до сих пор проблема с 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.
Ответ 12
Вы можете использовать onAttach или если вы не хотите размещать onAttach всюду, тогда вы можете поместить метод, который возвращает ApplicationContext в основном классе App:
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)
Пожалуйста, дайте мне знать, если это не сработает для вас.
Ответ 13
Еще одним хорошим решением было бы использование Android LiveData с архитектурой MVVM. Вы должны определить объект LiveData внутри вашей ViewModel и наблюдать его в своем фрагменте, а когда значение LiveData изменяется, он уведомит вашего наблюдателя (фрагмент в данном случае) только в том случае, если ваш фрагмент находится в активном состоянии, поэтому будет гарантировано, что вы сделает ваш пользовательский интерфейс работающим и получит доступ только к активности, когда ваш фрагмент находится в активном состоянии. Это одно преимущество, которое поставляется с LiveData
Конечно, когда этот вопрос был впервые задан, LiveData не было. Я оставляю здесь этот ответ, потому что, как я вижу, эта проблема все еще существует, и она может кому-то помочь.
Ответ 14
Лучший и надежный способ:
FragmentActivity activity = (FragmentActivity) getActivity();
activity.finish();