input 系统

input系统涉及的源码主要有

(libinputflinger.so)frameworks/native/services/inputflinger/

- InputDispatcher.cpp

- InputReader.cpp

- InputManager.cpp

- EventHub.cpp

- InputListener.cpp

(libinput.so) frameworks/native/libs/input/

- InputTransport.cpp

- Input.cpp

- InputDevice.cpp

- Keyboard.cpp

- KeyCharacterMap.cpp

- IInputFlinger.cpp

(system_server)frameworks/base/services/core/

- java/com/android/server/input/InputManagerService.java

- jni/com_android_server_input_InputManagerService.cpp

如果需要调试input系统,应该gdb app_process,并在Android设备上attach system_server

input系统的主要类图如下:

以上类图主要关注: - InputReader负责从输入设备(EventHub)获取事件,并将该事件通知给QueuedInputListener,并最终通知到InputDispatcher - InputDispatcher负责将输入事件分发到正确的窗口上,并会处理ANR问题 - InputManager是InputReader和InputDispatcher的枢纽,类似MVC中到Controller作用,并对外提供InputManagerService

InputDispatcher实现

要想分析input ANR产生的原理需要先了解InputDispatcher的实现。

InputDispatcher的实现主要涉及3个Queue: 1. InboundQueue: 这个队列里面存储的是从InputReader送来到输入事件 2. OutboundQueue:这个队列里面存储的是即将要发送给应用的输入事件 3. WaitQueue:这个队列里面存储的是已经发给应用的事件,但应用还未处理完成

InputDispatcher内部维护了一个线程,负责不断从InboundQueue读取事件,获取到焦点窗口后,将事件放入OutboundQueue,并分发给应用后放入WaitQueue。WaitQueue是否有元素,元素的“生成时间”与焦点窗口是否应该触发ANR联系紧密。

InputDispatcher的主要流程如下:

流程图中看,正常的时序如下: 1. 在无等待事件时,才会消耗InboundQueue 2. 只有在窗口就绪的情况下才会将事件从InboundQueue(实际上已经缓存到mPendingEvent)移到OutboundQueue 3. OutboundQueue中的事件会进入WaitQueue,并等待应用处理完成后从WaitQueue移除

发生ANR的时序如下: 1. 窗口未就绪的判断条件是当前时间大于WaitQueue队头500ms 2. 窗口未就绪的话,当前事件会转为wait状态,并设置waitCause, waitTimeoutTime(waitTimeoutTime=currentTime+5s)等 3. 窗口未就绪且已经处于wait状态,则会检查当前时间是否大于waitTimeoutTime,如果是,则触发ANR

因此,发生ANR的条件是:WaitQueue有元素,且mPendingEvent等待(加入OutboundQueue)超时

以上过程,主要涉及InputDispatcher.cpp中的如下函数:

dispatchOnce: 循环入口

dispatchOnceInnerLocked:消费InboundQueue

dispatchMotionLocked:分发触摸事件

findTouchedWindowTargetsLocked/findFocusedWindowTargetsLocked:寻找焦点窗口

checkWindowReadyForMoreInputLocked:检查窗口是否就绪

handleTargetsNotReadyLocked:串口未就绪处理逻辑

onANRLocked:触发ANR

dumpsys input

在了解了原理后,可以借助dumpsys input来分析一些问题。

执行dumpsys input后,看到的信息结构如下:

INPUT MANAGER (dumpsys input)

Event Hub State:

......

Input Reader State:

......

Input Dispatcher State:

......

Input Dispatcher State at time of last ANR:

......

这里主要看下Input Dispatcher State。 dispatcher等dump由dumpDispatchStateLocked函数打印。主要结构如下:

Input Dispatcher State:

DispatchEnabled: 1

DispatchFrozen: 0

FocusedApplication: name='AppWindowToken{3f0c92ab token=Token{1ea37cfa ActivityRecord{235b7325 u0 com.android.launcher3/.Launcher t607}}}', dispatchingTimeout=5000.000ms

