InputMethodManager输入法窗口为啥dumpsys是全屏?千里马带你疑难解惑输入法相关

背景:

hi,粉丝朋友:
大家好!近来陆续有粉丝朋友在群里讨论到输入法,这个部分其实马哥之前也是研究过的,所以相对来说比较熟悉,其实android设计输入法的思路其实和壁纸是一模一样的。大体设计图如下:
在这里插入图片描述

一般看到的输入法界面就是输入法进程,一般都是实现了InputMethodService的子类实现,这里面会创建对应的输入法窗口展示,所以这里要说明输入法它不是个Activity哈,只是个全局窗口而已,和状态栏啥的一样。

刚好有个同学问了一个输入法的疑问,为啥dumpsys window windows和dumpsys SurfaceFlinger时候明明看到输入法是全屏,这个是怎么回事?

1、dumpsys相关SurfaceFlinger和window看看全屏情况

以下是winsocpe的SurfaceFlinger图层情况
在这里插入图片描述确实这里看到的SurfaceFlinger中对应的InputMethod的windowstate的activebuffer确实是全屏的:

activeBuffer : w:1440, h:2876, stride:2816, format:1

再看看对应的window相关的信息:
在这里插入图片描述也展示出来的大小是
frame : (0, 84) - (1440, 2960)
也都是全屏。

2、疑问点既然输入法全屏为啥activity还可以响应事件

首先显示部分遮盖理解:

这个部分相对比较好理解,因为窗口虽然是占全屏幕,但是可以又一部分透明透看到底部的窗口

其次触摸部分:

层级结构看输入法应该是盖在Activity的窗口上面,而且输入法又是全屏的,那么理论上所有的触摸事件就应该到输入法的窗口,而不应该到Activity的窗口。
但是实际情况又是如下图所示:
在这里插入图片描述

即只有输入法显示的部分才是输入法可以触摸的,触摸activity的部分那么activity就会响应对应的触摸事件。

需要回答这个问题就又需要使用我们input课程的知识:
需要试看课程的可以点击这里:https://www.bilibili.com/video/BV1YS4y1K7rj/
也可以+w:androidframework007 找我
在这里插入图片描述
要学回查看dumpsys input输出信息:


