国产亚洲精品福利在线无卡一,国产精久久一区二区三区,亚洲精品无码国模,精品久久久久久无码专区不卡

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

西昌網(wǎng)站建設(shè)我想在百度上發(fā)布廣告怎么發(fā)

西昌網(wǎng)站建設(shè),我想在百度上發(fā)布廣告怎么發(fā),做空間的網(wǎng)站嗎,蘇州區(qū)建設(shè)局網(wǎng)站文章目錄 前言一、輸入系統(tǒng)的基本組成部分二、輸入系統(tǒng)相關(guān)源碼分析1、IMS 構(gòu)建1.1、SystemServer # startOtherServices()1.2、InputManagerService1.3、NativeInputManager # nativeInit()1.4、NativeInputManager1.5、InputManager1.6、InputDispatcher1.7、InputReader1.8、…

文章目錄

  • 前言
  • 一、輸入系統(tǒng)的基本組成部分
  • 二、輸入系統(tǒng)相關(guān)源碼分析
    • 1、IMS 構(gòu)建
      • 1.1、SystemServer # startOtherServices()
      • 1.2、InputManagerService
      • 1.3、NativeInputManager # nativeInit()
      • 1.4、NativeInputManager
      • 1.5、InputManager
      • 1.6、InputDispatcher
      • 1.7、InputReader
      • 1.8、EventHub
      • 1.9、小結(jié)
    • 2、IMS 啟動
      • 2.1、IMS # start()
      • 2.2、NativeInputManager # nativeStart()
      • 2.3、InputManager # start()
      • 2.4、InputDispatcher # start()
      • 2.5、InputReader # start()
      • 2.6、InputThread
    • 3、IMS 系統(tǒng)就緒
  • 三、總結(jié)
    • 1、IMS 啟動時序圖
    • 2、IMS 成員關(guān)系圖
  • 參考

前言

Android 輸入系統(tǒng)(Input System) 的工作原理,包括:輸入設(shè)備的管理輸入事件的加工方式及派發(fā)流程。首先輸入設(shè)備包括:觸摸屏,鍵盤,鼠標(biāo)和手柄等,其中觸摸屏與鍵盤是 Android 最普遍也是最標(biāo)準(zhǔn)的輸入設(shè)備。當(dāng)用戶操作輸入設(shè)備時,Linux 內(nèi)核接收到相應(yīng)的硬件中斷,然后將中斷加工成原始的輸入事件數(shù)據(jù)并寫入其對應(yīng)的設(shè)備節(jié)點中,在用戶空間可以通過輸入系統(tǒng)內(nèi)部的讀取函數(shù)將原始事件數(shù)據(jù)讀出,并進(jìn)行一系列的翻譯加工成 Android 輸入事件,然后在所有的窗口中尋找合適的事件接收者,并派發(fā)給它來消費(fèi)該輸入事件??梢?#xff0c;輸入系統(tǒng)在整個輸入事件處理過程中起到了承上啟下的銜接作用。


一、輸入系統(tǒng)的基本組成部分

IMS 的總體流程與參與者
上圖展示了輸入事件的處理流程以及輸入系統(tǒng)中最基本的參與者,下面簡要介紹一下各個參與者:

  • Linux 內(nèi)核:接受輸入設(shè)備的中斷,并將原始輸入事件的數(shù)據(jù)寫入設(shè)備節(jié)點中;
  • 設(shè)備節(jié)點:內(nèi)核與 InputManagerService 的橋梁,它將原始事件的數(shù)據(jù)暴露給用戶空間,以便 InputManagerService 可以從中讀取事件;
  • InputManagerService:Android 系統(tǒng)服務(wù),以后簡稱 IMS,其分為 Java 層和 Native 層兩部分。Java 層負(fù)責(zé)與 WindowManagerService 通信。而 Native 層則是InputReaderInputDispatcher 兩個輸入系統(tǒng)關(guān)鍵組件的運(yùn)行容器;
  • EventHub:直接訪問所有的設(shè)備節(jié)點。并且正如其名字所描述的,它通過一個名為 getEvents( ) 的函數(shù)將所有輸入系統(tǒng)相關(guān)的待處理的底層事件返回給使用者,包括原始輸入事件、設(shè)備節(jié)點的增刪等。
  • InputReaderIMS 中的關(guān)鍵組件之一,運(yùn)行于一個獨立的線程中,負(fù)責(zé)管理輸入設(shè)備的列表與配置,以及進(jìn)行輸入事件的加工處理。通過其線程循環(huán)不斷地通過 getEvent( ) 函數(shù)從 EventHub 中將事件取出并進(jìn)行處理。對于設(shè)備節(jié)點的增刪事件,將會更新輸入設(shè)備列表與配置。對于原始輸入事件,InputReader 對其進(jìn)行翻譯、組裝、封裝為包含更多信息、更具可讀性的輸入事件,然后交給 InputDispatcher 進(jìn)行派發(fā);
  • InputReaderPolicy:為 InputReader 的事件加工處理提供一些策略配置,例如鍵盤布局信息等;
  • InputDispatcherIMS 中的另一個關(guān)鍵組件,也運(yùn)行于一個獨立的線程中。InputDispatcher 中保管了來自 WindowManagerService 的所有窗口的信息,其收到來自 InputReader 的輸入事件后,會在其保管的窗口中尋找合適的窗口,并將事件派發(fā)給此窗口;
  • InputDispatcherPolicy:為 InputDispatcher 的派發(fā)過程提供策略控制。例如截取某些特定的輸入事件用作特殊用途,或者阻止將某些事件派發(fā)給目標(biāo)窗口。一個典型的例子就是 HOME 鍵InputDispatcherPolicy 截取到 PhoneWindowManager 中進(jìn)行處理,并阻止窗口收到 HOME 鍵按下的事件;
  • WindowManagerService:雖不是輸入系統(tǒng)中的成員,但卻對 InputDispatcher 的正常工作起到了至關(guān)重要的作用。當(dāng)新建窗口時,WMS 為新窗口和 IMS 之間創(chuàng)建了事件傳遞所用的通道。另外,WMS 還將所有窗口的信息,包括窗口的可點擊區(qū)域、焦點窗口等信息,實時地更新到 IMSInputDispatcher 中,使得 InputDispatcher 可以正確地將事件派發(fā)到指定的窗口;
  • ViewRootImpl:對某些窗口,如壁紙窗口、SurfaceView 的窗口來說,窗口就是輸入事件派發(fā)的終點。而對其他的如 Activity、對話框等使用了 Android 控件系統(tǒng)的窗口來說,輸入事件的終點是控件 View。ViewRootImpl 將窗口所接收的輸入事件沿著控件樹將事件派發(fā)給感興趣的控件 View

