IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> android touch事件传递流程分析 -> 正文阅读

[移动开发]android touch事件传递流程分析

由于项目需要,需要了解下android touch数据的传递流程,看了下代码后把过程记录下来,方便后面查阅。

本文章基于Android11开源源码,所有代码均可以在aosp官方提供的地址去查阅下载,Android11具体流程可能和其他Android版本有点不一致,

如果文中有不对的地方欢迎同学们指出一起讨论交流~

从4条线(4个方向)来分析android 的touch 数据传送机制,沿途主要关注touch数据从kernel出来到app的路线,把这条路打通,不关注细节,以及touch数据是如何决定给到哪个activity(窗口,view)的,

目录

ViewRootImpl 注册 InputChannel

InputFlinger 从kernel读取touch数据

InputFlinger 把touch数据发送给View

ViewRootImpl接收touch数据并给到app


一句话概括,app往wms那边添加窗口的同时,wms创建一对socket pair,用InputChannel封装,一个给app,一个给InputFlinger,之后InputFlinger通过这个unix socket fd把touch数据发给app。

拿button举例子,整体流程大概是,app起来后,会在ViewRootImpl 那边往wms添加window的时候,同时创建一个InputChannel 也一起带过去,wms会作为中介把这个InputChannel 传给InputFlinger, 点击button后,inputflinger读到从kernel出来的数据后,会通过这个InputChannel把数据给到ViewRootImpl, 接着通过ViewPostImeInputStage给到View,再创建一个PerformClick通过View.post()去执行,回调button的onClick()接口。(怎么知道给到哪个activity哪个window哪个View就是接下来分析过程中理清的)

ViewRootImpl 注册 InputChannel

app activity 起来后,创建了PhoneWindow 后往wms 添加window时,在ViewRootImpl创建了InputChannel作为出参一起传过去,wms通过socket pair 创建了2个InputChannel,一个作为client 一个作为server,client把fd给ViewRootImpl传过来的那个InputChannel, server给 传给InputFlinger, InputFlinger 创建Connection 保存下来,之后用它来回传touch数据给app。

看下图:

看下代码:

//frameworks/base/core/java/android/view/ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
           int userId) {
       //......
      
      //创建 InputChannel 
      inputChannel = new InputChannel();
      
      //在添加窗口的时候通过binder ipc一起给到wms
      //note1
      res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
      getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
      mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
      mAttachInfo.mDisplayCutout, inputChannel,
      mTempInsets, mTempControls);
      
      //同时创建WindowInputEventReceiver用来接收touch数据(其实不只touch,
      //应该是包括按键,键盘啥的,我们目前先就只关注touch)
      //note2
      mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
        Looper.myLooper());

      //......
      //创建用于处理input数据的InputStage,
      //note19
     mSyntheticInputStage = new SyntheticInputStage();
     InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
     InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
        "aq:native-post-ime:" + counterSuffix);
     InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
     InputStage imeStage = new ImeInputStage(earlyPostImeStage,
        "aq:ime:" + counterSuffix);
     InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
     InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
        "aq:native-pre-ime:" + counterSuffix);
     mFirstInputStage = nativePreImeStage;
     mFirstPostImeInputStage = earlyPostImeStage;     
      
}

//frameworks/base/core/java/android/view/IWindowSession.aidl
int addToDisplayAsUser(IWindow window, int seq, in WindowManager.LayoutParams attrs,
            in int viewVisibility, in int layerStackId, in int userId,
            out Rect outFrame, out Rect outContentInsets, out Rect outStableInsets,
            out DisplayCutout.ParcelableWrapper displayCutout, out InputChannel outInputChannel,
            out InsetsState insetsState, out InsetsSourceControl[] activeControls);
//注意这边inputChannel是作为出参传进去的,后面在WindowState那边创建了socket pair后
//会重新给他赋值使其作为接收数据的客户端拥有可以接收touch数据的fd。
//当然这边是跨进程的然后中间是有binder做了处理简化了流程。

跟下mWindowSession.addToDisplayAsUser(),

其中这边mWindowSession 实例为 Session对象, 是在WindowManagerGlobal 那边从wms拿到的,之后在创建ViewRootImpl的时候再传过来,可以简单看下~

//在这边赋值的
//frameworks/base/core/java/android/view/ViewRootImpl.java
public ViewRootImpl(Context context, Display display, IWindowSession session,
        boolean useSfChoreographer) {
//.......
    mWindowSession = session;
//.......       
}

//这边传过来的
public ViewRootImpl(Context context, Display display) {
        this(context, display, WindowManagerGlobal.getWindowSession(),
                false /* useSfChoreographer */);
}

//frameworks/base/core/java/android/view/WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow, int userId) {
 //......
 
     //创建ViewRootImpl
     root = new ViewRootImpl(view.getContext(), display);
 
 //.......       
 }
 
//这边通过binder ipc从wms那边去获取的,再接着看下wms实现
//frameworks/base/core/java/android/view/WindowManagerGlobal.java
public static IWindowSession getWindowSession() {
    //......
    //从wms那边拿
    sWindowSession = windowManager.openSession(......);
    return sWindowSession;
}

//wms直接new了一个Session对象返回
//frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
public IWindowSession openSession(IWindowSessionCallback callback) {
    return new Session(this, callback);
}