FocusedWindow: name='Window{37886f95 u0 com.android.launcher3/com.android.launcher3.Launcher}'

TouchStatesByDisplay:

0: down=false, split=false, deviceId=6, source=0x00002002

Windows:

Windows:

......

MonitoringChannels:

......

RecentQueue: length=10:

......

PendingEvent:

InboundQueue:

ReplacedKeys:

Connections:

......

8: channelName='37886f95 com.android.launcher3/com.android.launcher3.Launcher (server)', windowName='Window{37886f95 u0 com.android.launcher3/com.android.launcher3.Launcher}', status=NORMAL, monitor=false, inputPublisherBlocked=false

OutboundQueue:

WaitQueue:

......

AppSwitch: not pending

Configuration:

KeyRepeatDelay: 50.0ms

KeyRepeatTimeout: 500.0ms

以上可以看到InboundQueue,OutboundQueue,WaitQueue 3个Queue的状态,以及PendingEvent的值。InputDispatcher与应用之间维持的socket连接叫做Connection,用于通知事件和反馈事件处理完成。

从上面dump的信息看,焦点窗口是launcher3,没有未处理的输入事件。

ANR Demo 1 —— dispatchTouchEvent超时

因为dispatchTouchEvent这个函数处理结束后才会通过Connection通知InputDispatcher事件处理完成。因此,我们重载这个函数来模拟ANR:

override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {

Log.i(TAG, "handle event: "+ev)

Thread.sleep(100000)

return super.dispatchTouchEvent(ev)

}

函数执行一开始sleep 10s,来阻塞当前事件的处理。

程序运行后触屏点击一次Activity, 打印"handle event",并10s sleep,这时,执行adb shell dumpsys input,输出如下:

PendingEvent:

InboundQueue:

Connections:

......

11: channelName='14a0d447 com.cy.anr/com.cy.anr.MainActivity (server)', windowName='Window{14a0d447 u0 com.cy.anr/com.cy.anr.MainActivity}', status=NORMAL, monitor=false, inputPublisherBlocked=false

OutboundQueue:

WaitQueue: length=3

MotionEvent(deviceId=5, source=0x00001002, action=0, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (740.0, 75.0)]), policyFlags=0x62000000, targetFlags=0x00000105, resolvedAction=0, age=1411.8ms, wait=1411.3ms

MotionEvent(deviceId=5, source=0x00001002, action=2, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (741.0, 80.0)]), policyFlags=0x62000000, targetFlags=0x00000105, resolvedAction=2, age=1363.3ms, wait=1363.2ms

MotionEvent(deviceId=5, source=0x00001002, action=1, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (741.0, 80.0)]), policyFlags=0x62000000, targetFlags=0x00000105, resolvedAction=1, age=1347.2ms, wait=1347.0ms

可以看到PendingEvent, InboundQueue和OutboundQueue都是空,而WaitQueue中有3个MotionEvent,分别对应action 0,2,1,也就是Down,Move,Up。

产生这样输出的原理是,在点击前,窗口没有在处理任何输入,因此对于down可以添加到WaitQueue。而对于Move和Up,由于是几乎同时产生和处理的,因此在判断窗口是否就绪时(当前时间是否大于WaitQueue队头500ms),发现窗口就绪,因此一并加入到了WaitQueue.

根据以上分析,此时,WaitQueue有元素,那么(500ms后)再次点击,往InqueueBound加入事件后,由于WaitQueue仍在处理,窗口未就绪,因此PendingEvent无法加入到WaitQueue。

如果5s后再次尝试添加PendingEvent时窗口仍未就绪,则会触发ANR。

再点击一次,看看dump输出:

PendingEvent:

MotionEvent(deviceId=5, source=0x00001002, action=0, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (716.0, 75.0)]), policyFlags=0x62000000, age=2067.0ms

InboundQueue: length=1

MotionEvent(deviceId=5, source=0x00001002, action=1, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (716.0, 75.0)]), policyFlags=0x62000000, age=1985.6ms

Connections:

......

