中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁 > news >正文

東莞高端品牌網(wǎng)站建設(shè)營銷推廣的工具有哪些

東莞高端品牌網(wǎng)站建設(shè),營銷推廣的工具有哪些,數(shù)商云商城,政務(wù)網(wǎng)站建設(shè)云計算中心本文基于Android12。 InputChannel表示其他進(jìn)程通過文件描述符傳遞輸入事件到View的通道,因為需要跨進(jìn)程傳輸,實現(xiàn)了Parcelable序列化接口,所以也能夠理解Java層的InputChannel后面為什么使用copyTo()方法初始化。 輸入事件的接收方是View&…

本文基于Android12。

InputChannel表示其他進(jìn)程通過文件描述符傳遞輸入事件到View的通道,因為需要跨進(jìn)程傳輸,實現(xiàn)了Parcelable序列化接口,所以也能夠理解Java層的InputChannel后面為什么使用copyTo()方法初始化。

輸入事件的接收方是View,所以InputChannel的創(chuàng)建肯定和View的創(chuàng)建流程有關(guān),關(guān)于View的創(chuàng)建流程參考:https://blog.csdn.net/qq_36063677/article/details/129908973。

一、InputChannel初始化

ViewRootImpl在setView()方法實例化了Java層的InputChannle對象,但是正如ViewRootImpl創(chuàng)建的mSurface對象一樣,這只是一個引用,一個“空的”對象,后續(xù)在WindowManagerService經(jīng)過實際的初始化,再通過copyTo()方法拷貝到InputChannel引用中。

    // ViewRootImpl.javapublic void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,int userId) {// 1.創(chuàng)建 inputChannel引用。InputChannel inputChannel = null;if ((mWindowAttributes.inputFeatures& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {inputChannel = new InputChannel();}// 2.傳遞引用給mWindowSession,inputChannel在WMS中被初始化,并通過copyTo()全拷貝到inputChannel引用。res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,getHostVisibility(), mDisplay.getDisplayId(), userId,mInsetsController.getRequestedVisibilities(), inputChannel, mTempInsets,mTempControls, attachedFrame, sizeCompatScale);// 3.通過inputChannel創(chuàng)建WindowInputEventReceiver對象,接收處理輸入事件。ALOOPER_EVENT_INPUTif (inputChannel != null) {mInputEventReceiver = new WindowInputEventReceiver(inputChannel,Looper.myLooper());}}

setView()方法中關(guān)于InputChannel的操作主要有3步:

  1. 創(chuàng)建 inputChannel引用。就是實例化InputChannel引用,InputChannel構(gòu)造方法是個空方法,所以什么實際操作都沒有做。
  2. 傳遞引用給mWindowSession,inputChannel在WMS中被初始化,并通過copyTo()全拷貝到inputChannel引用。
  3. 通過inputChannel創(chuàng)建WindowInputEventReceiver對象,接收處理輸入事件。

我們從第2步開始分析。

1.1 openInputChannle

mWindowSession將inputChannel引用傳遞給WMS.addWindow()方法:

    // WindowManagerService.java    public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,InputChannel outInputChannel, InsetsState outInsetsState,InsetsSourceControl[] outActiveControls, Rect outAttachedFrame,float[] outSizeCompatScale) {final WindowState win = new WindowState(this, session, client, token, parentWindow,appOp[0], attrs, viewVisibility, session.mUid, userId,session.mCanAddInternalSystemWindow);final boolean openInputChannels = (outInputChannel != null&& (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);if  (openInputChannels) {win.openInputChannel(outInputChannel);}}

addWindow()創(chuàng)建了WindowState對象,調(diào)用其openInputChannel(outInputChannel)方法。

    // WindowState.java    void openInputChannel(InputChannel outInputChannel) {if (mInputChannel != null) {String name = getName();mInputChannel = mWmService.mInputManager.createInputChannel(name);if (outInputChannel != null) {mInputChannel.copyTo(outInputChannel);}}}

WindowState通過InputMangerService創(chuàng)建InputChannel,經(jīng)過NativeInputManagerService類的native方法createInputChannel(name),最終到InputDispatcher::createInputChannel(name)方法實際創(chuàng)建。