好了,确认mWindowSession 就是Session了,接着回到note1的 mWindowSession.addToDisplayAsUser(),继续看InputChanne的注册,其实就是通过binder ipc到了wms那边

//frameworks/base/services/core/java/com/android/server/wm/Session.java
public int addToDisplayAsUser(IWindow window, int seq, WindowManager.LayoutParams attrs,
        int viewVisibility, int displayId, int userId, Rect outFrame,
        Rect outContentInsets, Rect outStableInsets,
        DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
        InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) {
    return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
            outContentInsets, outStableInsets, outDisplayCutout, outInputChannel,
            outInsetsState, outActiveControls, userId);

直接调用wms的addWindow()接口



//frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
public int addWindow(Session session, IWindow client, int seq,
        LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,
        Rect outContentInsets, Rect outStableInsets,
        DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
        InsetsState outInsetsState, InsetsSourceControl[] outActiveControls,
        int requestUserId) {
//......        
    //给WindowState了
    win.openInputChannel(outInputChannel);
//......
}

//接着往WindowState看
//frameworks/base/services/core/java/com/android/server/wm/WindowState.java
void openInputChannel(InputChannel outInputChannel) {
//......
    //创建一对InputChannle 
    InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
    //作为server和client
    mInputChannel = inputChannels[0];
    mClientChannel = inputChannels[1];
    //server端InputFlinger那边
    //这边wms、InputManagerService、InputFlinger 共进程,直接调用后,
    //InputManagerService也是直接调用给到InputFlinger
    mWmService.mInputManager.registerInputChannel(mInputChannel);    
    
    //......
    //client 赋值给出参然后返回到ViewRootImpl作为接收touch数据客户端
    mClientChannel.transferTo(outInputChannel);    
//......
}

其实也就是在这创建了2个能进行通信的c++ InputChannel,分别作为通信的client和server,里边分别包含socket pair 创建的2个fd,做为client的InputChannel赋值给ViewRootClient 传过来的java InputChannel,做为server的InputChannel 通过进程内函数(非binder)调用给到InputFlinger那边,分别用来接收和发送touch数据。

接着看 mWmService.mInputManager.registerInputChannel(),

//frameworks/base/services/core/java/com/android/server/input/InputManagerService.java
public void registerInputChannel(InputChannel inputChannel) {
    if (inputChannel == null) {
        throw new IllegalArgumentException("inputChannel must not be null.");
    }
    nativeRegisterInputChannel(mPtr, inputChannel);
    
 }
 

//frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
static void nativeRegisterInputChannel(JNIEnv* env, jclass /* clazz */, jlong ptr,
                                       jobject inputChannelObj) {
//......
 status_t status = im->registerInputChannel(env, inputChannel);
//......
}

status_t NativeInputManager::registerInputChannel(JNIEnv* /* env */,
                                                  const sp<InputChannel>& inputChannel) {
    ATRACE_CALL();
    return mInputManager->getDispatcher()->registerInputChannel(inputChannel);
}

//到InputDispatcher那边了
//frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel) {
#if DEBUG_REGISTRATION
    ALOGD("channel '%s' ~ registerInputChannel", inputChannel->getName().c_str());
#endif

    { // acquire lock
        std::scoped_lock _l(mLock);
        //查重
        sp<Connection> existingConnection = getConnectionLocked(inputChannel->getConnectionToken());
        if (existingConnection != nullptr) {
            ALOGW("Attempted to register already registered input channel '%s'",
                  inputChannel->getName().c_str());
            return BAD_VALUE;
        }
        
        //这边根据 channel 创建connection ,后面用它来发数据给app
        //note13
        sp<Connection> connection = new Connection(inputChannel, false /*monitor*/, mIdGenerator);

        int fd = inputChannel->getFd();
        //这边注册上,其实就是把fd和connection做为一个键值对保存起来而已,
        //后面发数据的时候从这把connection取出来发数据
        mConnectionsByFd[fd] = connection;
        mInputChannelsByToken[inputChannel->getConnectionToken()] = inputChannel;
        
        //
        mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
    } // release lock

    // Wake the looper because some connections have changed.
    mLooper->wake();
    return OK;
}

好了,一路看下来,所谓的注册,就是在InputFlinger的InputDispatcher(note3)这边,把connection和对应的socket fd给保存下来了,放在hash map 里。

接下来看下InputFlinger从kernel读数据流程~

InputFlinger 从kernel读取touch数据

InputFlinger如何开机启动使用epoll 从/dev/input/eventX 读数据的流程就不说了,感兴趣的同学可以自己看下(frameworks/native/services/inputflinger),咱们就从数据从kernel读出来开始,请注意一下这边说的inputflinger是指inputflinger binder服务,目前在Android11是跑在system server,和 InputManagerService共进程(不过二者通信依然用binder),是由InputManagerService用jni创建了InputManager对象后注册为InputFlinger service,所以这边(Android11)InputFlinger实际的实现其实是c++ InputManager 类,这点可能与其他Android版本不同。

数据是在InputReader那边起了一个线程去读的,唉,强迫症让我感觉不从流程源头是从哪来的开始讲有点别扭,不然还是从源头讲下这个读数据线程怎么起来的吧

//system server 创建InputManagerService对象
//frameworks/base/services/java/com/android/server/SystemServer.java
private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
//.......
    inputManager = new InputManagerService(context);    
//.......
}

