1、什么是回调
所谓回调:就是A类中调用B类中的某个方法C,然后B类中反过来调用A类中的方法D。
Class A实现接口CallBack callback——背景1
class A中包含一个class B的引用b ——背景2
class B有一个参数为callback的方法f(CallBack callback) ——背景3
A的对象a调用B的方法 f(CallBack callback) ——A类调用B类的某个方法 C
然后b就可以在f(CallBack callback)方法中调用A的方法 ——B类调用A类的某个方法D
2、举例说明
废话不多说,像许多网上介绍回调机制的文章一样,我这里也以一个现实的例子开头:假设你公司的总经理出差前需要你帮他办件事情,这件事情你需要花些时间去做,这时候总经理肯定不能守着你做完再出差吧,于是就他告诉你他的手机号码叫你如果事情办完了你就打电话告诉他一声;这是一个现实生活中常能碰到的例子,我们用呢就用代码的方式来实现一个这个过程,看一下这个过程究竟是怎样的。
首先在Eclipse中新建一个Java项目:CallBackDemoInJava;
然后再新建三个类:Manager(该类用来模拟总经理)、Personnel(该类用来模拟员工)、Main(主类);
Manager的代码如下:
package com.aige.test;
/**
* @description 该类用来模拟总经理
* @author Aige
* <a href="http://www.jobbole.com/members/chchxinxinjun">@since</a> 2014/11/6
*/
public class Manager {
/**
* @param personnel
* 传入一个员工类的对象
*/
public Manager(Personnel personnel) {
// 想让该让员工做什么
personnel.doSomething(this, "整理公司文件");
}
/**
* @description 当员工做完总经理让他做的事后就通过该方法通知总经理
* @param result
* 事情结果
*/
public void phoneCall(String result) {
System.out.println("事情" + result);
}
}
Manager类是个模拟总经理的类,当该类的对象被造出来后就会通过Personnel的对象去执行Personnel中的doSomething方法告诉员工做些什么Personnel的代码如下
package com.aige.test;
/**
* @description 该类用来模拟员工
* @author Aige
* <a href="http://www.jobbole.com/members/chchxinxinjun">@since</a> 2014/11/6
*/
public class Personnel {
public void doSomething(Manager manager, String task) {
// 总经理通过doSomething方法告诉员工要做什么
System.out.println("总经理要你做" + task);
String result = "做完了";
// 当事情做完了我们就通过总经理公布的phoneCall方法通知总经理结果
manager.phoneCall(result);
}
}
总经理通过调用Personnel中的doSomething方法告诉员工该做些什么,当员工做完后就通过Manager中的phoneCall方法通知总经理结果。那么好的!万事俱备,我们在Main中测试运行下代码看看结果:
package com.aige.test;
public class Main {
public static void main(String[] args) {
// 首先我们需要一个员工
Personnel personnel = new Personnel();
// 其次把这个员工指派给总经理
new Manager(personnel);
}
}
代码执行结果如下:
回到我们刚才举的那个现实中的例子:总经理出差前要你去办件事情,这件事情通过doSomething告诉你了,事情要是办完了就打总经理的电话phoneCall通知他一声。这里的phoneCall我们就称为回调方法,为什么称之为回调呢?你问我我也不清楚哈~这你得问Sun公司了,不过我们从代码的执行过程可以看出数据的流向大致是Manager—–>Personnel—–>Manager,这不就是一个“回调”的过程么?现在我们来总结下满足回调的两个基本条件:
1.Class A调用Class B中的X方法
2.ClassB中X方法执行的过程中调用Class A中的Y方法完成回调
一切看上去都很完美~以上例子代码简单通俗地描述了回调,但是这里我就会有这样一个疑问:假设总经理出差前交了件事情给我去办,不巧……副总经理也要给我件事去办,更无耻的是……主管也发任务过来了,都要求说做完就打电话通知他们……这时我们就要定义更多类,什么总经理类啦~经理类啦~主管类啦~杂七杂八的类,但是这些杂七杂八的大爷们都要求做完事情就电话通知,每个类都会有一个类似phoneCall的方法作为回调方法,这时,我们利用面向对象的思想来看~是不是可以把这个回调方法抽象出来作为一个独立的抽象类或接口呢?多态的思想油然而生~~~鉴于JAVA接口的好处,我们就定义一个名为CallBack的接口作为回调接口,再在该接口下定义一个名为backResult的抽象方法作为回调方法,让那些总经理类啦~经理类啦~主管类啦~什么的都去实现该接口,这时候我们就来改造下我们的项目:
CallBack代码如下:
package com.aige.test;
/**
* @description 回调接口
* @author Aige
* <a href="http://www.jobbole.com/members/chchxinxinjun">@since</a> 2014/11/6
*/
public interface CallBack {
// 回调方法
public void backResult(String result);
}
Manager代码改造后如下:实现CallBack接口重写backResult方法:
/**
* @description 该类用来模拟总经理
* @author Aige
* <a href="http://www.jobbole.com/members/chchxinxinjun">@since</a> 2014/11/6
*/
public class Manager implements CallBack {
/**
* @param personnel
* 传入一个员工类的对象
*/
public Manager(Personnel personnel) {
// 想让该让员工做什么
personnel.doSomething(this, "整理公司文件");
}
/**
* @description 当员工做完总经理让他做的事后就通过该方法通知总经理
* @param result
* 事情结果
*/
public void backResult(String result) {
System.out.println("事情" + result);
}
}
Personnel代码改造后如下:doSomething方法不再传入一个Manager对象而是一个CallBack接口:
package com.aige.test;
/**
* @description 该类用来模拟员工
* @author Aige
* <a href="http://www.jobbole.com/members/chchxinxinjun">@since</a> 2014/11/6
*/
public class Personnel {
public void doSomething(CallBack callBack, String task) {
// 总经理通过doSomething方法告诉员工要做什么
System.out.println("总经理要你做" + task);
String result = "做完了";
// 当事情做完了我们就通过总经理公布的phoneCall方法通知总经理结果
callBack.backResult(result);
}
}
Main代码不变,执行结果也是一样的。
3、再举一反三---加深理解
这个例子采用异步加回调
有一天小王遇到一个很难的问题,问题是“1 + 1 = ?”,就打电话问小李,小李一下子也不知道,就跟小王说,等我办完手上的事情,就去想想答案,小王也不会傻傻的拿着电话去等小李的答案吧,于是小王就对小李说,我还要去逛街,你知道了答案就打我电话告诉我,于是挂了电话,自己办自己的事情,过了一个小时,小李打了小王的电话,告诉他答案是2
/**
* 这是一个回调接口
* @author xiaanming
*
*/
public interface CallBack {
/**
* 这个是小李知道答案时要调用的函数告诉小王,也就是回调函数
* @param result 是答案
*/
public void solve(String result);
}
/**
* 这个是小王
* @author xiaanming
* 实现了一个回调接口CallBack,相当于----->背景一
*/
public class Wang implements CallBack {
/**
* 小李对象的引用
* 相当于----->背景二
*/
private Li li;
/**
* 小王的构造方法,持有小李的引用
* @param li
*/
public Wang(Li li){
this.li = li;
}
/**
* 小王通过这个方法去问小李的问题
* @param question 就是小王要问的问题,1 + 1 = ?
*/
public void askQuestion(final String question){
//这里用一个线程就是异步,
new Thread(new Runnable() {
@Override
public void run() {
/**
* 小王调用小李中的方法,在这里注册回调接口
* 这就相当于A类调用B的方法C
*/
li.executeMessage(Wang.this, question);
}
}).start();
//小网问完问题挂掉电话就去干其他的事情了,诳街去了
play();
}
public void play(){
System.out.println("我要逛街去了");
}
/**
* 小李知道答案后调用此方法告诉小王,就是所谓的小王的回调方法
*/
@Override
public void solve(String result) {
System.out.println("小李告诉小王的答案是--->" + result);
}
}
/**
* 这个就是小李啦
* @author xiaanming
*
*/
public class Li {
/**
* 相当于B类有参数为CallBack callBack的f()---->背景三
* @param callBack
* @param question 小王问的问题
*/
public void executeMessage(CallBack callBack, String question){
System.out.println("小王问的问题--->" + question);
//模拟小李办自己的事情需要很长时间
for(int i=0; i<10000;i++){
}
/**
* 小李办完自己的事情之后想到了答案是2
*/
String result = "答案是2";
/**
* 于是就打电话告诉小王,调用小王中的方法
* 这就相当于B类反过来调用A的方法D
*/
callBack.solve(result);
}
}
/**
* 测试类
* @author xiaanming
*
*/
public class Test {
public static void main(String[]args){
/**
* new 一个小李
*/
Li li = new Li();
/**
* new 一个小王
*/
Wang wang = new Wang(li);
/**
* 小王问小李问题
*/
wang.askQuestion("1 + 1 = ?");
}
}
4、上面是一个异步回调,我们看看同步回调吧,onClick()方法
先举个生活中常见的例子:
A有一个问题要问B,B说:“把你的手机号码给我吧,然后你手机随时开机着,等会儿我回去查到结果了就打你手机”,当B查到了结果,就会马上打回A的手机号码,A就收到了结果。这就是一个完整的回调过程。
看看Android中的按钮监听事件onclicklistener
一般我们的做法都是如下:
Button b = (Button)this.findViewById(R.id.mybtn);
b.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v){
Toast.makeText(MainActivity.this,"按钮被点击",1000).show();
}
});
其实,这里的MainActivity类就相当于我们前面那个例子中的提问者A,MainActivity中的按钮b就相当于A的手机号码,b.setOnClickListener(...)是为按钮b注册监听事件,相当于将联系方式告诉B(上面例子中的帮助解决问题的人)在Android中触摸按钮点击事件是由Android系统调用的,Button继承链的顶端是View类,当用户触碰屏幕上的按钮时,就相当于B想到了解决问题的方法,即触发了View中封装好的onClick(),系统便会反过来调用用户重写的onClick()方法,这里的onClick就是我们所说的回调方法。
//这个是View的一个回调接口
/**
* 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);
}
package com.example.demoactivity;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;
/**
* 这个就相当于Class A
* @author xiaanming
* 实现了 OnClickListener接口---->背景一
*/
public class MainActivity extends Activity implements OnClickListener{
/**
* Class A 包含Class B的引用----->背景二
*/
private Button button;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = (Button)findViewById(R.id.button1);
/**
* Class A 调用View的方法,而Button extends View----->A类调用B类的某个方法 C
*/
button.setOnClickListener(this);
}
/**
* 用户点击Button时调用的回调函数,你可以做你要做的事
* 这里我做的是用Toast提示OnClick
*/
@Override
public void onClick(View v) {
Toast.makeText(getApplication(), "OnClick", Toast.LENGTH_LONG).show();
}
}
下面是View类的setOnClickListener方法,就相当于B类咯,只把关键代码贴出来/**
* 这个View就相当于B类
* @author xiaanming
*
*/
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
/**
* Listener used to dispatch click events.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected OnClickListener mOnClickListener;
/**
* setOnClickListener()的参数是OnClickListener接口------>背景三
* 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(OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
mOnClickListener = l;
}
/**
* Call this view's OnClickListener, if it is defined.
*
* @return True there was an assigned OnClickListener that was called, false
* otherwise is returned.
*/
public boolean performClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
if (mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
//这个不就是相当于B类调用A类的某个方法D,这个D就是所谓的回调方法咯
mOnClickListener.onClick(this);
return true;
}
return false;
}
}
这个例子就是Android典型的回调机制,看完这个你是不是更进一步的理解了回调机制呢? 线程run()也是一个回调方法,当执行Thread的start()方法就会回调这个run()方法,还有处理消息都比较经典等等
我们再举个自定义Button的例子来说明这个过程:首先为我们自定义的按钮建立一个监听接口:
public interface MyOnClickListener{
//这个方法就相当于我们平时写button实现监听事件时所重写的onClick()方法,留给用户重写
public void myOnClick();
}
自定义Button类:
public class MyButton {
private MyOnClickListener listener;
public MyButton(Context context){
}
//注册监听事件(此处引用接口类)
public void setMyOnClickListener(MyOnClickListener listener) {
this.listener = listener;
}
//模拟用户触摸屏幕时系统底层进行的操作
public void doClick(){
listener.myOnClick();
}
}
用户调用:
MyButton b = new MyButton(this);
//注册监听事件
b.setMyOnClickListener(new MyOnClickListener() {
//这个就是回调函数
@Override
public void myOnClick() {
// TODO Auto-generated method stub
//在此处由用户自行重写,当用户点击屏幕时,会触发系统去调用doClick方法
Toast.makeText(SecondActivity.this, "自定义的按钮被点击", 1000).show();
}
});
//模拟用户触摸屏幕的行为,调用系统处理点击的方法
b.doClick();
在上面的过程中分为以下几步:
1.为按钮setMyOnClickListener注册监听,将括号里面的(new MyOnClickListener(){....})作为参数传给MyButton
2.由于MyButton中有一个setMyOnClickListener方法,通过this.listener = listener;可以将第一步传进来的MyOnClickListener对象赋给其成员变量
3.用户点击屏幕,触发系统底层的doClick函数
4.在doClick函数中listener.myOnClick()中,由第二步我们已知listener已被赋予成用户传进来的具体实现,所以此时的listener.myOnClick所调用的方法就是用户传进来的listener中重写的myOnClick方法,到这里也就是相当于反过来调用回用户所重写的myOnClick(),这里的myOnClick也就是我们常说的回调方法。
在上面这个例子的基础上,我们再通过加入一个字符串来加深回调的理解:
在原来的基础上,给监听接口中的方法加入一个参数:
public interface MyOnClickListener{
public void myOnClick(String s);
}
在doClick方法中写入一个字符串:
public void doClick(){
listener.myOnClick("回调字符串...");
}
在重写点击方法那里进行打印:
MyButton b = new MyButton();
b.setMyOnClickListener(new MyOnClickListener() {
@Override
public void myOnClick(String s) {
// TODO Auto-generated method stub
//这里的s就会是刚才我们传进去的那个字符串
Log.d("LayoutInflater---->", ""+s);
}
});
可以看到,s正是MyButton中传回来的那个字符串!这正好证明了系统调用点击方法时会反过来调用回我们所重写的myOnClick方法
总结:看完以上,还要强调一点:回调函数不是我们主动地去调用onClick方法,而是在某个时刻触发系统,让系统反过来调用我们所实现的onClick方法!其实Android中还有很多地方需要用到回调机制,比如Activity生命周期的各个函数、Handler机制中的收发消息等等,
5、基于回调的事件处理
1、回调机制则是发生某个事件时UI组件自己处理事件。(貌似基于回调的事件处理是用在自定义的UI组件中)
自定义某个组件然后继承该GUI组件类,并重写该类事件处理方法
以View为例、先说一下常见的方法:
boolean onKeyDown(int keyCode, KeyEvent event) 按下
boolean onKeyLongPress(int keyCode, KeyEvent event) 长按
boolean onKeyShortcut(int keyCode, KeyEvent event) 键盘快捷键事件触发
boolean onKeyUp(int keyCode ,KeyEvent event) 松开某个键
boolean onTouchEvent(MotionEvent event) 触屏事件
boolean onTrackballEvent(MotionEvent event) 轨迹球屏事件
在界面布局中使用自定义View时,要用全限定类名(包名.类名),
有个简单方法:将鼠标光标放在自定义类上面,将出现的框中内容直接复制就行
2、基于回调事件的传播
所有基于回调的事件处理方法都有一个boolean类型的返回值,该返回值用于标识该处理方法能否完全处理该事件
True : 表示该处理方法已完全处理该事件,事件不会传播出去
False: 没有完全处理该事件,事件会传播出去
既然要传播,都传向哪里呢,下面介绍
有3个地方需要返回boolean类型,也就是事件是否完全处理
(1) 自定义组件重写的回调方法return一个
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.widget.Button;
public class MyButton extends Button
{
public MyButton(Context context , AttributeSet set)
{
super(context , set);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
super.onKeyDown(keyCode , event);
Log.v("--MyButton--" , "the onKeyDown in MyButton");
// 返回false,表明并未完全处理该事件,该事件依然向外扩散
return false;
}
}
(2) 绑定监听机制实现的事件处理方法中 return 一个
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button bn = (Button) findViewById(R.id.bn);
// 为bn绑定事件监听器
bn.setOnKeyListener(new OnKeyListener()
{
@Override
public boolean onKey(View source
, int keyCode, KeyEvent event)
{
// 只处理按下键的事件
if (event.getAction() == KeyEvent.ACTION_DOWN)
{
Log.v("-Listener-", "the onKeyDown in Listener");
}
// 返回false,表明该事件会向外传播
return true; // ①
}
});
}
(3) Activity中重写回调方法,return一个
// 重写onKeyDown方法,该方法可监听它所包含的所有组件的按键被按下事件
@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
super.onKeyDown(keyCode , event);
Log.v("-Activity-" , "the onKeyDown in Activity");
//返回false,表明并未完全处理该事件,该事件依然向外扩散
return false;
}
下面说一下传播的顺序:传播顺序是:首先传播到组件绑定的监听器上,然后是自定义组件重写的事件方法,最后传播到组件所在Activity,当然,如果任何一个返回了true,那么事件将不会继续向外传播。
参考文章
http://android.jobbole.com/81967/
http://www.cnblogs.com/zxlblogs/p/4051938.html
http://blog.youkuaiyun.com/xiaanming/article/details/8703708/
http://blog.youkuaiyun.com/it_zjyang/article/details/51417001