C&C++ 实现Android Handler

本文根据众多互联网博客内容整理后形成,引用内容的版权归原始作者所有,仅限于学习研究使用,不得用于任何商业用途。

使用Android的Handler之后,回去写c++代码时,时刻怀念Android里面的Handler,希望有一个c++版本的Handler。为了能自己实现c++版本的Handler,今天准备去梳理一下这Android中的Handler。

理解Android中的Handler

Handler的适用场景

只要写多线程程序,必不可少要碰到线程通信的问题。其中一个办法就是在工作线程中开辟一个消息队列,别的线程往消息队列发消息,即可完成与本线程的通信。工作线程顺序的从消息队列中读取消息并逐一处理。这种模型的好处是:

  1. 消息可以缓存
  2. 工作线程中消息是顺序处理的,所以不需要加锁。

Handler适用的就是这种场景:多个线程往一个工作线程发消息,工作线程顺序挨个处理

google设计Handler最初的原因

在安卓应用中,多个线程去处理UI的刷新,而UI操作不是线程安全的,如果要实现多线程对UI的操作,就需要在每一个要进行UI操作的地方进行加锁。加锁操作引入的问题

锁的管理复杂,开发者容易忘记加锁,解锁,或者乱加锁造成死锁
加锁是有性能消耗的
Android选用java作为开发语言就是看中其开发效率,引入锁加大开发难度了!

因此UI操作就该由一个单一线程进行处理,在Android中,UI线程就是应用进程的第一个线程–主线程。所以每一个android应用的开发者都会被告诫:不要在主线程里搞事情阻塞UI处理。
为了简化操作,google设计了Handler机制,开发者只需要创建一个Handler,然后在别的线程利用主线程的handler发送消息即可完成与主线程的通信。

Handler, Looper, Thread ,Message之间的关系

名称 含义
Looper 用来管理消息队列的,它提供一个loop()接口,不停的从消息队列中取消息来处理。
Thread 是Looper运行的载体,一个Thread有且只有一个Looper。
Handler 是用来往运行与Thread里的Looper的消息队列发消息用的,同时Handler里有一个消息处理函数,Thread会用Looper取出消息,然后在Thread里执行这个函数来处理消息。
Message 消息内容的封装,里边包含消息的处理函数,用户可以在发消息的时候动态指定其执行的处理函数
image.png

Android中Handler的使用

下面以实际代码简单看下如何用Handler与主线程的通信的:

class MainActivity extends Activity {
    Handler mHandler = new Handler(){
        //主线程Looper收到消息后,会当没有指定具体回调函数,会回调这个函数处理消息。
        //也就说下面的函数将会在主线程内执行
        @Override
        public void handleMessage(Message msg) {
            switch(msg.what){
                case 1:
                    break;
                default:
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        //onCreate这里是主线程中调用的,
        //使用Handler重要的一点就是要明白代码在哪个线程里执行
        //下面,我们在子线程里边给主线程发消息

        // 方式1:
        new Thread(){
            @Override
            public void run(){
                //往主线程消息队列发消息
                //这种方式最终会在主线程中执行 mHandler.handleMessage(msg)
                mHandler.sendEmptyMessage(1);
            }
        }.start();

        // 方式2:
        new Thread(){
            @Override
            public void run(){
                //我觉得这种方式才是Handler的精髓,用户在代码里随处都可以写处理UI操作的代码
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        //这里的代码将会被在主线程中执行!真是太方便了
                    }
                });
            }
        }.start();
    }
}

主线程里边的Looper是自动创建的吗?

初次使用Handler,都会有一个疑问:以上的代码,没有创建Looper,Looper是自动创建的吗?
实际上,不会自动创建,只是在执行到Activity的onCreate之前,系统已经帮忙创建Looper了。
framework/base/core/java/android/app/ActivityThread.java

public static void main(String[] args) {
    //这里创建main线程的Looper
    Looper.prepareMainLooper();
    //...

    //死循环处理消息,知道为什么不能阻塞主线程了吧!
    //主线程就是取消息然后以阻塞的方式一个个顺序执行
    //你在主线程中搞耗时的东西,你的UI就要卡在!!!
    Looper.loop();
}

Handler怎么关联上Looper的?

前面说的每一个线程里面有且仅有一个Looper,在创建Handler时,会去获取当前线程的Looper,并赋值给Handler对象。
请看Handler类的构造函数
frameworks/base/core/java/android/os/Handler.java

public Handler() {
    //就是这个Looper.myLooper();
    mLooper = Looper.myLooper();

    //这里就是如果工作线程没有创建looper前,创建Handler会失败的原因
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
}

public Handler(Looper looper) {
    ...
    //最终执行mLooper = looper;
}

//myLooper利用的就是linux提供的线程局部存储实现的,每个线程都有独立的一个。
public static final Looper myLooper() {
    return (Looper)sThreadLocal.get();
}

可见:Handler从创建开始就要指定其Looper, 默认构造函数就是使用当前线程的Looper

自定义的工作线程怎么创建Looper和使用Handler?

如果我们要在自己的线程中使用Handler机制,则需要执行创建Looper对象。
创建Looper的方法很简单,就是执行Looper.prepair()即可。

