getActivity()在Fragment函数中返回null

我有一个片段(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();

一定是我做错了什么,但我不知道是什么。

175778 次浏览

commit调度事务,即它不会立即发生,而是在下一次主线程准备好时安排在主线程上的工作。

我建议加一个

onAttach(Activity activity)

方法赋值给Fragment,并在它上面放一个断点,看看它什么时候相对于你对asd()的调用被调用。你会看到它在调用asd()退出的方法之后被调用。onAttach调用是将Fragment附加到其活动的地方,从这一点开始getActivity()将返回非空(注意还有一个onDetach()调用)。

在commit()之后调用回调函数的顺序:

  1. 无论你在commit()之后手动调用什么方法
  2. onAttach ()
  3. onCreateView ()
  4. onActivityCreated ()

我需要做一些涉及一些视图的工作,所以onAttach()不适合我;它坠毁。所以我移动了我的代码的一部分,设置一些参数在一个方法调用后立即提交()(1.),然后在onCreateView()(3.)内处理视图的代码的另一部分。

PJL是对的。 我采纳了他的建议,这就是我所做的:

  1. 为片段定义全局变量:

    private final Object attachingActivityLock = new Object();

    private boolean syncVariable = false; < / p >

  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();
}
}
}

如果你有一些UI更新,记得在UI线程上运行它们。我需要更新ImgeView,所以我做了:

image.post(new Runnable() {


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

最好的办法是在onAttach被调用时保持活动引用,并在任何需要的地方使用活动引用。

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


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

当你在另一个线程中调用getActivity()时,就会发生这种情况。典型的情况是在HTTP请求完成(例如在onResponse中)时调用getActivity()(例如Toast)。

为了避免这种情况,你可以定义一个字段名mActivity并使用它来代替getActivity()。该字段可以在Fragment的onAttach()方法中初始化,如下所示:

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


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

在我的项目中,我通常用这个特性为我所有的Fragments定义一个基类:

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;
}
}
}

快乐的编码,

自Android API级别23起,onAttach(Activity Activity)已弃用。你需要使用onAttach(Context Context)。http://developer.android.com/reference/android/app/Fragment.html onAttach (android.app.Activity)

Activity是一个上下文,所以如果你可以简单地检查上下文是否是一个Activity,并在必要时强制转换它。

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


Activity a;


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


}

请按照以下步骤操作。我想这会对你有帮助。

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


}

在哪里调用这个函数?如果你在Fragment的构造函数中调用它,它将返回null

只要在onCreateView()方法执行时调用getActivity()即可。

那些仍然有问题的onAttach(活动活动),它只是改变了上下文-

    @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;
}
}

由user1868713建议。

建议在onAttach中保持对活动的引用的其他答案只是建议对实际问题的一个创可贴。当getActivity返回null时,这意味着Fragment没有附加到Activity。最常见的情况是,当Activity由于旋转而消失,或者Activity正在完成,但Fragment仍然注册了一些回调监听器阻止它被垃圾收集时,这种情况就会发生。当监听器被调用时,如果你需要对Activity做一些事情,但Activity已经没有了,你可以做的不多。在你的代码中,你应该检查getActivity() != null,如果它不存在,那么什么都不做。如果你保留了一个指向已经消失的Activity的引用,你就阻止了这个Activity被垃圾收集。你尝试做的任何UI操作都不会被用户看到。我可以想象一些情况,在回调监听器中,你想要有一个与ui无关的Context,在这种情况下,获取Application Context可能更有意义。注意,onAttach技巧不是一个大内存泄漏的唯一原因是,通常在回调侦听器执行后,它就不再需要了,可以与片段、它的所有视图和活动上下文一起被垃圾收集。如果你setRetainInstance(true),有一个更大的机会发生内存泄漏,因为Activity字段也将被保留,但旋转后可能是前一个Activity,而不是当前的Activity。

我正在使用OkHttp,我刚刚遇到了这个问题。


第一部分@thucnguyen的想法是对的

这发生在您在另一个线程中调用getActivity()时,该线程在片段被删除后结束。典型的情况是在HTTP请求完成时调用getActivity()(例如Toast)(例如onResponse)。

一些HTTP调用正在执行甚至在活动结束后(因为HTTP请求完成可能需要一段时间)。然后,我通过HttpCallback尝试更新一些Fragment字段,并在尝试getActivity()时得到了一个null异常。

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

在我看来,解决方案是防止当片段不再活跃时发生回调了(不仅仅是Okhttp)。

解决办法:预防。

如果你看一下片段的生命周期(更多信息在这里),你会注意到有onAttach(Context context)onDetach()方法。它们分别在Fragment属于一个活动之后和停止之前被调用。

这意味着我们可以通过在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();
}
}
另一个好的解决方案是使用Android的LiveData和MVVM架构。 你可以在你的ViewModel中定义一个LiveData对象,并在你的片段中观察它,当LiveData值发生变化时,只有当你的片段处于活动状态时,它才会通知你的观察者(在这种情况下是片段),所以这将保证你的UI工作,只有当你的片段处于活动状态时才会访问活动。这是LiveData

的一个优点

当然,当这个问题第一次被提出时,还没有LiveData。我把这个答案留在这里,因为在我看来,这个问题仍然存在,它可能会对某些人有所帮助。

在onActivityCreated()中调用getActivity()方法

我用这种方法解决了我的问题。我已经从前一个类传递了getApplicationContext,它已经访问了getApplicationContext。我已经将Inputstream对象传递给我的新类营养素。

try{
InputStream is= getApplicationContext().getAssets().open("nutrient_list.json");
Nutrients nutrients=Nutrients.getNutrients(topRecognition,is);


} catch (IOException e) {
e.printStackTrace();
}

编写一个通用方法,确保永远不会得到空Activity。

public class BaseFragment extends Fragment {
private Context contextNullSafe;


@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
/*View creation related to this fragment is finished here. So in case if contextNullSafe is null
* then we can populate it here.In some discussion in - https://stackoverflow.com/questions/6215239/getactivity-returns-null-in-fragment-function
* and https://stackoverflow.com/questions/47987649/why-getcontext-in-fragment-sometimes-returns-null,
* there are some recommendations to call getContext() or getActivity() after onCreateView() and
* onViewCreated() is called after the onCreateView() call every time */
if (contextNullSafe == null) getContextNullSafety();
}




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


/**CALL THIS IF YOU NEED CONTEXT*/
public Context getContextNullSafety() {
if (getContext() != null) return getContext();
if (getActivity() != null) return getActivity();
if (contextNullSafe != null) return contextNullSafe;
if (getView() != null && getView().getContext() != null) return getView().getContext();
if (requireContext() != null) return requireContext();
if (requireActivity() != null) return requireActivity();
if (requireView() != null && requireView().getContext() != null)
return requireView().getContext();
                

return null;
            

}


/**CALL THIS IF YOU NEED ACTIVITY*/
public FragmentActivity getActivityNullSafety() {
if (getContextNullSafety() != null && getContextNullSafety() instanceof FragmentActivity) {
/*It is observed that if context it not null then it will be
* the related host/container activity always*/
return (FragmentActivity) getContextNullSafety();
}
return null;
}