对Android中“回调”的理解

最近看书或者博客的时候老是遇到回调函数、回调接口,在没有搞懂的情况下继续学习,头都是大的。于是看了很多博客,现在总算是弄明白了一点,以下写出自己对Android中“回调”的很粗浅的理解。

首先说一下最抽象的形式——2个类,A类和B类。A类含有1个接口、1个接口变量、(可能含有)1个为接口变量赋值的方法以及1个会使用接口变量的“地方”;B类实现A中的接口,(可能)含有1个A类实例的引用,并且(可能用A类中为接口变量赋值的方法)将“自己”传递给A类的接口变量。

这么说相当的抽象,用一个稍微具体一点的例子来说明一下:

首先是A类

public class A{
    ...
    //1个接口变量
    private Callback mCallback;
    ...

    //1个接口
    public interface Callback{
        void doSomething();
    }
    ...

    //1个给接口变量赋值的方法
    public void setCallback(Callback callback){
        mCallback = callback;
    }
    ...

    //1个使用接口变量的地方
    public void onExecute(){
        ...
        mCallback.doSomething();
        ...
    }
    ...
}

然后是B类

public class B implements A.Callback{
    ...
    //A类的实例的引用
    private A mAInstance;
    ...

    //B类实现了A类的接口
    public void doSomething(){
        Log.d("TAG","will do something");
    }
    ...
    //B类将自己(实际上是接口的实现)传给A类实例的接口变量
    mAInstance.setCallback(this);       
}

总结一下就是以下的几个点:

  • A类中有一个接口变量和接口。
  • B类实现A类的接口(这个接口就是所谓的回调接口)。
  • A类的接口变量会(通过过某种方法)获得靠B类实现的接口。
  • A类在一个适当的时机“使用”这个接口变量,即调用接口中的函数(这个函数就是所谓的回调函数)。

用生活中的事情打比方,其实很像是某人甲买杀手去杀死仇人= =,甲只是告诉杀手杀人这个目的,具体怎么杀死甲的仇人,由杀手去决定。这里甲是A类,杀手是B类,甲在某时刻告诉杀手杀人是A类调用回调接口里面的回调函数,杀手杀人的方法是B类实现A类的回调接口…

作为Android中更加具体的例子,我们来看看以下的场景:

场景一:
在Android中,听到最多的大概就是就是onclick回调方法,我们对比着A类和B类来看。

首先我们来看B类,在这里一般为一个Activity。

public class MainActivity extends Activity implements View.OnClickListener{
    ...
    //Button的引用(A类的引用)
    private Button mButton;
    ...

    protected void onCreate(Bundle savedInstanceState){
        ...
        //Activity将自己传给Button的OnClickListener接口变
        //量(B类将自己传给A类实例的接口变量)
        mButton.setOnClickListener(this);
        ...
    }
    //Activity实现了Button的OnClickListener接口(B类实现
    //了A类的接口)
    public void onClick(){
        Log.d("TAG", "点击了");
    }
    ...
}

这是一个很常见的Activity中的点击了Button之后我们进行一系列逻辑操作的例子。

然后来看一下A类,这里为1个Button或者说是View,我们在Button以及它的父类TextView中都没有看到类似接口变量的东西,我们继续找TextView的父类View,Ctrl+F我们找到了想要的东西。

1个接口变量

    /**
     * Listener used to dispatch click events.
     * This field should be made private, so it is hidden from the SDK.
     * {@hide}
     */
     public OnClickListener mOnClickListener;

1个接口

    /**
     * Interface definition for a callback to be invoked when a view is clicked.
     */
    public interface OnClickListener {
        /**
         * Called when a view has been clicked.
         *
         * @param v The view that was clicked.
         */
        void onClick(View v);
    }

1个为接口变量赋值的方法

    /**
     * Register a callback to be invoked when this view is clicked. If this view is not
     * clickable, it becomes clickable.
     *
     * @param l The callback that will run
     *
     * @see #setClickable(boolean)
     */
    public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }

1个使用这个接口的地方

    /**
     * Call this view's OnClickListener, if it is defined.  Performs all normal
     * actions associated with clicking: reporting accessibility event, playing
     * a sound, etc.
     *
     * @return True there was an assigned OnClickListener that was called, false
     *         otherwise is returned.
     */
    public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        return result;
    }

