getActivity()在Fragment函数中返回null

时间:2011-06-02 13:33:47

标签: android android-fragments android-activity null android-context

我有一个像这样的公共方法的片段(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();

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

16 个答案:

答案 0 :(得分:153)

commit安排事务,即它不会立即发生,而是在主线程准备就绪时安排在主线程上工作。

我建议添加一个

onAttach(Activity activity)

Fragment的方法,并在其上设置一个断点,并查看相对于您对asd()的调用的时间。您将看到在调用asd()出口的方法之后调用它。 onAttach调用是Fragment附加到其活动的位置,从此点getActivity()将返回非空(nb还有onDetach()调用。)

答案 1 :(得分:83)

最好摆脱这种情况是在调用onAttach时保持活动参考,并在需要的地方使用活动参考,例如。

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

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

答案 2 :(得分:71)

当您在删除片段后完成的另一个线程中调用getActivity()时,会发生这种情况。典型的情况是在HTTP请求完成时调用getActivity()(例如,Toast)(例如,在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;
    }
}
}

快乐编码,

答案 3 :(得分:16)

Since Android API level 23, onAttach(Activity activity) has been deprecated. You need to use onAttach(Context context). http://developer.android.com/reference/android/app/Fragment.html#onAttach(android.app.Activity)

Activity is a context so if you can simply check the context is an Activity and cast it if necessary.

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

    Activity a;

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

}

答案 4 :(得分:11)

其他答案表明在onAttach中保留对活动的引用只是建议对真正的问题进行绑定。当getActivity返回null时,表示Fragment未附加到Activity。最常见的情况是,当Activity因旋转或Activity完成而消失时,会发生这种情况,但Fragment有某种回调侦听器。如果您需要对Activity执行某些操作但是Activity已经消失,那么当调用者被调用时,您无能为力。在您的代码中,您应该只检查getActivity() != null,如果它不存在则不做任何事情。如果您保留对活动的引用,那么您将阻止活动被垃圾回收。用户不会看到您可能尝试执行的任何UI操作。我可以想象一些情况,在回调监听器中你可能想要一个非UI相关的上下文,在这种情况下,获取Application上下文可能更有意义。请注意,onAttach技巧不是大内存泄漏的唯一原因是因为通常在回调监听器执行之后它将不再需要,并且可以与Fragment,其所有View和活动背景。如果你setRetainInstance(true)内存泄漏的可能性更大,因为活动字段也将被保留,但在轮换之后可能是之前的活动而不是当前活动。

答案 5 :(得分:10)

PJL是对的。 我已经使用了他的建议,这就是我所做的:

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

    private final Object attachingActivityLock = new Object();

    private boolean syncVariable = false;

  2. 实施

  3. @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);
        }
    });
    

答案 6 :(得分:7)

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

  1. 在commit()
  2. 之后立即手动调用的方法
  3. onAttach()
  4. onCreateView()
  5. onActivityCreated()
  6. 我需要做一些涉及一些视图的工作,所以onAttach()对我不起作用;它崩溃了。所以我移动了部分代码,它在commit()(1。)之后的一个方法中设置了一些参数,然后在onCreateView()(3。)内处理视图的代码的另一部分。

答案 7 :(得分:2)

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

第一部分@thucnguyen was on the right track

  

当您在删除片段后完成的另一个线程中调用getActivity()时会发生这种情况。典型的情况是在HTTP请求完成时调用getActivity()(例如Toast)(例如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)。

修复:预防。

如果您查看fragment lifecycle(更多信息here),您会发现有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();
    }
}

答案 8 :(得分: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

}

答案 9 :(得分:1)

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

执行方法getActivity()时,只需致电onCreateView()

答案 10 :(得分:0)

那些仍然遇到onAttach(活动活动)问题的人,刚刚改为上下文 -

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

在大多数情况下,保存上下文对您来说已经足够了 - 例如,如果您想要执行getResources(),您可以直接从上下文中执行此操作。如果你仍然需要在你的Activity中创建上下文 -

 @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的建议。

答案 11 :(得分:0)

您可以使用onAttach,或者如果您不想在任何地方放置onAttach,那么您可以在主App类上放置一个返回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)

如果这不适合你,请告诉我。

答案 12 :(得分:0)

另一个好的解决方案是将Android的LiveData与MVVM架构结合使用。 您将在ViewModel中定义一个LiveData对象,并在片段中对其进行观察,并且当LiveData值更改时,仅当您的片段处于活动状态时,它才会通知您的观察者(在这种情况下为片段),因此可以确保您仅当您的片段处于活动状态时,UI才能工作并访问活动。这是LiveData

附带的一项优势

当然,当第一次提出这个问题时,没有LiveData。我在这里留下这个答案,因为据我所知,仍然存在这个问题,可能会对某人有所帮助。

答案 13 :(得分:0)

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

答案 14 :(得分:0)

我已经用这种方式解决了我的问题。我已经从上一个可以访问getApplicationContext的类中传递了getApplicationContext。我已经将Inputstream对象传递给了我的新类Nutrients。

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

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

答案 15 :(得分:0)

编写一个通用方法,确保您永远不会获得空活动。

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