//构造函数里边,用jni调native init 接口
//frameworks/base/services/core/java/com/android/server/input/InputManagerService.java
public InputManagerService(Context context) {
//.......
    mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
//.......
}
//创建NativeInputManager对象
//frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
static jlong nativeInit(JNIEnv* env, jclass /* clazz */, jobject serviceObj, jobject contextObj,
                        jobject messageQueueObj) {
 //......                       
      NativeInputManager* im =
            new NativeInputManager(contextObj, serviceObj, messageQueue->getLooper());   
 //......                       
 }
 
 //创建InputMnager对象并注册成为inputflinger binder service
 NativeInputManager::NativeInputManager(jobject contextObj, jobject serviceObj,
                                       const sp<Looper>& looper)
      : mLooper(looper), mInteractive(true) {
 //......
    mInputManager = new InputManager(this, this);
    defaultServiceManager()->addService(String16("inputflinger"), mInputManager, false);
 //......     
 }
 
 
//创建完InputManagerService 后,
//system server 接着调用InputManagerService 的start接口    
//frameworks/base/services/java/com/android/server/SystemServer.java
private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
//.......
    inputManager = new InputManagerService(context); 
    
//......
    inputManager.start();
//.......
}

//又走到native那去了
//frameworks/base/services/core/java/com/android/server/input/InputManagerService.java
public void start() {
//......
    nativeStart(mPtr);
//......
}
//调用inputmanager 的start接口
//frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
static void nativeStart(JNIEnv* env, jclass /* clazz */, jlong ptr) {
    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);

    status_t result = im->getInputManager()->start();
}


//frameworks/native/services/inputflinger/InputManager.cpp
status_t InputManager::start() {
//......
    //这边去起从InputReader读数据的线程在这里咱们先忽略,
    //后面再回过头来看 note12
    mDispatcher->start();
    
    //这边去起从kernel读数据线程,接着看
    result = mReader->start();
 //......
}

//frameworks/native/services/inputflinger/reader/InputReader.cpp
//note4
status_t InputReader::start() {
    mThread = std::make_unique<InputThread>(
            "InputReader", [this]() { loopOnce(); }, [this]() { mEventHub->wake(); });
}

void InputReader::loopOnce() {
//......
    //这边把touch数据从kernel读出来(通过/dev/input/eventX)
    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
//......
}

好了,已经如何读数据的流程打通了,接下来看下如何把数据给到app~

InputFlinger 把touch数据发送给View

数据读出来后,就通过InputChannel 发给ViewRootImpl了,看下流程是怎么样的~

//frameworks/native/services/inputflinger/reader/InputReader.cpp
void InputReader::loopOnce() {
//......

    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
    
    //处理数据
    processEventsLocked(mEventBuffer, count);
//......
}

看下图:

整体流程大概是:InputDispatcher在初始化的时候就会创建一个InputDispatcher线程不断的从一个叫InputDispatcher::mInboundQueue队列读数据,没数据会睡下去,

note4那边的InputReader 线程把touch数据从kernel读出来后,经过一系列处理(处理了啥咱们先不看了,只管把流程打通),把touch事件放入InputDispatcher::mInboundQueue队列,然后唤醒InputDispatcher线程从队列把事件读出来,再选出哪个目标View(这个以后咱们要重点关注,现在先不看),接着用前面该View注册的InputChannel把事件通过unix socket 发给ViewRootImpl。

接下来从代码上,先看下InputReader从kernel读出数据的流程,再看下InputDispatcher拿数据的流程。

InputReader:

//frameworks/native/services/inputflinger/reader/InputReader.cpp
void InputReader::loopOnce() {
//......

    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
    
    //处理数据
    processEventsLocked(mEventBuffer, count);
    
//.......
    
    //note10
    mQueuedListener->flush();
//......
}
//其实上面那2处做的事情,前者是将数据处理后放入QueuedInputListener::mArgsQueue
//后者是将数据出队后继续处理,咱们先看下前者

void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
//......
   //数据继续给进去
   processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
//......
}

void InputReader::processEventsForDeviceLocked(int32_t eventHubId, const RawEvent* rawEvents,
                                               size_t count) {
 //......
    //走到InputDevice那边了 
    device->process(rawEvents, count);                                            
}

//frameworks/native/services/inputflinger/reader/InputDevice.cpp
void InputDevice::process(const RawEvent* rawEvents, size_t count) {
//......
  //调用每个mapper的process,这边我通过打callstack得到调用的是 MultiTouchInputMapper,
  //TouchInputMapper这边有好几个,具体什么情况走哪个还没有去理清
  for_each_mapper_in_subdevice(rawEvent->deviceId, [rawEvent](InputMapper& mapper) {
             mapper.process(rawEvent);
   });
//......
}

//接着看
//frameworks/native/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
void MultiTouchInputMapper::process(const RawEvent* rawEvent) {
    //调用父类的
    TouchInputMapper::process(rawEvent);

    mMultiTouchMotionAccumulator.process(rawEvent);
}

//frameworks/native/services/inputflinger/reader/mapper/TouchInputMapper.cpp
void TouchInputMapper::process(const RawEvent* rawEvent) {
//.......
  sync(rawEvent->when);
//.......
}

void TouchInputMapper::sync(nsecs_t when) {
//.......
    //顾名思义,看起来是继续处理裸数据
    processRawTouches(false /*timeout*/);
//.......
}