Input Dispatcher State:
  DispatchEnabled: true
  DispatchFrozen: false
  InputFilterEnabled: false
  FocusedDisplayId: 0
  FocusedApplications:
    displayId=0, name='ActivityRecord{49e3140 u0 com.android.quicksearchbox/.SearchActivity} t439}', dispatchingTimeout=5000ms
  FocusedWindows:
    displayId=0, name='313a198 com.android.quicksearchbox/com.android.quicksearchbox.SearchActivity'
  FocusRequests:
    displayId=0, name='313a198 com.android.quicksearchbox/com.android.quicksearchbox.SearchActivity' result='OK'
  Pointer Capture Requested: false
  Current Window with Pointer Capture: None
  TouchStates: <no displays touched>
  Display: 0
    logicalSize=1440x2960
        transform (ROT_0) (IDENTITY)
    Windows:
      0: name='[Gesture Monitor] swipe-up', id=106, displayId=0, inputConfig=NOT_FOCUSABLE | TRUSTED_OVERLAY | SPY, alpha=1.00, frame=[0,0][0,0], globalScale=1.000000, applicationInfo.name=[Gesture Monitor] swipe-up, applicationInfo.token=<null>, touchableRegion=[-14399,-29599][14400,29600], ownerPid=1047, ownerUid=10097, dispatchingTimeout=5000ms, hasToken=true, touchOcclusionMode=BLOCK_UNTRUSTED
        transform (ROT_0) (IDENTITY)
      1: name='[Gesture Monitor] edge-swipe', id=81, displayId=0, inputConfig=NOT_FOCUSABLE | TRUSTED_OVERLAY | SPY, alpha=1.00, frame=[0,0][0,0], globalScale=1.000000, applicationInfo.name=[Gesture Monitor] edge-swipe, applicationInfo.token=<null>, touchableRegion=[-14399,-29599][14400,29600], ownerPid=762, ownerUid=10099, dispatchingTimeout=5000ms, hasToken=true, touchOcclusionMode=BLOCK_UNTRUSTED
        transform (ROT_0) (IDENTITY)
      2: name='StrictModeFlash', id=93, displayId=0, inputConfig=NO_INPUT_CHANNEL | NOT_VISIBLE | NOT_FOCUSABLE | NOT_TOUCHABLE | PREVENT_SPLITTING | TRUSTED_OVERLAY, alpha=1.00, frame=[0,0][1440,2960], globalScale=1.000000, applicationInfo.name=, applicationInfo.token=<null>, touchableRegion=<empty>, ownerPid=0, ownerUid=0, dispatchingTimeout=0ms, hasToken=false, touchOcclusionMode=BLOCK_UNTRUSTED
        transform (ROT_0) (IDENTITY)
      3: name='4b4d6f1 PointerLocation - display 0', id=49, displayId=0, inputConfig=NO_INPUT_CHANNEL | NOT_FOCUSABLE | NOT_TOUCHABLE | PREVENT_SPLITTING | TRUSTED_OVERLAY, alpha=1.00, frame=[0,0][1440,2960], globalScale=1.000000, applicationInfo.name=, applicationInfo.token=<null>, touchableRegion=<empty>, ownerPid=572, ownerUid=1000, dispatchingTimeout=0ms, hasToken=false, touchOcclusionMode=BLOCK_UNTRUSTED
        transform (ROT_0) (IDENTITY)
      4: name='6f6f2e3 NavigationBar0', id=86, displayId=0, inputConfig=NOT_FOCUSABLE | TRUSTED_OVERLAY | WATCH_OUTSIDE_TOUCH, alpha=1.00, frame=[0,2792][1440,2960], globalScale=1.000000, applicationInfo.name=, applicationInfo.token=<null>, touchableRegion=<empty>, ownerPid=762, ownerUid=10099, dispatchingTimeout=5000ms, hasToken=true, touchOcclusionMode=BLOCK_UNTRUSTED
        transform (ROT_0) (TRANSLATE)
            1.0000  0.0000  -0.0000
            0.0000  1.0000  -2792.0000
            0.0000  0.0000  1.0000
      5: name='1afd988 StatusBar', id=87, displayId=0, inputConfig=NOT_FOCUSABLE | TRUSTED_OVERLAY, alpha=1.00, frame=[0,0][1440,84], globalScale=1.000000, applicationInfo.name=, applicationInfo.token=<null>, touchableRegion=[0,0][1440,84], ownerPid=762, ownerUid=10099, dispatchingTimeout=5000ms, hasToken=true, touchOcclusionMode=BLOCK_UNTRUSTED
        transform (ROT_0) (IDENTITY)
      6: name='recents_animation_input_consumer', id=101, displayId=0, inputConfig=NOT_VISIBLE | TRUSTED_OVERLAY, alpha=1.00, frame=[0,0][1440,2960], globalScale=1.000000, applicationInfo.name=recents_animation_input_consumer, applicationInfo.token=0x77cb0493d7d0, touchableRegion=[0,0][1440,2960], ownerPid=572, ownerUid=1000, dispatchingTimeout=5000ms, hasToken=true, touchOcclusionMode=BLOCK_UNTRUSTED
        transform (ROT_0) (IDENTITY)
      7: name='99d71b InputMethod', id=278, displayId=0, inputConfig=NOT_FOCUSABLE | TRUSTED_OVERLAY, alpha=1.00, frame=[0,84][1440,2960], globalScale=1.000000, applicationInfo.name=, applicationInfo.token=<null>, touchableRegion=[0,1759][1440,2960], ownerPid=1194, ownerUid=10081, dispatchingTimeout=5000ms, hasToken=true, touchOcclusionMode=BLOCK_UNTRUSTED
        transform (ROT_0) (TRANSLATE)
            1.0000  -0.0000  0.0000
            -0.0000  1.0000  -84.0000
            0.0000  0.0000  1.0000
      8: name='313a198 com.android.quicksearchbox/com.android.quicksearchbox.SearchActivity', id=273, displayId=0, inputConfig=0x0, alpha=1.00, frame=[0,0][1440,2960], globalScale=1.000000, applicationInfo.name=ActivityRecord{49e3140 u0 com.android.quicksearchbox/.SearchActivity} t439}, applicationInfo.token=0x77cb04980b50, touchableRegion=[0,0][1440,2960], ownerPid=1441, ownerUid=10084, dispatchingTimeout=5000ms, hasToken=true, touchOcclusionMode=BLOCK_UNTRUSTED
        transform (ROT_0) (IDENTITY)
      9: name='c6a010e ActivityRecordInputSink com.android.quicksearchbox/.SearchActivity', id=120, displayId=0, inputConfig=NO_INPUT_CHANNEL | NOT_FOCUSABLE, alpha=1.00, frame=[0,0][0,0], globalScale=0.000000, applicationInfo.name=, applicationInfo.token=<null>, touchableRegion=[-14399,-29599][14400,29600], ownerPid=572, ownerUid=1000, dispatchingTimeout=0ms, hasToken=false, touchOcclusionMode=BLOCK_UNTRUSTED
        transform (ROT_0) (IDENTITY)
      10: name='efb17ce ActivityRecordInputSink com.android.camera2/com.android.camera.CameraActivity', id=141, displayId=0, inputConfig=NO_INPUT_CHANNEL | NOT_VISIBLE | NOT_FOCUSABLE | NOT_TOUCHABLE, alpha=1.00, frame=[0,0][0,0], globalScale=0.000000, applicationInfo.name=, applicationInfo.token=<null>, touchableRegion=[-14399,-29599][14400,29600], ownerPid=572, ownerUid=1000, dispatchingTimeout=0ms, hasToken=false, touchOcclusionMode=BLOCK_UNTRUSTED
        transform (ROT_0) (IDENTITY)
      11: name='8dd0f70 ActivityRecordInputSink com.android.settings/.SubSettings', id=213, displayId=0, inputConfig=NO_INPUT_CHANNEL | NOT_VISIBLE | NOT_FOCUSABLE | NOT_TOUCHABLE, alpha=1.00, frame=[0,0][0,0], globalScale=0.000000, applicationInfo.name=, applicationInfo.token=<null>, touchableRegion=[0,0][1440,2960], ownerPid=572, ownerUid=1000, dispatchingTimeout=0ms, hasToken=false, touchOcclusionMode=BLOCK_UNTRUSTED
        transform (ROT_0) (IDENTITY)
      12: name='214bf5f ActivityRecordInputSink com.android.settings/.SubSettings', id=196, displayId=0, inputConfig=NO_INPUT_CHANNEL | NOT_VISIBLE | NOT_FOCUSABLE | NOT_TOUCHABLE, alpha=1.00, frame=[0,0][0,0], globalScale=0.000000, applicationInfo.name=, applicationInfo.token=<null>, touchableRegion=[0,0][1440,2960], ownerPid=572, ownerUid=1000, dispatchingTimeout=0ms, hasToken=false, touchOcclusionMode=BLOCK_UNTRUSTED
        transform (ROT_0) (IDENTITY)
      13: name='2001b8f ActivityRecordInputSink com.android.settings/.homepage.SettingsHomepageActivity', id=178, displayId=0, inputConfig=NO_INPUT_CHANNEL | NOT_VISIBLE | NOT_FOCUSABLE | NOT_TOUCHABLE, alpha=1.00, frame=[0,0][0,0], globalScale=0.000000, applicationInfo.name=, applicationInfo.token=<null>, touchableRegion=[0,0][1440,2960], ownerPid=572, ownerUid=1000, dispatchingTimeout=0ms, hasToken=false, touchOcclusionMode=BLOCK_UNTRUSTED
        transform (ROT_0) (IDENTITY)
      14: name='edb3266 com.android.launcher3/com.android.launcher3.uioverrides.QuickstepLauncher', id=266, displayId=0, inputConfig=DUPLICATE_TOUCH_TO_WALLPAPER, alpha=1.00, frame=[0,0][1440,2960], globalScale=1.000000, applicationInfo.name=ActivityRecord{c22322a u0 com.android.launcher3/.uioverrides.QuickstepLauncher} t437}, applicationInfo.token=0x77cb049417f0, touchableRegion=[0,0][1440,2960], ownerPid=1047, ownerUid=10097, dispatchingTimeout=5000ms, hasToken=true, touchOcclusionMode=BLOCK_UNTRUSTED
        transform (ROT_0) (IDENTITY)
      15: name='91a1ad2 ActivityRecordInputSink com.android.launcher3/.uioverrides.QuickstepLauncher', id=109, displayId=0, inputConfig=NO_INPUT_CHANNEL | NOT_FOCUSABLE, alpha=1.00, frame=[0,0][0,0], globalScale=0.000000, applicationInfo.name=, applicationInfo.token=<null>, touchableRegion=[-14399,-29599][14400,29600], ownerPid=572, ownerUid=1000, dispatchingTimeout=0ms, hasToken=false, touchOcclusionMode=BLOCK_UNTRUSTED
        transform (ROT_0) (IDENTITY)
      16: name='Wallpaper BBQ wrapper#79', id=79, displayId=0, inputConfig=NO_INPUT_CHANNEL, alpha=1.00, frame=[-71,-147][1760,3108], globalScale=1.000000, applicationInfo.name=, applicationInfo.token=<null>, touchableRegion=<empty>, ownerPid=762, ownerUid=10099, dispatchingTimeout=5000ms, hasToken=false, touchOcclusionMode=BLOCK_UNTRUSTED
        transform (ROT_0) (SCALE TRANSLATE)
            0.5160  -0.0000  37.1499
            -0.0000  0.5160  76.3636
            0.0000  0.0000  1.0000
      17: name='b2dd605 com.android.systemui.ImageWallpaper', id=78, displayId=0, inputConfig=NOT_FOCUSABLE | NOT_TOUCHABLE | PREVENT_SPLITTING | IS_WALLPAPER, alpha=1.00, frame=[-71,-147][-71,-147], globalScale=1.000000, applicationInfo.name=, applicationInfo.token=<null>, touchableRegion=<empty>, ownerPid=762, ownerUid=10099, dispatchingTimeout=5000ms, hasToken=true, touchOcclusionMode=BLOCK_UNTRUSTED
        transform (ROT_0) (SCALE TRANSLATE)
            0.5160  -0.0000  37.1499
            -0.0000  0.5160  76.3636
            0.0000  0.0000  1.0000