11: channelName='14a0d447 com.cy.anr/com.cy.anr.MainActivity (server)', windowName='Window{14a0d447 u0 com.cy.anr/com.cy.anr.MainActivity}', status=NORMAL, monitor=false, inputPublisherBlocked=false

OutboundQueue:

WaitQueue: length=2

MotionEvent(deviceId=5, source=0x00001002, action=0, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (691.0, 62.0)]), policyFlags=0x62000000, targetFlags=0x00000105, resolvedAction=0, age=3362.6ms, wait=3362.0ms

MotionEvent(deviceId=5, source=0x00001002, action=1, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (691.0, 62.0)]), policyFlags=0x62000000, targetFlags=0x00000105, resolvedAction=1, age=3265.9ms, wait=3265.8ms

WaitQueue和之前的dump相同,因为应用还在sleep, dwon仍未处理完成。

但PendingEvent为down, InboundQueue为up(快速点击时不会有move),然后,5s后触发了ANR。

ANR Demo 2 —— Button onClick处理超时

以上通过dispatchTouchEvent结合原理分析了一次ANR的产生过程。然而,如果时在onButtonClicked中模拟ANR,则会有所不同。

fun onTestBtnClick(view: View){

Thread.sleep(100000)

}

第一次点击:

PendingEvent:

InboundQueue:

Connections:

......

11: channelName='3d4d7501 com.cy.anr/com.cy.anr.MainActivity (server)', windowName='Window{3d4d7501 u0 com.cy.anr/com.cy.anr.MainActivity}', status=NORMAL, monitor=false, inputPublisherBlocked=false

OutboundQueue:

WaitQueue:

PendingEvent, InboundQueue, OutboundQueue, WaitQueue均为空。

第二次点击:

PendingEvent:

InboundQueue:

Connections:

......

11: channelName='323faa65 com.cy.anr/com.cy.anr.MainActivity (server)', windowName='Window{323faa65 u0 com.cy.anr/com.cy.anr.MainActivity}', status=NORMAL, monitor=false, inputPublisherBlocked=false

OutboundQueue:

WaitQueue: length=2

MotionEvent(deviceId=5, source=0x00001002, action=0, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (701.0, 72.0)]), policyFlags=0x62000000, targetFlags=0x00000105, resolvedAction=0, age=4015.0ms, wait=4014.3ms

MotionEvent(deviceId=5, source=0x00001002, action=1, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (701.0, 72.0)]), policyFlags=0x62000000, targetFlags=0x00000105, resolvedAction=1, age=3950.4ms, wait=3950.2ms

PendingEvent, InboundQueue, OutboundQueue均为空。WaitQueue有此次点击的两个事件(down/up)。5s后并未发生ANR。

第三次点击:

PendingEvent:

MotionEvent(deviceId=5, source=0x00001002, action=0, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (723.0, 76.0)]), policyFlags=0x62000000, age=2377.9ms

InboundQueue: length=2

MotionEvent(deviceId=5, source=0x00001002, action=2, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (718.0, 74.0)]), policyFlags=0x62000000, age=2329.0ms

MotionEvent(deviceId=5, source=0x00001002, action=1, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (718.0, 74.0)]), policyFlags=0x62000000, age=2312.8ms

Connections:

......

11: channelName='323faa65 com.cy.anr/com.cy.anr.MainActivity (server)', windowName='Window{323faa65 u0 com.cy.anr/com.cy.anr.MainActivity}', status=NORMAL, monitor=false, inputPublisherBlocked=false

OutboundQueue:

WaitQueue: length=2

MotionEvent(deviceId=5, source=0x00001002, action=0, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (701.0, 72.0)]), policyFlags=0x62000000, targetFlags=0x00000105, resolvedAction=0, age=4015.0ms, wait=4014.3ms

MotionEvent(deviceId=5, source=0x00001002, action=1, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (701.0, 72.0)]), policyFlags=0x62000000, targetFlags=0x00000105, resolvedAction=1, age=3950.4ms, wait=3950.2ms