void TouchInputMapper::processRawTouches(bool timeout) {
//......
   //不知为何函数名字要带cook,不过走这没错啦我从callstack看出来的
   cookAndDispatch(mCurrentRawState.when);
//......
}

void TouchInputMapper::cookAndDispatch(nsecs_t when) {
//......
    //走了这里
    dispatchTouches(when, policyFlags);
//.......
}

void TouchInputMapper::dispatchTouches(nsecs_t when, uint32_t policyFlags) {
//.......
 dispatchMotion(when, policyFlags, mSource, AMOTION_EVENT_ACTION_POINTER_DOWN, 0, 0,
                metaState, buttonState, 0,
                mCurrentCookedState.cookedPointerData.pointerProperties,
                mCurrentCookedState.cookedPointerData.pointerCoords,
                mCurrentCookedState.cookedPointerData.idToIndex, dispatchedIdBits,
                downId, mOrientedXPrecision, mOrientedYPrecision, mDownTime);    
//.......    
}

void TouchInputMapper::dispatchMotion(nsecs_t when, uint32_t policyFlags, uint32_t source,
                                      int32_t action, int32_t actionButton, int32_t flags,
                                      int32_t metaState, int32_t buttonState, int32_t edgeFlags,
                                      const PointerProperties* properties,
                                      const PointerCoords* coords, const uint32_t* idToIndex,
                                      BitSet32 idBits, int32_t changedId, float xPrecision,
                                      float yPrecision, nsecs_t downTime) {
                                      
 //......                                     
  //这边取出listenner然后调用其 notifyMotion  
  //note5                       
  getListener()->notifyMotion(&args);                                    
//......                                      
}

其中,listenner这边是指InputReader::mQueuedListener,为何是它的话,感兴趣的同学可以看下下面这个代码段,不感兴趣的同学可以跳过后看下一个代码段,

//本代码端理一下为何上边getListenner拿到的是InputReader::mQueuedListener
//请注意前面note5是在MuitiTouchInputMapper对象里边的,


//frameworks/native/services/inputflinger/reader/InputReader.cpp
InputReader::InputReader(std::shared_ptr<EventHubInterface> eventHub,
                         const sp<InputReaderPolicyInterface>& policy,
                         const sp<InputListenerInterface>& listener)
      : mContext(this), //InputRead在构造的时候,顺便构造其mContext,把自己地址存进mContext
        mEventHub(eventHub),
        mPolicy(policy),
        mGlobalMetaState(0),
        mGeneration(1),
        mNextInputDeviceId(END_RESERVED_ID),
        mDisableVirtualKeysTimeout(LLONG_MIN),
        mNextTimeout(LLONG_MAX),
        mConfigurationChangesToRefresh(0) {
    mQueuedListener = new QueuedInputListener(listener);

    { // acquire lock
        AutoMutex _l(mLock);

        refreshConfigurationLocked(0);
        updateGlobalMetaStateLocked();
    } // release lock
}

//这边mContext为InputReader::ContextImpl类型,看下构造函数
InputReader::ContextImpl::ContextImpl(InputReader* reader)
      : mReader(reader), mIdGenerator(IdGenerator::Source::INPUT_READER) {}
//所以InputReader::mContex为InputReader::ContextImpl类型,
//其mReader 里边存了InputReader note6    


std::shared_ptr<InputDevice> InputReader::createDeviceLocked(
        int32_t eventHubId, const InputDeviceIdentifier& identifier) {
//......
  //这边创建InputDevice,把mContext,也就是InputReader::ContextImpl传进去

  device = std::make_shared<InputDevice>(&mContext, deviceId, bumpGenerationLocked(),
                                               identifier);     
//......        
}

//frameworks/native/services/inputflinger/reader/InputDevice.cpp
InputDevice::InputDevice(InputReaderContext* context, int32_t id, int32_t generation,
                         const InputDeviceIdentifier& identifier)
      : mContext(context),
      //InputReader::ContextImpl存在InputDevice的mContext里边  note7
        mId(id),
        mGeneration(generation),
        mControllerNumber(0),
        mIdentifier(identifier),
        mClasses(0),
        mSources(0),
        mIsExternal(false),
        mHasMic(false),
        mDropUntilNextSync(false) {}
        
//而InputDevice addEventHubDevice里创建了前面note5的MultiTouchInputMapper
void InputDevice::addEventHubDevice(int32_t eventHubId, bool populateMappers) {
//.......
    //InputDevice把自己的地址给了InputDeviceContext,
    //做为其mDevice成员的值,有取出InputReader做为其mContext的值
    std::unique_ptr<InputDeviceContext> contextPtr(new InputDeviceContext(*this, eventHubId));
//......
    //contextPtr的地址在InputMapper的mDeviceContext也保留了一份
    mappers.push_back(std::make_unique<MultiTouchInputMapper>(*contextPtr));
//.......
}

InputDeviceContext::InputDeviceContext(InputDevice& device, int32_t eventHubId)
      : mDevice(device),
      //根据note7  所以这边存的是InputReader::ContextImpl note8
        mContext(device.getContext()),
        mEventHub(device.getContext()->getEventHub()),
        mId(eventHubId),
        mDeviceId(device.getId()) {}