上面是所有的触摸window的在input中的情况,我们重点关注是InputMethod这个窗口:

7: name=‘99d71b InputMethod’, id=278, displayId=0, inputConfig=NOT_FOCUSABLE | TRUSTED_OVERLAY, alpha=1.00, frame=[0,84][1440,2960], globalScale=1.000000, applicationInfo.name=, applicationInfo.token=, touchableRegion=[0,1759][1440,2960], ownerPid=1194, ownerUid=10081, dispatchingTimeout=5000ms, hasToken=true, touchOcclusionMode=BLOCK_UNTRUSTED
transform (ROT_0) (TRANSLATE)
1.0000 -0.0000 0.0000
-0.0000 1.0000 -84.0000
0.0000 0.0000 1.0000

可以看到的它的画面大小确实是frame=[0,84][1440,2960],但是有一个touchableRegion=[0,1759][1440,2960],这个区域才是真正的触摸区域,这里我们就看出了猫腻,这里y是从1759的位置才开始可以触摸了,也就是我们上面看到的输入法的键盘区域。
即这个touchableRegion就解答了为啥上部分区域触摸事件没有给输入法窗口,因为输入法窗口触摸区域就不包含那一部分,所以触摸事件在上部分区域就会继续传递到下面的窗口即Activity窗口。
那么问题来了请问这个touchableRegion是在哪里进行的设置呢?

