android token机制_Android ALooper消息机制分析

在Android的mediaserver代码中,需要处理复杂的时序问题,和多个模块间的调用依赖。为了解决这些问题,使用了Looper+Handler的消息处理机制。

消息机制可以认为是一种“多线程”的编程模式,其优点在于:

  • 简化多线程问题。消息机制中,一般会有一个线程负责消息的读取、消息派发。其他线程只管生产请求/消息,传递给处理线程即可。因为消息的处理被集中在单一线程内,因此,一般情况下可忽略多线程问题,以单线程的思维组织代码。
  • 异步处理。用户动作的产生是不确定的,存在峰值。如果提供同步函数处理用户请求,可能导致卡顿。消息机制可以异步处理用户请求,避免该类问题。
  • 时序可控。由于消息会放入队列,然后单线程处理,所以得益于队列的FIFO特性,可以很严格地控制消息的处理顺序。

当然,也会引入一些缺点:

  • 消息堆积。消息堆积的后果是,可能导致处理者(Handler)在非预期的状态下处理消息。
  • 非实时。消息的处理的不一定是实时的(被排队)
参考资料: https://www. oschina.net/translate/t op-10-uses-for-message-queue

下面来剖析下android mediaserver中libstagefright的消息机制实现方案。

头文件在:android/frameworks/av/include/media/stagefright/foundation
源文件在:frameworks/av/media/libstagefright/foundation

类的设计

9918f2fdac9407edcb1347d0994aa7cd.png

几个主要的类:

  • ALooper:looper一般与一个线程对应,要么在已有的线程上启动,要么新建一个线程启动。looper主要负责消息的派发和提供线程环境用于消息的执行
  • AHandler:handler中主要定义了onMessageReceived这一纯虚方法,要求子类实现。looper消息派发时会回调这个方法,handler子类在该方法内根据消息类型进行处理。
  • AMessage:message表示一条消息,消息可以携带一些自定义字段。同时在AMessage上设计了post族的方法用于发送消息。消息只能被发往一个Handler处理。
  • AReplyToken:这类似一个标识,表示要回复的是哪一条消息

用法

启动ALooper:

ALooper* looper = new ALooper;
looper->start();

实现AHandler:

struct MyHandler: public AHandler {
protected:
    virtual void onMessageReceived(const sp<AMessage> &msg){
        switch(msg->what()) {
        case 1:
            break;
        ……
        }
    }
};

MyHandler* handler = new MyHandler;
looper->registerHandler(handler);

发送消息:

AMessage* msg = new AMessage(1, handler);
msg->setInt32("extra", 3);
msg->post();

发送并等待消息回复(等待消息处理完成):

AMessage* msg = new AMessage(1, handler);
msg->setInt32("extra", 3);

AMessage response;
msg->postAndAwaitResponse(&response);

response在handler中设置:

//in onMessageReceived(const sp<AMessage> &msg)
sp<AMessage> response = new AMessage;
response->setInt32("err", err);

sp<AReplyToken> replyID;
if(msg->senderAwaitsResponse(&replyID)){
    response->postReply(replyID);
}

实现分析

内存管理

android源码中自己实现了一套c++的垃圾回收机制。参考:https://blog.csdn.net/u012124438/article/details/71075423

为方便阅读,提炼几个要点:

  • 其垃圾回收机制主要原理是“引用计数”
  • 继承RefBase就可以接入垃圾回收机制
  • sp: strong pointer,强引用,引用计数归零时将被释放
  • wp: weak pointer,弱引用,不影响对象的释放。需要通过promote获取sp才能调用对象方法

ALooper、AHandler和AMessage都继承自RefBase。

下面图示标明了这3个类之间的引用关系:

8637a85301d051337bf28f0f142c3683.png
虚线表示弱引用,实现表示强引用
箭头方向表示A持有B,如ALooper持有AMessage的强引用。

基于引用计数的内存管理最大的缺陷在于”循环引用“。因为循环引用会导致两者始终都处于计数不为0的状态,从而无法被释放。

所以,在引用关系的设计上,作者尽可能地使用了弱引用,只在必要时使用强引用。

ALooper和AHandler属于长生命周期对象,因此在设计上没有对其持有强引用的情况(在需要调用其方法的时候,会短暂promote为强引用),这样方便looper和handler实例灵活管理自己的生命期。

AMessage属于短生命周期对象,使用上基本是即用即创建。比如:

void NuPlayer::pause() {
    (new AMessage(kWhatPause, this))->post();
}

所以,需要一个对象来持有它的强引用,设计上作者将这个职责分配给了ALooper(中的队列)

postAndAwaitResponse分析

接下来我们以AMessage::postAndAwaitResponse函数为例分析源码。

如前所述,这个函数会把消息发送给ALooper,并阻塞当前线程直到AHandler回复了该消息。

源码如下:

