android中有很多需要數(shù)據(jù)交互的部分,交互的方式也有很多種不同,四大組件各有各的方法, 今天主要是談?wù)勱P(guān)于回調(diào)部分的看法。
首先介紹一下什么是回調(diào)函數(shù):
回調(diào)函數(shù)就是一個通過函數(shù)指針調(diào)用的函數(shù)。如果你把函數(shù)的指針(地址)作為參數(shù)傳遞給另一個函數(shù),當(dāng)這個指針被用來調(diào)用其所指向的函數(shù)時,我們就說這是回調(diào)函數(shù)。
所以說回調(diào)函數(shù)只是觸發(fā),回調(diào)函數(shù)不是由該函數(shù)的實現(xiàn)方直接調(diào)用,而是在特定的事件或條件發(fā)生時由另外的一方調(diào)用的,用于對該事件或條件進(jìn)行響應(yīng)。
舉個栗子:
朋友過生日,你打電話給蛋糕店去預(yù)定蛋糕,蛋糕店說做好了給你打電話,你說OK,給我打了電話我去取蛋糕,然后你把電話留給了蛋糕店。
分解一下動作:包括你的和蛋糕店的。
你的動作:預(yù)定蛋糕(業(yè)務(wù)邏輯),留電話(登記回調(diào)函數(shù)),取蛋糕(響應(yīng)回調(diào)函數(shù))
蛋糕店的動作:做蛋糕(觸發(fā)回調(diào)函數(shù)業(yè)務(wù)邏輯),給你打電話(調(diào)用回調(diào)函數(shù))。
那么以上就是一個基本的回調(diào)函數(shù)部分和內(nèi)容。
android中的廣播機(jī)制回調(diào):
類比一下:
我的應(yīng)用想要獲取驗證碼,當(dāng)系統(tǒng)來短信的時候通知應(yīng)用,應(yīng)用要讀取短信內(nèi)容,用來代填驗證碼
應(yīng)用要做的:代填驗證碼(業(yè)務(wù)邏輯),約定通知方式(登記回調(diào)函數(shù)),讀取短信內(nèi)容(響應(yīng)回調(diào)函數(shù))
系統(tǒng)要做的:收到短信(觸發(fā)回調(diào)函數(shù)業(yè)務(wù)邏輯),通知應(yīng)用(調(diào)用回調(diào)函數(shù))
在安卓中這個問題一般都是用廣播來解決的,約定通知方式(注冊監(jiān)聽系統(tǒng)收到廣播的action),讀取短信內(nèi)容(也就是在自己注冊的廣播中進(jìn)行相關(guān)操作),以下是代碼:
//初始化廣播
private SMSBroadcastReceiver receiver = new SMSBroadcastReceiver();
//注冊廣播
private void registerSmsReceiver(){
IntentFilter filter = new IntentFilter();
filter.addAction("android.provider.Telephony.SMS_RECEIVED");
registerReceiver(receiver,filter);
receiver.setSMSmessageListener(this);
}
//解除注冊廣播
private void unregisterSmsReceiver(){
unregisterReceiver(receiver);
}
//廣播中獲取短信內(nèi)容的相關(guān)邏輯(業(yè)務(wù)邏輯)
public class SMSBroadcastReceiver extends BroadcastReceiver {
private SMSmessage smsmessage;
@Override
public void onReceive(Context context, Intent intent) {
Object[] pduses= (Object[])intent.getExtras().get("pdus");
for(Object pdus: pduses){
byte[] pdusmessage = (byte[])pdus;
SmsMessage sms = SmsMessage.createFromPdu(pdusmessage);
String mobile = sms.getOriginatingAddress();//發(fā)送短信的手機(jī)號碼
String content = sms.getMessageBody(); //短信內(nèi)容
}
}
}
以上我們只做了客戶端部分的代碼,另一半是系統(tǒng)幫助我們完成的,那么以下是我們獨立通過廣播去完成一項操作
當(dāng)B應(yīng)用登錄(成功/失?。┑臅r候,B告知A自己的登陸狀態(tài),A應(yīng)用彈出toast告知用戶B應(yīng)用登陸完成
A應(yīng)用要做的:彈出toast(業(yè)務(wù)邏輯),約定通知方式(登記回調(diào)函數(shù)),獲取登陸狀態(tài)(響應(yīng)回調(diào)函數(shù))
B應(yīng)用要做的:登陸操作及其結(jié)果(觸發(fā)回調(diào)函數(shù)業(yè)務(wù)邏輯),通知A應(yīng)用(調(diào)用回調(diào)函數(shù)),以下是代碼:
- ** A應(yīng)用:**
//初始化廣播
ActivationResultReceiver receiver = new ActivationResultReceiver();
//注冊廣播
private void registerActivationResultReceiver(){
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("com.login.ACTIVATION_FINISH_INFO");
registerReceiver(receiver, intentFilter);
}
//解除注冊廣播
private void unregisterActivationResultReceiver(){
unregisterReceiver(receiver);
}
//獲取登陸狀態(tài)(響應(yīng)回調(diào)函數(shù))
public class ActivationResultReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if(intent != null && intent.getAction().equals("com.login.ACTIVATION_FINISH_INFO")){
SharedPreferences sharedPreferences = context.getSharedPreferences("ACTIVATION_STATUS", Context.MODE_WORLD_READABLE);
Editor editor = sharedPreferences.edit();//獲取編輯器
if(intent.getStringExtra("status").equals("success")){
//彈出toast(業(yè)務(wù)邏輯)
Toast.makeText(context, "激活成功", Toast.LENGTH_SHORT).show();
editor.putBoolean("activation_status", true);
}else if(intent.getStringExtra("status").equals("failed")){
//彈出toast(業(yè)務(wù)邏輯)
Toast.makeText(context, "激活失敗" + intent.getIntExtra("activateErrorCode",0), Toast.LENGTH_SHORT).show();
editor.putBoolean("activation_status", false);
}else {
editor.putBoolean("activation_status", false);
}
editor.commit();
}
}
}
- ** B應(yīng)用:**
//登陸操作及其結(jié)果(觸發(fā)回調(diào)函數(shù)業(yè)務(wù)邏輯)
public static void ActivationResult(Context context,Boolean result,ActivationException exception){
if(result){
sendActivateResultReceiver(true,exception,context);
}else{
sendActivateResultReceiver(false,exception,context);
}
}
//通知A應(yīng)用(調(diào)用回調(diào)函數(shù))
private static void sendActivateResultReceiver(Boolean result,ActivationException exception,Context context){
Intent intent = new Intent();
intent.setAction("com.login.ACTIVATION_FINISH_INFO");
if(result){
intent.putExtra("status", "success");
}else{
intent.putExtra("status", "failed");
if(exception != null){
intent.putExtra("activateErrorCode", exception.activateErrorCode);
intent.putExtra("activateErrorMessage", exception.activateErrorMessage);
}
}
intent.setPackage(RECEIVER_PACKAGENAME);
context.sendBroadcast(intent);
}
也就是說在廣播回調(diào)方式中包括幾個條件:
- 客戶端注冊廣播,聲明相關(guān)的觸發(fā)條件(action)
- 客戶端完成接收廣播以后的相關(guān)業(yè)務(wù)邏輯(onRecevier方法內(nèi)部)
- 服務(wù)端在需要的觸發(fā)時機(jī)發(fā)送廣播,同時也可以通過intent將一些數(shù)據(jù)傳給客戶端
- 廣播的內(nèi)部實現(xiàn)(android API已經(jīng)幫我們做好了)
android中的接口回調(diào):
關(guān)于handler內(nèi)部就是接口實現(xiàn),將在以后提及
繼續(xù)類比一下:
舉一個最簡單的例子,按鈕的點擊事件,就是一個接口回調(diào)事件
我們自定義應(yīng)用本身要做的事情很簡答,第一就是調(diào)用setOnClickListener()這個方法(登記回調(diào)函數(shù)),
第二就是實現(xiàn)OnClickLister接口的onClick(View v)方法(響應(yīng)回調(diào)函數(shù)),在其中添加的方法就是自己的業(yè)務(wù)邏輯
而系統(tǒng)幫我們做了(觸發(fā)回調(diào)函數(shù)業(yè)務(wù)邏輯)和(調(diào)用回調(diào)函數(shù))
//調(diào)用setOnClickListener()這個方法(登記回調(diào)函數(shù))
Button activation;
activation.setOnClickListener(new View.OnClickListener() {
//實現(xiàn)OnClickLister接口的onClick(View v)方法(響應(yīng)回調(diào)函數(shù))
@Override
public void onClick(View v) {
}
});
同樣我們接著自己構(gòu)造一個接口去完整的實現(xiàn)該功能,那么現(xiàn)在大量使用這個方式的就是在應(yīng)用登錄時候的UI頁面刷新上了。
一般登錄都是從點擊登錄按鈕開始,將用戶名密碼等信息經(jīng)過拼接加密,發(fā)送給服務(wù)端,服務(wù)端經(jīng)過驗證返回errorCode和errorMessage
當(dāng)客戶端收到以后,根據(jù)errorCode的不同對UI進(jìn)行不同的變化操作,這個就是典型的回調(diào)式操作了
構(gòu)造接口LogInInterface
** UI線程要做的:調(diào)用setCallBack()(登記回調(diào)函數(shù)),實現(xiàn)LogInInterface接口(響應(yīng)回調(diào)函數(shù))根據(jù)不同的errorCode進(jìn)行不同的變化(業(yè)務(wù)邏輯)
后臺網(wǎng)絡(luò)線程要做的:,setCallBack()(觸發(fā)回調(diào)函數(shù)業(yè)務(wù)邏輯),callBack.callBackByEnterResult();(調(diào)用回調(diào)函數(shù))
- UI線程:
//登記回調(diào)函數(shù)
JITTFCardManager.getInstance().setCallBack(context, new LogInInterface() {
//響應(yīng)回調(diào)函數(shù)
@Override
public void callBackByEnterResult() {
//內(nèi)部是業(yè)務(wù)邏輯
}
@Override
public void callBackByRetry() {
//內(nèi)部是業(yè)務(wù)邏輯
}
});
- 后臺線程:
public interface LogInInterface {
public void callBackByEnterResult();
public void callBackByRetry();
}
//觸發(fā)回調(diào)函數(shù)業(yè)務(wù)邏輯
public void setCallBack(Context context, LogInInterface callBack) {
this.callBack = callBack;
}
if (result) {
Toast.makeText(context, "身份認(rèn)證成功", Toast.LENGTH_SHORT).show();
//調(diào)用回調(diào)函數(shù)
callBack.callBackByEnterResult();
} else {
Toast.makeText(context, "身份認(rèn)證失敗", Toast.LENGTH_SHORT).show();
//調(diào)用回調(diào)函數(shù)
callBack.callBackByRetry();
}
最后來對比一下廣播回調(diào)和接口回調(diào):
首先因為都是觸發(fā)式回調(diào),實現(xiàn)了觀察者模式,對于代碼的復(fù)用和耦合度的降低都是有顯著貢獻(xiàn)的。
廣播機(jī)制回調(diào):可以跨進(jìn)程調(diào)用,廣播也是安卓跨進(jìn)程通信的重要組成部分,但是同樣,廣播機(jī)制回調(diào)是最慢的,而且自我構(gòu)造的進(jìn)程間廣播容易被國產(chǎn)的一些系統(tǒng)屏蔽掉,這點也比較頭疼
接口回調(diào):線程間通信的方式,速度快,構(gòu)造起來也比較簡潔,屬于java本身的一種回調(diào)方式(安卓官方提供的handler機(jī)制內(nèi)部其實就是callback接口回調(diào))
速度上來說:接口 》 handler 》廣播 適用性來說廣播最好
而關(guān)于回調(diào)及線程間的交互,安卓官方最推薦的一種就是handler的方式,也是安卓api在接口基礎(chǔ)上發(fā)展的方式,下一篇將來談?wù)刪andler。