第三次点击5s后才出现ANR。为什么与dispatchTouchEvent不同,需要点击3次呢?

因为onButtonClick是在dispatchTouchEvent执行完成后转抛到主线程的一条消息。也就是当进入onButtonClick函数时,第一次点击的down/up事件已经执行完成,并从WaitQueue移除。第二次点击因为WaitQueue为空,认为窗口就绪,所以事件被加入到WaitQueue,但是应用主线程被阻塞,所以dispatchTouchEvent不能及时处理消息,事件就停留在了WaitQueue中。第三次点击时,窗口未就绪,事件被放入PendingEvent,5s超时后触发ANR。

鼠标和触屏差异

当用鼠标操作Demo2时,会发现第二次点击时就触发了ANR。这是因为鼠标事件多了一个action=7(ACTION_HOVER_MOVE),并且是在处理完onButtonClick后才会从WaitQueue移除。

不妨看下第一次点击后的dump:

PendingEvent:

InboundQueue:

Connections:

11: channelName='a40bbc6 com.cy.anr/com.cy.anr.MainActivity (server)', windowName='Window{a40bbc6 u0 com.cy.anr/com.cy.anr.MainActivity}', status=NORMAL, monitor=false, inputPublisherBlocked=false

OutboundQueue:

WaitQueue: length=1

MotionEvent(deviceId=6, source=0x00002002, action=7, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (754.3, 84.0)]), policyFlags=0x62000000, targetFlags=0x00000105, resolvedAction=7, age=2587.3ms, wait=2586.6ms

可以看到WaitQueue length=1,其中的元素为MotionEvent action=7。

UI线程对Input事件的处理

那么是否只要确保主线程处理时间小于5s就可以不发生(input)ANR了呢?答案是否定的。

在进一步分析“何时应用产生ANR”之前,需要先了解下UI线程对Input事件的处理过程。

UI线程与InputDispatcher间的交互是通过一个名为InputChannel的类实现的,详细的通信过程如下:

如图所示,InputDispatcher通过publishKeyEvent将事件通过InputChannel(内部基于sokcetpair实现)“发送”到UI线程后,会“等待”UI线程处理完成。(实现上这是一个异步过程,并非同步等待)

上图未提及的了几个重要信息: 1. 如果收到应用发来的消息,则唤醒InputDispatcher的线程处理该消息,处理完该消息后,如果有未发送的事件(PendingEvent或InboundQueue有未处理事件),则会再次尝试发送。 2. consumeEvents的内部实现。consumeEvents内部循环读取socket recvBuf中的事件,直至buf为空,也就是说,在一次的主线程循环中,consumeEvents会处理多个InputDispatcher发来的event。 3. consumeEvents被nativePollOnce中调用,nativePollOnce在主线程每次获取下一条消息时都被执行(MessageQueue.next())

这些信息影响下列行为: 1. 在dispatchTouchEvent中sleep 4s,快速点击,会触发ANR 2. 在onButtonClicked中sleep 4s,快速点击按钮,并不会出发ANR

在具体分析这两个行为的ANR前,看下这两个函数的调用栈: 1. dispatchTouchEvent:

dispatchTouchEvent:30, MainActivity {com.cy.anr}

......

onInputEvent:6210, ViewRootImpl$WindowInputEventReceiver {android.view}

dispatchInputEvent:185, InputEventReceiver {android.view}

nativePollOnce:-1, MessageQueue {android.os}

next:143, MessageQueue {android.os}

loop:122, Looper {android.os}

main:5280, ActivityThread {android.app}

nativePollOnce隐藏了具体的native调用。如果去分析native代码,可以知道:InputEventReceiver.dispatchInputEvent被native的consumeEvents调用。onButtonClicked:

onTestBtnClick:23, MainActivity {com.cy.anr}

invoke:-1, Method {java.lang.reflect}

invoke:372, Method {java.lang.reflect}

onClick:4044, View$1 {android.view}

performClick:4809, View {android.view}

run:20006, View$PerformClick {android.view}

handleCallback:739, Handler {android.os}

