最近看书或者博客的时候老是遇到回调函数、回调接口,在没有搞懂的情况下继续学习,头都是大的。于是看了很多博客,现在总算是弄明白了一点,以下写出自己对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中“回调”的拙见,如果有错误或需要补充的地方我会及时更新,谢谢!