admin管理员组文章数量:1642194
在安卓底层框架中,大量使用了AMessage,ALopper,AHandler来实现消息循环处理机制。比如在Nuplayer底层播放器中使用了七八个进程来异步处理事务,其错综复杂程度如果单单使用一般的通信机制来处理,可能有点水果刀宰牛的感觉。于是强如谷歌另开发了一套消息循环处理机制来实现消息队列,这就是AMessage,ALopper,Ahandler三剑客。
顾名思义,消息,循环,处理器,我想你应该大致猜出来它们的处理流程,这就像是一个流水线,消息就是我们待处理的包裹,循环当然是传送带(ALopper)来将包裹(AMessage)传送到处理机器(AHandler)。大致流程图如下(别人画的,找不到出处):
可以说很生动形象了,再附上类图如下:
现在我们应该大指知晓了它们的调用关系,现在我们结合代码具体分析一下:
AHandler:
代码很少了,我都写了注释,直接贴上吧:
//xref: /frameworks/av/include/media/stagefright/foundation/AHandler.h
namespace android {
struct AMessage;
struct AHandler : public RefBase {//RefBase是安卓源码的鼻祖,定义了强弱指针的实现,所有类都要从它派生
AHandler()
: mID(0),
mVerboseStats(false),
mMessageCounter(0) {
}
ALooper::handler_id id() const {
return mID;
}
sp<ALooper> looper() const {
return mLooper.promote();//promote()是针对wp<>来说的,提升为强指针。
}
wp<ALooper> getLooper() const {
return mLooper;
}
wp<AHandler> getHandler() const {
// allow getting a weak reference to a const handler
return const_cast<AHandler *>(this);//this是指向类本身的指针,是const型的。
}
protected:
virtual void onMessageReceived(const sp<AMessage> &msg) = 0;//很显然,非常重要了,需要子类重写这个函数来处理消息。
private:
friend struct AMessage; // deliverMessage()
friend struct ALooperRoster; // setID()
ALooper::handler_id mID;
wp<ALooper> mLooper;
inline void setID(ALooper::handler_id id, const wp<ALooper> &looper) {
mID = id;
mLooper = looper;
}
bool mVerboseStats;
uint32_t mMessageCounter;
KeyedVector<uint32_t, uint32_t> mMessages;
void deliverMessage(const sp<AMessage> &msg);//什么?为什么私有?看到那两个friends了吗
DISALLOW_EVIL_CONSTRUCTORS(AHandler);//定义了赋值和拷贝运算
};
} // namespace android
cpp:
namespace android {
void AHandler::deliverMessage(const sp<AMessage> &msg) {//很显然,只是一个统计消息数量的函数
onMessageReceived(msg);//调用这个虚函数,毫无疑问的会调用到子类去。
mMessageCounter++;
if (mVerboseStats) {
uint32_t what = msg->what();
ssize_t idx = mMessages.indexOfKey(what);
if (idx < 0) {
mMessages.add(what, 1);
} else {
mMessages.editValueAt(idx)++;
}
}
}
} // namespace android
AHandler代码很简单,各部分都给出了注释,主要的就是setId()和deliverMessage()函数了,不再细说。接着看ALooper。
ALooper:
代码不多,码来!
namespace android {
struct AHandler;
struct AMessage;
struct AReplyToken;
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);
//ALopper有自己的注册簿,会把可用的handler进行管理,后面再讲
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; //cpp里有它的定义
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 此下的这部分私有函数只能被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 ReplyToken为一个类
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);//定义赋值和拷贝运算符
};
} // namespace android
cpp代码:
namespace android {
ALooperRoster gLooperRoster;
//继承自Thread,使得ALooper在新线程中运作
struct ALooper::LooperThread : public Thread {
LooperThread(ALooper *looper, bool canCallJava)
: Thread(canCallJava),
mLooper(looper),
mThreadId(NULL) {
}
virtual status_t readyToRun() {
mThreadId = androidGetThreadId();
return Thread::readyToRun();
}
virtual bool threadLoop() {
return mLooper->loop();
}
bool isCurrentThread() const {
return mThreadId == androidGetThreadId();
}
protected:
virtual ~LooperThread() {}
private:
ALooper *mLooper;
android_thread_id_t mThreadId;
DISALLOW_EVIL_CONSTRUCTORS(LooperThread);
};
// static
int64_t ALooper::GetNowUs() {
return systemTime(SYSTEM_TIME_MONOTONIC) / 1000ll;
}
ALooper::ALooper()
: mRunningLocally(false) {
// clean up stale AHandlers. Doing it here instead of in the destructor avoids
// the side effect of objects being deleted from the unregister function recursively. 从注册簿清楚Handler
gLooperRoster.unregisterStaleHandlers();
}
ALooper::~ALooper() {
stop();
// stale AHandlers are now cleaned up in the constructor of the next ALooper to come along
}
void ALooper::setName(const char *name) {
mName = name;
}
ALooper::handler_id ALooper::registerHandler(const sp<AHandler> &handler) {
return gLooperRoster.registerHandler(this, handler);//从注册簿中注册handler
}
void ALooper::unregisterHandler(handler_id handlerID) {
gLooperRoster.unregisterHandler(handlerID);//从注册簿删除handler
}
status_t ALooper::start(
bool runOnCallingThread, bool canCallJava, int32_t priority) {
if (runOnCallingThread) {
{
Mutex::Autolock autoLock(mLock);
//刚开始之前,mThread应该为NULL,mRunningLocally应该为false,否则返回出错
if (mThread != NULL || mRunningLocally) {
return INVALID_OPERATION;
}
mRunningLocally = true;
}
do {
} while (loop());
return OK;
}
Mutex::Autolock autoLock(mLock);
if (mThread != NULL || mRunningLocally) {
return INVALID_OPERATION;
}
mThread = new LooperThread(this, canCallJava);
status_t err = mThread->run(
mName.empty() ? "ALooper" : mName.c_str(), priority);
if (err != OK) {
mThread.clear();
}
return err;
}
status_t ALooper::stop() {
sp<LooperThread> thread;
bool runningLocally;
{
Mutex::Autolock autoLock(mLock);
thread = mThread;
runningLocally = mRunningLocally;
mThread.clear();
mRunningLocally = false;
}
if (thread == NULL && !runningLocally) {
return INVALID_OPERATION;
}
if (thread != NULL) {
thread->requestExit();
}
mQueueChangedCondition.signal();//唤醒
{
Mutex::Autolock autoLock(mRepliesLock);
mRepliesCondition.broadcast();
}
if (!runningLocally && !thread->isCurrentThread()) {
// If not running locally and this thread _is_ the looper thread,
// the loop() function will return and never be called again.
thread->requestExitAndWait();
}
return OK;
}
void ALooper::post(const sp<AMessage> &msg, int64_t delayUs) {
Mutex::Autolock autoLock(mLock);
//获取消息触发时间
int64_t whenUs;
if (delayUs > 0) {
whenUs = GetNowUs() + delayUs;
} else {
whenUs = GetNowUs();
}
List<Event>::iterator it = mEventQueue.begin();
while (it != mEventQueue.end() && (*it).mWhenUs <= whenUs) {
++it;
}
Event event;
event.mWhenUs = whenUs;
event.mMessage = msg;
if (it == mEventQueue.begin()) {
mQueueChangedCondition.signal();
}
//按照时间顺序将事件插入事件队列
mEventQueue.insert(it, event);
}
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;
//确切的阻塞,防止CPU资源占用过多
mQueueChangedCondition.waitRelative(mLock, delayUs * 1000ll);
return true;
}
event = *mEventQueue.begin();
mEventQueue.erase(mEventQueue.begin());
}
event.mMessage->deliver();//等下我们分析AMessage时再看
// 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;
}
//接下来的三个一般不会调用
// to be called by AMessage::postAndAwaitResponse only
sp<AReplyToken> ALooper::createReplyToken() {
return new AReplyToken(this);
}
// to be called by AMessage::postAndAwaitResponse only
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;
}
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;
}
} // namespace android
ALooper也不过如此,首先它运行在一个单独的线程中,它的私有函数对AMessage开放(AMessage还是有素质的,只调post()函数)。此外,为了方便管理,定义了一个注册簿来统一管理可以使用的handler。结合定义我们看到,需要调用start()函数才能让它Loop()起来。其他的函数也就不那么重要了。接下来我们看看他的注册簿ALooperRoster。
ALooperRoster:
直接把头文件贴出来吧。
// xref: /frameworks/av/include/media/stagefright/foundation/ALooperRoster.h
namespace android {
struct ALooperRoster {
ALooperRoster();
ALooper::handler_id registerHandler(
const sp<ALooper> &looper, const sp<AHandler> &handler);
void unregisterHandler(ALooper::handler_id handlerID);
void unregisterStaleHandlers();
void dump(int fd, const Vector<String16>& args);
private:
struct HandlerInfo {
wp<ALooper> mLooper;
wp<AHandler> mHandler;
};
Mutex mLock;
KeyedVector<ALooper::handler_id, HandlerInfo> mHandlers;
ALooper::handler_id mNextHandlerID;
DISALLOW_EVIL_CONSTRUCTORS(ALooperRoster);
};
} // namespace android
可以看到,主要就是四个函数。registerHandler():将AHandler和ALopper绑定成HandlerInfo,再加上一个编号存入mHandlers。 unrigsterhandler():将特定的handler从注册簿中删除。unregisterStableHandlers():将mHandlers中所有对应的Looper==null的item从注册簿中删除。dump():没的说,将目前的mhandlers输入到文件中,你用不到吧?
好了,接下来我们看最为复杂的AMessage,代码较多,我部分性的贴出。
AMessage:
xref: /frameworks/av/include/media/stagefright/foundation/AMessage.h
AMessage.h中还另外定义了一个类:AReplyToken,就是我们之前看到了,来看看它吧。
struct AReplyToken : public RefBase {
explicit AReplyToken(const sp<ALooper> &looper)
: mLooper(looper),
mReplied(false) {
}
private:
friend struct AMessage;
friend struct ALooper;
wp<ALooper> mLooper;
sp<AMessage> mReply;
bool mReplied;
sp<ALooper> getLooper() const {
return mLooper.promote();
}
// if reply is not set, returns false; otherwise, it retrieves the reply and returns true
bool retrieveReply(sp<AMessage> *reply) {
if (mReplied) {
*reply = mReply;
mReply.clear();
}
return mReplied;
}
// sets the reply for this token. returns OK or error
status_t setReply(const sp<AMessage> &reply);
};
只有两个重要的函数,setReply()和retrievereply(),我们看一下它们的定义:
status_t AReplyToken::setReply(const sp<AMessage> &reply) {
if (mReplied) {
ALOGE("trying to post a duplicate reply");
return -EBUSY;
}
CHECK(mReply == NULL);
mReply = reply;
mReplied = true;
return OK;
}
// if reply is not set, returns false; otherwise, it retrieves the reply and returns true
bool retrieveReply(sp<AMessage> *reply) {
if (mReplied) {
*reply = mReply;
mReply.clear();
}
return mReplied;
}
好吧,只是通过自己的私有成员sp<AMessage> mReply进行了调度(设置和获取)。我们来看AMessage吧:
看看它的头:
struct AMessage : public RefBase {
AMessage();
AMessage(uint32_t what, const sp<const AHandler> &handler);
//........//
// 从parcel读取item
static sp<AMessage> FromParcel(const Parcel &parcel,
size_t maxNestingLevel = 255);
// Write this AMessage to a parcel.
// All items in the AMessage must have types that are recognized by
// FromParcel(); otherwise, TRESPASS error will occur.
// 向parcel中写入数据
void writeToParcel(Parcel *parcel) const;
void setWhat(uint32_t what);
uint32_t what() const;
void setTarget(const sp<const AHandler> &handler);
void clear();
//与parcel进行交互,省略...//
status_t post(int64_t delayUs = 0);
//接下来这几个函数不就是ALooper的私有函数吗,你应该知道他们的调用关系了吧
// Posts the message to its target and waits for a response (or error)
// before returning.
status_t postAndAwaitResponse(sp<AMessage> *response);
// If this returns true, the sender of this message is synchronously
// awaiting a response and the reply token is consumed from the message
// and stored into replyID. The reply token must be used to send the response
// using "postReply" below.
bool senderAwaitsResponse(sp<AReplyToken> *replyID);
// Posts the message as a response to a reply token. A reply token can
// only be used once. Returns OK if the response could be posted; otherwise,
// an error.
status_t postReply(const sp<AReplyToken> &replyID);
// 将自己拷贝到文件里
// Performs a deep-copy of "this", contained messages are in turn "dup'ed".
// Warning: RefBase items, i.e. "objects" are _not_ copied but only have
// their refcount incremented.
sp<AMessage> dup() const;
// 深浅比较?什么玩意?我看了一下定义,返回了参数other和this的不相同部分,返回的是this部分
// Performs a shallow or deep comparison of |this| and |other| and returns
// an AMessage with the differences.
// Warning: RefBase items, i.e. "objects" are _not_ copied but only have
// their refcount incremented.
// This is true for AMessages that have no corresponding AMessage equivalent in |other|.
// (E.g. there is no such key or the type is different.) On the other hand, changes in
// the AMessage (or AMessages if deep is |false|) are returned in new objects.
sp<AMessage> changesFrom(const sp<const AMessage> &other, bool deep = false) const;
AString debugString(int32_t indent = 0) const;
enum Type {
kTypeInt32,
kTypeInt64,
kTypeSize,
kTypeFloat,
kTypeDouble,
kTypePointer,
kTypeString,
kTypeObject,
kTypeMessage,
kTypeRect,
kTypeBuffer,
};
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;
struct Item {
union {
int32_t int32Value;
int64_t int64Value;
size_t sizeValue;
float floatValue;
double doubleValue;
void *ptrValue;
RefBase *refValue;
AString *stringValue;
Rect rectValue;
} u;
const char *mName;
size_t mNameLength;
Type mType;
void setName(const char *name, size_t len);
};
enum {
kMaxNumItems = 64 //最多只能从parcel中解析出64个item
};
Item mItems[kMaxNumItems];
size_t mNumItems;
Item *allocateItem(const char *name);// 新建item
void freeItemValue(Item *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();
DISALLOW_EVIL_CONSTRUCTORS(AMessage);//定义拷贝和赋值运算符
};
这些函数的定义都很浅显易懂。我们现在来看看它们的定义:
AMessage::AMessage(void)
: mWhat(0),
mTarget(0),
mNumItems(0) {
}
AMessage::AMessage(uint32_t what, const sp<const AHandler> &handler)
: mWhat(what),
mNumItems(0) {
setTarget(handler);
}
void AMessage::setTarget(const sp<const AHandler> &handler) {
if (handler == NULL) {
mTarget = 0;
mHandler.clear();
mLooper.clear();
} else {
mTarget = handler->id();
mHandler = handler->getHandler();
mLooper = handler->getLooper();
}
}
看来我们使用AMessage得传入一个handler参数啊。我们再来看看之前看到的deliver函数:
void AMessage::deliver() {
sp<AHandler> handler = mHandler.promote();
if (handler == NULL) {
ALOGW("failed to deliver message as target handler %d is gone.", mTarget);
return;
}
handler->deliverMessage(this);
}
果不其然调用的是handler的deliverMessage,并把自己传了过去。再看看post()函数:
status_t AMessage::post(int64_t delayUs) {
sp<ALooper> looper = mLooper.promote();
if (looper == NULL) {
ALOGW("failed to post message as target looper for handler %d is gone.", mTarget);
return -ENOENT;
}
looper->post(this, delayUs);
return OK;
}
原来调用的Looper的post()函数,参数也刚好和我们之前分析的对上了。再看看postReply()函数:
status_t AMessage::postReply(const sp<AReplyToken> &replyToken) {
if (replyToken == NULL) {
ALOGW("failed to post reply to a NULL token");
return -ENOENT;
}
sp<ALooper> looper = replyToken->getLooper();
if (looper == NULL) {
ALOGW("failed to post reply as target looper is gone.");
return -ENOENT;
}
return looper->postReply(replyToken, this);
}
还是调用的lopper的postReply()函数,我们之前分析过了。再接着看postAndAwaitResponse()函数,毫无疑问它是和上面PostReply()想关联的。
status_t AMessage::postAndAwaitResponse(sp<AMessage> *response) {
sp<ALooper> looper = mLooper.promote();
if (looper == NULL) {
ALOGW("failed to post message as target looper for handler %d is gone.", mTarget);
return -ENOENT;
}
sp<AReplyToken> token = looper->createReplyToken();
if (token == NULL) {
ALOGE("failed to create reply token");
return -ENOMEM;
}
setObject("replyID", token);
looper->post(this, 0 /* delayUs */);
return looper->awaitResponse(token, response);
}
它也调用了looper的awaitResponse函数,看来有必要把它们摘出来看看了。
这是ALooper中的代码:
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;
}
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,不难看出awaitResponse函数的while()会一直循环下去,因为replyToken->retrieveReply总为false(没有设置置,怎么得到)。可一直循环不是办法,所以用状态mRepliesCondition.wait()将其阻塞,什么时候可以释放呢?当然是找mRepliesCondition.broadcast()了,一个在postReply中一个在stop()函数中,那肯定不用说,分析postreply()。而Looper的postReply又是由AMessage调用,所以到头来还是AMessage决定了自己await什么时候返回,但是这样有什么意义呢?reply是答复的意思,所以我们当然要在处理完后从handler端重新定义一个AMessage来回复,即postReply()。
其他的函数都是本身数据交互或者与parcel数据交互的函数。我们就不再细细分析了。
下面我们就结合具体的实现来了解一下他们的调用关系。请看NuPlayer的setDataSource函数:
void NuPlayer::setDataSourceAsync(int fd, int64_t offset, int64_t length) {
sp<AMessage> msg = new AMessage(kWhatSetDataSource, this);
sp<AMessage> notify = new AMessage(kWhatSourceNotify, this);
sp<GenericSource> source =
new GenericSource(notify, mUIDValid, mUID);
ALOGV("setDataSourceAsync fd %d/%lld/%lld source: %p",
fd, (long long)offset, (long long)length, source.get());
status_t err = source->setDataSource(fd, offset, length);
if (err != OK) {
ALOGE("Failed to set data source!");
source = NULL;
}
msg->setObject("source", source);
msg->post();
mDataSourceType = DATA_SOURCE_TYPE_GENERIC_FD;
}
代码贴的有点多,只看最后吧msg->setobject() 后就紧跟着post(), 我们之前看到,msg->post()调用的是ALopper的post():
void ALooper::post(const sp<AMessage> &msg, int64_t delayUs) {
Mutex::Autolock autoLock(mLock);
...
List<Event>::iterator it = mEventQueue.begin();
while (it != mEventQueue.end() && (*it).mWhenUs <= whenUs) {
++it;
}
Event event;
event.mWhenUs = whenUs;
event.mMessage = msg;
...
mEventQueue.insert(it, event);
}
只要mEventQueue不为空,loop()函数就不会停止运转(因为有数据要处理啊,在线程里转啊转,如果没数据就阻塞)。我们看看:
bool ALooper::loop() {
Event event;
{
//判断合法相关与设置阻塞
}
event.mMessage->deliver();
return true;
}
最重要的就是这个event.mMessage->deliver()函数了,它把数据交给了Handler处理,我们去看看:
void AMessage::deliver() {
sp<AHandler> handler = mHandler.promote();
if (handler == NULL) {
ALOGW("failed to deliver message as target handler %d is gone.", mTarget);
return;
}
handler->deliverMessage(this);
}
额,白跑一趟,我们直接去Handler:
void AHandler::deliverMessage(const sp<AMessage> &msg) {
onMessageReceived(msg);
mMessageCounter++;
~
}
直接调用了AHandler子类的deliverMessage()函数,我们去看看(一般情况下,这个子类还是发出Message类本身):
void NuPlayer::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case A:
break;
case B:
...
}
}
到此,消息处理结束。安卓源码里的调用大部分都是如此。好了现在我们可以回头再看看类图,应该就明白差不多了。
版权声明:本文标题:安卓源码消息机制----AMessage,ALooper,AHandler 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://www.elefans.com/dongtai/1729332188a1196531.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论