二、輸入系統(tǒng)相關(guān)源碼分析

我們知道,Zygote 進(jìn)程創(chuàng)建并啟動后,在 fork 出的子進(jìn)程 SystemServer 的初始化過程中啟動 Android 系統(tǒng)所有的 Service 服務(wù),這些系統(tǒng)服務(wù)分為三大類:引導(dǎo)服務(wù)、核心服務(wù)其他服務(wù),具體的啟動流程可參考探索Framework之SystemServer進(jìn)程的啟動詳解。

輸入系統(tǒng)服務(wù) IMS 是在啟動其他服務(wù)里面啟動的,接下來從源碼的角度來繼續(xù)探索分析。

1、IMS 構(gòu)建

SystemServer 類中找到啟動其他服務(wù) startOtherServices() 方法的代碼,提取主要的邏輯代碼進(jìn)行分析,源碼如下:

1.1、SystemServer # startOtherServices()

xref: /frameworks/base/services/java/com/android/server/SystemServer.java

public final class SystemServer implements Dumpable {......private void startOtherServices(@NonNull TimingsTraceAndSlog t) {final Context context = mSystemContext;WindowManagerService wm = null;......InputManagerService inputManager = null;......try {......// 啟動 InputManagerService 服務(wù)t.traceBegin("StartInputManagerService"); // 新建 InputManagerService 對象inputManager = new InputManagerService(context);......t.traceBegin("StartWindowManagerService"); // 啟動 WindowManagerService 服務(wù)mSystemServiceManager.startBootPhase(t, SystemService.PHASE_WAIT_FOR_SENSOR_SERVICE);// 使用新建的 IMS 對象來構(gòu)建 WMS 對象wm = WindowManagerService.main(context, inputManager, !mFirstBoot, mOnlyCore,new PhoneWindowManager(), mActivityManagerService.mActivityTaskManager);......// 將 InputManagerService 發(fā)布到 ServiceManager 以便調(diào)用者可以訪問 IMS 提供的接口ServiceManager.addService(Context.INPUT_SERVICE, inputManager,/* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL);t.traceBegin("SetWindowManagerService"); // ActivityManagerService 設(shè)置 WindowManagerServicemActivityManagerService.setWindowManager(wm);t.traceBegin("StartInputManager"); // 設(shè)置向 WMS 發(fā)起回調(diào)的 callback 對象inputManager.setWindowManagerCallbacks(wm.getInputManagerCallback());inputManager.start(); // 啟動 InputManagerService,具體見......} catch (Throwable e) {......// 日志輸出并拋出異常}......final InputManagerService inputManagerF = inputManager;t.traceBegin("MakeInputManagerServiceReady");try {if (inputManagerF != null) {// 輸入系統(tǒng) IMS 準(zhǔn)備就緒inputManagerF.systemRunning();}} catch (Throwable e) {reportWtf("Notifying InputManagerService running", e);}......}......
}

IMS 的啟動流程可以分為以下三個步驟:

