也谈 Android 中的回调

在 Android 系统中,回调几乎随处可见,最常见的就是给点击事件设置监听,如下:

btn_login.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        // TODO log in
    }
});

如果你开发过 Android 应用,相信上面的代码你不会陌生。下面就根据我的理解谈一谈啥回调,为啥使用回调,以及如何在自己的代码中定义回调。

0x00. 什么是回调?

我们先来看一下百度百科中对回调的定义:

回调是一种双向调用模式,也就是说,被调用方在接口被调用时也会调用对方的接口。

看不懂?没关系,我们再来看下面这段话:

在面向对象的语言中,回调是通过接口或抽象类来实现的,我们把实现这种接口的类称为回调类,回调类的对象成为回调对象。

我们来把上面两句话归结一下:

回调就是抽象类(或接口)的实例实现父类的抽象方法后,将该方法交还给父类调用。

0x01. 为啥使用回调?

诚如定义中所言:“回调是一种模式”,既然是模式,肯定是为了解决某一类问题而出现的。那么回调解决了什么问题呢?

这里我们引用一下刘济华老师《漫谈设计模式》一书中买车票回家过节的例子:

小明准备坐火车回家:

public class XiaoMing {
    public void celebrateSpringFestival() {
        // Buy ticket
        buyTicket();

        // Travelling by train
        travellingByTrain();

        //  Celebrating Chinese New Year
        celebrating();
    }

    private void celebrating() {}

    private void travellingByTrain() {}

    private void buyTicket() {}
}

而小红离家比较近,她想坐大巴回去,也好办:

public class XiaoHong {
    public void celebrateSpringFestival() {
        // Buy ticket
        buyTicket();

        // Travelling by bus
        travellingByBus();

        //  Celebrating Chinese New Year
        celebrating();
    }
    private void celebrating() {}

    private void travellingByBus() {}

    private void buyTicket() {}
}

细心的你可以发现,两段代码中有相当一部分重复。对于重复代码我们如何去优化呢?是的,使用继承大法。但是,每个人的乘车方式都是不同的,我们无法在基类中作出明确定义,而乘车又是回家过节必需的过程(不考虑离家近的同学步行回家的情况~)。我们只有把乘车方法定义为抽象方法,让各实现类自行决定如何乘车:

public abstract class BeiPiao {
    public void celebrateSpringFestival() {
        // Buy ticket
        buyTicket();

        // Travelling by bus
        travelling();

        //  Celebrating Chinese New Year
        celebrating();
    }

    abstract void travelling();

    private void celebrating() {
        System.out.println("celebrating...")
    }

    private void buyTicket() {
        System.out.println("buy ticket...")
    }
}
public class XiaoMing extends BeiPiao {
    @Override
    void travelling() {
        System.out.println("travelling by train");
    }
}
public class XiaoHong extends BeiPiao {
    @Override
    void travelling() {
        System.out.println("travelling by bus");       
    }
}

这时,公司小秘要统计各位同事怎么回家:

public class Secretary {
    public void howToGetBack(BeiPiao piao) {
        piao.celebrateSpringFestival();
    }
}
public class Main {
    public static void main(String[] args) {
        Secretary secretary = new Secretary();
        secretary.howToGetBack(new XiaoHong());
        secretary.howToGetBack(new XiaoMing());
    }
}
// 输出
travelling by bus
travelling by train

然而公司有位新来的同事,小蜜叫不出名字,这咋办?

public class Main {
    public static void main(String[] args) {
        Secretary secretary = new Secretary();
        secretary.howToGetBack(new XiaoHong());
        secretary.howToGetBack(new XiaoMing());
        secretary.howToGetBack(new BeiPiao() {
            @Override
            void travelling() {
                System.out.print("travelling by taxi");
            }
        });
    }
}

// 输出
travelling by bus
travelling by train
travelling by taxi

哈哈,是不是有点熟悉的感觉了?BeiPiao类中的travelling()就是一个回调函数。

其实我们这里还顺带讲了一个设计模式——模板方法模式,父类先定义好模板,具体细节交给子类去实现。

讲了这么多,相信你应该对回调有了近一步的了解,下个小结我们会讲如何在自己的代码中定义回调函数。

0x02. 如何在自己的代码中定义回调?

背景是这样的:最近在看郭霖大神的《第一行代码》,想系统的从头梳理一遍 Android 的基础知识。想着自己做一个 Demo 应用,定义一个 ListView,每个条目对应一个知识点。由于每个条目响应的动作都不同,而我又不想在onItemClickListener()方法中添加无限多个 switch-case 判断。于是我就自定义了一个Func类,该类代表着一个功能点:

public class Func {
    private OnClickListener onClickListener;
    private String name;

    public Func(String name, OnClickListener onClickListener) {
        this.name = name;
        this.onClickListener = onClickListener;
    }

    public String getName() {
        return name;
    }

    public void onClick() {
        onClickListener.action();
    }

    public interface OnClickListener {
        void action();
    }
}

自定义接口 OnClickListener 中的action()就是一个回调函数。

Adapter 类中的getView()方法:

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        Button btn_show_func;
        if (convertView != null) {
            btn_show_func = (Button) convertView.getTag();
        } else {
            convertView = View.inflate(context, R.layout.list_func, null);
            btn_show_func = (Button) convertView.findViewById(R.id.btn_show_func);
            convertView.setTag(btn_show_func);
        }

        btn_show_func.setText(funcs.get(position).getName());
        btn_show_func.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // funcs 是包含多个 Func 类对象的 List 集合
                funcs.get(position).onClick();
            }
        });

        return convertView;
    }

我们可以这样添加一个条目:

Func force_offline = new Func(getString(R.string.force_offline), new Func.OnClickListener() {
    @Override
    public void action() {
        sendBroadcast(new Intent("com.lovexiaov.learnfromzero.ACTION_FORCE_OFFLINE"));
    }
});

funcs.add(force_offline);

Talk is cheap, show me the code!

附上GitHub代码地址: LearnFromZero

0x03. 小结

合理利用回调可以使代码易于维护和阅读,就像上面的代码,只需要修改一个地方就可以完成添加 ListView Item 的功能。快动手实现属于你自己的回调吧~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,569评论 25 707
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,295评论 18 399
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • 文/古 泉渊 001 深夜噪音 夜里躺在床上,熟睡之际总有啮齿类走动、啃咬的声音,或是从头顶的天花板传来,或是从床...
    古泉渊阅读 463评论 0 0
  • 《农村那些事》 过去因为穷 孩子都穿大人的衣服 现在因为富 大人都穿孩子的衣服 过去因为穷 娶外地的媳...
    白清风阅读 279评论 1 1