3、touchableRegion设置追踪

这里我们知道一般input中的window是由InputMonitor设置的:
frameworks/base/services/core/java/com/android/server/wm/InputMonitor.java
中搜索到如下类似代码:

            inputWindowHandle.setTouchableRegion(mTmpRegion);

在这个方法加入对应的堆栈打印
frameworks/base/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java

   void setTouchableRegion(Region region) {
        if (mHandle.touchableRegion.equals(region)) {
            return;
        }
      if (region.getBounds().top > 1000) {
            android.util.Log.i("lsm111","setTouchableRegion " + region + " mHandle = "+mHandle,new Exception());
       }
        mHandle.touchableRegion.set(region);
        mChanged = true;
    }

相关的堆栈setTouchableRegion

* daemon started successfully
--------- beginning of main
09-15 11:59:46.669   572   593 I lsm111  : setTouchableRegion SkRegion((0,1675,1440,2876))
09-15 11:59:46.669   572   593 I lsm111  : java.lang.Exception
09-15 11:59:46.669   572   593 I lsm111  : 	at com.android.server.wm.InputWindowHandleWrapper.setTouchableRegion(InputWindowHandleWrapper.java:146)
09-15 11:59:46.669   572   593 I lsm111  : 	at com.android.server.wm.InputMonitor.populateInputWindowHandle(InputMonitor.java:315)
09-15 11:59:46.669   572   593 I lsm111  : 	at com.android.server.wm.InputMonitor$UpdateInputForAllWindowsConsumer.accept(InputMonitor.java:636)
09-15 11:59:46.669   572   593 I lsm111  : 	at com.android.server.wm.InputMonitor$UpdateInputForAllWindowsConsumer.accept(InputMonitor.java:508)
09-15 11:59:46.669   572   593 I lsm111  : 	at com.android.server.wm.WindowContainer$ForAllWindowsConsumerWrapper.apply(WindowContainer.java:2624)
09-15 11:59:46.669   572   593 I lsm111  : 	at com.android.server.wm.WindowContainer$ForAllWindowsConsumerWrapper.apply(WindowContainer.java:2614)
09-15 11:59:46.669   572   593 I lsm111  : 	at com.android.server.wm.WindowState.applyInOrderWithImeWindows(WindowState.java:4903)
09-15 11:59:46.669   572   593 I lsm111  : 	at com.android.server.wm.WindowState.forAllWindows(WindowState.java:4747)
09-15 11:59:46.669   572   593 I lsm111  : 	at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1611)
09-15 11:59:46.669   572   593 I lsm111  : 	at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1611)
09-15 11:59:46.669   572   593 I lsm111  : 	at com.android.server.wm.DisplayContent$ImeContainer.forAllWindowForce(DisplayContent.java:5175)
09-15 11:59:46.669   572   593 I lsm111  : 	at com.android.server.wm.DisplayContent.forAllImeWindows(DisplayContent.java:2814)
09-15 11:59:46.669   572   593 I lsm111  : 	at com.android.server.wm.WindowState.applyImeWindowsIfNeeded(WindowState.java:4896)
09-15 11:59:46.669   572   593 I lsm111  : 	at com.android.server.wm.WindowState.applyInOrderWithImeWindows(WindowState.java:4902)
09-15 11:59:46.669   572   593 I lsm111  : 	at com.android.server.wm.WindowState.forAllWindows(WindowState.java:4747)
09-15 11:59:46.669   572   593 I lsm111  : 	at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1611)
09-15 11:59:46.669   572   593 I lsm111  : 	at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1611)
09-15 11:59:46.669   572   593 I lsm111  : 	at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1611)
09-15 11:59:46.669   572   593 I lsm111  : 	at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1611)
09-15 11:59:46.669   572   593 I lsm111  : 	at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1611)
09-15 11:59:46.669   572   593 I lsm111  : 	at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1611)
09-15 11:59:46.669   572   593 I lsm111  : 	at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1611)
09-15 11:59:46.669   572   593 I lsm111  : 	at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1611)
09-15 11:59:46.669   572   593 I lsm111  : 	at com.android.server.wm.WindowContainer.forAllWindows(WindowContainer.java:1628)
09-15 11:59:46.669   572   593 I lsm111  : 	at com.android.server.wm.InputMonitor$UpdateInputForAllWindowsConsumer.updateInputWindows(InputMonitor.java:548)
09-15 11:59:46.669   572   593 I lsm111  : 	at com.android.server.wm.InputMonitor$UpdateInputForAllWindowsConsumer.-$$Nest$mupdateInputWindows(Unknown Source:0)
09-15 11:59:46.669   572   593 I lsm111  : 	at com.android.server.wm.InputMonitor$UpdateInputWindows.run(InputMonitor.java:134)
09-15 11:59:46.669   572   593 I lsm111  : 	at android.os.Handler.handleCallback(Handler.java:942)
09-15 11:59:46.669   572   593 I lsm111  : 	at android.os.Handler.dispatchMessage(Handler.java:99)
09-15 11:59:46.669   572   593 I lsm111  : 	at android.os.Looper.loopOnce(Looper.java:201)
09-15 11:59:46.669   572   593 I lsm111  : 	at android.os.Looper.loop(Looper.java:288)
09-15 11:59:46.669   572   593 I lsm111  : 	at android.os.HandlerThread.run(HandlerThread.java:67)
09-15 11:59:46.669   572   593 I lsm111  : 	at com.android.server.ServiceThread.run(ServiceThread.java:44)