  1. 構(gòu)建 IMS 實例對象,并建立上層與底層的映射關(guān)系。
  2. 啟動 IMS,其內(nèi)部就是啟動 native 層輸入系統(tǒng)的幾個重要參與者(后續(xù)會分析)。
  3. IMS 系統(tǒng)就緒,此時 Java 層會同步一些配置給 native 層輸入系統(tǒng)。

首先是 IMS 實例對象的構(gòu)建,分析查看 IMS 類的源碼

1.2、InputManagerService

xref: /frameworks/base/services/core/java/com/android/server/input/InputManagerService.java

public class InputManagerService extends IInputManager.Stubimplements Watchdog.Monitor {......private final Context mContext;private final InputManagerHandler mHandler;......private static native long nativeInit(InputManagerService service,Context context, MessageQueue messageQueue);......public InputManagerService(Context context) {this.mContext = context;// 獲取 DisplayThread 的 Looper 創(chuàng)建 IMS 內(nèi)部的 InputManagerHandler 對象this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());mStaticAssociations = loadStaticInputPortAssociations();mUseDevInputEventForAudioJack =context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack="+ mUseDevInputEventForAudioJack);// 每一個分為 Java 和 Native 兩部分的對象在創(chuàng)建時都會有一個 native 函數(shù)// 在創(chuàng)建 Java 層對象的同時 native 層也創(chuàng)建一個,注意:使用的是同一個 Looper 對象// mPtr 指向底層創(chuàng)建的 NativeInputManager 對象mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());String doubleTouchGestureEnablePath = context.getResources().getString(R.string.config_doubleTouchGestureEnableFile);mDoubleTouchGestureEnableFile = TextUtils.isEmpty(doubleTouchGestureEnablePath) ? null :new File(doubleTouchGestureEnablePath);// 新建 IMS 的本地系統(tǒng)服務(wù) LocalService,其繼承自 InputManagerInternal 抽象接口,并加入到 LocalServices 中LocalServices.addService(InputManagerInternal.class, new LocalService());}
}

方法中,獲取 DisplayThreadLooper,新建 InputManagerHandler 對象,然后調(diào)用 native 層的 nativeInit() 函數(shù),創(chuàng)建NativeInputManager 對象,最后新建 IMS 的本地系統(tǒng)服務(wù) LocalService,其繼承自 InputManagerInternal 抽象接口,并加入到 LocalServices 中。

DisplayThreadsystem_server 進(jìn)程中是單例的,且只能被 WindowManager、DisplayManager、InputManager 使用。

1.3、NativeInputManager # nativeInit()

xref: /frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp

static const JNINativeMethod gInputManagerMethods[] = { // JNI 注冊的映射關(guān)系/* name, signature, funcPtr */{"nativeInit","(Lcom/android/server/input/InputManagerService;Landroid/content/Context;Landroid/os/""MessageQueue;)J",(void*)nativeInit},
};static jlong nativeInit(JNIEnv*env, jclass /* clazz */,jobject serviceObj, jobject contextObj, jobject messageQueueObj) {// 由傳入的 Java 層的 MessageQueue 轉(zhuǎn)換獲取 native 層的 MessageQueuesp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);if (messageQueue == nullptr) {jniThrowRuntimeException(env, "MessageQueue is not initialized.");return 0;}// 新建 NativeInputManager 對象,此對象將是 native 層組件與 Java 層 IMS 進(jìn)行通信的橋梁NativeInputManager * im = new NativeInputManager(contextObj, serviceObj,messageQueue -> getLooper());im -> incStrong(0);// 返回指向 NativeInputManager 對象的指針給 Java 層的 IMS,IMS 將其保存在 mPtr 成員變量中return reinterpret_cast < jlong > (im);
}

通過 JNI 注冊的映射關(guān)系,找到 native 層的 nativeInit() 函數(shù),首先由傳入的 Java 層的 MessageQueue 轉(zhuǎn)換獲取 native 層的 NativeMessageQueue 對象,然后新建 NativeInputManager 對象。

1.4、NativeInputManager

xref: /frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp

class NativeInputManager : public virtual RefBase,public virtual InputReaderPolicyInterface,public virtual InputDispatcherPolicyInterface,public virtual PointerControllerPolicyInterface {public:NativeInputManager(jobject contextObj, jobject serviceObj, const sp<Looper>& looper);inline sp<InputManagerInterface> getInputManager() const { return mInputManager; }private:sp<InputManagerInterface> mInputManager;  jobject mServiceObj; // IMS 對象sp<Looper> mLooper; // Looper 對象Mutex mLock;struct Locked {......例如,mLocked.showTouches 是// 如果為 True,則啟用指針手勢bool pointerGesturesEnabled;// 由開發(fā)者選項中 "Show taps" 決定的,其功能是在屏幕上顯示一個觸摸點bool showTouches;......} mLocked GUARDED_BY(mLock);std::atomic<bool> mInteractive;......static inline JNIEnv* jniEnv() {return AndroidRuntime::getJNIEnv();}
};NativeInputManager::NativeInputManager(jobject contextObj,jobject serviceObj, const sp<Looper>&looper) :mLooper(looper),mInteractive(true) {JNIEnv * env = jniEnv();// 保存 Java 層的 InputManagerService 對象mServiceObj = env -> NewGlobalRef(serviceObj);{	// mLocked 的類型是 struct Locked,這里初始化了一些參數(shù),這些參數(shù)會被 Java 層改變AutoMutex _l (mLock);mLocked.systemUiLightsOut = false;mLocked.pointerSpeed = 0;mLocked.pointerGesturesEnabled = true;mLocked.showTouches = false;mLocked.pointerCapture = false;mLocked.pointerDisplayId = ADISPLAY_ID_DEFAULT;}mInteractive = true;// 創(chuàng)建了 native 層的 InputManager,它才是底層輸入系統(tǒng)的服務(wù)InputManager * im = new InputManager(this, this);mInputManager = im;// 將 InputManager 注冊到 ServiceManager 中defaultServiceManager()->addService(String16("inputflinger"), im);
}
  1. NativeInputManager 的構(gòu)造函數(shù)中,創(chuàng)建一個全局引用,并通過 mServiceObj 指向 Java 層的 IMS 對象,便于后續(xù)可以通過 mServiceObj 調(diào)用 JavaIMS 對象的方法。
  2. 初始化參數(shù),這里要注意一個結(jié)構(gòu)體變量 mLocked,它的一些參數(shù)都是由 Java 層控制的。
  3. 然后將自己作為參數(shù)來新建 InputManager 對象,并將 InputManager 注冊到 ServiceManager 中,InputManager 才是 native 層輸入系統(tǒng)的服務(wù)。

注意:由 NativeInputManager 類的聲明可以看到,其實現(xiàn)了 InputReaderPolicyInterfaceInputDispatcherPolicyInterface 兩個接口。

1.5、InputManager

xref: /frameworks/native/services/inputflinger/InputManager.h

class InputManager : public InputManagerInterface, public BnInputFlinger {
protected:~InputManager() override;public:InputManager(const sp<InputReaderPolicyInterface>& readerPolicy,const sp<InputDispatcherPolicyInterface>& dispatcherPolicy);......
private:sp<InputReaderInterface> mReader;sp<InputClassifierInterface> mClassifier;sp<InputDispatcherInterface> mDispatcher;
};

xref: /frameworks/native/services/inputflinger/InputManager.cpp

InputManager::InputManager(const sp<InputReaderPolicyInterface>& readerPolicy,const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {// 創(chuàng)建 InputDispatcher 對象,使用 InputDispatcherPolicyInterface 接口,用于對事件進(jìn)行分發(fā)mDispatcher = createInputDispatcher(dispatcherPolicy);// 創(chuàng)建 InputClassifier 對象,使用 InputListenerInterface,用于對事件分類mClassifier = new InputClassifier(mDispatcher);// 創(chuàng)建 InputReader 對象,使用 InputReaderPolicyInterface 和 InputListenerInterface// 其通過 EventHub 監(jiān)聽"/dev/input"事件,獲取事件,然后把事件加工后,發(fā)送給 InputClassfiermReader = createInputReader(readerPolicy, mClassifier);
}

InputManager 內(nèi)部創(chuàng)建了三個子模塊:InputReaderInputClassifier、InputDispatcher,其作用如下:

  1. InputReader:負(fù)責(zé)從 EventHub 中獲取事件,然后把事件加工后,發(fā)送給 InputClassfier
  2. InputClassifer:負(fù)責(zé)把事件發(fā)送給 InputDispatcher,但是它會對觸摸事件進(jìn)行一個分類工作。
  3. InputDispatcher:對進(jìn)行事件分發(fā)。

此外,在上一小節(jié)的分析中,我們知道在構(gòu)建 InputManager 實例對象時使用了兩個 this 參數(shù),而 InputManager 構(gòu)造函數(shù)需要的兩個接口參數(shù)正是由 NativeInputManager 實現(xiàn)的,而具體使用這兩個接口的不是 InputManager 自身,而是它內(nèi)部的子模塊 InputDispatcherInputReader。

InputDispatcherInputReader 在構(gòu)建時都傳遞了 NativeInputManager 對象參數(shù),并賦值到各自的 mPolicy 變量,后續(xù)可直接通過 mPolicy 調(diào)用 JavaIMS 對象方法,因此 InputManagerJava 層通信的能力是由子模塊 InputDispatcherInputReader 實現(xiàn)的。

接下來首先來看看 InputDispatcher 是如何通過 createInputDispatcher() 函數(shù)創(chuàng)建的,詳見接下來兩節(jié)的源碼分析。

1.6、InputDispatcher

xref: /frameworks/native/services/inputflinger/dispatcher/InputDispatcherFactory.cpp

sp<InputDispatcherInterface> createInputDispatcher(const sp<InputDispatcherPolicyInterface>& policy) {return new android::inputdispatcher::InputDispatcher(policy);
}

方法很簡單,內(nèi)部直接新建 InputDispatcher 對象,再繼續(xù)查看 InputDispatcher 的構(gòu)造函數(shù):

xref: /frameworks/native/services/inputflinger/dispatcher/InputDispatcher.h

class InputDispatcher : public android::InputDispatcherInterface {
protected:~InputDispatcher() override;public:explicit InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy);
......
private:std::unique_ptr<InputThread> mThread;sp<InputDispatcherPolicyInterface> mPolicy;sp<Looper> mLooper;sp<InputReporterInterface> mReporter;
};

xref: /frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp

InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy): mPolicy(policy),mPendingEvent(nullptr),mLastDropReason(DropReason::NOT_DROPPED),mIdGenerator(IdGenerator::Source::INPUT_DISPATCHER),mAppSwitchSawKeyDown(false),mAppSwitchDueTime(LONG_LONG_MAX),mNextUnblockedEvent(nullptr),mDispatchEnabled(false),mDispatchFrozen(false),mInputFilterEnabled(false),// mInTouchMode will be initialized by the WindowManager to the default device config.// To avoid leaking stack in case that call never comes, and for tests,// initialize it here anyways.mInTouchMode(true),mMaximumObscuringOpacityForTouch(1.0f),mFocusedDisplayId(ADISPLAY_ID_DEFAULT),mFocusedWindowRequestedPointerCapture(false),mWindowTokenWithPointerCapture(nullptr),mLatencyAggregator(),mLatencyTracker(&mLatencyAggregator),mCompatService(getCompatService()) {mLooper = new Looper(false); // 新建自己的 Looper 對象mReporter = createInputReporter(); // 新建 InputReporter 對象mKeyRepeatState.lastKeyEntry = nullptr;policy->getDispatcherConfiguration(&mConfig);
}

在調(diào)用 InputDispatcher 的構(gòu)造函數(shù)構(gòu)建實例對象的同時將入?yún)?policy 賦值給 mPolicy 進(jìn)行保存 (這里入?yún)?policy 即是 NativeInputManager 對象)。其次新建自己的 Looper 對象,然后使用類似創(chuàng)建 InputDispatcher 的 createInputReporter() 函數(shù)新建 InputReporter 對象,代碼比較簡單,不再深入追蹤,感興趣的可自行查看。

