美文网首页
Android多媒体框架--04:ALooper-AHandle

Android多媒体框架--04:ALooper-AHandle

作者: DarcyZhou | 来源:发表于2023-04-18 08:20 被阅读0次

    "本文转载自:[yanbixing123]的Android MultiMedia框架完全解析 - ALooper-AHandler-AMessage机制分析"

    1.概述

      NuPlayer基于Stagefright的基础类构建,使用了Android native层的ALooper-AHandler-AMessage机制来异步处理消息。在这个机制中所有的处理都是异步的,将变量封装到一个消息AMessage结构体中,然后放到队列中去,后台专门有一个线程会从这个队列中取出消息然后执行,执行函数就是onMessageReceived,在NuPlayer中就是使用了该消息机制。

    image.png
    • AMessage:消息载体,用于构造消息,通过post方法投递出去给ALooper;

    • AHandler:消息处理类,一般当做父类,继承该类的子类需要实现onMessageReceived方法,它是最终对消息进行处理的;

    • ALooper:它运行着一个后台线程,来循环处理接收到的消息。与AHander一一对应,负责存储消息并分发AHandler的消息,与AMessage一对多关系;

    • LooperThread:此线程调用ALooper的loop方法来分发消息;

    • ALooperRoaster:与Handler是一对多的关系, 管理Looper和Handler一一对应关系,负责释放stale handler。

    2.AHandler

      首先需要分析AHandler,因为在Amessage::setTarget函数中,需要使用AHandler中的const_cast<AHandler *>(this)和mLooper,而这两个值需要在AHandler中初始化。

      先来看AHandler的定义:frameworks/av/include/media/stagefright/foundation/AHandler.h

    struct AHandler : public RefBase {
        AHandler()
            : mID(0),
              mVerboseStats(false),
              mMessageCounter(0) {
        }//构造函数,为mID,mVerboseStats,mMessageCounter三个变量赋初始值。
    
        ALooper::handler_id id() const {
            return mID;
        }
    
        sp<ALooper> looper() const {
            return mLooper.promote();
        }
    
        wp<ALooper> getLooper() const {
            return mLooper;
        }
    
        wp<AHandler> getHandler() const {
            // allow getting a weak reference to a const handler
            return const_cast<AHandler *>(this);
        }
    
    protected:
        virtual void onMessageReceived(const sp<AMessage> &msg) = 0;
    //这是个虚函数,每个继承自AHandler的子类都需要实现这个虚函数,比如在NuPlayer中就实现了这个虚函数,最终对于Message的处理还是在这个函数中。
    
    private:
        friend struct AMessage;      // deliverMessage()
        friend struct ALooperRoster; // setID()
    
        ALooper::handler_id mID;
        wp<ALooper> mLooper;
    
        inline void setID(ALooper::handler_id id, wp<ALooper> looper) {
            mID = id;
            mLooper = looper;
        }//这个函数供友元类ALooperRoster来使用。
    
        bool mVerboseStats;
        uint32_t mMessageCounter;
        KeyedVector<uint32_t, uint32_t> mMessages;
    //这里是一个KeyedVector,用来存放Messages,它是一个键值对类型的容器。
    
        void deliverMessage(const sp<AMessage> &msg);
    //这个函数是AHandler.cpp需要去实现的一个函数,在里面调用onMessageReceived(msg)函数后再对mMessageCounter进行操作。这个函数是在AMessage::deliver()函数中调用的。
    
        DISALLOW_EVIL_CONSTRUCTORS(AHandler);
    };
    
    

    AHandler没有直接对外的接口(只有获取成员变量的接口),基本上只有一个onMessageReceived用于子类的继承,deliverMessage用于给类AMessage使用,setID用于给友元类ALooperRoster来使用。所以从这点来说,真正的代码可能在AMessage中。

      那这个AHandler是给谁用的呢?从名字上来看,它是一个句柄,同时也是这个ALooper-AHandler-AMessage机制的一个核心,所以如果想要在某个程序中使用这个消息机制,就要让主体继承自这个AHandler,然后在主体中实现onMessageReceived函数,同时也就可以使用AMessage了。比如在Media playback中,这个主体就是NuPlayer,所以NuPlayer继承自AHanlder。

    3.ALooperRoster

      从AHandler中setID函数的实现上面来看,是为AHandler中的的mID,mLooper两个成员赋初始值,而调用这个setID函数的正是ALooperRoster::registerHandler()和ALooperRoster::unregisterHandler()这两个函数,同时Roster的翻译就是花名册,所以,可以理解为ALooperRoster类来将ALooper和AHandler类关联起来,它是用来管理ALooper的。

    • ALooperRoster.cpp
    ALooper::handler_id ALooperRoster::registerHandler(
            const sp<ALooper> &looper, const sp<AHandler> &handler) {
        Mutex::Autolock autoLock(mLock);
    
        if (handler->id() != 0) {
            CHECK(!"A handler must only be registered once.");
            return INVALID_OPERATION;
        }
    
        HandlerInfo info;
        info.mLooper = looper;
        info.mHandler = handler;
        ALooper::handler_id handlerID = mNextHandlerID++;
        mHandlers.add(handlerID, info);
    
        handler->setID(handlerID, looper);
    
        return handlerID;
    }
    
    

    4.AMessage

    • AMessage.h
    struct AMessage : public RefBase {
        AMessage();
        AMessage(uint32_t what, const sp<const AHandler> &handler);
    //两个构造函数,但是第二个是常用的构造函数,通常指定id和需要哪个AHanlder来处理。
    
        static sp<AMessage> FromParcel(const Parcel &parcel);
        void writeToParcel(Parcel *parcel) const;
    
        void setWhat(uint32_t what);
        uint32_t what() const;
    //如果不通过构造函数传入id的话,也可以通过这两个设置。
    
        void setTarget(const sp<const AHandler> &handler);
    //这个函数还是蛮重要的,通过这个函数,可以为这个类中的mTarget,mHandler,mLooper赋值,这个函数通常会被这样调用:msg->setTarget(this);通过这一步,就可以为msg设置Handler和Looper。注意这里本身传入的就是一个AHandler。
    
        void clear();
    
        void setInt32(const char *name, int32_t value);
        void setInt64(const char *name, int64_t value);
        void setSize(const char *name, size_t value);
        void setFloat(const char *name, float value);
        void setDouble(const char *name, double value);
        void setPointer(const char *name, void *value);
        void setString(const char *name, const char *s, ssize_t len = -1);
        void setString(const char *name, const AString &s);
        void setObject(const char *name, const sp<RefBase> &obj);
        void setBuffer(const char *name, const sp<ABuffer> &buffer);
        void setMessage(const char *name, const sp<AMessage> &obj);
    
        void setRect(
                const char *name,
                int32_t left, int32_t top, int32_t right, int32_t bottom);
    
        bool contains(const char *name) const;
    
        bool findInt32(const char *name, int32_t *value) const;
        bool findInt64(const char *name, int64_t *value) const;
        bool findSize(const char *name, size_t *value) const;
        bool findFloat(const char *name, float *value) const;
        bool findDouble(const char *name, double *value) const;
        bool findPointer(const char *name, void **value) const;
        bool findString(const char *name, AString *value) const;
        bool findObject(const char *name, sp<RefBase> *obj) const;
        bool findBuffer(const char *name, sp<ABuffer> *buffer) const;
        bool findMessage(const char *name, sp<AMessage> *obj) const;
    
        bool findRect(
                const char *name,
                int32_t *left, int32_t *top, int32_t *right, int32_t *bottom) const;
    
        status_t post(int64_t delayUs = 0);
    //投递消息,新建一个消息后,设置属性,就可以调用post投递了。
    
        status_t postAndAwaitResponse(sp<AMessage> *response);
    
        bool senderAwaitsResponse(sp<AReplyToken> *replyID);
    
        status_t postReply(const sp<AReplyToken> &replyID);
    
        sp<AMessage> dup() const;
    
        size_t countEntries() const;
        const char *getEntryNameAt(size_t index, Type *type) const;
    
    protected:
        virtual ~AMessage();
    
    private:
        friend struct ALooper; // deliver()
    
        uint32_t mWhat;
    
        // used only for debugging
        ALooper::handler_id mTarget;
    
        wp<AHandler> mHandler;
        wp<ALooper> mLooper;
    
        Item *allocateItem(const char *name);
        void freeItemValue(Item *item);
        const Item *findItem(const char *name, Type type) const;
    
        void setObjectInternal(
                const char *name, const sp<RefBase> &obj, Type type);
    
        size_t findItemIndex(const char *name, size_t len) const;
    
        void deliver();//这个接口给ALooper调用,发送消息的接口。
    
        DISALLOW_EVIL_CONSTRUCTORS(AMessage);
    };
    
    

      从上面的接口可以看出,在使用AMessage时只需要指定消息的id和要处理该消息的AHandler即可,可以通过AMessage的构造函数中指定,也可以单独调用setWhat和setTarget接口。AMessage构造完成之后,可以调用setxxx设置对应的参数,通过findxxx获取传递的参数。最后通过post即可将消息投递到AHandler的消息队列中。

      下面就是一个消息从创建到发送的过程:

    sp<AMessage> notify = dupNotify();
    notify->setInt32("what", kWhatPrepared);
    notify->setInt32("err", err);
    notify->post();
    

    5.ALooper

    • ALooper.h
    struct ALooper : public RefBase {
        typedef int32_t event_id;
        typedef int32_t handler_id;
    
        ALooper();
    
        // Takes effect in a subsequent call to start().
        void setName(const char *name);
    
        handler_id registerHandler(const sp<AHandler> &handler);
        void unregisterHandler(handler_id handlerID);
    
        status_t start(
                bool runOnCallingThread = false,
                bool canCallJava = false,
                int32_t priority = PRIORITY_DEFAULT
                );
    
        status_t stop();
    
        static int64_t GetNowUs();
    
        const char *getName() const {
            return mName.c_str();
        }
    
    protected:
        virtual ~ALooper();
    
    private:
        friend struct AMessage;       // post()
    
        struct Event {
            int64_t mWhenUs;
            sp<AMessage> mMessage;
        };
    
        Mutex mLock;
        Condition mQueueChangedCondition;
    
        AString mName;
    
        List<Event> mEventQueue;
    
        struct LooperThread;
        sp<LooperThread> mThread;
        bool mRunningLocally;
    
        // use a separate lock for reply handling, as it is always on another thread
        // use a central lock, however, to avoid creating a mutex for each reply
        Mutex mRepliesLock;
        Condition mRepliesCondition;
    
        // START --- methods used only by AMessage
    
        // posts a message on this looper with the given timeout
        void post(const sp<AMessage> &msg, int64_t delayUs);
    
        // creates a reply token to be used with this looper
        sp<AReplyToken> createReplyToken();
        // waits for a response for the reply token.  If status is OK, the response
        // is stored into the supplied variable.  Otherwise, it is unchanged.
        status_t awaitResponse(const sp<AReplyToken> &replyToken, sp<AMessage> *response);
        // posts a reply for a reply token.  If the reply could be successfully posted,
        // it returns OK. Otherwise, it returns an error value.
        status_t postReply(const sp<AReplyToken> &replyToken, const sp<AMessage> &msg);
    
        // END --- methods used only by AMessage
    
        bool loop();
    
        DISALLOW_EVIL_CONSTRUCTORS(ALooper);
    };
    
    

    ALooper的对外接口比较简单,通常就是NuPlayerDriver构造函数中的调用逻辑,先创建一个ALooper对象,然后调用setName和start接口,之后调用registerHandler设置一个Handler,这样就完成了初始化。

    (1)创建过程

    mLooper(new ALooper),
    mLooper->setName("NuPlayerDriver Looper");
    
    mLooper->start(
                false, /* runOnCallingThread */
                true,  /* canCallJava */
                PRIORITY_AUDIO);
    
    mLooper->registerHandler(mPlayer);
    
    

    (2)在ALooper::start函数中会启动一个线程,并调用ALooper::loop函数,该函数主要实现消息的实际执行,如下所示:

    bool ALooper::loop() {
        Event event;
        {
            Mutex::Autolock autoLock(mLock);
            if (mThread == NULL && !mRunningLocally) {
                return false;
            }
            if (mEventQueue.empty()) {
                mQueueChangedCondition.wait(mLock);
                return true;
            }
            int64_t whenUs = (*mEventQueue.begin()).mWhenUs;
            int64_t nowUs = GetNowUs();
    
            if (whenUs > nowUs) {
                int64_t delayUs = whenUs - nowUs;
                mQueueChangedCondition.waitRelative(mLock, delayUs * 1000ll);
    
                return true;
            }
    
            event = *mEventQueue.begin();
            mEventQueue.erase(mEventQueue.begin());
        }
    
        event.mMessage->deliver();
    
        // NOTE: It's important to note that at this point our "ALooper" object
        // may no longer exist (its final reference may have gone away while
        // delivering the message). We have made sure, however, that loop()
        // won't be called again.
    
        return true;
    }
    
    

    1)ALooper中维护着一个Event链表,List<Event> mEventQueue;这个Event结构体中包含一个AMessage消息和一个时间值,在ALooper::loop()函数中会一直监听着这个链表,如果有一个Event的话,就从链表中取出Event,调用event.mMessage->deliver()函数。

    2)在AMessage::deliver()函数中,就会去获取这个AMessage的AHandler,然后调用对应AHandler的deliverMessage函数:handler->deliverMessage(this)。

    3)在AHandler::deliverMessage(const sp<AMessage> &msg)函数中,就会调用AHandler中的onMessageReceived(msg)函数,如果这时候传入的是AHandler子类的话,就会去调用AHandler子类的onMessageReceived(msg)函数。

    6.代码实例

    (1)

    void NuPlayer::setVideoSurfaceTextureAsync(
            const sp<IGraphicBufferProducer> &bufferProducer) {
        sp<AMessage> msg = new AMessage(kWhatSetVideoSurface, this);
    
        if (bufferProducer == NULL) {
            msg->setObject("surface", NULL);
        } else {
            msg->setObject("surface", new Surface(bufferProducer, true /* controlledByApp */));
        }
    
        msg->post();
    }
    
    

    上面这些消息都是异步实现的,如果想要同步消息,在这个消息机制中的设计很巧妙,它是新建一个AMessage,然后通过这个新建的AMessage来传输回复的消息,实例如下:

    status_t NuPlayer::getPlaybackSettings(AudioPlaybackRate *rate /* nonnull */) {
        sp<AMessage> msg = new AMessage(kWhatGetPlaybackSettings, this);
        sp<AMessage> response;
        status_t err = msg->postAndAwaitResponse(&response);
        if (err == OK && response != NULL) {
            CHECK(response->findInt32("err", &err));
            if (err == OK) {
                readFromAMessage(response, rate);
            }
        }
        return err;
    }
    
    

    (2)

    sp<AReplyToken> replyID;
    CHECK(msg->senderAwaitsResponse(&replyID));
    
    

    (3)

    sp<AMessage> response = new AMessage;
    response->setInt32("err", err);
    
    response->postReply(replyID);
    
    

    (4) msg中findObject和setObject的用法:

    void NuPlayer::setVideoSurfaceTextureAsync(
            const sp<IGraphicBufferProducer> &bufferProducer) {
        sp<AMessage> msg = new AMessage(kWhatSetVideoSurface, this);
    
        if (bufferProducer == NULL) {
            msg->setObject("surface", NULL);
        } else {
            msg->setObject("surface", new Surface(bufferProducer, true /* controlledByApp */));
        }
    
        msg->post();
    }
    
    

    (5)之后在OnMessageReceived函数中:

            case kWhatSetVideoSurface:
            {
    
                sp<RefBase> obj;
                CHECK(msg->findObject("surface", &obj));
                sp<Surface> surface = static_cast<Surface *>(obj.get());
    
    

    这里通过一个static_cast转换,就能够把这个sp<Surface>结构体通过msg传递过来。

    7.小结

      从上面的分析过程可知,ALooper-AHandler-AMessag异步消息机制使用和实现都很复杂,但是NuPlayer中还是使用了该消息机制。主要原因是NuPlayer多媒体架构中有很多耗时较长的操作,但是用户对于画面的流程度的容忍度很低,举一个很简单的例子,在视频的播放过程中,如果直接把进度条拖到中间的位置开始播放,当点击完毕后,NuPlayer内部就开始执行了seek操作,但是这时候耗时比较长,画面就卡在这里了,用户体验度很差。但是如果使用类似的机制,画面继续播放之前缓冲的数据,而NuPlayer抓紧去执行seek操作,当seek操作执行完毕后,直接把画面切换到这个位置开始播放,这样的话,画面不会卡住,而用户体验度就会好很多。

    相关文章

      网友评论

          本文标题:Android多媒体框架--04:ALooper-AHandle

          本文链接:https://www.haomeiwen.com/subject/nrjbddtx.html