//contextPtr的地址在InputMapper的mDeviceContext也保留了一份,
//也就是mDeviceContext指向的是InputDeviceContext对象  note9
//MultiTouchInputMapper、TouchInputMapper、InputMapper三者为孙、父、爷爷的继承关系
MultiTouchInputMapper::MultiTouchInputMapper(InputDeviceContext& deviceContext)
      : TouchInputMapper(deviceContext) {}

TouchInputMapper::TouchInputMapper(InputDeviceContext& deviceContext)
      : InputMapper(deviceContext),
        mSource(0),
        mDeviceMode(DEVICE_MODE_DISABLED),
        mRawSurfaceWidth(-1),
        mRawSurfaceHeight(-1),
        mSurfaceLeft(0),
        mSurfaceTop(0),
        mPhysicalWidth(-1),
        mPhysicalHeight(-1),
        mPhysicalLeft(0),
        mPhysicalTop(0),
        mSurfaceOrientation(DISPLAY_ORIENTATION_0) {}      
 
 InputMapper::InputMapper(InputDeviceContext& deviceContext) : mDeviceContext(deviceContext) {}
      
//好了,咱们可以再回头来看note5的getListener了
void TouchInputMapper::dispatchMotion(nsecs_t when, uint32_t policyFlags, uint32_t source,
                                      int32_t action, int32_t actionButton, int32_t flags,
                                      int32_t metaState, int32_t buttonState, int32_t edgeFlags,
                                      const PointerProperties* properties,
                                      const PointerCoords* coords, const uint32_t* idToIndex,
                                      BitSet32 idBits, int32_t changedId, float xPrecision,
                                      float yPrecision, nsecs_t downTime) {
                                      
 //......                                     
  //note5                       
  getListener()->notifyMotion(&args);                                    
//......                                      
}

//frameworks/native/services/inputflinger/reader/mapper/InputMapper.h
inline InputListenerInterface* getListener() { return getContext()->getListener(); }

inline InputReaderContext* getContext() { return mDeviceContext.getContext(); }

//根据note9,走到InputDeviceContext里边,接着看
//framworks/native/services/inputflinger/reader/include/InputDevice.h
inline InputReaderContext* getContext() { return mContext; }
//根据note8,这边mContext就是前面构造InputDevice时传进来的InputReader::ContextImpl呀~

//接着看
//frameworks/native/services/inputflinger/reader/InputReader.cpp
InputListenerInterface* InputReader::ContextImpl::getListener() {
    return mReader->mQueuedListener.get();
}

好了,确认这边拿到的就是InputReader::mQueuedListener了,有点绕 哈哈,其实也可以跳过上面那段直接继续看下面这段

//咱们回到note5,
//frameworks/native/services/inputflinger/InputListener.cpp
void QueuedInputListener::notifyMotion(const NotifyMotionArgs* args) {
    traceEvent(__func__, args->id);
    mArgsQueue.push_back(new NotifyMotionArgs(*args));
}

这边把touch数据放入这个叫mArgsQueue的队列(用vector实现),那既然有入队,肯定有出队嘛,不过到这,怎么就把本小节开头的InputReader::loopOnce() 里边的前者看完了, 咱们接着看后者,看数据出队后做了啥,从note10看进去,

//frameworks/native/services/inputflinger/include/InputListener.h
void QueuedInputListener::flush() {
    size_t count = mArgsQueue.size();
    for (size_t i = 0; i < count; i++) {
    //把每个数据取出来,调用其notify接口,
        NotifyArgs* args = mArgsQueue[i];
        args->notify(mInnerListener);
        delete args;
    }
    mArgsQueue.clear();
}
//note11
//其中,这边mInnerListener 是InputClassifier,
// 而InputClassifier里边mListener装着InputDispatcher
//这部分简单一些,感兴趣的同学可以自己从下面这个地方开始跟一下~
//frameworks/native/services/inputflinger/InputManager.cpp
InputManager::InputManager(
        const sp<InputReaderPolicyInterface>& readerPolicy,
        const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
    mDispatcher = createInputDispatcher(dispatcherPolicy);
    mClassifier = new InputClassifier(mDispatcher);
    mReader = createInputReader(readerPolicy, mClassifier);
}

//好了言归正传接着看数据怎么传的,
//frameworks/native/services/inputflinger/InputListener.cpp
void NotifyMotionArgs::notify(const sp<InputListenerInterface>& listener) const {
    listener->notifyMotion(this);
}

//走到InputClassifier那去了
//frameworks/native/services/inputflinger/InputClassifier.cpp
void InputClassifier::notifyMotion(const NotifyMotionArgs* args) {
    std::scoped_lock lock(mLock);
    // MotionClassifier is only used for touch events, for now
    const bool sendToMotionClassifier = mMotionClassifier && isTouchEvent(*args);
    if (!sendToMotionClassifier) {
        mListener->notifyMotion(args);
        return;
    }

    NotifyMotionArgs newArgs(*args);
    newArgs.classification = mMotionClassifier->classify(newArgs);
    mListener->notifyMotion(&newArgs);
}

//而这边InputClassifier::mListener 就是InputDispatcher,接着看

//frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
//.......
        // Just enqueue a new motion event.
        MotionEntry* newEntry =
                new MotionEntry(args->id, args->eventTime, args->deviceId, args->source,
                                args->displayId, policyFlags, args->action, args->actionButton,
                                args->flags, args->metaState, args->buttonState,
                                args->classification, args->edgeFlags, args->xPrecision,
                                args->yPrecision, args->xCursorPosition, args->yCursorPosition,
                                args->downTime, args->pointerCount, args->pointerProperties,
                                args->pointerCoords, 0, 0);
        //数据入队
        needWake = enqueueInboundEventLocked(newEntry);    
        
        if (needWake) {
            //唤醒读数据的线程将数据出队
            mLooper->wake();
        }