1.7、InputReader

接著來看看 InputReader 是如何通過 createInputReader() 函數(shù)創(chuàng)建的,一起跟著源碼來學(xué)習(xí)。
xref: /frameworks/native/services/inputflinger/reader/InputReaderFactory.cpp

sp<InputReaderInterface> createInputReader(const sp<InputReaderPolicyInterface>& policy,const sp<InputListenerInterface>& listener) {// 創(chuàng)建 EventHub 對象傳入到 InputReader 的構(gòu)造函數(shù)中來新建 InputReader 對象return new InputReader(std::make_unique<EventHub>(), policy, listener);
}

該方法里面,在新建 InputReader 對象時,結(jié)合 InputReader 類的構(gòu)造函數(shù)可知,第一個參數(shù)是 EventHub 的實例對象,那么 EventHub 對象是怎么創(chuàng)建的呢?

這里需要知道一些 C++ 有關(guān)的知識,std::make_unique 的語法如下:

template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args);

std::make_unique:是 C++11 標(biāo)準(zhǔn)引入的一個模版函數(shù),用于動態(tài)分配指定類型的內(nèi)存,并返回一個指向分配內(nèi)存的唯一指針 (即 std::unique_ptr)。語法中,T 是指定的類型,Args 是可變長模板參數(shù)包,用于傳遞給指定類型的構(gòu)造函數(shù)的參數(shù)。在調(diào)用 std::make_unique 時,通過 Args 包傳入構(gòu)造函數(shù)的參數(shù)會被轉(zhuǎn)發(fā)給類型 T 的構(gòu)造函數(shù),以生成相應(yīng)的對象實例。該函數(shù)返回的指針是一個 std::unique_ptr 類型,表示一個擁有指向動態(tài)內(nèi)存的所有權(quán)的對象。

xref: /frameworks/native/services/inputflinger/reader/include/InputReader.h

class InputReader : public InputReaderInterface {
public:InputReader(std::shared_ptr<EventHubInterface> eventHub,const sp<InputReaderPolicyInterface>& policy,const sp<InputListenerInterface>& listener);virtual ~InputReader();
protected:// 在循環(huán)過程的每次迭代中,InputReader 讀取并處理一條來自 EventHub 的傳入消息void loopOnce();private:std::unique_ptr<InputThread> mThread;std::shared_ptr<EventHubInterface> mEventHub;sp<InputReaderPolicyInterface> mPolicy;sp<QueuedInputListener> mQueuedListener;
};

xref: /frameworks/native/services/inputflinger/reader/InputReader.cpp

