android 輸入法刪除輸入框裡面字元流程分析
我們先看刪除主要呼叫的方法,該方法在InputMethodService的繼承類子類中
getCurrentInputConnection().deleteSurroundingText(1, 0);
此getCurrentInputConnection方法拿到的物件是什麼呢?是InputConnectionWrapper類,進入該方法,【注意點一,後面將解釋getCurrentInputConnection()為什麼拿到的物件是InputConnectionWrapper】
public class InputConnectionWrapper implements InputConnection {
public boolean deleteSurroundingText(int leftLength, int rightLength) { Log.d(TAG, "InputConnectionWrapper deleteSurroundingText"); try { mIInputContext.deleteSurroundingText(leftLength, rightLength); return true; } catch (RemoteException e) { return false; } }
而上面的mIInputContext是IInputContext類物件,這個在哪實現的呢?【注意點二,後續將解釋,mIInputContext是在哪初始化的】
[email protected]:/work/project/a1001eh/frameworks/base$ grep -r "IInputContext.Stub" ./
./core/java/com/android/internal/view/IInputConnectionWrapper.java:public class IInputConnectionWrapper extends IInputContext.Stub {
好吧,我們進入到IInputConnectionWrapper中,進入該方法:
public void deleteSurroundingText(int leftLength, int rightLength) {
dispatchMessage(obtainMessageII(DO_DELETE_SURROUNDING_TEXT,
leftLength, rightLength));
}
繼續看此訊息的處理:
case DO_DELETE_SURROUNDING_TEXT: {
Log.w(TAG, "IInputConnectionWrapper class " + "deleteSurroundingText DO_DELETE_SURROUNDING_TEXT msg");
InputConnection ic = mInputConnection.get();
if (ic == null || !isActive()) {
Log.w(TAG, "IInputConnectionWrapper class " + "deleteSurroundingText on inactive InputConnection");
return;
}
ic.deleteSurroundingText(msg.arg1, msg.arg2);
return;
}
那麼這個mInputConnection.get()得到的是什麼呢?其實就是當前EditView的BaseInputConnection【注意點三,後面將解釋,為什麼這裡的mInputConnection.get()得到的是BaseInputConnection物件】,進入該BaseInputConnection類的deleteSurroundingText方法
public boolean deleteSurroundingText(int leftLength, int rightLength) {
if (DEBUG) Log.v(TAG, "deleteSurroundingText " + leftLength
+ " / " + rightLength);
Log.d("PateoInputMethod", "BaseInputConnection deleteSurroundingText ");
final Editable content = getEditable();
if (content == null) return false;
beginBatchEdit();
int a = Selection.getSelectionStart(content);
int b = Selection.getSelectionEnd(content);
if (a > b) {
int tmp = a;
a = b;
b = tmp;
}
// ignore the composing text.
int ca = getComposingSpanStart(content);
int cb = getComposingSpanEnd(content);
if (cb < ca) {
int tmp = ca;
ca = cb;
cb = tmp;
}
if (ca != -1 && cb != -1) {
if (ca < a) a = ca;
if (cb > b) b = cb;
}
int deleted = 0;
if (leftLength > 0) {
int start = a - leftLength;
if (start < 0) start = 0;
content.delete(start, a);
deleted = a - start;
}
if (rightLength > 0) {
b = b - deleted;
int end = b + rightLength;
if (end > content.length()) end = content.length();
content.delete(b, end);
}
endBatchEdit();
return true;
}
到這裡,我需要解釋上面的三個注意點了
第三個注意點:
【注意點三,後面將解釋,為什麼這裡的mInputConnection.get()得到的是BaseInputConnection物件】
我們先來看下mInputConnection.get()的初始化,其在IInputConnectionWrapper類中
public IInputConnectionWrapper(Looper mainLooper, InputConnection conn) {
mInputConnection = new WeakReference<InputConnection>(conn);
mMainLooper = mainLooper;
mH = new MyHandler(mMainLooper);
}
再看看IInputConnectionWrapper這個構造方法在哪被呼叫的,我們看到InputMethodManager中有個IInputConnectionWrapper的整合類中呼叫了此方法
private static class ControlledInputConnectionWrapper extends IInputConnectionWrapper {
private final InputMethodManager mParentInputMethodManager;
public ControlledInputConnectionWrapper(final Looper mainLooper, final InputConnection conn,
final InputMethodManager inputMethodManager) {
super(mainLooper, conn);
mParentInputMethodManager = inputMethodManager;
}
@Override
public boolean isActive() {
return mParentInputMethodManager.mActive;
}
}
那麼上面的super(mainLooper, conn)的呼叫處的ControlledInputConnectionWrapper構造方法又是在哪被呼叫的呢?在InputMethodManager類的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);
}
上面的servedContext = new ControlledInputConnectionWrapper(vh.getLooper(), ic, this);中的ic是在該方法中被賦值的,看下:
InputConnection ic = view.onCreateInputConnection(tba);
追蹤其view
/**
* This is the view that should currently be served by an input method,
* regardless of the state of setting that up.
*/
View mServedView;
跟蹤mServedView的賦值
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;
}
if (DEBUG) Log.v(TAG,"mServedInputConnection=" + mServedInputConnection);
ic = mServedInputConnection;
mServedView = mNextServedView;
mCurrentTextBoxAttribute = null;
mCompletions = null;
mServedConnecting = true;
}
if (ic != null) {
ic.finishComposingText();
}
return true;
}
繼續跟蹤mNextServedView
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);
}
上面這個focusInLocked(View view) 又是被誰呼叫的呢?
/**
* Call this when a view receives focus.
* @hide
*/
public void focusIn(View view) {
synchronized (mH) {
focusInLocked(view);
}
}
上面的focus方法又是在哪被呼叫的呢?是在view中,如下:
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);
}
而這個view是什麼呢?而這個this就可能是view的繼承物件比如TextView,或繼承TextView的EditText,而上面的InputConnection ic = view.onCreateInputConnection(tba);實際就是呼叫了它的子類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;
}
上面的注意下:public class EditableInputConnection extends BaseInputConnection 現在你明白了吧:mInputConnection.get()得到的就是BaseInputConnection物件
第一個注意點
注意點一,後面將解釋getCurrentInputConnection()為什麼拿到的物件是InputConnectionWrapper
當游標進入輸入框的時候,會走入初始化DO_START_INPUT訊息,在這裡進行了InputConnectionWrapper物件的生成
case DO_START_INPUT: {
Log.w(TAG, "==================================================>IInputMethodWrapper class " + " DO_START_INPUT msg");
HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj;
IInputContext inputContext = (IInputContext)args.arg1;
InputConnection ic = inputContext != null
? new InputConnectionWrapper(inputContext) : null;
EditorInfo info = (EditorInfo)args.arg2;
info.makeCompatible(mTargetSdkVersion);
inputMethod.startInput(ic, info);
return;
}
在這裡進行了new InputConnectionWrapper(inputContext)其inputContext又是哪來的呢?
public void startInput(IInputContext inputContext, EditorInfo attribute) {
mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_START_INPUT,
inputContext, attribute));
}
最終跟蹤到InputMethodManagerService類中
case MSG_START_INPUT:
Slog.d(TAG,"InputMethodManagerService class MSG_START_INPUT msg ");
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;
InputBindResult attachNewInputLocked(boolean initial) {
if (DEBUG) Slog.v(TAG, "InputMethodManagerService class" + "attachNewInputLocked method coming...,mBoundToMethod=" + mBoundToMethod + " ,initial=" + 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));
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;
InputBindResult startInputLocked(IInputMethodClient client,
IInputContext inputContext, EditorInfo attribute, int controlFlags) {
@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);
}
}
}
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);
主要:
InputConnection ic = view.onCreateInputConnection(tba);
上面還有一句
servedContext = new ControlledInputConnectionWrapper(vh.getLooper(), ic, this);
即把mServedInputConnection = ic放入了mInputConnection = new WeakReference<InputConnection>(conn);中方便後續get
private static class ControlledInputConnectionWrapper extends IInputConnectionWrapper {
private final InputMethodManager mParentInputMethodManager;
public ControlledInputConnectionWrapper(final Looper mainLooper, final InputConnection conn,
final InputMethodManager inputMethodManager) {
super(mainLooper, conn);
mParentInputMethodManager = inputMethodManager;
}
@Override
public boolean isActive() {
return mParentInputMethodManager.mActive;
}
}
public IInputConnectionWrapper(Looper mainLooper, InputConnection conn) {
mInputConnection = new WeakReference<InputConnection>(conn);
mMainLooper = mainLooper;
mH = new MyHandler(mMainLooper);
}
上面基本說清楚了,但是如果我們再擴充套件下有關IInputContextCallback.Stub,則會看到如下一些內容
public CharSequence getSelectedText(int flags) {
CharSequence value = null;
try {
InputContextCallback callback = InputContextCallback.getInstance();
mIInputContext.getSelectedText(flags, callback.mSeq, callback);
synchronized (callback) {
callback.waitForResultLocked();
if (callback.mHaveValue) {
value = callback.mSelectedText;
}
}
callback.dispose();
} catch (RemoteException e) {
return null;
}
return value;
}
我們看到callback傳入了mIInputContext.getSelectedText方法,看下下面
[email protected]:/work/project/a1001eh/frameworks/base$ grep -r "IInputContext.Stub" ./
./core/java/com/android/internal/view/IInputConnectionWrapper.java:public class IInputConnectionWrapper extends IInputContext.Stub {
public void getSelectedText(int flags, int seq, IInputContextCallback callback) {
dispatchMessage(obtainMessageISC(DO_GET_SELECTED_TEXT, flags, seq, callback));
}
case DO_GET_SELECTED_TEXT: {
SomeArgs args = (SomeArgs)msg.obj;
try {
InputConnection ic = mInputConnection.get();
if (ic == null || !isActive()) {
Log.w(TAG, "IInputConnectionWrapper class " + "getSelectedText on inactive InputConnection");
args.callback.setSelectedText(null, args.seq);
return;
}
args.callback.setSelectedText(ic.getSelectedText(
msg.arg1), args.seq);
} catch (RemoteException e) {
Log.w(TAG, "IInputConnectionWrapper class " + "Got RemoteException calling setSelectedText", e);
}
return;
}
再上面看到了又回調了setSelectedText方法
args.callback.setSelectedText(null, args.seq);
args.callback.setSelectedText(ic.getSelectedText(
msg.arg1), args.seq);
進入back這個類,看看有哪些方法:
static class InputContextCallback extends IInputContextCallback.Stub {
public int mSeq;
public boolean mHaveValue;
public CharSequence mTextBeforeCursor;
public CharSequence mTextAfterCursor;
public CharSequence mSelectedText;
public ExtractedText mExtractedText;
public int mCursorCapsMode;
// A 'pool' of one InputContextCallback. Each ICW request will attempt to gain
// exclusive access to this object.
private static InputContextCallback sInstance = new InputContextCallback();
private static int sSequenceNumber = 1;
/**
* Returns an InputContextCallback object that is guaranteed not to be in use by
* any other thread. The returned object's 'have value' flag is cleared and its expected
* sequence number is set to a new integer. We use a sequence number so that replies that
* occur after a timeout has expired are not interpreted as replies to a later request.
*/
private static InputContextCallback getInstance() {
synchronized (InputContextCallback.class) {
// Return sInstance if it's non-null, otherwise construct a new callback
InputContextCallback callback;
if (sInstance != null) {
callback = sInstance;
sInstance = null;
// Reset the callback
callback.mHaveValue = false;
} else {
callback = new InputContextCallback();
}
// Set the sequence number
callback.mSeq = sSequenceNumber++;
return callback;
}
}
/**
* Makes the given InputContextCallback available for use in the future.
*/
private void dispose() {
synchronized (InputContextCallback.class) {
// If sInstance is non-null, just let this object be garbage-collected
if (sInstance == null) {
// Allow any objects being held to be gc'ed
mTextAfterCursor = null;
mTextBeforeCursor = null;
mExtractedText = null;
sInstance = this;
}
}
}
public void setTextBeforeCursor(CharSequence textBeforeCursor, int seq) {
synchronized (this) {
if (seq == mSeq) {
mTextBeforeCursor = textBeforeCursor;
mHaveValue = true;
notifyAll();
} else {
Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
+ ") in setTextBeforeCursor, ignoring.");
}
}
}
public void setTextAfterCursor(CharSequence textAfterCursor, int seq) {
synchronized (this) {
if (seq == mSeq) {
mTextAfterCursor = textAfterCursor;
mHaveValue = true;
notifyAll();
} else {
Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
+ ") in setTextAfterCursor, ignoring.");
}
}
}
public void setSelectedText(CharSequence selectedText, int seq) {
synchronized (this) {
if (seq == mSeq) {
mSelectedText = selectedText;
mHaveValue = true;
notifyAll();
} else {
Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
+ ") in setSelectedText, ignoring.");
}
}
}
public void setCursorCapsMode(int capsMode, int seq) {
synchronized (this) {
if (seq == mSeq) {
mCursorCapsMode = capsMode;
mHaveValue = true;
notifyAll();
} else {
Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
+ ") in setCursorCapsMode, ignoring.");
}
}
}
public void setExtractedText(ExtractedText extractedText, int seq) {
synchronized (this) {
if (seq == mSeq) {
mExtractedText = extractedText;
mHaveValue = true;
notifyAll();
} else {
Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
+ ") in setExtractedText, ignoring.");
}
}
}
/**
* Waits for a result for up to {@link #MAX_WAIT_TIME_MILLIS} milliseconds.
*
* <p>The caller must be synchronized on this callback object.
*/
void waitForResultLocked() {
long startTime = SystemClock.uptimeMillis();
long endTime = startTime + MAX_WAIT_TIME_MILLIS;
while (!mHaveValue) {
long remainingTime = endTime - SystemClock.uptimeMillis();
if (remainingTime <= 0) {
Log.w(TAG, "Timed out waiting on IInputContextCallback");
return;
}
try {
wait(remainingTime);
} catch (InterruptedException e) {
}
}
}
}
相關推薦
android 輸入法刪除輸入框裡面字元流程分析
我們先看刪除主要呼叫的方法,該方法在InputMethodService的繼承類子類中 getCurrentInputConnection().deleteSurroundingText(1, 0);此getCurrentInputConnection方法拿到的物件是
Android 最多輸入30個字元就不能輸入,彈出提示框提醒
android顯示edittext最多輸入字元,Android 中的EditText最大可輸入字元數可以通過xml檔案中為EditText設定maxLength屬性或者在程式碼中為EditText設定L
移動端輸入法擋住輸入框
load lin onload ice spa red init event -c 問題:在移動端火狐瀏覽下,輸入框鍵盤遮擋住 解決方案: ? element.scrollIntoView():讓元素滾動到可視區域? 參數:true 對象的頂端與當前窗口的頂部對齊???
處理安卓手機輸入法遮擋輸入框問題
// 處理安卓手機輸入法遮擋輸入框問題 if ((/Android/gi).test(navigator.userAgent)) { window.addEventListener('resize', f
Android四方形輸入框、密碼框
Android四方形輸入框、密碼框 一、自定義view繼承EditText,程式碼中控制輸入框的個數和是否為可見密碼格式 package com.example.testcostumview.inputbox; import android.content
react實現刪除輸入框內容
react中實現刪除輸入框中的內容 import React,{Component} from 'react' class Clear extends Component{ constructor(props){ super(props) this.state={ data:
Android 支付密碼輸入框,自定義EditText實現密碼輸入框功能;
剛擼出來的密碼輸入框,註釋和邏輯看著挺清晰的,一些屬性還沒有新增,下個部落格把屬性新增上去; 看一下圖: 直接看程式碼吧! import android.content.Context; import android.graphics.Canvas; import android.
android 讓EditText輸入框失去焦點(親測可用)
在顯示一些記錄的時候,不希望EditText有游標,也就是失去焦點,可以通過呼叫edittext.clearFocus()來實現,但clearFocus()的會將焦點移到Activity的第一個View。如果不幸你的EditText就是第一個View,那麼單純地呼叫clear
Android開發之輸入框EditText介紹
這篇文章主要為大家詳細介紹了Android佈局之輸入框EditText設計,具有一定的參考價值,感興趣的小夥伴們可以參考一下 現在先簡單介紹一下技術點: 1.如何使用圓角輸入框和按鈕背景 2.如何實現“手機號”、“密碼”後面的豎線 3.如何巢狀輸入框的佈局 4.
Android自定義輸入框
public class InputCustomDialog extends Dialog { public InputCustomDialog(Context context, int theme) { super(context, theme);
Android 按鈕以及輸入框的樣式
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="recta
Android自定義輸入框樣式
資料來自:菜鳥教程自行編寫一個ShapeDrawable的資原始檔!然後TextView將blackgroung 設定為這個drawable資源即可!shapeDrawable資原始檔的幾個節點以及屬性
jQuery實現動態新增刪除輸入框
在一個專案中,一個實體需要動態新增另外的實體多個,即在新增或者編輯這個實體的時候,需要動態新增輸入框。利用jQuery簡單實現了一下,小有成就感。 <%if(isAdd){%> <div class="am-g am-form-group am-mar
ios textView 輸入時,輸入框裡面的文字上下浮動Bug
在寫一個類似微信聊天框的功能時,遇到了這個bug 最後的解決是在無意中發現的 頁面中 有一個tableView 和 一個 view 組成,view 上面有個聊天框 就是textView view 應該是在上面的 也是無心 在把檢視加到view上面時 [self.view
JS學習筆記 - fgm練習 - 限制輸入框的字元型別 正則 和 || 或運算子的運用 i++和++i
<script> window.onload = function(){ var aInp = document.getElementsByTagName('input'); var oSum = document.getElemen
對頁面不能為空的檢驗,在輸入框裡面提示不能為空的封裝
<form action=""> <div class="group"> <label for="">留言內容:<
iOS 限制輸入框的字元長度
目的達到長度可以輸入,但是顯示和獲取的資料永遠都是限定的長度 1、對textfield進行監聽 [TextFieldaddTarget:selfaction:@selector(textFie
解決pycharm/Webstorm中文輸入法游標/輸入框不能跟隨問題
系統環境:win10 64Bit IDE:Pycharm 2018.1.1(Professional Edition) 輸入法:搜狗智慧輸入法 原因其實還是Pycharm自帶JVM執行環境的鍋,大家沒有更改Pycharm自帶jvm之前紅框內應該是,下面圖片
輸入框裡面的值通過正則匹配改變導致的游標問題(坑!!)
前言需求是這樣的,輸入加油卡號,每隔輸入4位自動加上一個橫槓,如圖:這個游標問題是個坑,,加班到10點還沒解決好。。。解決方法首先,這裡我使用的方法是監聽輸入,使用正則匹配。 <p className="addcard_tip">卡號:</p> &l
UITextField輸入框中是否有個叉號(用於一次性刪除輸入框中的內容)
//輸入框中是否有個叉號,在什麼時候顯示,用於一次性刪除輸入框中的內容 text.clearButtonMode = UITextFieldViewModeAlways; typedef enum { UITextFieldViewModeNever,