//......
}

//接着看
bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) {
//......
      //数据在这又入队了
      mInboundQueue.push_back(entry);   
//......
}

好了,现在又看到数据入到InputDispatcher::mInboundQueue队列了,咱们去看下哪里给它出队的

//从note12咱们接着看,
//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;
}

//创建了一个线程去跑dispatchOnce()函数,
//没事件时睡下去,有事件时被前面的入队事件唤醒后开始工作
void InputDispatcher::dispatchOnce() {
//.......
     dispatchOnceInnerLocked(&nextWakeupTime);
//.......
}

//接着看
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
//.......
    done = dispatchMotionLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);
//......
}

bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, MotionEntry* entry,
                                           DropReason* dropReason, nsecs_t* nextWakeupTime) {
//.......                                           
   //TODO 这边会去选出到底是要把touch数据给到哪个View,选完装到inputTargets里边
   //具体咋选的后面再单独研究下
   injectionResult =
         findTouchedWindowTargetsLocked(currentTime, *entry, inputTargets, nextWakeupTime,
                                               &conflictingPointerActions); 
//......   
   //选完View后,接着继续发数据
   dispatchEventLocked(currentTime, entry, inputTargets);                                        
//......                                           
}

void InputDispatcher::dispatchEventLocked(nsecs_t currentTime, EventEntry* eventEntry,
                                          const std::vector<InputTarget>& inputTargets) {
//.......
    //根据inputTarget取出前面note13 注册的connection,其中里边包含用来和View通信的InputChannel
    sp<Connection> connection =
           getConnectionLocked(inputTarget.inputChannel->getConnectionToken());
           
    prepareDispatchCycleLocked(currentTime, connection, eventEntry, inputTarget);
                                           
//.......                                          
}

void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
                                                 const sp<Connection>& connection,
                                                 EventEntry* eventEntry,
                                                 const InputTarget& inputTarget) {
 //.......                                                
     //从这进去,接着看
     enqueueDispatchEntriesLocked(currentTime, connection, splitMotionEntry, inputTarget);                                                
//......                                                 
}

void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime,
                                                   const sp<Connection>& connection,
                                                   EventEntry* eventEntry,
                                                   const InputTarget& inputTarget) {
 //.......                                                  
     //从这进去                                          
     startDispatchCycleLocked(currentTime, connection);                                                  
}

void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
                                               const sp<Connection>& connection) {
//......                                               
    status =connection->inputPublisher.publishKeyEvent(dispatchEntry->seq, dispatchEntry->resolvedEventId,
                                                 keyEntry->deviceId, keyEntry->source,
                                                 keyEntry->displayId, std::move(hmac),
                                                 dispatchEntry->resolvedAction,
                                                 dispatchEntry->resolvedFlags, keyEntry->keyCode,
                                                 keyEntry->scanCode, keyEntry->metaState,
                                                 keyEntry->repeatCount, keyEntry->downTime,
                                                 keyEntry->eventTime);
//....
}

//走到InputPublisher去了,看下
//frameworks/native/libs/input/InputTransport.cpp
status_t InputPublisher::publishKeyEvent(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 flags, int32_t keyCode, int32_t scanCode,
                                         int32_t metaState, int32_t repeatCount, nsecs_t downTime,
                                         nsecs_t eventTime) {
//.......                                         
     //好了到底了,这边通过InputChannel用unix socket把数据发给View
     return mChannel->sendMessage(&msg);
}                                      

//至于这把mChannel是怎么来的为何是前面app通过wms传过来的InputChannel,
//咱们从note13  再看下
//frameworks/native/services/inputflinger/dispatcher/Connection.cpp
Connection::Connection(const sp<InputChannel>& inputChannel, bool monitor,
                       const IdGenerator& idGenerator)
      : status(STATUS_NORMAL),
        inputChannel(inputChannel),
        monitor(monitor),
        inputPublisher(inputChannel),
        inputState(idGenerator) {}

//frameworks/native/libs/input/InputTransport.cpp
InputPublisher::InputPublisher(const sp<InputChannel>& channel) :
        mChannel(channel) {
}

//这样子就清楚啦~

好了,InputFlinger从kernel读数据并发给View咱们就先看到这(其中还遗留一个TODO是选发给那个View后面再单独看),这部分流程主要工作都是集中在InputReader、InputDisapter、InputDevice。

接下来咱们看下ViewRootImpl使用InputChannel 接收到touch数据后怎么给到具体的View~

ViewRootImpl接收touch数据并给到app

这边咱们拿一个具体的例子来看,就比如点击一个button后,这个button的onClick()是怎么被调用的。

先来个callstack吧:

at com.example.test4.MainActivity$5.onClick(MainActivity.java:192)
at android.view.View.performClick(View.java:7513)
at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:992)
at android.view.View.performClickInternal(View.java:7490)
at android.view.View.access$3600(View.java:821)
at android.view.View$PerformClick.run(View.java:28564)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:232)
at android.app.ActivityThread.main(ActivityThread.java:8151)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:959)

再来个图:

整体流程大概是,ViewRootImpl 使用WindowInputEventReceiver 接收到touch数据后,将数据装入队列,然后又从队列把数据读出来,创建一个Runable task,把数据给进去然后回调button的onClick()。

咱们从InputChannel接收数据的地方开始看,接note2 继续看,在setView时创建了InputChannel,给到wms时为其赋了unix socketpair的fd值后,会作为创建WindowInputEventReceiver对象的参数给进去,

//framworks/base/core/java/android/view/ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView, int userId) {
//......    
    //note14
    mInputEventReceiver = new WindowInputEventReceiver(inputChannel, Looper.myLooper());
//......
}
//而在ViewRootImpl构造时(setView之前),就会创建一个线程,使用mInputEventReceiver来接收数据并处理
//这操作有点小骚,其实此时mInputEventReceiver为空,所以它搞了个判空操作

final class ConsumeBatchedInputImmediatelyRunnable implements Runnable {
    @Override
    public void run() {
        mConsumeBatchedInputImmediatelyScheduled = false;
        doConsumeBatchedInput(-1);
    }

final ConsumeBatchedInputImmediatelyRunnable mConsumeBatchedInputImmediatelyRunnable = 
    new ConsumeBatchedInputImmediatelyRunnable();
    
boolean doConsumeBatchedInput(long frameTimeNanos) {
    final boolean consumedBatches;
    if (mInputEventReceiver != null) {
        //把数据读出来
        //note16
        consumedBatches = mInputEventReceiver.consumeBatchedInputEvents(frameTimeNanos);
    } else {
        consumedBatches = false;
    }
    //这边去处理数据
    //note18
    doProcessInputEvents();
    return consumedBatches;
}

其中,上面的mInputEventReceiver.consumeBatchedInputEvents()会在native层使用inputChannel读数据,接着从native调用java层的dispatchInputEvent接口把数据又给过来,

而doProcessInputEvents()接着去处理从native给过来的数据,发送给View。

咱们先看下consumeBatchedInputEvents()流程:

 //从note14开始看
 
//framworks/base/core/java/android/view/ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView, int userId) {
//......    
    //inputChannel 做为入参给进去
    mInputEventReceiver = new WindowInputEventReceiver(inputChannel, Looper.myLooper());
//......
}

final class WindowInputEventReceiver extends InputEventReceiver
public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
      //再给到父类InputEventReceiver
     super(inputChannel, looper);
}

//frameworks/base/core/java/android/view/InputEventReceiver.java
public InputEventReceiver(InputChannel inputChannel, Looper looper) {
//......
    //自己保留一份引用
    mInputChannel = inputChannel;
    //再给到native层
    mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
        inputChannel, mMessageQueue);   
//......    
}

//frameworks/base/core/jni/android_view_InputEventReceiver.cpp
static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,
        jobject inputChannelObj, jobject messageQueueObj) {
//......
    sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
            inputChannelObj);
//......
   //创建一个NativeInputEventReceiver对象,inputChannel作为入参带进去         
    sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env,
            receiverWeak, inputChannel, messageQueue);            
//......        
}

NativeInputEventReceiver::NativeInputEventReceiver(JNIEnv* env,
        jobject receiverWeak, const sp<InputChannel>& inputChannel,
        const sp<MessageQueue>& messageQueue) :
        mReceiverWeakGlobal(env->NewGlobalRef(receiverWeak)),
        //保存一份对inputChannel的指针引用
        //note15
        mInputConsumer(inputChannel), mMessageQueue(messageQueue),
        mBatchedInputEventPending(false), mFdEvents(0) {
//......
}

好的,ViewRootImpl的inputChannel在NativeInputEventReceiver的mInputConsumer保留了一份,

ok,接着看note16

public final boolean consumeBatchedInputEvents(long frameTimeNanos) {
    if (mReceiverPtr == 0) {
        Log.w(TAG, "Attempted to consume batched input events but the input event "
                + "receiver has already been disposed.");
    } else {
        //走到native层
        return nativeConsumeBatchedInputEvents(mReceiverPtr, frameTimeNanos);
    }
    return false;
}
//frameworks/base/core/jni/android_view_InputEventReceiver.cpp
static jboolean nativeConsumeBatchedInputEvents(JNIEnv* env, jclass clazz, jlong receiverPtr,
        jlong frameTimeNanos) {
     //根据note15,这边调用InputChannel把来自InputFlinger的touch数据读出来
    status_t status = receiver->consumeEvents(env, true /*consumeBatches*/, frameTimeNanos,
            &consumedBatch);

    //......
}

status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
        bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
 //......       
         //用channel去把数据读出来
        status_t status = mInputConsumer.consume(&mInputEventFactory,
                consumeBatches, frameTime, &seq, &inputEvent,
                &motionEventType, &touchMoveNum, &flag);    
 //......       
 }

//frameworks/native/libs/input/InputTransport.cpp
status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consumeBatches,
                                nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent,
                                int* motionEventType, int* touchMoveNumber, bool* flag) {
 //......                     
    //inputChannel接收数据          
    status_t result = mChannel->receiveMessage(&mMsg);  
 //......
     //根据读到的数据做个转换
     inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);  
 //......  
     //调用java接口 dispatchInputEvent,把inputEventObj 传过去
    env->CallVoidMethod(receiverObj.get(),
        gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);    
 //......            
 }
 
 