1.1.1 InputDispatcher::createInputChannel

    // InputDispatcher.h    // All registered connections mapped by input channel token.std::unordered_map<sp<IBinder>, sp<Connection>, StrongPointerHash<IBinder>> mConnectionsByTokenGUARDED_BY(mLock);// InputDispatcher.cppResult<std::unique_ptr<InputChannel>> InputDispatcher::createInputChannel(const std::string& name) {std::unique_ptr<InputChannel> serverChannel;std::unique_ptr<InputChannel> clientChannel;// 1.通過CPP層的InputChannel創(chuàng)建serverChannel,clientChannelstatus_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);{std::scoped_lock _l(mLock);const sp<IBinder>& token = serverChannel->getConnectionToken();// 2.得到serverChannel的文件描述符 fdint fd = serverChannel->getFd();// 3.將connection對象添加到 mConnectionsByToken管理sp<Connection> connection =new Connection(std::move(serverChannel), false /*monitor*/, mIdGenerator);mConnectionsByToken.emplace(token, connection);std::function<int(int events)> callback = std::bind(&InputDispatcher::handleReceiveCallback,this, std::placeholders::_1, token);mGlobalMonitorsByDisplay[displayId].emplace_back(serverChannel, pid);// 4.監(jiān)聽serverChannel的文件描述符 fd,當(dāng)有事件發(fā)生時,回調(diào)callbackmLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, new LooperEventCallback(callback), nullptr);}// 5.喚醒mLoopermLooper->wake();return clientChannel;}

