回调在维基百科中定义为:html
在计算机程序设计中,回调函数,是指经过函数参数传递到其余代码的,某一块可执行代码的引用。网络
其目的是容许底层代码调用在高层定义的子程序。
举个例子可能更明白一些:以Android中用retrofit
进行网络请求为例,这个是异步回调的一个例子。
在发起网络请求以后,app能够继续其余事情,网络请求的结果通常是经过onResponse
与onFailure
这两个方法返回获得。看一下相关部分的代码:app
call.enqueue(new Callback<HistoryBean>() { @Override public void onResponse(Call<HistoryBean> call, Response<HistoryBean> response) { HistoryBean hb = response.body(); if(hb == null) return; showText.append(hb.isError() + ""); for(HistoryBean.ResultsBean rb : hb.getResults()){ showText.append(rb.getTitle() + "/n"); } } @Override public void onFailure(Call<HistoryBean> call, Throwable t) { } });
忽略上面CallBack
中的泛型,按照维基百科中的定义,匿名内部类里面的所有代码能够当作函数参数传递到其余代码的,某一块可执行代码的引用。 onResponse
与onFailure
这两个方法就是回调方法。底层的代码就是已经写好不变的网络请求部分,高层定义的子程序就是回调,由于具体的实现交给了使用者,因此具有了很高的灵活性。上面就是经过enqueue(Callback callback)
这个方法来关联起来的。框架
上面说的回调是很通用的概念,放到程序书写上面,就能够说:异步
A类中调用B类中的某个方法C,而后B类中在反过来调用A类中的方法D,在这里面D就是回调方法。B类就是底层的代码,A类是高层的代码。ide
因此经过上面的解释,咱们能够推断出一些东西,为了表示D方法的通用性,咱们采用接口的形式让D方法称为一个接口方法,那么若是B类要调用A类中的方法D,那势必A类要实现这个接口,这样,根据实现的不一样,就会有多态性,使方法具有灵活性。
A类要调用B类中的某个方法C,那势必A类中必须包含B的引用,要否则是没法调用的,这一步称之为注册回调接口。那么如何实现B类中反过来调用A类中的方法D呢,直接经过上面的方法C,B类中的方法C是接受一个接口类型的参数,那么只须要在C方法中,用这个接口类型的参数去调用D方法,就实现了在B类中反过来调用A类中的方法D,这一步称之为调用回调接口。
这也就实现了B类的C方法中,须要反过来再调用A类中的D方法,这就是回调。A调用B是直调,能够当作高层的代码用底层的API,咱们常常这样写程序。B调用A就是回调,底层API须要高层的代码来执行。
最后,总结一下,回调方法的步骤:函数
f(CallBack callback)
f(CallBack callback)
——注册回调接口f(CallBack callback)
方法中调用A的方法——调用回调接口咱们以一个儿子在玩游戏,等妈妈把饭作好在通知儿子来吃为例,按照上面的步骤去写回调;
上面的例子中,显然应该儿子来实现回调接口,母亲调用回调接口。因此咱们先定义一个回调接口,而后让儿子去实现这个回调接口。
其代码以下:测试
public interface CallBack { void eat(); }
public class Son implements CallBack{ private Mom mom; //A类持有对B类的引用 public void setMom(Mom mom){ this.mom = mom; } @Override public void eat() { System.out.println("我来吃饭了"); } public void askMom(){ //经过B类的引用调用含有接口参数的方法。 System.out.println("饭作了吗?"); System.out.println("没作好,我玩游戏了"); new Thread(() -> mom.doCook(Son.this)).start(); System.out.println("玩游戏了中......"); } }
而后咱们还须要定义一个母亲的类,里面有一个含有接口参数的方法doCook
优化
public class Mom { //在含有接口参数的方法中利用接口参数调用回调方法 public void doCook(CallBack callBack){ new Thread(new Runnable() { @Override public void run() { try { System.out.println("作饭中......"); Thread.sleep(5000); callBack.eat(); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } }
咱们经过一个测试类:this
public class Test { public static void main(String[] args) { Mom mom = new Mom(); Son son = new Son(); son.setMom(mom); son.askMom(); } }
这个例子就是典型的回调的例子。Son类实现了接口的回调方法,经过askMom这个方法调用Mom类中的doCook,实现注册回调接口,至关于A类中调用B类的代码C。在Mom类中的doCook来回调Son类中的eat,来告诉Son类中的结果。
这样,咱们就实现了一个简单的,符合定义的回调。
咱们主要看一下Son类的代码:
public class Son implements CallBack{ public Mom mom; public Son(Mom mom){ this.mom = mom; } public void askMom(){ System.out.println("饭作了吗?"); System.out.println("没作好,我玩游戏了"); new Thread(() -> mom.doCook(Son.this)).start(); System.out.println("玩游戏了中......"); } @Override public void eat() { System.out.println("好了,我来吃饭了"); } }
这个类里面,除了输出一些语句以外,真正有用的部分是mom.doCook(Son.this)
以及重写eat方法。因此,咱们能够经过匿名内部类的形式,简写这个回调。其代码以下:
public class CallBackTest { public static void main(String[] args) { Mom mom = new Mom(); new Thread(()-> mom.doCook(() -> System.out.println("吃饭了......"))).start(); } }
取消Son类,直接在主方法中经过匿名内部类去实现eat方法。其实匿名内部类就是回调的体现。
回调上面咱们讲了 就是A调用B类中的方法C,而后在方法C里面经过A类的对象去调用A类中的方法D。
咱们在说一下异步与同步,先说同步的概念
同步
同步指的是在调用方法的时候,若是上一个方法调用没有执行完,是没法进行新的方法调用。也就是说事情必须一件事情一件事情的作,作完上一件,才能作下一件。
异步
异步相对于同步,能够不须要等上个方法调用结束,才调用新的方法。因此,在异步的方法调用中,是须要一个方法来通知使用者方法调用结果的。
实现异步的方式
在Java中最常实现的异步方式就是让你想异步的方法在一个新线程中执行。
咱们会发现一点,异步方法调用中须要一个方法来通知使用者调用结果,结合上面所讲,咱们会发现回调方法就适合作这个事情,经过回调方法来通知使用者调用的结果。
那异步回调就是A调用B的方法C时是在一个新线程当中去作的。
上面的母亲通知儿子吃饭的例子,就是一个异步回调的例子。在一个新线程中,调用doCook方法,最后经过eat来接受返回值,固然使用lamdba优化以后的,本质是同样的。
同步回调就是A调用B的方法C没有在一个新线程,在执行这个方法C的时候,咱们什么都不能作,只能等待他执行完成。
咱们看一个Android中的一个同步回调的例子:
button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.i("button","被点击"); } });
button经过setOnClickListener
注册回调函数,和上面写的同样,经过匿名内部类的形式将接口的引用传进去。因为button调用setOnClickListener
没有新建一个线程,因此这个是同步的回调。
而异步回调,就是咱们开篇讲的那个例子:
call.enqueue(new Callback<HistoryBean>() { @Override public void onResponse(Call<HistoryBean> call, Response<HistoryBean> response) { HistoryBean hb = response.body(); if(hb == null) return; showText.append(hb.isError() + ""); for(HistoryBean.ResultsBean rb : hb.getResults()){ showText.append(rb.getTitle() + "/n"); } } @Override public void onFailure(Call<HistoryBean> call, Throwable t) { } });
这个enqueue方法是一个异步方法去请求远程的网络数据。其内部实现的时候是经过一个新线程去执行的。
经过这两个例子,咱们能够看出同步回调与异步回调的使用实际上是根据不一样的需求而设计。不能说一种取代另外一种,像上面的按钮点击事件中,若是是异步回调,用户点击按钮以后其点击效果不是立刻出现,而用户又不会执行其余操做,那么会感受很奇怪。而像网络请求的异步回调,由于受限于请求资源可能不存在,网络链接不稳定等等缘由致使用户不清楚方法执行的时候,因此会用异步回调,发起方法调用以后去作其余事情,而后等回调的通知。
上面提到的回调方法,除了网络请求框架的回调除外,其回调方法都是没有参数,下面,咱们看一下在回调方法中加入参数来实现一些通讯问题。
若是咱们想要A类获得B类通过一系列计算,处理后数据,并且两个类是不能经过简单的将B的引用给A类就能够获得数据的。咱们能够考虑回调。
步骤以下:
上面说的步骤,有点抽象。下面咱们看一个例子,一个是Client,一个是Server。Client去请求Server通过耗时处理后的数据。
public class Client{ public Server server; public String request; //连接Server,获得Server引用。 public Client connect(Server server){ this.server = server; return this; } //Client,设置request public Client setRequest(String request){ this.request = request; return this; } //异步发送请求的方法,lamdba表达式。 public void enqueue(Server.CallBack callBack){ new Thread(()->server.setCallBack(request,callBack)).start(); } }
public class Server { public String response = "这是一个html"; //注册回调接口的方法,把数据经过参数传给回调接口 public void setCallBack(String request,CallBack callBack){ System.out.println("已经收到request,正在计算当中......"); new Thread(() -> { try { Thread.sleep(5000); callBack.onResponse(request + response); } catch (InterruptedException e) { e.printStackTrace(); callBack.onFail(e); } }).start(); } //在拥有数据的那个类里面写一个接口 public interface CallBack{ void onResponse(String response); void onFail(Throwable throwable); } }
接下来,咱们看一下测试的例子:
public class CallBackTest { public static void main(String[] args) { Client client = new Client(); client.connect(new Server()).setRequest("这个文件是什么?").enqueue(new Server.CallBack() { @Override public void onResponse(String response) { System.out.println(response); } @Override public void onFail(Throwable throwable) { System.out.println(throwable.getMessage()); } }); } }
结果以下:
已经收到request,正在计算当中...... 这个文件是什么?这是一个html
以上就是经过回调的方式进行通讯