至于performClick这个方法什么时候执行,这涉及到Touch事件分发的一些知识,我们大可以理解为“在某个时候执行了回调函数onClick”

看了这个具体的例子以后,有这样一种感觉——“Button(View)只是知道在某个时刻要去执行onClick,而具体的细节要交给实现了接口的Activity去决定”,这让人想起面向对象的思想:每一个Button都可以被点击,但是每个Button被点击后执行的逻辑可能不相同,Button只对外暴露一个接口,具体的实现细节谁实现谁负责。

场景二:
这里参考的是《Android权威指南》
假如在平板上,有1个Activity管理着2个Fragment,左半边的是笔记列表NotesFragment,有半边是笔记内容DetailFragment。我们点击左边的列表的note,在右边相应显示出note的detail。我们马上会想到在NotesFragment的点击回调函数实现里,获取FragmenManager,然后用FragmenManager实例执行事务将1个DetailFragment添加到Activity右半边的布局,设想的代码如下:

public void onListItemClick(ListView l, View v, int position, long id){
    ...
    Fragment fragment = DetailFragment.newInstance(position);
    FragmentManager fm = getActivity().getSupportFragmentManager();
    fm.begainTransaction()
    .add(R.id.detailFragmentContainer, fragment)
    .commit();
}

但是这样的做法很老套,Fragment天生是一种独立开发的构件,它不需要知道托管它的Activity是如何工作的,它们之间应该独立,Activity添加DetailFragment的工作是Activity应该处理的事情。我们在NotesFragment中定义回调接口,托管它的Activity实现回调接口。NotesFragment只是在某个时刻命令托管它的Activity去完成添加DetailFragment的任务。大概的代码如下:

public class NotesFragment extends ListFragment{
    ...
    //1个接口变量
    private Callback mCallback;
    ...
    //1个接口
    public interface Callback{
        void onNoteSelected(int position);
    }
    ...
    //给接口变量赋值的地方
    public void onAttach(Activity activity){
        super.onAttach();
        mCallback = (Callback)activity;
    }

    public void onDtach(){
        super.onDetach();
        mCallback = null;
    }
    ...
    public void onListItemClick(ListView l, View v, int position, long id){
    ...
    //使用接口变量的地方
    mCallback.onNoteSelected(position);
    ...
    }
}
public class NotesActivity extends Activity implements NotesFragment.Callback{
    ...
    //实现NotesFragment的接口
    public void onNoteSelected(int position){
        //添加DetailFragment到右半屏幕
        ...
    }
    ...
}

我们可以看到,NotesFragment中在点击的回调函数中调用了自己的回调函数,Activity实现了具体添加操作。这里和场景一中略有不同的是NotesFragment中没有一个专门为接口变量赋值的方法,托管它的Activity中也没有获得NotesFragment实例的引用,并把自己传回去,因为这一切都可以在系统的回调函数onAttach中完成。

好了,现在我们对“回调”应该有一个还算清晰的认识了,我们再来看看《Thinking in Java》中对接口的描述:接口为我们提供了一种将接口与实现分离的更加结构化的方法,所有实现了接口的类看起来都像这样。我理解的是一个类实现了一个接口,那么它就具有某种“能力”或“特性”去做某些事情,回调接口,就是A类让“有能力”的B类去完成某些事情,A类只负责命令,B类负责具体实施。上面两个场景都是让Activity去处理不同的细节。这也很契合面向对象的思想,我们把普通事物的共性抽取出来,而这些共性之中又充斥着特性,每个不同的特性就需要交给特定的情况处理,通过暴露接口方法可以减少很多重复,代码更加优雅。我们再看到回调函数,应该有这样的意识——一定有某个类在某个时刻调用了这个回调函数。

最后再看一下百度百科里对于回调函数的定义(虽然好像是针对C语言写的,但是思想是一样的),你可能就更加明白了:

定义:回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
机制:
⑴定义一个回调函数;
⑵提供函数实现的一方在初始化的时候,将回调函数的函数指针注册给调用者;
⑶当特定的事件或条件发生的时候,调用者使用函数指针调用回调函数对事件进行处理。

以上就是小弟对Android中“回调”的拙见,如果有错误或需要补充的地方我会及时更新,谢谢!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值