dispatchMessage:95, Handler {android.os}

loop:135, Looper {android.os}

main:5280, ActivityThread {android.app}

可以看到,dispatchTouchEvent是在收到InputChannel消息后的那次主线程循环中,从consumeEvents一路调用过来的。而onButtonClicked则不是,onButtonClicked是识别完touch意图后转换为另一个“事件”,在consumeEvents调用链中加入到消息队列后调度的。

接下来具体分析上述两种行为。

ANR Demo 3 —— dispatchTouchEvent 4s sleep 并快速点击

从原理可知,dispatchTouchEvent发生在InputChannel消息处理的当次主线程循环中,因此sleep dispatchTouchEvent造成的阻塞,对WaitQueue而言是“同步”的。

假设每次touch的时间间隔为300ms,3次点击的示意图如下:

以上示意图中的时间为deliveryTime,即加入(准备加入)WaitQueue的时间。

分析如下: 1. 第1次点击,没有在处理事件,可以加入;第2次点击,距离WaitQueue.head小于500ms,可以加入WaitQueue; 2. 第3次点击,超过500ms,因此放入PendingEvent,等待下次循环重试 3. 4s后,Down+0处理完成,第3次点击的Down(Down+600)现在距离WaitQueue队头4600,窗口为就绪,仍然无法加入到WaitQueue,继续等待 4. 在第5000ms时线程唤醒,发现仍然无法加入到WaitQueue,触发ANR

ANR Demo 4 —— onButtonClicked 4s sleep 并快速点击

从原理可知,onButtonClicked发生在InputChannel消息处理的下次循环,因此sleep onButtonClicked造成的阻塞,对WaitQueue而言是“异步”的。

假设每次touch的时间间隔为300ms,4次点击的示意图如下:

分析如下: 1. 第一次点击,没有在处理事件,加入后被快速处理完成,转成了onButtonClicked事件,主线程开始处理onButtonCicked。因此示意图省略了第一次点击,直接从第2次点击开始。 2. 第2、3次顺利加入WaitQueue 3. 第4次,由于deliveryTime大于WaitQueue.head 500ms,所以加入PendingEvent 4. 4s后,onButtonClicked处理完成。主线程又开始处理InputChannel消息,之前分析过consumeEvents会处理多个InputChannel消息,所以WaitQueue被清空了 5. WaitQueue被清空后,等待中的Down/Up(第4次点击)被加入WaitQueue,并又立即被处理,最后清空了WaitQueue。 6. 极限条件下,第4次点击可能没被立即处理,到下次onButtonClicked处理完成就会被处理(不是所有onButtonClicked处理完成,因为nativePollOnce每次主线程循环都执行)

因此,对于这个Demo而言,不论点击速度多快,效果上都是,WaitQueue中,tail.deliveryTime - head.deliveryTime <= 500ms, PendingEvent和InboundQueue中缓冲了未处理的事件;当主线程处理完一次循环后,WaitQueue被消化完,未处理事件也可能被消化完。不论是否未处理事件是否被消化完,继续点击,会回到WaitQueue累计近500ms事件、PendingEvent和InboundQueue中缓冲了未处理的事件的状态,如此重复。

总结如果在dispatchTouchEvent, onTouchEvent等处于consumeEvents调用链中,执行耗时操作,即使未达到5s,也可能因为事件累计导致ANR。导致ANR的条件是在有PendingEvent时,WaitQueue中累积事件的总处理时长大于5s。

如果在onButtonClicked等非consumeEvents调用链中,执行耗时操作,一定是大于5s(且有PendingEvent)才会触发ANR。

