android 【点击输入框调出输入法前的】输入框获取焦点和输入法的初始化分析



进入一个界面,通过一个buttuon点击打开一个输入框,比如无线网络设置蓝牙名称编辑框,这个时候编辑框会默认有焦点,就会默认调用焦点变化的函数TextViewon  FocusChanged方法如下:

    @Override
    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
      	Log.d(TAG, "TextView class onFocusChanged method");
        if (mTemporaryDetach) {
            // If we are temporarily in the detach state, then do nothing.
            super.onFocusChanged(focused, direction, previouslyFocusedRect);
            return;
        }

进入super.onFocusChanged方法,如下:

   /**
     * Called by the view system when the focus state of this view changes.
     * When the focus change event is caused by directional navigation, direction
     * and previouslyFocusedRect provide insight into where the focus is coming from.
     * When overriding, be sure to call up through to the super class so that
     * the standard focus handling will occur.
     *
     * @param gainFocus True if the View has focus; false otherwise.
     * @param direction The direction focus has moved when requestFocus()
     *                  is called to give this view focus. Values are
     *                  {@link #FOCUS_UP}, {@link #FOCUS_DOWN}, {@link #FOCUS_LEFT},
     *                  {@link #FOCUS_RIGHT}, {@link #FOCUS_FORWARD}, or {@link #FOCUS_BACKWARD}.
     *                  It may not always apply, in which case use the default.
     * @param previouslyFocusedRect The rectangle, in this view's coordinate
     *        system, of the previously focused view.  If applicable, this will be
     *        passed in as finer grained information about where the focus is coming
     *        from (in addition to direction).  Will be null otherwise.
     */
    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
        if (gainFocus) {
            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
        }

        InputMethodManager imm = InputMethodManager.peekInstance();
        if (!gainFocus) {
            if (isPressed()) {
                setPressed(false);
            }
            if (imm != null && mAttachInfo != null
                    && mAttachInfo.mHasWindowFocus) {
              	Log.d(TAG, "View class onFocusChanged method InputMethodManager focusOut");
                imm.focusOut(this);
            }
            onFocusLost();
        } else if (imm != null && mAttachInfo != null
                && mAttachInfo.mHasWindowFocus) {
        	Log.d(TAG, "View class onFocusChanged method InputMethodManager focusIn");
            imm.focusIn(this);
        }

        invalidate(true);
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnFocusChangeListener != null) {
            li.mOnFocusChangeListener.onFocusChange(this, gainFocus);
        }

        if (mAttachInfo != null) {
            mAttachInfo.mKeyDispatchState.reset(this);
        }
    }
日志:View class onFocusChanged method InputMethodManager focusIn,从日志来看是调用了imm.focusIn(this);方法,进入该方法,如下:

    /**
     * Call this when a view receives focus.
     * @hide
     */
    public void focusIn(View view) {
        synchronized (mH) {
            focusInLocked(view);
        }
    }

    void focusInLocked(View view) {
        if (DEBUG) Log.v(TAG,"InputMethodManager class " +  "focusIn: " + view);
        
        if (mCurRootView != view.getRootView()) {
            // This is a request from a window that isn't in the window with
            // IME focus, so ignore it.
            if (DEBUG) Log.v(TAG,"InputMethodManager class " +  "Not IME target window, ignoring");
            return;
        }
        
        mNextServedView = view;
        scheduleCheckFocusLocked(view);
    }
输出日志:InputMethodManager class focusIn: com.pateo.as.settings.view.AsEditText@418c5850
    void scheduleCheckFocusLocked(View view) {
        Handler vh = view.getHandler();
        if (vh != null && !vh.hasMessages(ViewRootImpl.CHECK_FOCUS)) {
            // This will result in a call to checkFocus() below.
            vh.sendMessage(vh.obtainMessage(ViewRootImpl.CHECK_FOCUS));
        }
    }

我们来看这个CHECK_FOCUS的处理,在相应的类ViewRootImpl中

        case CHECK_FOCUS: {
            InputMethodManager imm = InputMethodManager.peekInstance();
            if (imm != null) {
                imm.checkFocus();
            }
        } break;

