onAttach(Activity) 已过时:我在哪里可以检查活动是否实现了回调接口

22

在API 23之前,我使用Fragment的onAttach方法获取我的侦听器实例,然后在onDetach中清除引用。例如:

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    mListener = null;
    try {
        mListener = (SellFragmentListener) activity;
    } catch (ClassCastException e) {
        throw new ClassCastException(activity.toString()
                + " must implement SellFragmentListener");
    }
}

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

在onAttach(Context context)方法内执行相同的检查是否安全?还有更好的方法来获取持有者Activity实例吗?


onActivityCreated - mbmc
我通常在 onResume() / onPause() 上做这种事情,特别是如果监听器会对UI产生影响的话。 - Kevin Krumwiede
@tibo 我认为当一个Fragment被添加到现有的Activity中时,onActivityCreated方法不会被调用。 - Ariel Carbonaro
@KevinKrumwiede 那也是一个选项! - Ariel Carbonaro
3
是的,这是个好问题。它的意思是文档http://developer.android.com/training/basics/fragments/communicating.html是错误或不是最新的。 - Kiryl Bielašeŭski
7个回答

32

查看源代码:

/**
 * Called when a fragment is first attached to its context.
 * {@link #onCreate(Bundle)} will be called after this.
 */
public void onAttach(Context context) {
    mCalled = true;
    final Activity hostActivity = mHost == null ? null : mHost.getActivity();
    if (hostActivity != null) {
        mCalled = false;
        onAttach(hostActivity);
    }
}

/**
 * @deprecated Use {@link #onAttach(Context)} instead.
 */
@Deprecated
public void onAttach(Activity activity) {
    mCalled = true;
}

如果有主机Activity,onAttach(Context context)将调用onAttach(Activity activity)。您可以安全地使用onAttach(Activity activity)


1
mHost无法从包外访问...那么怎么办? - joninx

5

如Zsolt Mester的回答所示,onAttach(Activity activity)已被弃用,而是使用onAttach(Context context)。因此,您只需检查确保上下文是活动即可。

在Android Studio中,如果您转到文件> 新建>片段,则可以获得包含正确语法的自动生成代码。

import android.support.v4.app.Fragment;
...

public class MyFragment extends Fragment {

    private OnFragmentInteractionListener mListener;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // inflate fragment layout
        return inflater.inflate(R.layout.fragment_myfragment, container, false);
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof OnFragmentInteractionListener) {
            mListener = (OnFragmentInteractionListener) context;
        } else {
            throw new RuntimeException(context.toString()
                    + " must implement OnFragmentInteractionListener");
        }
    }

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

    public interface OnFragmentInteractionListener {
        // TODO: Update argument type and name
        void onFragmentInteraction(Uri uri);
    }
}

注意事项

  • 由于父Activity必须实现我们的OnFragmentInteractionListener(任意命名的接口),因此检查(context instanceof OnFragmentInteractionListener)可以确保上下文实际上是Activity。

  • 请注意我们正在使用支持库。否则,Android API 23之前的版本不会调用onAttach(Context context)

另请参阅


4

好的,被弃用的是onAttach(Activity activity);方法,但整个流程仍然保留。所以你不需要真正做任何事情,因为onAttach(Activity activity);仍将得到长期支持。


3

我从未使用过onAttach(Context context),但我认为你的代码基本上是好的。所以这是我的建议,使用你的代码:

public void onAttach (Context context) {
   super.onAttach(context);
   try {
        Activity activity = (Activity) context;
        mListener = (SellFragmentListener) activity;
   } catch (ClassCastException e) {
        throw new ClassCastException(activity.toString()
                + " must implement SellFragmentListener");
    }
}

主要区别在于我可以将context强制转换为Activity。这是因为Context可以传播到子类,即Activity。
另一个问题是,对于我们来说,API 23仍然遥远,不需要担心。如果你担心的话,使用build pragma(静态Build)可能是一个不错的选择。

1
您应该直接转换上下文。或者如果您想让上下文成为活动的实例。首先使用 if(context instanceof Activity) { ... } 进行检查。 - Daverix
@Daverix,有趣的评论“您应该直接转换上下文”。请具体说明。您是说对象上下文可以/应该直接转换为mListener吗?这将把代码从2行减少到仅1行。如果是这样,那只是一种编码风格的差异。 - The Original Android
是的,这可能是编码风格的差异,但在我看来仍然是一行不必要的代码。否则最好使用“instanceof”而不是捕获ClassCastException。但最终也许只能为您节省更多的行数并获得最小的性能提升。 - Daverix

1
我使用原生的Fragment,而不是来自支持库的其中之一。我在我的代码中放置了两个onAttach()方法并在运行不同SDK版本的设备上进行了一些调试。我发现: SDK 22及以下-只有onAttach(Activity)被调用。这并不奇怪,因为onAttach(Context)仅在SDK 23中引入。 SDK 23及以上-首先调用onAttach(Context),然后再调用onAttach(Activity)。(这符合@Zsolt Mester在帖子的另一个回答中提到的源代码。)

由于我的应用的minSdkVersion低于23,我决定完全省略onAttach(Context)方法,并简单地在现有的onAttach(Activity)方法中添加@SuppressWarnings("deprecation")注释。


1
我曾经面临过同样的情况。我查看了各种帖子,最终在片段的onCreate()块中实现了我的代码。到目前为止,我没有遇到任何问题,我认为这不会是一个问题,因为在生命周期中onCreate紧随onAttach之后立即调用。
 @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        try {
            listnener = (TilesOnClickListnener)getActivity();
        } catch (ClassCastException e) {
            throw new ClassCastException(getActivity().toString() + " must implement OnArticleSelectedListener");
        }
    }

还有,我忘了提到将上下文转换为Activity并不适用于我。 - SriMaharshi Manchem
为什么Activity类型转换可能不顺利?如果您在try/catch块之外声明activity,则我的代码可以很好地运行。请参考以下示例代码:public void onAttach(Context context) { super.onAttach(context); Activity activity = (Activity) context; try { mListener = (SellFragmentListener) activity; } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " 必须实现 SellFragmentListener 接口"); } } - WallyHale
太好了!如果它能正常工作的话。我还没有尝试过。谢谢你提供的信息。 - SriMaharshi Manchem

0
 public class MainActivity extends AppCompatActivity implements  topsection.TopSectionListener {

 @Override
 protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
     setContentView(R.layout.activity_main);
 }



 public void createMeme(String top, String bottom){
    bottomsection fragmentbottom = (bottomsection) getSupportFragmentManager().findFragmentById(R.id.fragment2);
    fragmentbottom.setMemeText(top, bottom);
  }
 }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接