class MyThread extends Thread {
    private Handler myHandler = null;
    private boolean mPrepaired = false;
    public Handler getMyHandler(){
        //没有创建Looper,并loop前,handler发消息没人去处理
        if (mPrepaired)
        return null;
        return myHandler;
    }

    @Override
    public void run() {
        //第一步:创建当前线程对应的Looper
        Looper.prepare();
        //第二步:创建handler,会自己匹配到当前线程的Looper
        //如果没有执行第一步,这里就会报异常
        myHandler = new Handler();
        mPrepaired = true;
        //第三步:让Looper跑起来,开始消息的读取和消息处理
        //没有执行loop操作,handler发的消息是没人处理的
        Looper.loop();
    }
}

HandlerThread介绍

google封装了一个HandlerThread类,进一步简化自定义线程消息处理,就不用那么麻烦的处理Looper的创建和启动了。HandlerThread的使用类似下面这样:

class MyHandler extends Handler {
    //这种方式,使用的创建Handler所在线程的Looper
    public MyHandler(){
    
    }
    //为了与自定义的线程关联,需要指定对应线程的looper。
    public MyHandler(Looper looper){
        super(looper);
    }
    
    @Override
    public void handleMessage(Message msg){
    
    }
}

HandlerThread t = new HandlerThread("mythread");
t.start();

// 注意那个t.getLooper(), 其实内部是阻塞等到线程执行完Looper.prepair()才返回的
Handler h = new MyHandler(t.getLooper());

frameworks/base/core/java/android/os/HandlerThread.java

public Looper getLooper() {
    if (!isAlive()) {
        return null;
    }

    // If the thread has been started, wait until the looper has been created.
    synchronized (this) {
        while (isAlive() && mLooper == null) {
            try {
                //这里等looper创建完毕
                wait();
            } catch (InterruptedException e) {
            }
        }
    }
    return mLooper;
}

如何实现c++版本的Handler呢?

用了Handler后,回去写c++代码,没有这种东西,但是我们基本了解Handler的实现原理了,实现一个就行。

如何确保线程里有且仅有一个消息队列?

linux的pthread库提供在线程内创建私有变量的接口:

#include <pthread.h>
int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));
int pthread_setspecific(pthread_key_t key,const void *pointer));
void *pthread_getspecific(pthread_key_t key);

如何实现android中java的handler.post(Runnable)类型的接口?

java里有匿名函数,使用很方便,其实在c++11里也支持这种写法了。

//这里搞一个匿名函数,输入参数为空
handler->post([](){
    
});

伪代码实现

typedef void (*MessageCallback)();

class Message {
public:
    int what;
    int arg1;
    //void* obj;
    Message* obtain();
    
    MessageCallback callback;
private:
    vector<Message> mMsgPool;
};

class Handler {
public:
    virtual ~Handler();
    void sendMessage(Message* msg) {
        mLooper->enQueue(msg);
    }
    
    void setLooper(Looper* Looper);
    //可以像java,post(new Runagle())那样用
    void post(MessageCallback callback);
    virtual void handleMessage(Message* msg){}
    
private:
     Looper* mLooper;
};

class Looper{
public:
    static void prepair();
    static void loop();
    
    //提供一个接口用来获取当前线程的Looper
    static Looper* getThreadLocalLooper();
    void enQueue(Message* msg){
        mMsgQ.enQueue(msg);
    }
    Message* deQueue(){
        return mMsgQ.deQueue();
    }
private:
    MessageQueue mMsgQ;
};

class MessageQueue {
public:
    void enQueue(Message* msg);
    Message* deQueue();
};

void threadfunc(Handler* handler) {
    Message* msg = Message::obtain();
    handler->sendMessage(msg );
}

int main(int argc, char** argv) {
    Looper::prepair();
    Handler *handler = new Handler();

    //开个线程来往主线程发消息
    std::thread t(threadfun,handler);
    t.detach();
    Looper::loop();
    
    return 0;
}

class MyThread {
public:
    MyThread() {
        mThread = NULL;
        mPrepaired = false;
    }
    Looper* getLooper(){
        while(!mPrepaired ){
            sleep(1);
        }
        return Looper::getThreadLocalLooper();
    }
    
    void start() {
        if (NULL == mThread) {
            mThread = new thread(threadfunc, this);
        }
    }
private:
    static void threadfunc(MyThread* t) {
        Looper::prepair();
        
        Looper::loop();
    }
    bool mPrepaired;
    thread* mThread;
};

//非主线程测试
int main2(int argc, char** argv) {

    //创建自定义线程
    MyThread t;
    
    //在线程内构造自己的Looper
    t.start();
    
    //Handler *handler = new XXXHandler();
    Handler *handler = new Handler();
    handler->setLooper(t.getLooper());
    
    //主线程向自定义线程的消息队列发消息
    while ( true ) {
        Message* msg = Message::obtain();
        t.getHandler()->sendMessage(msg);
        
        /*也可以像java那样,直接指定消息的回调处理
        t.getHandler()->post([](){
            cout << "haha" << endl;
        });*/
        sleep(1);
    }
    return 0;
}

参考文档
自己写个C++版本Handler来理解Android的Handler机制

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