在这里我们看到它继续调用了checkFocus,我们进入该方法

    public void checkFocus() {
        if (checkFocusNoStartInput(false)) {
          	Log.d(TAG, "InputMethodManager class checkFocus method");
            startInputInner(null, 0, 0, 0);
        }
    }

    private boolean checkFocusNoStartInput(boolean forceNewFocus) {
        // This is called a lot, so short-circuit before locking.
        if (mServedView == mNextServedView && !forceNewFocus) {
            return false;
        }

        InputConnection ic = null;
        synchronized (mH) {
            if (mServedView == mNextServedView && !forceNewFocus) {
                return false;
            }
            if (DEBUG) Log.v(TAG,"InputMethodManager class " +  "checkFocus: view=" + mServedView
                    + " next=" + mNextServedView
                    + " forceNewFocus=" + forceNewFocus);

            if (mNextServedView == null) {
                finishInputLocked();
                // In this case, we used to have a focused view on the window,
                // but no longer do.  We should make sure the input method is
                // no longer shown, since it serves no purpose.
                closeCurrentInput();
                return false;
            }

            ic = mServedInputConnection;

            mServedView = mNextServedView;
            mCurrentTextBoxAttribute = null;
            mCompletions = null;
            mServedConnecting = true;
        }

        if (ic != null) {
            ic.finishComposingText();
        }

        return true;

我们先在此停顿下,来梳理下上面的方面里面的一些内容,先看日志:

01-01 09:39:14.160 V/PateoInputMethod( 1806): InputMethodManager class checkFocus: view=com.android.internal.policy.impl.PhoneWindow$DecorView@41902530 next=com.pateo.as.settings.view.AsEditText@418c5850 forceNewFocus=false
01-01 09:39:14.160 V/PateoInputMethod( 1806): mServedInputConnection=null

从日志来看InputConnection类型的mServedInputConnection还没有被赋值,重要的是mNextServedView这个不为null是com.pateo.as.settings.view.AsEditText,所以这个时候不用去关闭输入法,看到流程最后返回true,当为true被返回后,执行上面的startInputInner方法,进入该方法

    boolean startInputInner(IBinder windowGainingFocus, int controlFlags, int softInputMode,
            int windowFlags) {
    	
        final View view;
        synchronized (mH) {
            view = mServedView;
            
            // Make sure we have a window token for the served view.
            if (DEBUG) Log.v(TAG,"InputMethodManager class " +  "Starting input: view=" + view);
            if (view == null) {
                if (DEBUG) Log.v(TAG,"InputMethodManager class " +  "ABORT input: no served view!");
                return false;
            }
        }
        
        // Now we need to get an input connection from the served view.
        // This is complicated in a couple ways: we can't be holding our lock
        // when calling out to the view, and we need to make sure we call into
        // the view on the same thread that is driving its view hierarchy.
        Handler vh = view.getHandler();
        if (vh == null) {
            // If the view doesn't have a handler, something has changed out
            // from under us, so just bail.
            if (DEBUG) Log.v(TAG,"InputMethodManager class " +  "ABORT input: no handler for view!");
            return false;
        }
        if (vh.getLooper() != Looper.myLooper()) {
            // The view is running on a different thread than our own, so
            // we need to reschedule our work for over there.
            if (DEBUG) Log.v(TAG,"InputMethodManager class " +  "Starting input: reschedule to view thread");
            vh.post(new Runnable() {
                public void run() {
                	 Log.d(TAG, "InputMethodManager class startInputInner method");
                    startInputInner(null, 0, 0, 0);
                }
            });
            return false;
        }
        
        // Okay we are now ready to call into the served view and have it
        // do its stuff.
        // Life is good: let's hook everything up!
        EditorInfo tba = new EditorInfo();
        tba.packageName = view.getContext().getPackageName();
        tba.fieldId = view.getId();
        InputConnection ic = view.onCreateInputConnection(tba);
        if (DEBUG) Log.v(TAG,"InputMethodManager class " +  "Starting input: tba=" + tba + " ic=" + ic);
        
        synchronized (mH) {
            // Now that we are locked again, validate that our state hasn't
            // changed.
            if (mServedView != view || !mServedConnecting) {
                // Something else happened, so abort.
                if (DEBUG) Log.v(TAG,"InputMethodManager class " +  
                        "Starting input: finished by someone else (view="
                        + mServedView + " conn=" + mServedConnecting + ")");
                return false;
            }

            // If we already have a text box, then this view is already
            // connected so we want to restart it.
            if (mCurrentTextBoxAttribute == null) {
                controlFlags |= CONTROL_START_INITIAL;
            }
            
            // Hook 'em up and let 'er rip.
            mCurrentTextBoxAttribute = tba;
            mServedConnecting = false;
            mServedInputConnection = ic;
            IInputContext servedContext;
            if (ic != null) {
                mCursorSelStart = tba.initialSelStart;
                mCursorSelEnd = tba.initialSelEnd;
                mCursorCandStart = -1;
                mCursorCandEnd = -1;
                mCursorRect.setEmpty();
                servedContext = new ControlledInputConnectionWrapper(vh.getLooper(), ic, this);
            } else {
                servedContext = null;
            }
            
            try {
                if (DEBUG) Log.v(TAG,"InputMethodManager class " +  "START INPUT: " + view + " ic="
                        + ic + " tba=" + tba + " controlFlags=#"
                        + Integer.toHexString(controlFlags));
                InputBindResult res;
                if (windowGainingFocus != null) {
                    res = mService.windowGainedFocus(mClient, windowGainingFocus,
                            controlFlags, softInputMode, windowFlags,
                            tba, servedContext);
                } else {
                    res = mService.startInput(mClient,
                            servedContext, tba, controlFlags);
                }
                if (DEBUG) Log.v(TAG,"InputMethodManager class " +  "Starting input: Bind result=" + res);
                if (res != null) {
                    if (res.id != null) {
                        mBindSequence = res.sequence;
                        mCurMethod = res.method;
                    } else if (mCurMethod == null) {
                        // This means there is no input method available.
                        if (DEBUG) Log.v(TAG,"InputMethodManager class " +  "ABORT input: no input method!");
                        return true;
                    }
                }
                if (mCurMethod != null && mCompletions != null) {
                    try {
                        mCurMethod.displayCompletions(mCompletions);
                    } catch (RemoteException e) {
                    }
                }
            } catch (RemoteException e) {
                Log.w(TAG,"InputMethodManager class " +  "IME died: " + mCurId, e);
            }
        }

        return true;
    }

看流程日志:InputMethodManager class Starting input: view=com.pateo.as.settings.view.AsEditText@418c5850

紧接着看到如下两行日志:

01-01 09:39:14.160 D/PateoInputMethod( 1806): TextView class onCreateInputConnection method start
01-01 09:39:14.160 D/PateoInputMethod( 1806): TextView class onCreateInputConnection return EditableInputConnection

好吧,从日志来看,是执行了如下方法:

InputConnection ic = view.onCreateInputConnection(tba);
此处的view我们已经从上面看出是AsEditText,public class AsEditText extends EditText,它是个EditText,最终回到了EditText的父类TextView,进入到TextView类的onCreateInputConnection方法中

   @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
    	
    	Log.d(TAG, "TextView class onCreateInputConnection method start");
    	
        if (onCheckIsTextEditor() && isEnabled()) {
            if (mInputMethodState == null) {
                mInputMethodState = new InputMethodState();
            }
            outAttrs.inputType = mInputType;
            if (mInputContentType != null) {
                outAttrs.imeOptions = mInputContentType.imeOptions;
                outAttrs.privateImeOptions = mInputContentType.privateImeOptions;
                outAttrs.actionLabel = mInputContentType.imeActionLabel;
                outAttrs.actionId = mInputContentType.imeActionId;
                outAttrs.extras = mInputContentType.extras;
            } else {
                outAttrs.imeOptions = EditorInfo.IME_NULL;
            }
//            if (focusSearch(FOCUS_DOWN) != null) {
//                outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
//            }
//            if (focusSearch(FOCUS_UP) != null) {
//                outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
//            }
//            if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION)
//                    == EditorInfo.IME_ACTION_UNSPECIFIED) {
//                if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
//                    // An action has not been set, but the enter key will move to
//                    // the next focus, so set the action to that.
//                    outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
//                } else {
//                    // An action has not been set, and there is no focus to move
//                    // to, so let's just supply a "done" action.
//                    outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
//                }
//                if (!shouldAdvanceFocusOnEnter()) {
//                    outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
//                }
//            }
//            if (isMultilineInputType(outAttrs.inputType)) {
//                // Multi-line text editors should always show an enter key.
//                outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
//            }
            outAttrs.hintText = mHint;
            if (mText instanceof Editable) {
            	Log.d(TAG, "TextView class onCreateInputConnection return EditableInputConnection");
                InputConnection ic = new EditableInputConnection(this);
                outAttrs.initialSelStart = getSelectionStart();
                outAttrs.initialSelEnd = getSelectionEnd();
                outAttrs.initialCapsMode = ic.getCursorCapsMode(mInputType);
                return ic;
            }
        }
        return null;
    }