InputReader::InputReader(std::shared_ptr<EventHubInterface> eventHub,const sp<InputReaderPolicyInterface>& policy,const sp<InputListenerInterface>& listener): mContext(this),mEventHub(eventHub),mPolicy(policy),mGlobalMetaState(0),mLedMetaState(AMETA_NUM_LOCK_ON),mGeneration(1),mNextInputDeviceId(END_RESERVED_ID),mDisableVirtualKeysTimeout(LLONG_MIN),mNextTimeout(LLONG_MAX),mConfigurationChangesToRefresh(0) {mQueuedListener = new QueuedInputListener(listener);{ // acquire lockstd::scoped_lock _l(mLock);refreshConfigurationLocked(0);updateGlobalMetaStateLocked();} // release lock
}

與構(gòu)建 InputDispatcher 對象類似,在調(diào)用 InputReader 的構(gòu)造函數(shù)構(gòu)建實例對象的同時將入?yún)?policy 賦值給 mPolicyeventHub 對象賦值給 mEventHub 保存,同時新建 QueuedInputListener 監(jiān)聽對象。

通過前一小節(jié)的分析可知,EventHub 實例對象是通過調(diào)用 std::make_unique() 函數(shù)來創(chuàng)建的,那接下來一起去看看 EventHub 具體都做了些什么?

1.8、EventHub

xref: /frameworks/native/services/inputflinger/reader/include/EventHub.h

class EventHub : public EventHubInterface {
public:EventHub();
private:int32_t mNextDeviceId;BitSet32 mControllerNumbers;std::unordered_map<int32_t, std::unique_ptr<Device>> mDevices;std::vector<std::unique_ptr<Device>> mOpeningDevices;std::vector<std::unique_ptr<Device>> mClosingDevices;int mEpollFd;int mINotifyFd;int mWakeReadPipeFd;int mWakeWritePipeFd;int mInputWd;int mVideoWd;// 一次最多可處理的信號fd的數(shù)量static const int EPOLL_MAX_EVENTS = 16;// 掛起的 epoll 事件數(shù)組和下一個要處理的事件的索引struct epoll_event mPendingEventItems[EPOLL_MAX_EVENTS];size_t mPendingEventCount;size_t mPendingEventIndex;bool mPendingINotify;
};

xref: /frameworks/native/services/inputflinger/reader/EventHub.cpp

EventHub::EventHub(void): mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD),mNextDeviceId(1),mControllerNumbers(),mOpeningDevices(nullptr),mClosingDevices(nullptr),mNeedToSendFinishedDeviceScan(false),mNeedToReopenDevices(false),mNeedToScanDevices(true),mPendingEventCount(0),mPendingEventIndex(0),mPendingINotify(false) {ensureProcessCanBlockSuspend();// 創(chuàng)建 Epoll 對象的描述符,監(jiān)聽設(shè)備節(jié)點是否有數(shù)據(jù)可讀(有無事件發(fā)生)mEpollFd = epoll_create1(EPOLL_CLOEXEC);LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));// 創(chuàng)建 INotify 對象,用于監(jiān)聽設(shè)備節(jié)點的路徑 /dev/input,是否有變化,如有設(shè)備增刪則對應(yīng)的設(shè)備節(jié)點的文件也會增刪mINotifyFd = inotify_init();// 添加 watch 監(jiān)聽存儲設(shè)備節(jié)點的路徑 DEVICE_PATH 的創(chuàng)建與刪除,當(dāng)有設(shè)備節(jié)點發(fā)生變化時,通過 INotify 對象可以讀取事件的詳細(xì)信息mInputWd = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);LOG_ALWAYS_FATAL_IF(mInputWd < 0, "Could not register INotify for %s: %s", DEVICE_PATH,strerror(errno));if (isV4lScanningEnabled()) {mVideoWd = inotify_add_watch(mINotifyFd, VIDEO_DEVICE_PATH, IN_DELETE | IN_CREATE);LOG_ALWAYS_FATAL_IF(mVideoWd < 0, "Could not register INotify for %s: %s",VIDEO_DEVICE_PATH, strerror(errno));} else {mVideoWd = -1;ALOGI("Video device scanning disabled");}// 構(gòu)建 epoll_event 結(jié)構(gòu)體,并為每一個需要監(jiān)控的描述符填充該結(jié)構(gòu)體,以描述監(jiān)控事件struct epoll_event eventItem = {};eventItem.events = EPOLLIN | EPOLLWAKEUP; // 事件掩碼,指明需要監(jiān)聽的事件類型,可讀eventItem.data.fd = mINotifyFd; // 數(shù)據(jù)字段,設(shè)置需要監(jiān)聽的描述符,這里是 mINotifyFd,即監(jiān)聽設(shè)備節(jié)點的路徑// 調(diào)用 epoll_ctl() 函數(shù)將 INotify 對象注冊到 Epoll 中,監(jiān)聽其文件描述符對應(yīng)的文件夾下是否有設(shè)備節(jié)點的增刪信息// 第一個參數(shù)即前面創(chuàng)建的 Epoll 對象的描述符,第二個參數(shù)表示具體操作,這里 ADD 表示增加注冊事件// 第三個參數(shù)表示需要監(jiān)聽的描述符,第四個參數(shù)是描述監(jiān)聽事件的詳細(xì)信息的 epoll_event 結(jié)構(gòu)體int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, & eventItem);LOG_ALWAYS_FATAL_IF(result != 0, "Could not add INotify to epoll instance.  errno=%d", errno);// 創(chuàng)建匿名管道 wakeFds,并將讀端交給 Epoll,寫端交給 InputReader,用于喚醒 Epoll,避免其阻塞在 epoll_wait()int wakeFds[ 2];result = pipe(wakeFds);LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe.  errno=%d", errno);// mWakeReaderFD 和 mWakeWriterFD 對應(yīng)管道的兩端mWakeReadPipeFd = wakeFds[0];mWakeWritePipeFd = wakeFds[1];result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking.  errno=%d", errno);result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking.  errno=%d", errno);eventItem.data.fd = mWakeReadPipeFd;// epoll_ctl() 函數(shù)可重復(fù)調(diào)用,將多個文件描述符的多種事件監(jiān)聽注冊到 Epoll 對象中// 將匿名管道的讀取端的描述符也注冊到 Epoll 中,用于監(jiān)聽讀取端的可讀事件,當(dāng)寫入端有數(shù)據(jù)寫入時// 管道的讀取端就有數(shù)據(jù)可讀,使得 epoll_wait() 得以返回,從而達(dá)到喚醒 InputReader 線程的目的,避免其一直阻塞result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance.  errno=%d", errno);
}