回到java层继续看

//frameworks/base/core/java/android/view/InputEventReceiver.java
private void dispatchInputEvent(int seq, InputEvent event) {
//......
    //注意此时对象是WindowInputEventReceiver,
    //所以是走到WindowInputEventReceiver::onInputEvent(),把touch数据继续给进去
    onInputEvent(event);
//......
}

//接着看
//frameworks/base/core/java/android/view/ViewRootImpl.java
public void onInputEvent(InputEvent event) {
//.......
    //这边对touch数据做了一个入队的操作
     enqueueInputEvent(event, this, 0, true);
//......
}

void enqueueInputEvent(InputEvent event, InputEventReceiver receiver, int flags, boolean processImmediately) {
      //根据touch数据创建了一个QueuedInputEvent,
      //这些数据格式转来转去的目前咱们可以先不用管,k
      //可以先关注数据的走向,其实最根本的数据我觉得也就书x,y +校准参数
     QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
     //这边有一个input数据的队列,用mPendingInputEventHead、mPendingInputEventTail
     //来描述队列头和队列尾部;
     //若队列为空构造第一个元素,
     //否则放入对尾部
     //note17
     if (last == null) {
        mPendingInputEventHead = q;
        mPendingInputEventTail = q;
     } else {
        last.mNext = q;
        mPendingInputEventTail = q;
     }
     
//......
}

好了,touch数据从inputChannel读出来后,就是把它放到用mPendingInputEventHead、mPendingInputEventTail 构造的一个队列里边,接下来咱们回到note18, 去看下数据的处理流程

//frameworks/base/core/java/android/view/ViewRootImpl.java
void doProcessInputEvents() {
    //把队列里边的数据取干净
    while (mPendingInputEventHead != null) {
        //从队列头开始取
        QueuedInputEvent q = mPendingInputEventHead;
//......
        //这把去发送数据
        deliverInputEvent(q);
    }

//.......
}

private void deliverInputEvent(QueuedInputEvent q) {
    //这边去出一个stage,总的stage有如前面setView时创建的在note19那些,
    //然后把touch数据给到每个stage去看是否属于自己管理范围内的数据,
    //如果是的话就处理,否则给到下一个InputStage,
    InputStage stage;
    if (q.shouldSendToSynthesizer()) {
        stage = mSyntheticInputStage;
    } else {
        stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
    }        
//.......
    //这边在咱们的点击button例子中,处理事件的是ViewPostImeInputStage,
    //所以咱们直接看下ViewPostImeInputStage的数据处理函数吧,
    //中间逐个InputStage处理数据流程咱们就不看了,我自己也还没理清楚
    //感兴趣的同学可以自己看下
    stage.deliver(q);
}

public final void deliver(QueuedInputEvent q) {
    //如果不是自己处理范围的数据就不处理
    //给下一个InputStage
    if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
        forward(q);
    } else if (shouldDropInputEvent(q)) {
        finish(q, false);
    } else {
        traceEvent(q, Trace.TRACE_TAG_VIEW);
        final int result;
        try {
        //否则自己处理
            result = onProcess(q);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        apply(q, result);
    }
}

//看ViewPostImeInputStage的onProcess接口
protected int onProcess(QueuedInputEvent q) {
    if (q.mEvent instanceof KeyEvent) {
        return processKeyEvent(q);
    } else {
        final int source = q.mEvent.getSource();
        if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
        // touch 按压事件 ,其他事件类型咱们先不看了
            return processPointerEvent(q);
        } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
            return processTrackballEvent(q);
        } else {
            return processGenericMotionEvent(q);
        }
 }
 
 private int processPointerEvent(QueuedInputEvent q) {
     //事件做了转换
     final MotionEvent event = (MotionEvent)q.mEvent;
 //......
     //给到view里边了
     boolean handled = mView.dispatchPointerEvent(event);
 //......
 }
 
//接着看
//frameworks/base/core/java/android/view/View.java
public final boolean dispatchPointerEvent(MotionEvent event) { 
   if (event.isTouchEvent()) {
        //如果是touch事件就走这
        return dispatchTouchEvent(event);
    } else {
        return dispatchGenericMotionEvent(event);
    }
}

public boolean dispatchTouchEvent(MotionEvent event) {
//......
     onTouchEvent(event)
//......
}

public boolean onTouchEvent(MotionEvent event) {
//......
    //底下创建一个任务然后post给主线程去执行
    
   if (mPerformClick == null) {
       mPerformClick = new PerformClick();
   }
   if (!post(mPerformClick)) {
       performClickInternal();
   }
//...... 
} 

//接着看
private final class PerformClick implements Runnable {
    @Override
    public void run() {
        recordGestureClassification(TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP);
        performClickInternal();
    }
}

private boolean performClickInternal() {
    // Must notify autofill manager before performing the click actions to avoid scenarios wher
    // the app has a click listener that changes the state of views the autofill service might
    // be interested on.
    notifyAutofillManagerOnClick();
    return performClick();
}

 public boolean performClick() {
 //......
     //这边去调用button 的onClick接口
     li.mOnClickListener.onClick(this);
 //......
 }

好了,数据如何给到app分析完了,还留有一个问题是,在InputFlinger那边如何决定给到哪个activity、window、view,后面再分析下。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-08-03 11:19:29  更:2021-08-03 11:20:35 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/5 20:35:09-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码