可以看出这里的setTouchableRegion设置是由触发
InputMonitor$UpdateInputForAllWindowsConsumer.updateInputWindows

那么Region来自哪里呢?

   if (!useSurfaceBoundsAsTouchRegion) {
   //实际是调用了WindowState的getSurfaceTouchableRegion方法
            w.getSurfaceTouchableRegion(mTmpRegion, w.mAttrs);
            inputWindowHandle.setTouchableRegion(mTmpRegion);
        }

这里又回调用到WindowState的getTouchableRegion方法:

    /** Get the touchable region in global coordinates. */
    void getTouchableRegion(Region outRegion) {
        final Rect frame = mWindowFrames.mFrame;
        switch (mTouchableInsets) {//关键的mTouchableInsets决定取哪个区域
            default:
            case TOUCHABLE_INSETS_FRAME:
                outRegion.set(frame);
                break;
            case TOUCHABLE_INSETS_CONTENT:
                applyInsets(outRegion, frame, mGivenContentInsets);
                break;
            case TOUCHABLE_INSETS_VISIBLE:
                applyInsets(outRegion, frame, mGivenVisibleInsets);
                break;
            case TOUCHABLE_INSETS_REGION: {//输入法执行到这里
                outRegion.set(mGivenTouchableRegion);
                if (frame.left != 0 || frame.top != 0) {
                    outRegion.translate(frame.left, frame.top);
                }
                break;
            }
        }
        cropRegionToRootTaskBoundsIfNeeded(outRegion);
        subtractTouchExcludeRegionIfNeeded(outRegion);
    }