EventHub 的構(gòu)造函數(shù)主要工作有:

  1. 新建并初始化 EpollINotify 對象等;
  2. 調(diào)用 inotify_add_watch 函數(shù),監(jiān)聽 “/dev/input” 目錄下的設(shè)備節(jié)點創(chuàng)建與刪除操作,然后通過 read() 函數(shù)讀取事件;
  3. INotify 添加到 Epoll 中,作為一個監(jiān)控對象;
  4. 創(chuàng)建管道,將管道讀取端的可讀事件添加到 Epoll 中,使 epoll_wait() 函數(shù)返回,喚醒 InputReader 線程來處理事件。

至此,IMSJava 層和 native 層的實例對象都已創(chuàng)建完成,并且在這個過程中,輸入系統(tǒng)的重要參與者也均創(chuàng)建完成。

1.9、小結(jié)

IMS的結(jié)構(gòu)體系
Java 層的 IMS 的主要工作是為 ReaderPolicyDispatcherPolicy 提供實現(xiàn),以及與 Android 其他系統(tǒng)服務(wù)進(jìn)行協(xié)作,其中最主要的協(xié)作者是 WMS

NativeInputManager 位于 IMSJNI 層,負(fù)責(zé) Native 層的組件與 Java 層的 IMS 之間的相互通信。同時為 InputReaderInputDispatcher 提供了策略請求的接口。策略請求被他轉(zhuǎn)發(fā)給 Java 層的 IMS,由 IMS 進(jìn)行最終的決策定奪。

InputManagerInputReaderInputDispatcher 的運(yùn)行容器,在啟動 InputReaderInputDispatcher 時,分別新建自己的運(yùn)行線程 InputThreadImpl 并啟動運(yùn)行。

2、IMS 啟動

在上一節(jié)的 SystemServer # startOtherServices() 方法中,在構(gòu)建完 IMS 后,IMS 系統(tǒng)中的各個重要參與者仍處于待命狀態(tài),需調(diào)用 IMS # start() 函數(shù)來啟動 IMS,繼續(xù)追蹤源碼分析:

2.1、IMS # start()

xref: /frameworks/base/services/core/java/com/android/server/input/InputManagerService.java

public class InputManagerService extends IInputManager.Stubimplements Watchdog.Monitor {......private static native void nativeStart(long ptr);......public void start() {Slog.i(TAG, "Starting input manager");// 啟動 native 層的 IMSnativeStart(mPtr);Watchdog.getInstance().addMonitor(this);// 監(jiān)聽Settings.System.POINTER_SPEED,這個表示手指的速度registerPointerSpeedSettingObserver();// 監(jiān)聽Settings.System.SHOW_TOUCHES,這個表示是否在屏幕上顯示觸摸坐標(biāo)registerShowTouchesSettingObserver();registerAccessibilityLargePointerSettingObserver();registerLongPressTimeoutObserver();registerMaximumObscuringOpacityForTouchSettingObserver();registerBlockUntrustedTouchesModeSettingObserver();// 監(jiān)聽用戶切換mContext.registerReceiver(new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {updatePointerSpeedFromSettings();updateShowTouchesFromSettings();updateAccessibilityLargePointerFromSettings();updateDeepPressStatusFromSettings("user switched");}}, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler);// 從數(shù)據(jù)庫獲取值,并傳遞給 native 層updatePointerSpeedFromSettings();updateShowTouchesFromSettings();updateAccessibilityLargePointerFromSettings();updateDeepPressStatusFromSettings("just booted");updateMaximumObscuringOpacityForTouchFromSettings();updateBlockUntrustedTouchesModeFromSettings();}......
}

IMS 的啟動過程如下:

  1. 啟動 native 層輸入系統(tǒng),其實就是啟動剛剛說到的 InputReaderInputDispatcher
  2. 注冊監(jiān)聽廣播,因為這些廣播與輸入系統(tǒng)的配置有關(guān),當(dāng)接收到這些廣播,會更新配置到 native 層。
  3. 直接讀取配置,更新到 native 層輸入系統(tǒng)。

2.2、NativeInputManager # nativeStart()

xref: /frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp

static const JNINativeMethod gInputManagerMethods[] = { // JNI 注冊的映射關(guān)系/* name, signature, funcPtr */{"nativeStart", "(J)V", (void*)nativeStart},
};static void nativeStart(JNIEnv* env, jclass /* clazz */, jlong ptr) {// 將 Java 層保存的 NativeInputManager 對象的指針轉(zhuǎn)換成 NativeInputManager 對象NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);// 查看 1.4 NativeInputManager 的源碼可知,獲取到 InputManager 對象,然后調(diào)用其 start() 函數(shù)status_t result = im->getInputManager()->start();if (result) {jniThrowRuntimeException(env, "Input manager could not be started.");}
}

首先將 Java 層保存 NativeInputManager 對象的指針 mPtr 轉(zhuǎn)換成 NativeInputManager 對象,然后調(diào)用 NativeInputManager # getInputManager() 函數(shù)獲取到 InputManager 對象,接著調(diào)用 InputManager # start() 函數(shù)繼續(xù)啟動流程。

reinterpret_cast 的功能可以分為兩類:1、指針和整數(shù)之間的轉(zhuǎn)換;2、不同類型的指針/成員指針/引用之間的轉(zhuǎn)換。

2.3、InputManager # start()

xref: /frameworks/native/services/inputflinger/InputManager.cpp

status_t InputManager::start() {// 啟動承載 InputDispatcher 的線程status_t result = mDispatcher->start();if (result) {ALOGE("Could not start InputDispatcher thread due to error %d.", result);return result;}// 啟動承載 InputReader 的線程result = mReader->start();if (result) {ALOGE("Could not start InputReader due to error %d.", result);mDispatcher->stop();return result;}return OK;
}

InputManager 的啟動過程很簡單,調(diào)用 InputDispatcherInputReader 的 start() 函數(shù),啟動承載它們運(yùn)行的線程,來看一下它們是如何啟動線程的。

2.4、InputDispatcher # start()

xref: /frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp

status_t InputDispatcher::start() {if (mThread) {return ALREADY_EXISTS;}mThread = std::make_unique<InputThread>("InputDispatcher", [this]() { dispatchOnce(); }, [this]() { mLooper->wake(); });return OK;
}

在方法內(nèi),首先判斷 mThread 是否已存在,存在則直接返回,不存在則通過 std::make_unique 函數(shù)來構(gòu)建 InputThread 的實例對象,但沒有看到啟動線程的代碼邏輯,帶著這個疑問,我們再去看看 InputReader 的 start() 方法。

2.5、InputReader # start()

xref: /frameworks/native/services/inputflinger/reader/InputReader.cpp

status_t InputReader::start() {if (mThread) {return ALREADY_EXISTS;}mThread = std::make_unique<InputThread>("InputReader", [this]() { loopOnce(); }, [this]() { mEventHub->wake(); });return OK;
}

方法的代碼邏輯跟 InputDispatcher 的差不多,也是沒有看到啟動線程的代碼邏輯,既然都是新建 InputThread 對象,那就具體來看一下 InputThread 類。

2.6、InputThread

xref: /frameworks/native/services/inputflinger/include/InputThread.h

class InputThread {
public:explicit InputThread(std::string name, std::function<void()> loop,std::function<void()> wake = nullptr);virtual ~InputThread();bool isCallingThread();
private:std::string mName; // 線程名std::function<void()> mThreadWake;sp<Thread> mThread; // 承載 InputDispatcher\InputReader 運(yùn)行的線程
};

xref: /frameworks/native/services/inputflinger/InputThread.cpp

class InputThreadImpl : public Thread {
public: // explicit 關(guān)鍵字的作用就是防止類構(gòu)造函數(shù)的隱式自動轉(zhuǎn)換,且只對有一個參數(shù)的類構(gòu)造函數(shù)有效explicit InputThreadImpl(std::function<void()> loop): Thread(/* canCallJava */ true), mThreadLoop(loop) {}~InputThreadImpl() {}private:std::function<void()> mThreadLoop; // 存儲一個可調(diào)用對象,這里指的是 lambda 表達(dá)式bool threadLoop() override {mThreadLoop();return true;}
};InputThread::InputThread(std::string name, std::function<void()> loop, std::function<void()> wake): mName(name), mThreadWake(wake) {// 使用封裝的可調(diào)用對象 loop 新建 InputThreadImpl 對象mThread = new InputThreadImpl(loop);// 啟動 InputThreadImpl 線程mThread->run(mName.c_str(), ANDROID_PRIORITY_URGENT_DISPLAY);
}

std::function:是一個通用的函數(shù)封裝類,它可以存儲、復(fù)制和調(diào)用任意可調(diào)用對象,包括函數(shù)指針、函數(shù)對象、成員函數(shù)指針和lambda 表達(dá)式等。通過使用 std::function 作為函數(shù)參數(shù),我們可以實現(xiàn)更加靈活的函數(shù)調(diào)用方式,提高代碼的可讀性和可維護(hù)性。

由代碼可知 InputThread 類其本身不是一個線程,其內(nèi)部是 InputThreadImpl 類來實現(xiàn)線程的具體功能。使用封裝的可調(diào)用對象 loop 構(gòu)建 InputThreadImpl 對象,然后調(diào)用其 run() 函數(shù)來啟動線程。

InputThreadImpl 類繼承自 Thread 類,而 C++Thread 類提供了一個名為 threadLoop() 的純虛函數(shù),當(dāng)線程開始運(yùn)行后,將會在內(nèi)建的線程循環(huán)中不斷地調(diào)用 threadLoop() 函數(shù),直到此函數(shù)返回 false,則退出線程循環(huán)結(jié)束線程。但從 InputThreadImpl 類的定義可以看出,threadLoop() 函數(shù)會一直保持循環(huán)(返回值始終為 true),并且每一次循環(huán),會調(diào)用一次 mThreadLoop() 函數(shù),而 mThreadLoop() 函數(shù)是由 InputDispacherInputReader 在啟動時封裝好傳入的可調(diào)用對象。

到這里,終于搞明白了,在 InputDispatcher 啟動時,會創(chuàng)建一個線程,然后循環(huán)調(diào)用 dispatchOnce() 函數(shù),同樣 InputReader 啟動時,也會創(chuàng)建一個線程,然后循環(huán)調(diào)用 loopOnce() 函數(shù)。

3、IMS 系統(tǒng)就緒

上面兩節(jié)已完成 IMS 及其重要參與者的構(gòu)建,并啟動了 IMS 系統(tǒng),接下來就是通知系統(tǒng) IMS 系統(tǒng)已完成啟動并準(zhǔn)備就緒,具體看一下源碼
xref: /frameworks/base/services/core/java/com/android/server/input/InputManagerService.java

public class InputManagerService extends IInputManager.Stubimplements Watchdog.Monitor {......// IMS 系統(tǒng)內(nèi)部的 Handler,用來處理鍵盤等輸入設(shè)備有關(guān)的消息private final InputManagerHandler mHandler;private WiredAccessoryCallbacks mWiredAccessoryCallbacks; // 有線連接的設(shè)備回調(diào)private boolean mSystemReady; // 標(biāo)志系統(tǒng)是否準(zhǔn)備完畢private NotificationManager mNotificationManager; // 通知管理......public void systemRunning() {......mNotificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);synchronized (mLidSwitchLock) {mSystemReady = true;......int switchState = getSwitchState(-1 /* deviceId */, InputDevice.SOURCE_ANY, SW_LID);for (int i = 0; i < mLidSwitchCallbacks.size(); i++) {LidSwitchCallback callback = mLidSwitchCallbacks.get(i);callback.notifyLidSwitchChanged(0 /* whenNanos */, switchState == KEY_STATE_UP);}}// 監(jiān)聽廣播,通知 native 層加載鍵盤布局IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);filter.addAction(Intent.ACTION_PACKAGE_REMOVED);filter.addAction(Intent.ACTION_PACKAGE_CHANGED);filter.addAction(Intent.ACTION_PACKAGE_REPLACED);filter.addDataScheme("package");mContext.registerReceiver(new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {// 其內(nèi)部繼續(xù)調(diào)用 reloadKeyboardLayouts() 函數(shù)updateKeyboardLayouts();}}, filter, null, mHandler);// 監(jiān)聽廣播,通知 native 層加載設(shè)備別名filter = new IntentFilter(BluetoothDevice.ACTION_ALIAS_CHANGED);mContext.registerReceiver(new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {reloadDeviceAliases();}}, filter, null, mHandler);// 通過 InputManagerHandler 發(fā)送消息來通知 native 層加載鍵盤布局和加載設(shè)備別名mHandler.sendEmptyMessage(MSG_RELOAD_DEVICE_ALIASES);mHandler.sendEmptyMessage(MSG_UPDATE_KEYBOARD_LAYOUTS);// 如果與系統(tǒng)連接的有線設(shè)備的回調(diào)不為空,則須回調(diào)通知其輸入系統(tǒng) IMS 已準(zhǔn)備完畢if (mWiredAccessoryCallbacks != null) { mWiredAccessoryCallbacks.systemReady();}}private void reloadKeyboardLayouts() {if (DEBUG) {Slog.d(TAG, "Reloading keyboard layouts.");}// 調(diào)用 native 層函數(shù)來加載鍵盤布局nativeReloadKeyboardLayouts(mPtr);}private void reloadDeviceAliases() {if (DEBUG) {Slog.d(TAG, "Reloading device names.");}// 調(diào)用 native 層函數(shù)來加載設(shè)備別名nativeReloadDeviceAliases(mPtr);}......
}

注冊監(jiān)聽廣播,通知 native 層加載鍵盤布局、設(shè)備別名,最后通過 JNI 調(diào)用 native 層的函數(shù)來加載鍵盤布局、設(shè)備別名。此外,如果與系統(tǒng)連接的有線設(shè)備注冊的回調(diào)不為空,則需回調(diào)通知其輸入系統(tǒng) IMS 已準(zhǔn)備就緒。


三、總結(jié)

1、IMS 啟動時序圖

IMS 啟動時序圖

2、IMS 成員關(guān)系圖

IMS 成員關(guān)系圖
最后結(jié)合 IMS 的啟動時序圖和成員關(guān)系圖可以更深刻的理解 IMS 系統(tǒng)的構(gòu)成與啟動過程,下一篇文章來繼續(xù)探索輸入系統(tǒng)的重要組成成員,等待后續(xù)吧!

參考