status_t AMessage::postAndAwaitResponse(sp<AMessage> *response) {
    //1. 判断looper是否还在工作,并获取强引用
    sp<ALooper> looper = mLooper.promote();
    if (looper == NULL) {
        ALOGW("failed to post message as target looper for handler %d is gone.", mTarget);
        return -ENOENT;
    }

    //2. 创建reply token
    sp<AReplyToken> token = looper->createReplyToken();
    if (token == NULL) {
        ALOGE("failed to create reply token");
        return -ENOMEM;
    }
    setObject("replyID", token);

    //3. 发送消息
    looper->post(this, 0 /* delayUs */);

    //4. 等待回复
    return looper->awaitResponse(token, response);
}

主要步骤:

  1. 判断looper是否还在工作,并获取强引用
  2. 创建reply token。token以名字replyID保存在当前消息内
  3. 发送消息
  4. 等待回复

接下来看如何”等消息“:

status_t ALooper::awaitResponse(const sp<AReplyToken> &replyToken, sp<AMessage> *response) {
    // return status in case we want to handle an interrupted wait
    Mutex::Autolock autoLock(mRepliesLock);
    CHECK(replyToken != NULL);
    while (!replyToken->retrieveReply(response)) {
        {
            Mutex::Autolock autoLock(mLock);
            if (mThread == NULL) {
                return -ENOENT;
            }
        }
        mRepliesCondition.wait(mRepliesLock);
    }
    return OK;
}

典型的条件变量的用法——在mutex保护的情况下等待条件变量唤醒。

条件变量的唤醒时机是AMessage::postReply => ALooper::postReply

status_t ALooper::postReply(const sp<AReplyToken> &replyToken, const sp<AMessage> &reply) {
    Mutex::Autolock autoLock(mRepliesLock);
    status_t err = replyToken->setReply(reply);
    if (err == OK) {
        mRepliesCondition.broadcast();
    }
    return err;
}

AReplyToken扮演了两个角色,分别是唯一标识,和回复的AMessag的容器。可以通过setReply/retrieveReply操作”容器“。

以上,postAndAwaitResponse基本分析完了。有种”好像也不过如此“的感觉……然而,并不是。

之所以实现看起来会很简单直白,与类的设计、职责划分、内存管理这三者的设计密不可分。只有把这些搞清楚了,代码才能如此清晰。可以做个练习,抛开这里的设计,读者可以尝试自行实现一个消息机制,就知道其中的精妙了。

如果你够精明,会发现一个问题:可能在wait的消息不只一个,为何只设计了一组Mutex/Condition呢?这样岂不是在mRepliesCondition.broadcast时会唤醒所有等待中的线程,而非唤醒目标线程?

我的理解是因为在stagefright的使用场景中,同时在等待回复消息的线程并不多(不超过5个),而且awaitResponse会在唤醒后检查条件是否满足,不满足时继续等待。因此,即使同时唤醒也消耗不大。好处是,可以只用一组Mutex/Condition,而不是为所有的消息都去创建(和销毁)Mutex/Condition。

至此,stagefright的消息机制实现就分析完了。可以说是一个轻量而灵活的实现方案。

以上分析的源码,经过重构后,只依赖c++11,压缩在一个头文件,一个源文件中。可跨平台使用。见:

compilelife/aloop​github.com
ea5a72a8d602ac294e3604f2d63abf8e.png
要在 Flask 中实现 Token 机制,可以使用 JWT(JSON Web Tokens)库。下面是一个简单的示例代码: ```python from flask import Flask, request from flask_jwt import JWT, jwt_required, current_identity from werkzeug.security import safe_str_cmp app = Flask(__name__) app.config['SECRET_KEY'] = 'super-secret' app.config['JWT_EXPIRATION_DELTA'] = timedelta(days=1) class User: def __init__(self, id, username, password): self.id = id self.username = username self.password = password def __str__(self): return f"User(id='{self.id}')" users = [ User(1, 'user1', 'password1'), User(2, 'user2', 'password2'), ] def authenticate(username, password): user = next((user for user in users if user.username == username), None) if user and safe_str_cmp(user.password.encode('utf-8'), password.encode('utf-8')): return user def identity(payload): user_id = payload['identity'] return next((user for user in users if user.id == user_id), None) jwt = JWT(app, authenticate, identity) @app.route('/protected') @jwt_required() def protected(): return f'Hello, {current_identity}!' if __name__ == '__main__': app.run() ``` 在上面的代码中,我们首先定义了一个 `User` 类来存储用户信息。然后定义了一个 `authenticate` 函数来用于用户认证,它会查找用户列表中是否存在指定的用户名和密码。如果存在,则返回该用户对象。 接下来,我们定义了一个 `identity` 函数,它会根据 JWT 中存储的用户 ID 来查找用户对象。最后,我们创建了一个 `jwt` 对象,并将其绑定到 Flask 中。 最后,我们定义了一个受保护的路由 `/protected`,并使用 `@jwt_required()` 装饰器来保护它。这意味着在访问该路由时,用户必须先提供有效的 JWT 才能继续访问。 当用户成功提供有效的 JWT 且通过身份验证时,`current_identity` 会返回该用户对象,我们可以在响应中使用它。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值