timeview未就绪_android input anr分析相关推荐

  1. TDengine 单节点Cluster not ready( 群集未就绪) 异常问题分析及解决方案

    TDengine单节点群集未就绪解决方案 问题表现 问题排查 问题解决 反思 问题表现 在开发中,创建单节点TDengine后,启动taosd服务后,经taos命令链接,jdbc链接正常,创建表空间, ...

  2. Android 系统(135)---Android anr 分析步骤总结

    Android anr 分析步骤总结 前言:最近经手了比较多的anr问题,声明经手不是解决,只是从log上推断造成anr的原因,以此作为根据转交给对应的人来处理. 1. ANR简介 ANR全名Appl ...

  3. (五十二) Android anr 分析步骤总结

    前言:最近经手了比较多的anr问题,声明经手不是解决,只是从log上推断造成anr的原因,以此作为根据转交给对应的人来处理. 1. ANR简介 ANR全名Application Not Respond ...

  4. 拉取 trace.txt 进行 anr 分析

    一.ANR 介绍 ANR的全称是application not responding,意思就是程序未响应. 首先ANR的发生是有条件限制的,分为以下三点: 1.只有主线程才会产生ANR,主线程就是UI ...

  5. java.io.IOException: 设备未就绪

    java.io.IOException: 设备未就绪.at java.io.WinNTFileSystem.canonicalize0(Native Method)at java.io.Win32Fi ...

  6. 从数据库导出Excel上线后出现IO异常:设备未就绪之解决方法

    最近做项目遇到一个问题,就是利用JavaPOI导出Excel表格时,在自己电脑上的工程上面可以使用,但是项目一上线,这个功能就报错,错误如下: 设备未就绪异常,报错的语句是createNewFile( ...

  7. linux input子系统分析--子系统核心.事件处理层.事件传递过程

    linux input子系统分析--子系统核心.事件处理层.事件传递过程 一.  输入子系统核心分析. 1.输入子系统核心对应与/drivers/input/input.c文件,这个也是作为一个模块注 ...

  8. linux input子系统分析--主要函数

    linux input子系统分析--主要函数 一. 各种注册函数 因为分析一所讲的每种数据结构都代表一类对象,所以每种数据结构都会对应一个注册函数,他们都定义在子系统核心的input.c文件中.主要有 ...

  9. linux input子系统分析--概述与数据结构

    linux input子系统分析--概述与数据结构 Input子系统处理输入事务,任何输入设备的驱动程序都可以通过Input输入子系统提供的接口注册到内核,利用子系统提供的功能来与用户空间交互.输入设 ...

最新文章

  1. 一份忧伤的大厂生存百科
  2. 利用MySQL数据库来处理中英文取首字母排序
  3. 一学即懂得计算机视觉
  4. #舍得Share#Flash Media Server4.5迅雷高速下载地址by lwxshow
  5. VS网站开发的发布部署的不同情况说明
  6. for-each 循环原理
  7. 用Python实现快速排序
  8. C# params的用法详解
  9. .NET Core 3.0 部署在docker上运行
  10. “约见”面试官系列之常见面试题之第八十五篇之css响应式(建议收藏)
  11. java数字类型_Java数据类型
  12. 中国移动全球通寻宝第四期攻略
  13. Maven学习总结(36)——Apache Maven 3.5.0抢鲜看
  14. php 增加数组下标_PHP数组排序更改下标KEY方法
  15. 一副对联,送给所有创业小公司
  16. Kubernetes 小白学习笔记(14)--k8s集群路线-kubernetes核心组件详解
  17. matlab 无法进行符号运算,无法使用syms 命令
  18. [DELPHI] 使用mod函数换行
  19. 分布式轻量级任务调度框架-XXL-JOB(最全面,附带本人实战)
  20. 杨焘鸣 杨涛鸣:怎样建立自己的人脉网络

热门文章

  1. 洛谷 P2341 [HAOI2006]受欢迎的牛
  2. JZOJ 1385. 直角三角形
  3. 关于在XP操作系统和IIS5.1环境下的MVC环境搭建之IIS错误
  4. NumPy基础操作(1)
  5. liunx新装tomcat之后,tomcat不能识别新发布的项目
  6. 别说我不懂排序!几种常见排序算法(一)
  7. centOS 安装及部署 SVN
  8. mongo 主从数据不同步
  9. Xcode7 无账号真机测试!!
  10. (转)微信公众平台开发02-接收信息及回复信息