  1. 深入理解Android:卷III
http://m.aloenet.com.cn/news/40746.html

相關(guān)文章:

  • php建設(shè)網(wǎng)站工具百度搜索推廣流程
  • 私人訂制寧波受歡迎全網(wǎng)seo優(yōu)化
  • 網(wǎng)站建設(shè)的前景長沙優(yōu)化網(wǎng)站廠家
  • 建設(shè)網(wǎng)站需要哪些流程百度seo費(fèi)用
  • 制造業(yè)人才網(wǎng)正規(guī)seo排名多少錢
  • 公眾平臺注冊網(wǎng)站怎么優(yōu)化搜索
  • 網(wǎng)站icp備案 年檢2345網(wǎng)址導(dǎo)航中國最好
  • 如何開發(fā)一個app建設(shè)一個網(wǎng)站關(guān)鍵詞搜索熱度查詢
  • 廣西搜索推廣東莞網(wǎng)絡(luò)優(yōu)化排名
  • 網(wǎng)站IcP在哪查今日剛剛發(fā)生的國際新聞
  • 哈爾濱網(wǎng)站建設(shè)1元錢2021年經(jīng)典營銷案例
  • 做企業(yè)網(wǎng)站的第一步需要啥紹興seo優(yōu)化
  • 四川seo整站優(yōu)化吧谷歌瀏覽器官方app下載
  • 國外做美食視頻網(wǎng)站谷歌海外推廣怎么做
  • 淄博網(wǎng)站建設(shè)推廣優(yōu)化自媒體賬號申請
  • 網(wǎng)站制作系統(tǒng)長沙官網(wǎng)seo技術(shù)廠家
  • 黃石規(guī)劃建設(shè)局網(wǎng)站一鍵優(yōu)化清理手機(jī)
  • 做一個網(wǎng)上商城網(wǎng)站建設(shè)費(fèi)用多少錢市場調(diào)研分析報告范文
  • 網(wǎng)站開發(fā)素材包網(wǎng)站的宣傳與推廣
  • 網(wǎng)站建設(shè)后臺管理怎么進(jìn)入烏魯木齊seo
  • 商城網(wǎng)站模版代碼重慶seo整站優(yōu)化方案范文
  • 開發(fā)動態(tài)網(wǎng)站有哪些技術(shù)百度人工客服電話24小時
  • 網(wǎng)站開發(fā)全包免費(fèi)手機(jī)優(yōu)化大師下載安裝
  • 外貿(mào)品牌網(wǎng)站設(shè)計公司鼓樓網(wǎng)頁seo搜索引擎優(yōu)化
  • 網(wǎng)站開發(fā)需求列表2021最火營銷方案
  • 怎么可以自己做網(wǎng)站被百度收到網(wǎng)站seo公司哪家好
  • 石家莊網(wǎng)站建設(shè)價格低廣州今日新聞頭條新聞
  • 表白網(wǎng)站怎樣做有創(chuàng)意品牌推廣活動策劃方案
  • 公路建設(shè)管理辦公室網(wǎng)站中國最好的營銷策劃公司
  • 石家莊做網(wǎng)站100個商業(yè)經(jīng)典案例