上面代码最重要的是InputConnection ic = new EditableInputConnection(this); return ic;这样就返回了一个InputConnection的子类实现对象EditableInputConnection,而这个EditableInputConnection有个很重要的方法commitText,暂时性记忆这点

跟着我们进行看输出的日志:

01-01 09:39:14.160 V/PateoInputMethod( 1806): InputMethodManager class Starting input: tba=android.view.inputmethod.EditorInfo@41bc3c28 ic=com.android.internal.widget.EditableInputConnection@41bc3f00
01-01 09:39:14.160 V/PateoInputMethod( 1806): InputMethodManager class START INPUT: com.pateo.as.settings.view.AsEditText@418c5850 ic=com.android.internal.widget.EditableInputConnection@41bc3f00 tba=android.view.inputmethod.EditorInfo@41bc3c28 controlFlags=#100

因为windowGainingFocus是传过来的null,所以调用了如下代码:

 res = mService.startInput(mClient,
                            servedContext, tba, controlFlags);

这个时候去启动输入法,这里的mService是InputMethodManagerService,进入该方法:

   @Override
    public InputBindResult startInput(IInputMethodClient client,
            IInputContext inputContext, EditorInfo attribute, int controlFlags) {
        synchronized (mMethodMap) {
            final long ident = Binder.clearCallingIdentity();
            try {
                return startInputLocked(client, inputContext, attribute, controlFlags);
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
        }
    }

    InputBindResult startInputLocked(IInputMethodClient client,
            IInputContext inputContext, EditorInfo attribute, int controlFlags) {
        // If no method is currently selected, do nothing.
        if (mCurMethodId == null) {
            return mNoBinding;
        }

        ClientState cs = mClients.get(client.asBinder());
        if (cs == null) {
            throw new IllegalArgumentException("unknown client "
                    + client.asBinder());
        }

        try {
        	
//        	boolean isInputMethodClientHasFocus = mIWindowManager.inputMethodClientHasFocus(cs.client);
//        	
//        	android.util.Log.d(TAG, "InputMethodManagerService class" +  "remove judge focus return null when focos false, isInputMethodClientHasFocus=" + isInputMethodClientHasFocus); 
        	
            if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) {
                // Check with the window manager to make sure this client actually
                // has a window with focus.  If not, reject.  This is thread safe
                // because if the focus changes some time before or after, the
                // next client receiving focus that has any interest in input will
                // be calling through here after that change happens.
                Slog.w(TAG, "InputMethodManagerService class" +  "Starting input on non-focused client " + cs.client
                        + " (uid=" + cs.uid + " pid=" + cs.pid + ")");
                return null;
            }
        } catch (RemoteException e) {
        }

        return startInputUncheckedLocked(cs, inputContext, attribute, controlFlags);
    }

    InputBindResult startInputUncheckedLocked(ClientState cs,
            IInputContext inputContext, EditorInfo attribute, int controlFlags) {
        // If no method is currently selected, do nothing.
        if (mCurMethodId == null) {
            return mNoBinding;
        }

        if (mCurClient != cs) {
            // If the client is changing, we need to switch over to the new
            // one.
            unbindCurrentClientLocked();
            if (DEBUG) Slog.v(TAG, "InputMethodManagerService class" +  "switching to client: client = "
                    + cs.client.asBinder());

            // If the screen is on, inform the new client it is active
            if (mScreenOn) {
                try {
                    cs.client.setActive(mScreenOn);
                } catch (RemoteException e) {
                    Slog.w(TAG, "InputMethodManagerService class" +  "Got RemoteException sending setActive notification to pid "
                            + cs.pid + " uid " + cs.uid);
                }
            }
        }

        // Bump up the sequence for this client and attach it.
        mCurSeq++;
        if (mCurSeq <= 0) mCurSeq = 1;
        mCurClient = cs;
        mCurInputContext = inputContext;
        mCurAttribute = attribute;

        // Check if the input method is changing.
        if (mCurId != null && mCurId.equals(mCurMethodId)) {
            if (cs.curSession != null) {
                // Fast case: if we are already connected to the input method,
                // then just return it.
                return attachNewInputLocked(
                        (controlFlags&InputMethodManager.CONTROL_START_INITIAL) != 0);
            }

看到最后的return attachNewInputLocked方法

    InputBindResult attachNewInputLocked(boolean initial) {
        if (!mBoundToMethod) {
            executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
                    MSG_BIND_INPUT, mCurMethod, mCurClient.binding));
            mBoundToMethod = true;
        }
        final SessionState session = mCurClient.curSession;
        if (initial) {
            executeOrSendMessage(session.method, mCaller.obtainMessageOOO(
                    MSG_START_INPUT, session, mCurInputContext, mCurAttribute));
        } else {
            executeOrSendMessage(session.method, mCaller.obtainMessageOOO(
                    MSG_RESTART_INPUT, session, mCurInputContext, mCurAttribute));
        }
        if (mShowRequested) {
            if (DEBUG) Slog.v(TAG, "InputMethodManagerService class" +  "Attach new input asks to show input");
            showCurrentInputLocked(getAppShowFlags(), null);
        }
        return new InputBindResult(session.session, mCurId, mCurSeq);
    }