InputDispatcher主要做了5件事:

  1. 通過CPP層的InputChannel創(chuàng)建serverChannel,clientChannel,最后返回clientChannel給Java層的InputChannel,到這里Java層InputChannel才被初始化完成,返回創(chuàng)建WindowInputEventReceiver對象。
  2. 得到serverChannel的文件描述符 fd。
  3. 將connection對象添加到 mConnectionsByToken管理,mConnectionsByToken定義在InputDispatcher.h文件,管理所有連接的InputChannel對象,map的key是token,又是使用Binder對象作為token。inputflinger文章中(https://blog.csdn.net/qq_36063677/article/details/130475299)4.3.3小節(jié)InputDispatcher在分發(fā)事件時就是通過這個mConnectionsByToken獲取到具體的connection,發(fā)送事件。
  4. 監(jiān)聽serverChannel的文件描述符 fd,ALOOPER_EVENT_INPUT表示為要監(jiān)聽的文件類型,當(dāng)有事件發(fā)生時,回調(diào)callback方法handleEvent(),也就是InputDispatcher::handleReceiveCallback()。LooperEventCallback繼承了LooperCallback類,LooperCallback是Looper監(jiān)聽文件描述符回調(diào)方法的標(biāo)準(zhǔn)類,當(dāng)文件描述符fd上有事件到來時,LooperCallback的handleEvent()方法會被執(zhí)行,關(guān)于Looper->addFd()更多細(xì)節(jié)參考:https://blog.csdn.net/chwan_gogogo/article/details/46953563
  5. 喚醒mLooper,mLooper在InputDispatcher構(gòu)造方法中被初始化,mLooper = new Looper(false);。

查看cpp層InputChannel::openInputChannelPair()具體細(xì)節(jié):

1.1.2 InputChannel::openInputChannelPair

    // InputTransport.cppstatus_t InputChannel::openInputChannelPair(const std::string& name,std::unique_ptr<InputChannel>& outServerChannel,std::unique_ptr<InputChannel>& outClientChannel) {int sockets[2];if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {outServerChannel.clear();outClientChannel.clear();return result;}int bufferSize = SOCKET_BUFFER_SIZE;setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));sp<IBinder> token = new BBinder();std::string serverChannelName = name + " (server)";android::base::unique_fd serverFd(sockets[0]);outServerChannel = InputChannel::create(serverChannelName, std::move(serverFd), token);std::string clientChannelName = name + " (client)";android::base::unique_fd clientFd(sockets[1]);outClientChannel = InputChannel::create(clientChannelName, std::move(clientFd), token);return OK;}std::unique_ptr<InputChannel> InputChannel::create(const std::string& name,android::base::unique_fd fd, sp<IBinder> token) {const int result = fcntl(fd, F_SETFL, O_NONBLOCK);return std::unique_ptr<InputChannel>(new InputChannel(name, std::move(fd), token));}InputChannel::InputChannel(const std::string name, android::base::unique_fd fd, sp<IBinder> token): mName(std::move(name)), mFd(std::move(fd)), mToken(std::move(token)) {}
  1. InputChannel使用socket通信,openInputChannelPair()方法創(chuàng)建兩個socke對象,一個client,一個server。socketpair()函數(shù)用于創(chuàng)建一對無名的、相互連接的套接子。如果函數(shù)成功,則返回0,創(chuàng)建好的套接字分別是sv[0]和sv[1],所以outServerChannel和outClientChannel這兩個socket在創(chuàng)建的時候就是相互連接的,之后只需要在各自的fd中通過send()發(fā)送數(shù)據(jù)就好。
  2. setsockopt()設(shè)置socket屬性,在<sys/socket.h>頭文件中聲明,實例化一個BBinder()對象作為token使用,這里outServerChannel和outClientChannel都使用的是同一個token。
  3. InputChannel::create()通過fcntl()設(shè)置socket為非阻塞類型,fcntl()方法可以改變已打開的文件性質(zhì)。
  4. 封裝InputChannel對象。

InputChannel初始化過程終于結(jié)束了,那么1.1.1小節(jié)中mLooper監(jiān)聽的serverChannel的文件描述符 fd什么時候會觸發(fā)呢?

答案在上一篇inputflinger文章中(https://blog.csdn.net/qq_36063677/article/details/130475299)4.3.3小節(jié)。

二、InputChannel發(fā)送事件

InputDispatcher在接收到事件后,InputDispatcher::dispatchEventLocked()從mConnectionsByToken變量中通過token獲取到Connection對象,最終在4.3.4.2 小節(jié)startDispatchCycleLocked()方法調(diào)用

connection->inputPublisher.publishMotionEvent()發(fā)送輸入事件,這里還是以Motion事件為例:

    // InputTransport.cppstatus_t InputPublisher::publishMotionEvent(uint32_t seq, int32_t eventId, int32_t deviceId, int32_t source, int32_t displayId,std::array<uint8_t, 32> hmac, int32_t action, int32_t actionButton, int32_t flags,int32_t edgeFlags, int32_t metaState, int32_t buttonState,MotionClassification classification, const ui::Transform& transform, float xPrecision,float yPrecision, float xCursorPosition, float yCursorPosition,const ui::Transform& rawTransform, nsecs_t downTime, nsecs_t eventTime,uint32_t pointerCount, const PointerProperties* pointerProperties,const PointerCoords* pointerCoords) {InputMessage msg;msg.header.type = InputMessage::Type::MOTION;msg.header.seq = seq;msg.body.motion.eventId = eventId;msg.body.motion.deviceId = deviceId;msg.body.motion.source = source;//.......return mChannel->sendMessage(&msg);}

mChannel是之前創(chuàng)建的InputChannel對象serverChannel,查看sendMessage(&msg)方法:

    // InputTransport.cppstatus_t InputChannel::sendMessage(const InputMessage* msg) {const size_t msgLength = msg->size();InputMessage cleanMsg;msg->getSanitizedCopy(&cleanMsg);ssize_t nWrite;do {nWrite = ::send(getFd(), &cleanMsg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL);} while (nWrite == -1 && errno == EINTR);return OK;}

調(diào)用socket的send()函數(shù)發(fā)送數(shù)據(jù)。

至此輸入事件通過socket發(fā)送出去了,InputDispatcher執(zhí)行回調(diào)LooperEventCallback,那么事件又是如何被接收的呢?

三、InputEventReceiver

ViewRootImpl在setView()方法實例化InputChannel后,作為參數(shù)初始化WindowInputEventReceiver對象,WindowInputEventReceiver繼承InputEventReceiver類。

3.1 WindowInputEventReceiver

WindowInputEventReceiver也在ViewRootImpl中定義:

    // ViewRootImpl.javafinal class WindowInputEventReceiver extends InputEventReceiver {public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {super(inputChannel, looper);}        @Overridepublic void onInputEvent(InputEvent event) {Trace.traceBegin(Trace.TRACE_TAG_VIEW, "processInputEventForCompatibility");List<InputEvent> processedEvents;try {processedEvents =mInputCompatProcessor.processInputEventForCompatibility(event);} finally {Trace.traceEnd(Trace.TRACE_TAG_VIEW);}if (processedEvents != null) {if (processedEvents.isEmpty()) {// InputEvent consumed by mInputCompatProcessorfinishInputEvent(event, true);} else {for (int i = 0; i < processedEvents.size(); i++) {enqueueInputEvent(processedEvents.get(i), this,QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY, true);}}} else {enqueueInputEvent(event, this, 0, true);}}}// InputEventReceiver.javapublic InputEventReceiver(InputChannel inputChannel, Looper looper) {mInputChannel = inputChannel;mMessageQueue = looper.getQueue();mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),mInputChannel, mMessageQueue);mCloseGuard.open("InputEventReceiver.dispose");}

WindowInputEventReceiver構(gòu)造方法中將參數(shù)傳遞給了父類InputEventReceiver,實現(xiàn)了onInput()方法,后續(xù)接收到事件后通過這個方法處理Java層的分發(fā)邏輯,Eventlooper是ViewRoolImpl的當(dāng)前線程Looper.myLooper(),也就是主線程,InputEventReceiver調(diào)用nativeInit()繼續(xù)下一步操作,創(chuàng)建NativeInputEventReceiver。

3.2 NativeInputEventReceiver

NativeInputEventReceiver定義在JNI文件中,方便后續(xù)回調(diào)JAVA方法。

    // android_view_InputEventReceiver.cppstatic jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,jobject inputChannelObj, jobject messageQueueObj) {// 獲取InputChannelstd::shared_ptr<InputChannel> inputChannel =android_view_InputChannel_getInputChannel(env, inputChannelObj);	// 獲取messageQueuesp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);// 1. 實例化NativeInputEventReceiversp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env,receiverWeak, inputChannel, messageQueue);// 2. receiver->initialize()status_t status = receiver->initialize();	receiver->incStrong(gInputEventReceiverClassInfo.clazz); // retain a reference for the objectreturn reinterpret_cast<jlong>(receiver.get())}

nativeInit()主要做了兩件事,實例化NativeInputEventReceiver,并且調(diào)用其initialize()方法。

3.2.1 NativeInputEventReceiver實例化

    // android_view_InputEventReceiver.cppNativeInputEventReceiver::NativeInputEventReceiver(JNIEnv* env, jobject receiverWeak, const std::shared_ptr<InputChannel>& inputChannel,const sp<MessageQueue>& messageQueue): mReceiverWeakGlobal(env->NewGlobalRef(receiverWeak)),mInputConsumer(inputChannel),mMessageQueue(messageQueue),mBatchedInputEventPending(false),mFdEvents(0) {}

NativeInputEventReceiver構(gòu)造方法中持有Java層InputEventRecevier對象的引用mReceiverWeakGlobal, 將inputChannel作為參數(shù)實例化mInputConsumer對象。

InputConsumer類定義在InputTransport.h頭文件,從InputChannel消費(fèi)事件。

    // InputTransport.cppInputConsumer::InputConsumer(const std::shared_ptr<InputChannel>& channel): mResampleTouch(isTouchResamplingEnabled()), mChannel(channel), mMsgDeferred(false) {}

client端的inputChannel被賦值給InputConsumer的mChannel變量。

回到NativeInputEventReceiver初始化流程中。

3.2.2 NativeInputEventReceiver::initialize

    // android_view_InputEventReceiver.cppstatus_t NativeInputEventReceiver::initialize() {setFdEvents(ALOOPER_EVENT_INPUT);return OK;}void NativeInputEventReceiver::setFdEvents(int events) {if (mFdEvents != events) {mFdEvents = events;int fd = mInputConsumer.getChannel()->getFd();if (events) {mMessageQueue->getLooper()->addFd(fd, 0, events, this, nullptr);} else {mMessageQueue->getLooper()->removeFd(fd);}}}

initialize()監(jiān)聽client InputChannel的文件描述符 fd,文件類型文件類型和發(fā)送端一樣,也是ALOOPER_EVENT_INPUT。在上述1.1.1小節(jié)中我們知道后續(xù)會回調(diào)LooperCallback的handleEvent()方法,NativeInputEventReceiver也繼承了LooperCallback類,實現(xiàn)了自己的handleEvent():

3.3 handleEvent

    // android_view_InputEventReceiver.cppint NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {if (events & ALOOPER_EVENT_INPUT) {JNIEnv* env = AndroidRuntime::getJNIEnv();status_t status = consumeEvents(env, false /*consumeBatches*/, -1, nullptr);mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");return status == OK || status == NO_MEMORY ? KEEP_CALLBACK : REMOVE_CALLBACK;}if (events & ALOOPER_EVENT_OUTPUT) {const status_t status = processOutboundEvents();if (status == OK || status == WOULD_BLOCK) {return KEEP_CALLBACK;} else {return REMOVE_CALLBACK;}}return KEEP_CALLBACK;}

handleEvent只是對事件類型進(jìn)行區(qū)分,如果是ALOOPER_EVENT_INPUT類型,詳細(xì)具體到處理流程在consumeEvents()方法。

3.4 consumeEvents

    status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {for (;;) {uint32_t seq;// 1.InputConsumer從socket讀取封裝事件InputEvent* inputEvent;status_t status = mInputConsumer.consume(&mInputEventFactory,consumeBatches, frameTime, &seq, &inputEvent);// 2.區(qū)分事件類型,封裝為Java層的InputEvent對象jobject inputEventObj;switch (inputEvent->getType()) {case AINPUT_EVENT_TYPE_KEY:inputEventObj = android_view_KeyEvent_fromNative(env,static_cast<KeyEvent*>(inputEvent));break;case AINPUT_EVENT_TYPE_MOTION: {MotionEvent* motionEvent = static_cast<MotionEvent*>(inputEvent);if ((motionEvent->getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) {*outConsumedBatch = true;}inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);break;}}// 3.調(diào)用Java層InputEventReceiver對象的dispatchInputEvent方法,參數(shù)為seq,inputEventObjif (inputEventObj) {env->CallVoidMethod(receiverObj.get(),gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);env->DeleteLocalRef(inputEventObj);}}}

consumeEvents()處理了事件接收的主要流程:

  1. 啟動一個死循環(huán),不斷從InputConsumer獲取事件,賦值到inputEvent引用,InputConsumer持有客戶端InputChannel的引用,從客戶端socket讀取數(shù)據(jù),將數(shù)據(jù)解析封裝成InputEvent對象。
  2. 將InputEvent類型進(jìn)行區(qū)分,具體共有AINPUT_EVENT_TYPE_KEY,AINPUT_EVENT_TYPE_MOTION,AINPUT_EVENT_TYPE_FOCUS,AINPUT_EVENT_TYPE_CAPTURE,AINPUT_EVENT_TYPE_DRAG,AINPUT_EVENT_TYPE_TOUCH_MODE六種類型,對于Motion事件,封裝成Java層的MotionEvent對象。
  3. receiverObj指向NativeInputEventReceiver實例化時傳遞過來的Java層InputEventReceiver引用,調(diào)用Java層InputEventReceiver對象的dispatchInputEvent方法,參數(shù)為seq,inputEventObj,至此回到了Java層的處理流程。

在分析Java層的處理流程之前,先看下InputConsumer是如何讀取socket數(shù)據(jù)解析成InputEvent對象的。

3.4.1 InputConsumer.consume

    // InputTransport.cppstatus_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consumeBatches,nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {while (!*outEvent) {status_t result = mChannel->receiveMessage(&mMsg);switch (mMsgmMsg.header.type) {case InputMessage::Type::KEY: {KeyEvent* keyEvent = factory->createKeyEvent();if (!keyEvent) return NO_MEMORY;initializeKeyEvent(keyEvent, &mMsg);*outSeq = mMsg.header.seq;*outEvent = keyEvent;break;}case InputMessage::Type::MOTION: {MotionEvent* motionEvent = factory->createMotionEvent();if (!motionEvent) return NO_MEMORY;updateTouchState(mMsg);initializeMotionEvent(motionEvent, &mMsg);*outSeq = mMsg.header.seq;*outEvent = motionEvent;break;}}}return OK;}

mChannel是NativeInputEventReceiver傳遞過去的客戶端InputChannel對象,InputConsumer從mChannel讀取消息,在上述第二小節(jié):InputChannel發(fā)送事件 可知,發(fā)送的InputMessage對象封裝了mMsg.header.type等事件類型消息,這里通過事件類型消息進(jìn)行區(qū)分,封裝成具體對應(yīng)的輸入事件,如KeyEvent,MotionEvent等,賦值給outEvent。

InputChannel通過recv()方法接收socket消息:

    // InputTransport.cppstatus_t InputChannel::receiveMessage(InputMessage* msg) {ssize_t nRead;do {nRead = ::recv(getFd(), msg, sizeof(InputMessage), MSG_DONTWAIT);} while (nRead == -1 && errno == EINTR);}

3.4.2 dispatchInputEvent

事件的讀取解析流程到此結(jié)束了,接下來看看InputReceiver是如何分發(fā)事件的。

    // InputEventReceiver.java// Called from native code.@SuppressWarnings("unused")@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)private void dispatchInputEvent(int seq, InputEvent event) {mSeqMap.put(event.getSequenceNumber(), seq);onInputEvent(event);}

InputEventReceiver調(diào)用onInputEvent(event)開始處理事件的分發(fā)流程,上述3.1小節(jié)可知,onInputEvent方法在WindowInputEventReceiver類中被覆蓋。Java層的事件分發(fā)流程參考:https://editor.csdn.net/md/?articleId=130476234

http://www.risenshineclean.com/news/47459.html

相關(guān)文章:

  • 怎樣獲得做網(wǎng)站的客戶源碼之家
  • wordpress快速扒站網(wǎng)站seo方案案例
  • 專業(yè)做網(wǎng)站排名多少錢網(wǎng)上如何推廣自己的產(chǎn)品
  • 抖音小程序變現(xiàn)真的能賺錢嗎seo優(yōu)化報價
  • h5制作多少錢seo教程最新
  • 保定網(wǎng)站制作軟件天津百度推廣電話號碼
  • 如何搭建一個簡單的網(wǎng)站seo推廣排名平臺有哪些
  • 蘇州高端網(wǎng)站建設(shè)設(shè)計深圳百度搜索排名優(yōu)化
  • 如何做英文網(wǎng)站推廣廣州百度快速優(yōu)化排名
  • 網(wǎng)站建設(shè)主要產(chǎn)品網(wǎng)盤資源
  • 貴州省城鄉(xiāng)與建設(shè)廳網(wǎng)站網(wǎng)絡(luò)營銷圖片素材
  • 本地的唐山網(wǎng)站建設(shè)零基礎(chǔ)seo入門教學(xué)
  • 網(wǎng)站設(shè)置安全哈爾濱seo優(yōu)化
  • 用ih5做微網(wǎng)站博客可以做seo嗎
  • 提升網(wǎng)站收錄網(wǎng)絡(luò)銷售平臺排名
  • 哈爾濱網(wǎng)站開發(fā)制作岳陽seo快速排名
  • 自己建立公司網(wǎng)站 怎樣做怎么做好營銷推廣
  • 使網(wǎng)站有流量萬能搜索引擎
  • 網(wǎng)站開發(fā)輔助工具地推怎么做最有效
  • 企業(yè)網(wǎng)站建設(shè)框架圖2345網(wǎng)址導(dǎo)航瀏覽器下載
  • 二手車網(wǎng)站模版售價美國站外推廣網(wǎng)站
  • 做博客的網(wǎng)站有哪些功能seo知識是什么意思
  • aspnet東莞網(wǎng)站建設(shè)多少錢alexa排名查詢統(tǒng)計
  • 做游戲代練去那個網(wǎng)站石家莊最新疫情
  • 路橋做網(wǎng)站衡陽seo優(yōu)化首選
  • 婚禮禮網(wǎng)站如何做的aso優(yōu)化服務(wù)站
  • 中山市做網(wǎng)站實力產(chǎn)品軟文是什么意思
  • 從化網(wǎng)站建設(shè)優(yōu)化今天最新新聞10條
  • wordpress 仿虎嗅主題seo百度推廣
  • 免費(fèi)的ftp網(wǎng)站2021百度熱搜年度榜