问题就是mGivenTouchableRegion来自哪里?哈哈这里经过寻找发现在WindowManagerService进行赋值的,具体堆栈如下:

09-15 16:09:34.367   551  1330 I lsm111  : Window{e201e2f u0 InputMethod} touchableRegion = SkRegion((0,1675,1440,2876))
09-15 16:09:34.367   551  1330 I lsm111  : java.lang.Exception
09-15 16:09:34.367   551  1330 I lsm111  : 	at com.android.server.wm.WindowManagerService.setInsetsWindow(WindowManagerService.java:2165)
09-15 16:09:34.367   551  1330 I lsm111  : 	at com.android.server.wm.Session.setInsets(Session.java:279)
09-15 16:09:34.367   551  1330 I lsm111  : 	at android.view.IWindowSession$Stub.onTransact(IWindowSession.java:813)
09-15 16:09:34.367   551  1330 I lsm111  : 	at com.android.server.wm.Session.onTransact(Session.java:175)
09-15 16:09:34.367   551  1330 I lsm111  : 	at android.os.Binder.execTransactInternal(Binder.java:1285)
09-15 16:09:34.367   551  1330 I lsm111  : 	at android.os.Binder.execTransact(Binder.java:1244)

是客户端发起的调用设置的Inset,这里就相当于到了Inset部分了,和类是状态栏,导航栏那个是一回事,在这就不深入分析了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

千里马学框架

帮助你了,就请我喝杯咖啡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值