上面的代码跟踪,做后进入到了MSG_START_INPUT这个方法中,我们进行跟进

          case MSG_START_INPUT:
                args = (HandlerCaller.SomeArgs)msg.obj;
                try {
                    SessionState session = (SessionState)args.arg1;
                    setEnabledSessionInMainThread(session);
                    session.method.startInput((IInputContext)args.arg2,
                            (EditorInfo)args.arg3);
                } catch (RemoteException e) {
                }
                return true;
最终进入到了InputMethodService类的startInput方法中

        public void startInput(InputConnection ic, EditorInfo attribute) {
            if (DEBUG) Log.v(TAG,"InputMethodService class" +  "startInput(): editor=" + attribute);
            doStartInput(ic, attribute, false);
        }

    void doStartInput(InputConnection ic, EditorInfo attribute, boolean restarting) {
        if (!restarting) {
            doFinishInput();
        }
        mInputStarted = true;
        mStartedInputConnection = ic;
        mInputEditorInfo = attribute;
        initialize();
        if (DEBUG) Log.v(TAG,"InputMethodService class" +  "CALL: onStartInput");
        onStartInput(attribute, restarting);
        if (mWindowVisible) {
            if (mShowInputRequested) {
                if (DEBUG) Log.v(TAG,"InputMethodService class" +  "CALL: onStartInputView");
                mInputViewStarted = true;
                onStartInputView(mInputEditorInfo, restarting);
                startExtractingText(true);
            } else if (mCandidatesVisibility == View.VISIBLE) {
                if (DEBUG) Log.v(TAG,"InputMethodService class" +  "CALL: onStartCandidatesView");
                mCandidatesViewStarted = true;
                onStartCandidatesView(mInputEditorInfo, restarting);
            }
        }
    }

看下相应的日志:

01-01 09:39:14.160 V/PateoInputMethod( 1461): InputMethodService classstartInput(): editor=android.view.inputmethod.EditorInfo@418f80b0
01-01 09:39:14.160 V/PateoInputMethod( 1461): InputMethodService classCALL: onFinishInput

进入了doFinishInput方法

   void doFinishInput() {
        if (mInputViewStarted) {
            if (DEBUG) Log.v(TAG,"InputMethodService class" +  "CALL: onFinishInputView");
            onFinishInputView(true);
        } else if (mCandidatesViewStarted) {
            if (DEBUG) Log.v(TAG,"InputMethodService class" +  "CALL: onFinishCandidatesView");
            onFinishCandidatesView(true);
        }
        mInputViewStarted = false;
        mCandidatesViewStarted = false;
        if (mInputStarted) {
            if (DEBUG) Log.v(TAG,"InputMethodService class" +  "CALL: onFinishInput");
            onFinishInput();
        }
        mInputStarted = false;
        mStartedInputConnection = null;
        mCurCompletions = null;
    }

到这里结束了,需要进一步回头看一些参数,下面再分析









你可能感兴趣的:(Framework,android)