Android 4.2 Input Event事件处理流程<一>---应用注册
来源:互联网 发布:道路里程数据库 编辑:程序博客网 时间:2024/05/29 03:53
一个应用要接受Android的各种input消息,就需要将自己注册进去,这样底层收到消息后才后将消息发给应用,应用注册要接受消息是在setView中触发的。看下这个流程:
setView由WindowManagerGlobal调用,setView是启动Activity过程中调用的:
handleLaunchActivity-->handleResumeActivity--> WindowManagerImpl.addView(decor, l)-->WindowManagerGlobal.addView(view, params, mDisplay, mParentWindow)--> root.setView(view, wparams, panelParentView);我们看一下setView
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { if (mView == null) { mView = view; mViewLayoutDirectionInitial = mView.getRawLayoutDirection(); mFallbackEventHandler.setView(view); mWindowAttributes.copyFrom(attrs); attrs = mWindowAttributes; // Keep track of the actual window flags supplied by the client. mClientWindowLayoutFlags = attrs.flags; setAccessibilityFocus(null, null); if (view instanceof RootViewSurfaceTaker) { mSurfaceHolderCallback = ((RootViewSurfaceTaker)view).willYouTakeTheSurface(); if (mSurfaceHolderCallback != null) { mSurfaceHolder = new TakenSurfaceHolder(); mSurfaceHolder.setFormat(PixelFormat.UNKNOWN); } } CompatibilityInfo compatibilityInfo = mCompatibilityInfo.get(); mTranslator = compatibilityInfo.getTranslator(); // If the application owns the surface, don't enable hardware acceleration if (mSurfaceHolder == null) { enableHardwareAcceleration(mView.getContext(), attrs); } boolean restore = false; if (mTranslator != null) { mSurface.setCompatibilityTranslator(mTranslator); restore = true; attrs.backup(); mTranslator.translateWindowLayout(attrs); } if (DEBUG_LAYOUT) Log.d(TAG, "WindowLayout in setView:" + attrs); if (!compatibilityInfo.supportsScreen()) { attrs.flags |= WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW; mLastInCompatMode = true; } mSoftInputMode = attrs.softInputMode; mWindowAttributesChanged = true; mWindowAttributesChangesFlag = WindowManager.LayoutParams.EVERYTHING_CHANGED; mAttachInfo.mRootView = view; mAttachInfo.mScalingRequired = mTranslator != null; mAttachInfo.mApplicationScale = mTranslator == null ? 1.0f : mTranslator.applicationScale; if (panelParentView != null) { mAttachInfo.mPanelParentWindowToken = panelParentView.getApplicationWindowToken(); } mAdded = true; int res; /* = WindowManagerImpl.ADD_OKAY; */ // Schedule the first layout -before- adding to the window // manager, to make sure we do the relayout before receiving // any other events from the system. requestLayout(); if ((mWindowAttributes.inputFeatures & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) { mInputChannel = new InputChannel(); } try { mOrigWindowType = mWindowAttributes.type; mAttachInfo.mRecomputeGlobalAttributes = true; collectViewAttributes(); res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mInputChannel); } catch (RemoteException e) { mAdded = false; mView = null; mAttachInfo.mRootView = null; mInputChannel = null; mFallbackEventHandler.setView(null); unscheduleTraversals(); setAccessibilityFocus(null, null); throw new RuntimeException("Adding window failed", e); } finally { if (restore) { attrs.restore(); } } if (mTranslator != null) { mTranslator.translateRectInScreenToAppWindow(mAttachInfo.mContentInsets); } mPendingContentInsets.set(mAttachInfo.mContentInsets); mPendingVisibleInsets.set(0, 0, 0, 0); if (DEBUG_LAYOUT) Log.v(TAG, "Added window " + mWindow); if (res < WindowManagerGlobal.ADD_OKAY) { mAttachInfo.mRootView = null; mAdded = false; mFallbackEventHandler.setView(null); unscheduleTraversals(); setAccessibilityFocus(null, null); switch (res) { case WindowManagerGlobal.ADD_BAD_APP_TOKEN: case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN: throw new WindowManager.BadTokenException( "Unable to add window -- token " + attrs.token + " is not valid; is your activity running?"); case WindowManagerGlobal.ADD_NOT_APP_TOKEN: throw new WindowManager.BadTokenException( "Unable to add window -- token " + attrs.token + " is not for an application"); case WindowManagerGlobal.ADD_APP_EXITING: throw new WindowManager.BadTokenException( "Unable to add window -- app for token " + attrs.token + " is exiting"); case WindowManagerGlobal.ADD_DUPLICATE_ADD: throw new WindowManager.BadTokenException( "Unable to add window -- window " + mWindow + " has already been added"); case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED: // Silently ignore -- we would have just removed it // right away, anyway. return; case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON: throw new WindowManager.BadTokenException( "Unable to add window " + mWindow + " -- another window of this type already exists"); case WindowManagerGlobal.ADD_PERMISSION_DENIED: throw new WindowManager.BadTokenException( "Unable to add window " + mWindow + " -- permission denied for this window type"); case WindowManagerGlobal.ADD_INVALID_DISPLAY: throw new WindowManager.InvalidDisplayException( "Unable to add window " + mWindow + " -- the specified display can not be found"); } throw new RuntimeException( "Unable to add window -- unknown error code " + res); } if (view instanceof RootViewSurfaceTaker) { mInputQueueCallback = ((RootViewSurfaceTaker)view).willYouTakeTheInputQueue(); } if (mInputChannel != null) { if (mInputQueueCallback != null) { mInputQueue = new InputQueue(mInputChannel); mInputQueueCallback.onInputQueueCreated(mInputQueue); } else { mInputEventReceiver = new WindowInputEventReceiver(mInputChannel, Looper.myLooper()); } } view.assignParent(this); mAddedTouchMode = (res & WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE) != 0; mAppVisible = (res & WindowManagerGlobal.ADD_FLAG_APP_VISIBLE) != 0; if (mAccessibilityManager.isEnabled()) { mAccessibilityInteractionConnectionManager.ensureConnection(); } if (view.getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { view.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); } } } }
关的逻辑主要有三处,一是调用requestLayout函数来通知InputManager,这个Activity窗口是当前被激活的窗口,二是调用sWindowSession(WindowManagerService内部类Session的远程接口)的addToDisplay成员函数来把键盘消息接收通道的一端注册在InputManager中,三是调用InputQueue的registerInputChannel成员函数来把键盘消息接收通道的另一端注册在本应用程序的消息循环(Looper)中。这样,当InputManager监控到有键盘消息时,就会先找到当前被激活的窗口,然后找到其在InputManager中对应的键盘消息接收通道,通过这个通道在InputManager中的一端来通知在应用程序消息循环中的另一端,就把键盘消息分发给当前激活的Activity窗口了。
requestLayout最终会调用到WindowManagerService的relayoutWindow
public class WindowManagerService extends IWindowManager.Stubimplements Watchdog.Monitor {......public int relayoutWindow(Session session, IWindow client,WindowManager.LayoutParams attrs, int requestedWidth,int requestedHeight, int viewVisibility, boolean insetsPending,Rect outFrame, Rect outContentInsets, Rect outVisibleInsets,Configuration outConfig, Surface outSurface) {......synchronized(mWindowMap) {......mInputMonitor.updateInputWindowsLw();}......}......}这里调用InputMonitor的updateInputWindowsLw
/* Updates the cached window information provided to the input dispatcher. */ public void updateInputWindowsLw(boolean force) { if (!force && !mUpdateInputWindowsNeeded) { return; } mUpdateInputWindowsNeeded = false; if (false) Slog.d(WindowManagerService.TAG, ">>>>>> ENTERED updateInputWindowsLw"); // Populate the input window list with information about all of the windows that // could potentially receive input. // As an optimization, we could try to prune the list of windows but this turns // out to be difficult because only the native code knows for sure which window // currently has touch focus. final WindowStateAnimator universeBackground = mService.mAnimator.mUniverseBackground; final int aboveUniverseLayer = mService.mAnimator.mAboveUniverseLayer; boolean addedUniverse = false; // If there's a drag in flight, provide a pseudowindow to catch drag input final boolean inDrag = (mService.mDragState != null); if (inDrag) { if (WindowManagerService.DEBUG_DRAG) { Log.d(WindowManagerService.TAG, "Inserting drag window"); } final InputWindowHandle dragWindowHandle = mService.mDragState.mDragWindowHandle; if (dragWindowHandle != null) { addInputWindowHandleLw(dragWindowHandle); } else { Slog.w(WindowManagerService.TAG, "Drag is in progress but there is no " + "drag window handle."); } } final int NFW = mService.mFakeWindows.size(); for (int i = 0; i < NFW; i++) { addInputWindowHandleLw(mService.mFakeWindows.get(i).mWindowHandle); } // Add all windows on the default display. final AllWindowsIterator iterator = mService.new AllWindowsIterator( WindowManagerService.REVERSE_ITERATOR); while (iterator.hasNext()) { final WindowState child = iterator.next(); final InputChannel inputChannel = child.mInputChannel; final InputWindowHandle inputWindowHandle = child.mInputWindowHandle; if (inputChannel == null || inputWindowHandle == null || child.mRemoved) { // Skip this window because it cannot possibly receive input. continue; } final int flags = child.mAttrs.flags; final int type = child.mAttrs.type; final boolean hasFocus = (child == mInputFocus); final boolean isVisible = child.isVisibleLw(); final boolean hasWallpaper = (child == mService.mWallpaperTarget) && (type != WindowManager.LayoutParams.TYPE_KEYGUARD); final boolean onDefaultDisplay = (child.getDisplayId() == Display.DEFAULT_DISPLAY); // If there's a drag in progress and 'child' is a potential drop target, // make sure it's been told about the drag if (inDrag && isVisible && onDefaultDisplay) { mService.mDragState.sendDragStartedIfNeededLw(child); } if (universeBackground != null && !addedUniverse && child.mBaseLayer < aboveUniverseLayer && onDefaultDisplay) { final WindowState u = universeBackground.mWin; if (u.mInputChannel != null && u.mInputWindowHandle != null) { addInputWindowHandleLw(u.mInputWindowHandle, u, u.mAttrs.flags, u.mAttrs.type, true, u == mInputFocus, false); } addedUniverse = true; } if (child.mWinAnimator != universeBackground) { addInputWindowHandleLw(inputWindowHandle, child, flags, type, isVisible, hasFocus, hasWallpaper); } } // Send windows to native code. mService.mInputManager.setInputWindows(mInputWindowHandles); // Clear the list in preparation for the next round. clearInputWindowHandlesLw(); if (false) Slog.d(WindowManagerService.TAG, "<<<<<<< EXITED updateInputWindowsLw"); }这里又调用到InputManagerService的setInputWindows,这里最终会通过JNI调用到NativeInputManager的setInputWindows,它有调用InputDispatcher的setInputWindows
void InputDispatcher::setInputWindows(const Vector<sp<InputWindowHandle> >& inputWindowHandles) {#if DEBUG_FOCUS ALOGD("setInputWindows");#endif { // acquire lock AutoMutex _l(mLock); Vector<sp<InputWindowHandle> > oldWindowHandles = mWindowHandles; mWindowHandles = inputWindowHandles; sp<InputWindowHandle> newFocusedWindowHandle; bool foundHoveredWindow = false; for (size_t i = 0; i < mWindowHandles.size(); i++) { const sp<InputWindowHandle>& windowHandle = mWindowHandles.itemAt(i); if (!windowHandle->updateInfo() || windowHandle->getInputChannel() == NULL) { mWindowHandles.removeAt(i--); continue; } if (windowHandle->getInfo()->hasFocus) { newFocusedWindowHandle = windowHandle; } if (windowHandle == mLastHoverWindowHandle) { foundHoveredWindow = true; } } if (!foundHoveredWindow) { mLastHoverWindowHandle = NULL; } if (mFocusedWindowHandle != newFocusedWindowHandle) { if (mFocusedWindowHandle != NULL) {#if DEBUG_FOCUS ALOGD("Focus left window: %s", mFocusedWindowHandle->getName().string());#endif sp<InputChannel> focusedInputChannel = mFocusedWindowHandle->getInputChannel(); if (focusedInputChannel != NULL) { CancelationOptions options(CancelationOptions::CANCEL_NON_POINTER_EVENTS, "focus left window"); synthesizeCancelationEventsForInputChannelLocked( focusedInputChannel, options); } } if (newFocusedWindowHandle != NULL) {#if DEBUG_FOCUS ALOGD("Focus entered window: %s", newFocusedWindowHandle->getName().string());#endif } mFocusedWindowHandle = newFocusedWindowHandle; } for (size_t i = 0; i < mTouchState.windows.size(); i++) { TouchedWindow& touchedWindow = mTouchState.windows.editItemAt(i); if (!hasWindowHandleLocked(touchedWindow.windowHandle)) {#if DEBUG_FOCUS ALOGD("Touched window was removed: %s", touchedWindow.windowHandle->getName().string());#endif sp<InputChannel> touchedInputChannel = touchedWindow.windowHandle->getInputChannel(); if (touchedInputChannel != NULL) { CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS, "touched window was removed"); synthesizeCancelationEventsForInputChannelLocked( touchedInputChannel, options); } mTouchState.windows.removeAt(i--); } } // Release information for windows that are no longer present. // This ensures that unused input channels are released promptly. // Otherwise, they might stick around until the window handle is destroyed // which might not happen until the next GC. for (size_t i = 0; i < oldWindowHandles.size(); i++) { const sp<InputWindowHandle>& oldWindowHandle = oldWindowHandles.itemAt(i); if (!hasWindowHandleLocked(oldWindowHandle)) {#if DEBUG_FOCUS ALOGD("Window went away: %s", oldWindowHandle->getName().string());#endif oldWindowHandle->releaseInfo(); } } } // release lock // Wake up poll loop since it may need to make new input dispatching choices. mLooper->wake();}这里根据窗口是否hasFocus来设置当前的newFocusedWindowHandle,即拥有焦点的窗口的句柄。这样,InputManagerService就把当前激活的Activity窗口保存在InputDispatcher中了,后面就可以把键盘消息分发给它来处理。
回到Step 1中的ViewRoot.setView函数中,接下来就调用addToDisplay册键盘消息接收通道的一端到InputManagerService中去,addToDisplay最终调用WindowManagerService的addWindow
public class WindowManagerService extends IWindowManager.Stubimplements Watchdog.Monitor {......public int addWindow(Session session, IWindow client,WindowManager.LayoutParams attrs, int viewVisibility,Rect outContentInsets, InputChannel outInputChannel) {......WindowState win = null;synchronized(mWindowMap) {......win = new WindowState(session, client, token,attachedWindow, attrs, viewVisibility);......if (outInputChannel != null) {String name = win.makeInputChannelName();InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);win.mInputChannel = inputChannels[0];inputChannels[1].transferToBinderOutParameter(outInputChannel);mInputManager.registerInputChannel(win.mInputChannel);}......}......}......}
创建输入通道之前,WindowManagerService会为当前Activity窗口创建一个WindowState对象win,用来记录这个Activity窗口的状态信息。当创建这对输入管道成功以后,也会把其中的一个管道保存在这个WindowState对象win的成员变量mInputChannel中
接下来我们就看一下InputChannel.openInputChannelPair函数的实现。最终调用到Jni层的android_view_InputChannel_nativeOpenInputChannelPair
static jobjectArray android_view_InputChannel_nativeOpenInputChannelPair(JNIEnv* env, jclass clazz, jstring nameObj) { const char* nameChars = env->GetStringUTFChars(nameObj, NULL); String8 name(nameChars); env->ReleaseStringUTFChars(nameObj, nameChars); sp<InputChannel> serverChannel; sp<InputChannel> clientChannel; status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel); if (result) { String8 message; message.appendFormat("Could not open input channel pair. status=%d", result); jniThrowRuntimeException(env, message.string()); return NULL; } jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, NULL); if (env->ExceptionCheck()) { return NULL; } jobject serverChannelObj = android_view_InputChannel_createInputChannel(env, new NativeInputChannel(serverChannel)); if (env->ExceptionCheck()) { return NULL; } jobject clientChannelObj = android_view_InputChannel_createInputChannel(env, new NativeInputChannel(clientChannel)); if (env->ExceptionCheck()) { return NULL; } env->SetObjectArrayElement(channelPair, 0, serverChannelObj); env->SetObjectArrayElement(channelPair, 1, clientChannelObj); return channelPair;}这里调用InputChannel::openInputChannelPair创建了server端和client端的通道,然后调用android_view_InputChannel_createInputChannel设置到Java里面,我们看一下openInputChannelPair
status_t InputChannel::openInputChannelPair(const String8& name, sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) { int sockets[2]; if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) { status_t result = -errno; ALOGE("channel '%s' ~ Could not create socket pair. errno=%d", name.string(), errno); outServerChannel.clear(); outClientChannel.clear(); return result; } int bufferSize = SOCKET_BUFFER_SIZE; setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize)); setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize)); setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize)); setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize)); String8 serverChannelName = name; serverChannelName.append(" (server)"); outServerChannel = new InputChannel(serverChannelName, sockets[0]); String8 clientChannelName = name; clientChannelName.append(" (client)"); outClientChannel = new InputChannel(clientChannelName, sockets[1]); return OK;}
这里通过建立一对匿名的已经连接的套接字,并对这对套接字进行一些设置。这样,这对套接字就已经创建好了,回到WindowManagerService,接下来调用setInputChannel设置到WindowState对象中,然后掉用registerInputChannel注册到inputManagerService,registerInputChannel最终调用JNI层的NativeInputManager::registerInputChannel
status_t NativeInputManager::registerInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel, const sp<InputWindowHandle>& inputWindowHandle, bool monitor) { return mInputManager->getDispatcher()->registerInputChannel( inputChannel, inputWindowHandle, monitor);}它又调用InputDispatcher的registerInputChannel
status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel, const sp<InputWindowHandle>& inputWindowHandle, bool monitor) {#if DEBUG_REGISTRATION ALOGD("channel '%s' ~ registerInputChannel - monitor=%s", inputChannel->getName().string(), toString(monitor));#endif { // acquire lock AutoMutex _l(mLock); if (getConnectionIndexLocked(inputChannel) >= 0) { ALOGW("Attempted to register already registered input channel '%s'", inputChannel->getName().string()); return BAD_VALUE; } sp<Connection> connection = new Connection(inputChannel, inputWindowHandle, monitor); int fd = inputChannel->getFd(); mConnectionsByFd.add(fd, connection); if (monitor) { mMonitoringChannels.push(inputChannel); } mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this); } // release lock // Wake the looper because some connections have changed. mLooper->wake(); return OK;}
这里通过Looper类的addFd成函数添加进去的了,在添加的时候,指定回调函数handleReceiveCallback,即当这个文件描述符所指向的文件有新的内容可读时,Looper就会调用这个hanldeReceiveCallback函数,
分析到这里,Server端的InputChannel就注册完成了,我们再看一下Client端通道的注册流程,回到setView,接下来调用
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
Looper.myLooper());
注册Client端的通道,WindowInputEventReceiver继承于InputEventReceiver,最终会调用基类的构造函数
public InputEventReceiver(InputChannel inputChannel, Looper looper) { if (inputChannel == null) { throw new IllegalArgumentException("inputChannel must not be null"); } if (looper == null) { throw new IllegalArgumentException("looper must not be null"); } mInputChannel = inputChannel; mMessageQueue = looper.getQueue(); mReceiverPtr = nativeInit(this, inputChannel, mMessageQueue); mCloseGuard.open("dispose"); }这里调用JNI函数nativeInit,nativeInit最终调用NativeInputEventReceiver的nativeInit
static jint nativeInit(JNIEnv* env, jclass clazz, jobject receiverObj, jobject inputChannelObj, jobject messageQueueObj) { sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env, inputChannelObj); if (inputChannel == NULL) { jniThrowRuntimeException(env, "InputChannel is not initialized."); return 0; } sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj); if (messageQueue == NULL) { jniThrowRuntimeException(env, "MessageQueue is not initialized."); return 0; } sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env, receiverObj, inputChannel, messageQueue); status_t status = receiver->initialize(); if (status) { String8 message; message.appendFormat("Failed to initialize input event receiver. status=%d", status); jniThrowRuntimeException(env, message.string()); return 0; } receiver->incStrong(gInputEventReceiverClassInfo.clazz); // retain a reference for the object return reinterpret_cast<jint>(receiver.get());}根据inputChannel新建一个NativeInputEventReceiver,并调用它的initialize
status_t NativeInputEventReceiver::initialize() { int receiveFd = mInputConsumer.getChannel()->getFd(); mMessageQueue->getLooper()->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, this, NULL); return OK;}这里同样调用Looper的addFd把它添加到Looper,并把回到函数设置为this,也即NativeInputEventReceiver,因为他实现了LooperCallback接口,这样应用端的消息通道就已经注册完成了
到这里通道就已经注册好了,我们后面再看input消息是是怎么传给应用的。
0 1
- Android 4.2 Input Event事件处理流程<一>---应用注册
- Android 4.2 Input Event事件处理流程<一>---inputManager启动
- Android 4.2 Input Event事件处理流程<一>事情派发
- Android 4.0 事件输入(Event Input)系统
- Android 4.0 事件输入(Event Input)系统
- Android 4.0 事件输入(Event Input)系统
- Android 4.0 事件输入(Event Input)系统
- Android 4.0 事件输入(Event Input)系统
- Android 4.0 事件输入(Event Input)系统
- Android 4.0 事件输入(Event Input)系统
- Android 4.0 事件输入(Event Input)系统
- Android 4.0 事件输入(Event Input)系统
- Android 4.0 事件输入(Event Input)系统
- Android 4.0 事件输入(Event Input)系统
- Android 4.0 事件输入(Event Input)系统
- Android 4.0 事件输入(Event Input)系统
- Android 4.0 事件输入(Event Input)系统
- Android 4.0 事件输入(Event Input)系统
- HDU 2955 Robberies
- ReportStudio入门教程(七十六) - JS拼接报表- 第2页显示表头
- 遍历Json
- hql的使用之小细节
- 关于android源码4.3 CTS测试的问题
- Android 4.2 Input Event事件处理流程<一>---应用注册
- B - Friends(8.4.1)
- 设计模式之观察者observer模式
- 关于tcpjava网络编程服务器端收不到信息
- 实模式与保护模式解惑之(一)——二者的起源与区别
- js ==与===区别(两个等号与三个等号)
- Tree Recovery(过遍历确定二叉树结构)
- 使用异步 I/O 大大提高应用程序的性能
- 华为编程大赛——高精度加减法