输入法浅谈

发布时间:2022-11-28 自然语言处理 ANDROID 输入法

一、输入法介绍

输入法是指将各种文字/符号等信息输入电子信息设备而采用的编码方式,例如我们常用的拼音输入法、手写输入法、语音输入法等都是在完成这一过程。通俗的来讲输入法是一种已经渗透到我们生活方方面面,各行各业的基本人机交互方式:

输入法场景特点
拼音输入法/五笔输入法/仓颉输入法电脑手机等设备打字、发邮件简单、高效、手打
手写输入法老人机笔迹识别、直打
语音输入法车载智能语音交互简单、直说、高效

从上面可以看出输入法根据运用场景的不同,其功能特点也有所不同,输入法可以进行以下分类:

  • 根据输入方式和特点分类:
    输入法分类【输入特点】

  • 根据语系/语种分类:
    输入法分类【输入语系/种】

  • 根据键盘布局特点
    输入法分类【键盘布局】

而我们最为熟悉的中文输入法根据编码原理的特点可进行以下分类
中文输入法【编码方式】
可以说虽然输入使用方便,操作简单,但其背后的实现技术复杂且巧妙。
(想进一步了解输入法如何对应各种字符可查看《字符编码介绍》, 进一步了解几种常见中文输入法实现可查看《中文输入法浅谈》)

以百度输入法app为例
百度输入法界面
输入法可分为以下几个组件:

输入法组件

输入法软件除了实现输入字符的基本功能外,往往还集成了其他功能,以提升其效率和体验,主流输入法软件功能汇总如下:
输入法功能举例

二、Android 10(Q) 输入法框架(IMF)介绍

简单介绍输入法基本概念功能,接下来看看Android平台输入法功能的相关实现技术。
所涉及源码及路径如下:

frameworks\base\core\java\android\inputmethodservice\InputMethodService.java
frameworks\base\core\java\android\view\ViewRootImpl.java
frameworks\base\core\java\android\view\WindowManagerGlobal.java
frameworks\base\core\java\android\view\inputmethod\InputMethodManager.java
frameworks\base\core\java\android\widget\TextView.java
frameworks\base\core\java\android\view\inputmethod\InputMethod.java
frameworks\base\core\java\android\inputmethodservice\SoftInputWindow.java
frameworks\base\core\java\com\android\internal\view\IInputSessionCallback.aidl
frameworks\base\services\core\java\com\android\server\inputmethod\InputMethodManagerService.java
frameworks\base\services\core\java\com\android\server\wm\WindowState.java
packages\inputmethods\PinyinIME\AndroidManifest.xml
packages\inputmethods\PinyinIME\src\com\android\inputmethod\pinyin\PinyinIME.java

2.1 IMF架构

IMF(Input Method Framework)即输入法框架,是Android 1.5添加的重要功能,用来支持软键盘和各种输入法。
IMF有三个部分组成:IMMS、IMS、IMM

IMF整体框架如下图:
IMF交互

2.1.1 InputMethodManagerService(IMMS)输入法管理服务

IMMS负责管理系统的所有输入法,包括输入法的加载及切换。

2.1.2 InputMethodService(IMS)输入法服务

IMS即输入法应用,例如LatinIME、PinyinIME应用(本质是IMS的一个实现类),实现输入法界面,控制字符输入等。
以PinyinIME为例:
packages\inputmethods\PinyinIME\AndroidManifest.xml

	 		<service android:name=".PinyinIME"
                android:label="@string/ime_name"
                    android:permission="android.permission.BIND_INPUT_METHOD">
                <intent-filter>
                    <action android:name="android.view.InputMethod" />
                </intent-filter>
                <meta-data android:name="android.view.im" android:resource="@xml/method" />
            </service>

packages\inputmethods\PinyinIME\src\com\android\inputmethod\pinyin\PinyinIME.java

/**
 * Main class of the Pinyin input method.
 */
public class PinyinIME extends InputMethodService {//PinyinIME继承IMS
	...
}

IMS类图结构如下:
IMS类图
由此可以看出PinyinIME输入法应用是一个带Dialog 的service。

2.1.3 InputMethodManager(IMM)输入法客户端

IMM即通常带有EditView的app应用,使用IMM来发起显示/隐藏输入法的请求,也可以配置输入法的一些属性,是app与IMMS通信的接口。每个程序有一个IMM的实例,在ViewRootImlp初始化时创建:
frameworks\base\core\java\android\view\ViewRootImpl.java

    public ViewRootImpl(Context context, Display display) {
        mContext = context;
        mWindowSession = WindowManagerGlobal.getWindowSession();
        ...
    }

frameworks\base\core\java\android\view\WindowManagerGlobal.java

    @UnsupportedAppUsage
    public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
                    // Emulate the legacy behavior.  The global instance of InputMethodManager
                    // was instantiated here.
                    InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();//准备创建IMM实例
                    ...
    }

frameworks\base\core\java\android\view\inputmethod\InputMethodManager.java

    public static void ensureDefaultInstanceForDefaultDisplayIfNecessary() {
        forContextInternal(Display.DEFAULT_DISPLAY, Looper.getMainLooper());
    }

    @NonNull
    private static InputMethodManager forContextInternal(int displayId, Looper looper) {
        final boolean isDefaultDisplay = displayId == Display.DEFAULT_DISPLAY;
        synchronized (sLock) {
            InputMethodManager instance = sInstanceMap.get(displayId);//从缓存Map中查找默认IMM
            if (instance != null) {
                return instance;
            }
            instance = createInstance(displayId, looper);//没有默认IMM则创建新的IMM实例
            // For backward compatibility, store the instance also to sInstance for default display.
            if (sInstance == null && isDefaultDisplay) {
                sInstance = instance;//如果当前创建的IMM是用于默认的显示,则用作全局单例实例
            }
            sInstanceMap.put(displayId, instance);//缓存到Map中
            return instance;
        }
    }

    @NonNull
    private static InputMethodManager createInstance(int displayId, Looper looper) {
        return isInEditMode() ? createStubInstance(displayId, looper)
                : createRealInstance(displayId, looper);
    }
    private static boolean isInEditMode() {	//对于layoutlib,要覆盖此方法以返回{@code true}
        return false;
    }

    @NonNull
    private static InputMethodManager createRealInstance(int displayId, Looper looper) {
        final IInputMethodManager service;
        try {
            service = IInputMethodManager.Stub.asInterface(	//获取输入法服务
                    ServiceManager.getServiceOrThrow(Context.INPUT_METHOD_SERVICE));
        } catch (ServiceNotFoundException e) {
            throw new IllegalStateException(e);
        }
        //创建IMM 实例
        final InputMethodManager imm = new InputMethodManager(service, displayId, looper);
        final long identity = Binder.clearCallingIdentity();
        try {
        	//将IMM实例添加到输入法服务
        	//imm.mClient是一个IInputMethodClient.aidl的实例,IMMS通过它告知IMM IInputMethodSession
        	//imm.mIInputContext是一个IInputContext.aidl的实例
            service.addClient(imm.mClient, imm.mIInputContext, displayId);
        } catch (RemoteException e) {
            e.rethrowFromSystemServer();
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
        return imm;
    }

2.2 输入法启动

这里以常见的EditText调出输入法界面为例,输入法的启动分为以下几个过程:

  • 控件获取焦点,通过IMM向IMMS请求绑定输入法
  • IMMS处理IMM请求,view绑定输入法
    整体启动时序图如下:
    在这里插入图片描述

2.2.1 控件获取焦点

一个app或控件要想启动IME,首先需要获取焦点。当打开需要调出IME的某个App时,焦点开始变更,我们从WindowState分发焦点开始往下梳理:
frameworks\base\services\core\java\com\android\server\wm\WindowState.java

    /**
     * Report a focus change.  Must be called with no locks held, and consistently
     * from the same serialized thread (such as dispatched from a handler).
     */
    void reportFocusChangedSerialized(boolean focused, boolean inTouchMode) {
        try {
            mClient.windowFocusChanged(focused, inTouchMode);//通知app的IWindow焦点改变
        } catch (RemoteException e) {
        }
        ...
    }

frameworks\base\core\java\android\view\ViewRootImpl.java

    public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
        synchronized (this) {
            mWindowFocusChanged = true;	//标记焦点改变
            mUpcomingWindowFocus = hasFocus;	//记录传下来的焦点
            mUpcomingInTouchMode = inTouchMode;	//记录传下来的触摸模式标记
        }
        Message msg = Message.obtain();
        msg.what = MSG_WINDOW_FOCUS_CHANGED;
        mHandler.sendMessage(msg);	//转移到主线程进行处理
    }


    final class ViewRootHandler extends Handler {
    	...
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            	...
                case MSG_WINDOW_FOCUS_CHANGED: {
                    handleWindowFocusChanged();
                } break;
                ...
            }
    }

    private void handleWindowFocusChanged() {
        final boolean hasWindowFocus;
        final boolean inTouchMode;
        synchronized (this) {
            if (!mWindowFocusChanged) { //焦点没有改变这不往下走,直接return
                return;
            }
            mWindowFocusChanged = false;
            hasWindowFocus = mUpcomingWindowFocus;
            inTouchMode = mUpcomingInTouchMode;
        }
        ...
        if (mAdded) {
            ...

            mLastWasImTarget = WindowManager.LayoutParams
                    .mayUseInputMethod(mWindowAttributes.flags);//通过window的flag判断是否需要与输入法进行交互

            InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
            ...
            // Note: must be done after the focus change callbacks,
            // so all of the view state is set up correctly.
            if (hasWindowFocus) {//只有获取到了焦点才会与输入法进行交互
                if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
                    imm.onPostWindowFocus(mView, mView.findFocus(),
                            mWindowAttributes.softInputMode,
                            !mHasHadWindowFocus, mWindowAttributes.flags);
                }
               ...
        }
    }

frameworks\base\core\java\android\view\inputmethod\InputMethodManager.java

    public void onPostWindowFocus(View rootView, View focusedView,
            @SoftInputModeFlags int softInputMode, boolean first, int windowFlags) {
        ...
        //设置输入法启动标志位
        int startInputFlags = 0;
        if (focusedView != null) {
            startInputFlags |= StartInputFlags.VIEW_HAS_FOCUS;
            if (focusedView.onCheckIsTextEditor()) {
                startInputFlags |= StartInputFlags.IS_TEXT_EDITOR;
            }
        }
        if (first) {	//第一次启动输入法
            startInputFlags |= StartInputFlags.FIRST_WINDOW_FOCUS_GAIN;
        }

        if (checkFocusNoStartInput(forceNewFocus)) {
        	//准备启动输入法
            if (startInputInner(StartInputReason.WINDOW_FOCUS_GAIN, rootView.getWindowToken(),
                    startInputFlags, softInputMode, windowFlags)) {
                return;
            }
        }
        ...
        }
    }

    boolean startInputInner(@StartInputReason int startInputReason,
            @Nullable IBinder windowGainingFocus, @StartInputFlags int startInputFlags,
            @SoftInputModeFlags int softInputMode, int windowFlags) {
        ...
        EditorInfo tba = new EditorInfo();	//创建EditorInfo实例,输入法会根据它显示不同面板
        tba.packageName = view.getContext().getOpPackageName();
        tba.fieldId = view.getId();
        InputConnection ic = view.onCreateInputConnection(tba);	//创建InputConnection,接收IMS传递的文本信息。
        ...
            if (ic != null) {
                ...
                //创建一个真正与IMS交互的Binder对象,后续IMS会通过ControlledInputConnectionWrapper间接的对InputConnection调用,从而操作目标控件
                servedContext = new ControlledInputConnectionWrapper(
                        icHandler != null ? icHandler.getLooper() : vh.getLooper(), ic, this);
            } else {
                servedContext = null;
                missingMethodFlags = 0;
            }
            mServedInputConnectionWrapper = servedContext;

            try {
				//通过IMMS开启输入法
                final InputBindResult res = mService.startInputOrWindowGainedFocus(
                        startInputReason, mClient, windowGainingFocus, startInputFlags,
                        softInputMode, windowFlags, tba, servedContext, missingMethodFlags,
                        view.getContext().getApplicationInfo().targetSdkVersion);
               ...
        }
        return true;
    }

2.2.2 IMMS处理IMM请求

frameworks\base\services\core\java\com\android\server\inputmethod\InputMethodManagerService.java

    @NonNull
    @Override
    public InputBindResult startInputOrWindowGainedFocus(...) {
        ...
        synchronized (mMethodMap) {
            final long ident = Binder.clearCallingIdentity();
            try {
                result = startInputOrWindowGainedFocusInternalLocked(startInputReason, client,
                        windowToken, startInputFlags, softInputMode, windowFlags, attribute,
                        inputContext, missingMethods, unverifiedTargetSdkVersion, userId);
        ...
        return result;
    }

    @NonNull
    private InputBindResult startInputOrWindowGainedFocusInternalLocked(...) {
        ...
        if (!didStart) {	//检查是否启动过,未启动过则往下继续
            if (attribute != null) {	//attribute为EditorInfo对象,检查是否为空
                if (!DebugFlags.FLAG_OPTIMIZE_START_INPUT.value()
                        || (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0) { 		//检查是否为文本编辑器
                    res = startInputUncheckedLocked(cs, inputContext, missingMethods, attribute,
                            startInputFlags, startInputReason);
		...
    }

    @GuardedBy("mMethodMap")
    @NonNull
    InputBindResult startInputUncheckedLocked(...) {
        ...
        InputMethodInfo info = mMethodMap.get(mCurMethodId);//从输入法列表中获取当前输入法服务信息
		//清理环境,解绑当前输入法
        unbindCurrentMethodLocked();
		//开始绑定IMS
        mCurIntent = new Intent(InputMethod.SERVICE_INTERFACE);
        mCurIntent.setComponent(info.getComponent());
        mCurIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
                com.android.internal.R.string.input_method_binding_label);
        mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
                mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0));

		//绑定当前IMS
        if (bindCurrentInputMethodServiceLocked(mCurIntent, this, IME_CONNECTION_BIND_FLAGS)) {
            mLastBindTime = SystemClock.uptimeMillis();
            mHaveConnection = true;
            ...
            //返回正在等待绑定的状态结果
            return new InputBindResult(
                    InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
                    null, null, mCurId, mCurSeq, null);
        }
        ...
    }


    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        synchronized (mMethodMap) {
            if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
                mCurMethod = IInputMethod.Stub.asInterface(service);
                if (mCurToken == null) {
                    Slog.w(TAG, "Service connected without a token!");
                    unbindCurrentMethodLocked();
                    return;
                }
                //IME绑定成功,并得到IInputMethod.aidl实例mCurMethod;发送msg准备初始化输入法
                executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(
                        MSG_INITIALIZE_IME, mCurTokenDisplayId, mCurMethod, mCurToken));
                if (mCurClient != null) {	//如果此时有app等待输入法则创建Session,准备启动输入法
                    clearClientSessionLocked(mCurClient);
                    requestClientSessionLocked(mCurClient);
                }
            }
        }
    }

    void requestClientSessionLocked(ClientState cs) {
        if (!cs.sessionRequested) {	//client未绑定过IME则往下执行
            InputChannel[] channels = InputChannel.openInputChannelPair(cs.toString());	//创建inputChannel数组,长度为2,一个用于发布输入事件,一个用于消费输入事件
            cs.sessionRequested = true;
            executeOrSendMessage(mCurMethod, mCaller.obtainMessageOOO(	//发送msg创建Session
                    MSG_CREATE_SESSION, mCurMethod, channels[1],
                    new MethodCallback(this, mCurMethod, channels[0])));
        }
    }
    
    void executeOrSendMessage(IInterface target, Message msg) {
         ...
             handleMessage(msg);
             msg.recycle();
         }
    }
    
    @MainThread
    @Override
    public boolean handleMessage(Message msg) {
        SomeArgs args;
        switch (msg.what) {
        	case MSG_CREATE_SESSION: {
                args = (SomeArgs)msg.obj;
                IInputMethod method = (IInputMethod)args.arg1;
                InputChannel channel = (InputChannel)args.arg2;
                try {
                    method.createSession(channel, (IInputSessionCallback)args.arg3);	//告知输入法,需要创建输入法对话
                ...
        }
    }

frameworks\base\core\java\com\android\internal\view\IInputSessionCallback.aidl

oneway interface IInputSessionCallback {
    void sessionCreated(IInputMethodSession session);
}

frameworks\base\services\core\java\com\android\server\inputmethod\InputMethodManagerService.java

    void onSessionCreated(IInputMethod method, IInputMethodSession session,
            InputChannel channel) {
        synchronized (mMethodMap) {
            if (mCurMethod != null && method != null
                    && mCurMethod.asBinder() == method.asBinder()) {
                if (mCurClient != null) {
                    clearClientSessionLocked(mCurClient);	//清除client旧的Session
                    IMMS 使用IInputMethod为客户端创建一个IInputMethodSession
                    mCurClient.curSession = new SessionState(mCurClient,
                            method, session, channel);
                    InputBindResult res = attachNewInputLocked(	//准备启动输入法
                            StartInputReason.SESSION_CREATED_BY_IME, true);
                    if (res.method != null) {
                        executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(	//通知client进行绑定
                                MSG_BIND_CLIENT, mCurClient.client, res));
                    }
                    return;
	...
    }


    @GuardedBy("mMethodMap")
    @NonNull
    InputBindResult attachNewInputLocked(@StartInputReason int startInputReason, boolean initial) {
        if (!mBoundToMethod) {	//没有将client绑定到输入法则需先进行绑定操作
            executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
                    MSG_BIND_INPUT, mCurMethod, mCurClient.binding));
            mBoundToMethod = true;
        }
		...
        final SessionState session = mCurClient.curSession;
        executeOrSendMessage(session.method, mCaller.obtainMessageIIOOOO(	//通知输入法要准备启动了
                MSG_START_INPUT, mCurInputContextMissingMethods, initial ? 0 : 1 /* restarting */,
                startInputToken, session, mCurInputContext, mCurAttribute));
        if (mShowRequested) {
            showCurrentInputLocked(getAppShowFlags(), null);	//准备启动输入法
        }
        //将结果SUCCESS_WITH_IME_SESSION返回给client
        return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION,
                session.session, (session.channel != null ? session.channel.dup() : null),
                mCurId, mCurSeq, mCurActivityViewToScreenMatrix);
    }

    @MainThread
    @Override
    public boolean handleMessage(Message msg) {
        SomeArgs args;
        switch (msg.what) {
            case MSG_START_INPUT: {
               	...
                    setEnabledSessionInMainThread(session);	//IMMS最终使用IInputMethod激活IInputMethodSession
                    session.method.startInput(startInputToken, inputContext, missingMethods,	//通过IInputMethod调用startInput()准备启动输入法,并传递IInputContext
                            editorInfo, restarting, session.client.shouldPreRenderIme);
                ...
            }
    }

    @GuardedBy("mMethodMap")
    boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
        ...
        if (mCurMethod != null) {
            if (DEBUG) Slog.d(TAG, "showCurrentInputLocked: mCurToken=" + mCurToken);
            executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(	//发送MSG_SHOW_SOFT_INPUT消息,显示输入法
                    MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod,
                    resultReceiver));
            mInputShown = true;
            ...
        return res;
    }

    @MainThread
    @Override
    public boolean handleMessage(Message msg) {
        SomeArgs args;
        switch (msg.what) {
        	...
	        case MSG_SHOW_SOFT_INPUT:
                args = (SomeArgs)msg.obj;
                try {
                   	//调用InputMethodService.InputMethodImpl.showSoftInput()
                    ((IInputMethod)args.arg1).showSoftInput(msg.arg1, (ResultReceiver)args.arg2);
                } catch (RemoteException e) {
                }
                args.recycle();
                return true;
             ...
        }
   	}

frameworks\base\core\java\android\inputmethodservice\InputMethodService.java

	...
    SoftInputWindow mWindow;
	...
    @Override public void onCreate() {
        ...
        //创建一个输入法窗口,并设置窗口属性,这个窗口本质是一个Dialog
        mWindow = new SoftInputWindow(this, "InputMethod", mTheme, null, null, mDispatcherState,
                WindowManager.LayoutParams.TYPE_INPUT_METHOD, Gravity.BOTTOM, false);
        // For ColorView in DecorView to work, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS needs to be set
        // by default (but IME developers can opt this out later if they want a new behavior).
        mWindow.getWindow().setFlags(
                FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);

        initViews();//初始化有关View
        mWindow.getWindow().setLayout(MATCH_PARENT, WRAP_CONTENT);//设置输入法窗口宽高
    }
	...
	//由上述IInputMethod调用
    public void startInput(InputConnection ic, EditorInfo attribute) {
        if (DEBUG) Log.v(TAG, "startInput(): editor=" + attribute);
        doStartInput(ic, attribute, false);
    }
    void doStartInput(InputConnection ic, EditorInfo attribute, boolean restarting) {
        if (!restarting) {
            doFinishInput();
        }
        mInputStarted = true;
        mStartedInputConnection = ic;//设置文本信息通信接口InputConnection,是IInputContext的封装
    	...    
    }
	public InputConnection getCurrentInputConnection() {
        InputConnection ic = mStartedInputConnection;
        if (ic != null) {
            return ic;
        }
        return mInputConnection;
    }
	...
    public class InputMethodImpl extends AbstractInputMethodImpl {
    	...
        @MainThread
        @Override
        public void showSoftInput(int flags, ResultReceiver resultReceiver) {
            ...
                    showWindow(true);
                }
            }
            // If user uses hard keyboard, IME button should always be shown.
            setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition);
            final boolean isVisible = mIsPreRendered
                    ? mDecorViewVisible && mWindowVisible : isInputViewShown();
            final boolean visibilityChanged = isVisible != wasVisible;
            if (resultReceiver != null) {
                resultReceiver.send(visibilityChanged
                        ? InputMethodManager.RESULT_SHOWN
                        : (wasVisible ? InputMethodManager.RESULT_UNCHANGED_SHOWN
                                : InputMethodManager.RESULT_UNCHANGED_HIDDEN), null);
            }
        }
		...
    }

    public void showWindow(boolean showInput) {
		...
        // request draw for the IME surface.
        // When IME is not pre-rendered, this will actually show the IME.
        if ((previousImeWindowStatus & IME_ACTIVE) == 0) {
            mWindow.show();
        }
        maybeNotifyPreRendered();
        mDecorViewWasVisible = true;
        mInShowWindow = false;
    }

frameworks\base\core\java\android\inputmethodservice\SoftInputWindow.java


    @Override
    public final void show() {
        switch (mWindowState) {
            case SoftInputWindowState.TOKEN_PENDING:
                throw new IllegalStateException("Window token is not set yet.");
            case SoftInputWindowState.TOKEN_SET:
            case SoftInputWindowState.SHOWN_AT_LEAST_ONCE:
                // Normal scenario.  Nothing to worry about.
                try {
                    super.show();
                    updateWindowState(SoftInputWindowState.SHOWN_AT_LEAST_ONCE);
                } catch (WindowManager.BadTokenException e) {
                    // Just ignore this exception.  Since show() can be requested from other
                    // components such as the system and there could be multiple event queues before
                    // the request finally arrives here, the system may have already invalidated the
                    // window token attached to our window.  In such a scenario, receiving
                    // BadTokenException here is an expected behavior.  We just ignore it and update
                    // the state so that we do not touch this window later.
                    Log.i(TAG, "Probably the IME window token is already invalidated."
                            + " show() does nothing.");
                    updateWindowState(SoftInputWindowState.REJECTED_AT_LEAST_ONCE);
                }
                return;
            case SoftInputWindowState.REJECTED_AT_LEAST_ONCE:
                // Just ignore.  In general we cannot completely avoid this kind of race condition.
                Log.i(TAG, "Not trying to call show() because it was already rejected once.");
                return;
            case SoftInputWindowState.DESTROYED:
                // Just ignore.  In general we cannot completely avoid this kind of race condition.
                Log.i(TAG, "Ignoring show() because the window is already destroyed.");
                return;
            default:
                throw new IllegalStateException("Unexpected state=" + mWindowState);
        }
    }

2.3 输入法传递输入信息给view

上述过程IMM通过请求IMMS启动了输入法,输入法再将输入文本信息通过IInputContext传递给客户端的view。
这里以PinyinIME为例,从打开PinyinIME -->输入"w" --> 选择第一个字"我"–> 将该字传递给view,整体流程如下:
PinyinIME
整体过程为:

  • 打开输入法:初始化输入模式、设置键盘布局、加载UI、设置监听等。
  • 按下键盘字母"w",显示候选词:
    • SoftKeyboardView键盘响应按键事件,弹出所按w键的冒泡提示。
    • PinyinIME的onKeyUp()响应,识别为字母按键,添加至拼音字符串,updateDecInfoForSearch()传给输入引擎解码,更新创作栏view和侯选栏view。
  • 选择侯选栏第一个字"我":
    • CandidateView onTouchEventReal()响应触摸事件,showBalloon()显示所选字的冒泡提示。
    • PinyinIME监听到点击事件,一方面将所选词通过commitResultText()传递给view,一方面根据所选词继续更新侯选词,进行联想。
    • 通过InputConnection通讯接口(IInputConnext的封装)将text信息最终传给BaseInputConnection,由replaceText()更新view的内容。
    • 如果text为单字符,在sendCurrentText()判断是否为需要处理的按键事件,例如send,search键等,传递给ViewRootImp放入input事件队列进行处理。

2.4 总结IMF交互过程

回顾开篇IMF整体框架图,总结IMF交互过程如下:
IFM交互图

  • IMM 利用IInputMethodManager请求IMMS
  • IMMS 绑定IMS,得到IInputMethod
  • IMMS 请求IInputMethod创建IInputMethodSession
  • IMMS 通过IInputMethodClient 告知IMM IInputMethodSession
  • IMM和IMS通过IInputMethodSession和IInputContext交互
三、 输入法debug常用命令

3.1 使用ime命令查看详情

$ ime
ime <command>:
  list [-a] [-s]
    prints all enabled input methods.
      -a: see all input methods
      -s: only a single summary line of each
  enable [--user <USER_ID>] <ID>
    allows the given input method ID to be used.
      --user <USER_ID>: Specify which user to enable. Assumes the current user if not specified.
  disable [--user <USER_ID>] <ID>
    disallows the given input method ID to be used.
      --user <USER_ID>: Specify which user to disable. Assumes the current user if not specified.
  set [--user <USER_ID>] <ID>
    switches to the given input method ID.
      --user <USER_ID>: Specify which user to enable. Assumes the current user if not specified.
  reset [--user <USER_ID>]
    reset currently selected/enabled IMEs to the default ones as if the device is initially booted w
    ith the current locale.
      --user <USER_ID>: Specify which user to reset. Assumes the current user if not specified.
ime命令参考作用
ime list查看已启用输入法详细信息
ime list -a查看所有输入法详细信息
ime list -s单行打印所有已启用输入法
ime enable com.android.inputmethod.latin/.LatinIME启用LatinIME
ime diable com.android.inputmethod.latin/.LatinIME禁用LatinIME
ime set com.android.inputmethod.latin/.LatinIME切换至LatinIME输入法
ime reset com.android.inputmethod.latin/.LatinIME重置LatinIME输入法配置

3.2 dumpsys input_method

130|zc16a:/ # dumpsys input_method
Current Input Method Manager state:
  Input Methods: mMethodMapUpdateCount=2
  InputMethod #0:
    mId=com.android.inputmethod.pinyin/.PinyinIME mSettingsActivityName=com.android.inputmethod.pinyin.SettingsActivity mIsVrOnly=false mSupportsSwitchingToNextInputMethod=false
    mIsDefaultResId=0x7f010000
    Service:
      priority=0 preferredOrder=0 match=0x108000 specificIndex=-1 isDefault=false
      ServiceInfo:
        name=com.android.inputmethod.pinyin.PinyinIME
        packageName=com.android.inputmethod.pinyin
        labelRes=0x7f080000 nonLocalizedLabel=null icon=0x0 banner=0x0
        enabled=true exported=true directBootAware=false
        permission=android.permission.BIND_INPUT_METHOD
        flags=0x0
        ApplicationInfo:
          packageName=com.android.inputmethod.pinyin
          labelRes=0x7f080000 nonLocalizedLabel=null icon=0x7f040000 banner=0x0
          processName=com.android.inputmethod.pinyin
          taskAffinity=com.android.inputmethod.pinyin
          uid=10069 flags=0x2088be45 privateFlags=0xc401000 theme=0x0
          requiresSmallestWidthDp=0 compatibleWidthLimitDp=0 largestWidthLimitDp=0
          sourceDir=/system/app/PinyinIME/PinyinIME.apk
          seinfo=default:targetSdkVersion=29
          seinfoUser=:complete
          dataDir=/data/user/0/com.android.inputmethod.pinyin
          deviceProtectedDataDir=/data/user_de/0/com.android.inputmethod.pinyin
          credentialProtectedDataDir=/data/user/0/com.android.inputmethod.pinyin
          enabled=true minSdkVersion=29 targetSdkVersion=29 versionCode=29 targetSandboxVersion=1
          supportsRtl=false
          fullBackupContent=true
          HiddenApiEnforcementPolicy=0
          usesNonSdkApi=true
          allowsPlaybackCapture=true
  InputMethod #1:
    mId=com.he.ardc/.ARDCIME mSettingsActivityName=null mIsVrOnly=false mSupportsSwitchingToNextInputMethod=false
    mIsDefaultResId=0x0
    Service:
      priority=0 preferredOrder=0 match=0x108000 specificIndex=-1 isDefault=false
      ServiceInfo:
        name=com.he.ardc.ARDCIME
        packageName=com.he.ardc
        labelRes=0x7f030001 nonLocalizedLabel=null icon=0x0 banner=0x0
        enabled=true exported=true directBootAware=false
        permission=android.permission.BIND_INPUT_METHOD
        flags=0x0
        ApplicationInfo:
          packageName=com.he.ardc
          processName=com.he.ardc
          taskAffinity=com.he.ardc
          uid=10060 flags=0x38e8be44 privateFlags=0x24001000 theme=0x0
          requiresSmallestWidthDp=0 compatibleWidthLimitDp=0 largestWidthLimitDp=0
          sourceDir=/data/app/com.he.ardc-2eyG7rZV7X8Cv-mHxfxR5Q==/base.apk
          seinfo=default:targetSdkVersion=26
          seinfoUser=:complete
          dataDir=/data/user/0/com.he.ardc
          deviceProtectedDataDir=/data/user_de/0/com.he.ardc
          credentialProtectedDataDir=/data/user/0/com.he.ardc
          enabled=true minSdkVersion=19 targetSdkVersion=26 versionCode=1379 targetSandboxVersion=1
          supportsRtl=true
          fullBackupContent=true
          HiddenApiEnforcementPolicy=2
          usesNonSdkApi=false
          allowsPlaybackCapture=false
  InputMethod #2:
    mId=com.koushikdutta.vysor/.VysorIME mSettingsActivityName=com.koushikdutta.vysor.DummyIMEActivity mIsVrOnly=false mSupportsSwitchingToNextInputMethod=true
    mIsDefaultResId=0x0
    Service:
      priority=0 preferredOrder=0 match=0x108000 specificIndex=-1 isDefault=false
      ServiceInfo:
        name=com.koushikdutta.vysor.VysorIME
        packageName=com.koushikdutta.vysor
        labelRes=0x7f0d001d nonLocalizedLabel=null icon=0x0 banner=0x0
        enabled=true exported=true directBootAware=false
        permission=android.permission.BIND_INPUT_METHOD
        flags=0x0
        ApplicationInfo:
          name=com.koushikdutta.vysor.VysorApplication
          packageName=com.koushikdutta.vysor
          labelRes=0x7f0d001d nonLocalizedLabel=null icon=0x7f0c0000 banner=0x0
          className=com.koushikdutta.vysor.VysorApplication
          processName=com.koushikdutta.vysor
          taskAffinity=com.koushikdutta.vysor
          uid=10125 flags=0x38a8be44 privateFlags=0xc001000 theme=0x7f0e0116
          requiresSmallestWidthDp=0 compatibleWidthLimitDp=0 largestWidthLimitDp=0
          sourceDir=/data/app/com.koushikdutta.vysor-th96FyIzBNMu_OEC5tVbbw==/base.apk
          seinfo=default:targetSdkVersion=30
          seinfoUser=:complete
          dataDir=/data/user/0/com.koushikdutta.vysor
          deviceProtectedDataDir=/data/user_de/0/com.koushikdutta.vysor
          credentialProtectedDataDir=/data/user/0/com.koushikdutta.vysor
          enabled=true minSdkVersion=19 targetSdkVersion=30 versionCode=1656572400 targetSandboxVersion=1
          supportsRtl=false
          fullBackupContent=true
          HiddenApiEnforcementPolicy=2
          usesNonSdkApi=false
          allowsPlaybackCapture=true
		  ...
四、展望/交流/讨论

综上简单介绍了输入法的基本概念,分类,特点;android 系统中PinyinIME的启动流程,文本的交互流程。当然输入法最核心的是在输入引擎上,好的输入引擎能够既快又准的显示输入内容,能够根据用户使用习惯来动态联想和纠错。这部分待研究熟悉后再分享交流。
在享受输入法带给我们便利的同时也需要警惕其背后的风险和隐患,主流的输入法基本是闭源,联网的,而我们的各类隐私信息经常需要通过输入法与各式各样的应用软件交互,这无疑存在很大的安全隐患,同时也由于主流输入法功能过于固定,并不适用各类行业和人群,因此开源输入法也应运而生,较为著名的是开源的,基于rime输入引擎的多平台支持的输入法(trime、Wease、Squirrel、ibus-rime),支持丰富的定制化功能,效果图如下:
在这里插入图片描述
除了文章开头提到的诸如键打、手写、语音方式的输入法,也有或将有更多有用和有趣的输入法,如手势识别,脑电波识别等等~

更多相关推荐


View not attached to window manager解决方案

发布时间:2014-07-19 ANDROID
前几日出现这样一个Bug是一个RuntimeException,详细信息是这样子的:java.lang.IllegalArgumentException:Viewnotattachedtowindowmanager   atandroid.view.WindowManagerImpl.findViewLocked(WindowManagerImpl.java:356)   atandroid.vi...

Android之Notification讲解

一、Notification简介:  Notification,俗称通知,是一种具有全局效果的通知,它展示在屏幕的顶端,首先会表现为一个图标的形式,当用户向下滑动的时候,展示出通知具体的内容。程序一般通过NotificationManager服务来发送Notification。  Android3.0增加了Notification.Builder类,该类可以轻松地创建Notification对象。...

定义关键帧

发布时间:2022-10-26 动画 ANDROID CSS3
1、定义关键帧@keyframes动画名称(英文){     0%{       /*动画的开始*/     }​     25%{       /*在25%的时候进行的一个动画*/     }​     50%{       /*在50%的时候进行的一个动画*/     }​     100%{       /*动画结束*/     }​   }​ from,to表示开始、结束状态,也可以使用0...

Android开发环境搭建(套装快速)

发布时间:2013-08-08 ANDROID
Android开发环境搭建目前有两种方式:一种是包括jdk安装、eclipse安装、AndroidSDK安装和ADT安装,第二种是jdk安装、SDKADTBundle安装(包含了带ADT的eclipse、AndroidSDK)。第二种可快速搭建,默认使用官方提供的eclipse和SDK。第一种方式,可自己选择eclipse和SDK版本。本文介绍第二种方式快速搭建开发环境。1.jdk安装2.Andr...

Android Environment 类详解

发布时间:2016-05-09 ANDROID ANDROID应用 ANDROID学习 存储
Android应用开发中,常使用Environment类去获取外部存储目录,在访问外部存储之前一定要先判断外部存储是否已经是可使用(已挂载&amp;可使用)状态,并且需要在AndroidManifest.xml文件中添加外部存储读和写的权限。根据官方API文档,接下来对android.os.Environment类做详细介绍。一、Environment类中提供了多个String类型的静态常量用于标...

Android DiskLruCache完全解析,硬盘缓存的最佳方案

发布时间:2017-01-09 ANDROID
本文转自郭霖:http://blog.csdn.net/guolin_blog/article/details/28863651转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/28863651概述记得在很早之前,我有写过一篇文章Android高效加载大图、多图解决方案,有效避免程序OOM,这篇文章是翻译自Android Doc的,其...

overdraw、cpu条形图、hierarchy viewer

发布时间:2018-01-24 ANDROID
一、Overdraw1、2、分析代码的性能左右两张图片的颜色不一样设置--&gt;开发者选项--&gt;调试GUP过度绘制(ShowGPUOverdraw)可以看到列表一过度绘制比列表二严重。3、设置--&gt;开发者选项--&gt;CPU绘制条形图,下面的条形图代表着cpu绘制的时长。从图中可以看出,列表一每一帧元素绘制的时间都是长于列表二的。也就是说列表一绘制的时长会更长一点。也从另一个角度说...

动态调试Android笔记

发布时间:2016-01-27 ANDROID NDK ANDROID
做一个动态调试Android应用的笔记,以备后患。工具IDA6.6破解版,小米3手机开发板(可以root)。 步骤:1.      在IDA里面找到dbgsrv文件夹中找到android_server,push到手机中adbpushandroid_server/data/data/as2.      修改权限AdbshellChmod777/data/data/as 3.      运行调试的程序...

手机Fiddler抓包

发布时间:2017-08-24 ANDROID ANDROID开发 软件 手机 局域网
对于Android开发的同事最头疼的事情莫过于真机抓包,然后Fiddler就可以帮助你解决这个难题,步骤:Fiddler下载地址(fiddler下载地址)安装到电脑,我的电脑系统是Win7打开Fiddler软件,界面和其他抓包软件大致一样,效果图如下:4.下面我们就进入重点了,真机抓包了,首先,确保安装Fiddler的电脑和你的手机在同一局域网内,因为Fiddler只是一个代理,需要将手机的代理指...

避免踩坑,内存不足时系统回收Activity的流程解析

发布时间:2022-11-16 移动开发 JAVA ANDROID 开发语言
前言android开发中,activity我们会经常遇到,作为view的容器,activity天然就具备了生命周期的特点,当然这篇不是讲生命周期,而是关于系统不足时回收的动作,有可能导致app运行时会出现一些不可预料的“逻辑”异常行为。以一个例子出发在一些比较久的项目中,可能会存在这样一个事务处理架构,比如有个推送到来,同时eventbus发送给activity1进行部分逻辑处理,然后再把处理好的...

类微信的门户页面框架设计(1)

发布时间:2022-09-30 微信 ANDROID AS类微信界面 ANDROID STUDIO
一.设计目标  完成类微信的门户页面框架设计,APP最少必须包含4个tab页面。框架设计需要使用fragment,activity,不得使用UNIAPP技术进行开发(H5或者小程序)二.功能说明   三.代码解析1.xml①底部代码:先放horizon的linearlayout,再在其中放四个vertical的linearlayout,layout1到layout4对应“微信”,“通讯录”,“发现...

类微信门户设计

发布时间:2022-09-30 微信 ANDROID
一、设计目标类微信的门户页面框架设计,APP最少必须包含4个tab页面。框架设计需要使用fragment,activity,不得使用UNIAPP技术进行开发(H5或者小程序)。二、功能说明本门户框架设计主要为了模拟微信的界面功能:微信,联系人,发现,以及设置。在本框架中,用户可点击其下方的四个按钮来完成不同界面之间的切换。三、完成步骤1.微信界面设计(一)TOP设计代码如下:&lt;TextVie...

Activity的生命周期与跳转

发布时间:2022-10-21 JAVA ANDROID 开发语言
设计目标:1、实现对Activity生命周期的理解,使用log展示生命周期的状态变化;2、根据博客:https://www.jianshu.com/p/c4cfe38a91ed的内容(进阶篇中的“点击”),在前次作业的基础上增加列表项的单项点击功能,具体要求是:新建一个新的activity1,recycleview的某一项点击后跳转到这个新的activity1。如:点击新闻列表会跳转到新闻详情页面...

Githup README.MD的书写方式

发布时间:2016-01-13 ANDROID
最近对它的README.md文件颇为感兴趣。便写下这贴,帮助更多的还不会编写README文件的同学们。README文件后缀名为md。md是markdown的缩写,markdown是一种编辑博客的语言。用惯了可视化的博客编辑器(比如CSDN博客,囧),这种编程式的博客编辑方案着实让人眼前一亮。不过GitHub支持的语法在标准markdown语法的基础上做了修改,称为GithubFlavoredMar...

动态频谱共享(DSS)功能介绍及Log分析

发布时间:2022-10-09 网络 ANDROID 5G
DSS(Dynamicspectrumsharing)动态频谱共享不同制式的网络可以共享同一个频段,并且可以根据需求动态分配带宽给不同的网络制式,频谱资源得到充分利用。不同网络制式各自的公共信道相互独立不受影响。静态频谱共享(固定分配带宽给NR和LTE)动态频谱共享(动态调整分配带宽给NR和LTE)  配置及Log分析高通平台在carrier_policy.xml中配置DSS&lt;initial...

Ubuntu上使用android4.0.3模拟器实现JNI例子

发布时间:2012-02-20 ANDROID STRING ECLIPSE BUTTON JNI UBUNTU
上一篇文章写了在Ubuntu环境下搭建NDKR7的过程,这篇文章来实现我的第一个JNI例子,Androidsdk的API版本是4.0.3,NDK的版本是R7的。一.   创建一个Android项目。         打开Eclipse后,执行File-&gt;New-&gt;Project,选择Android-&gt;AndroidProject,然后依照填好项目名字(我的项目名为MyJniTes...

经典的Word 2010小技巧

发布时间:2010-05-04 2010 工具 图形 输入法
1、 Word表格玩自动填充  在Word表格里选中要填入相同内容的单元格,单击“格式&amp;rarr;项目符号和编号”,进入“编号”选项卡,选择任意一种样式,单击“自定义”按钮,在“自定义编号列表”窗口中“编号格式”栏内输入要填充的内容,在“编号样式”栏内选择“无”,依次单击“确定”退出后即可。  2、 Word中巧输星期  单击“格式&amp;rarr;项目符号和编号”,进入“编号”选项卡,...

Android企业微信分享到小程序 通过URL获取网络图片Bitmap格式

发布时间:2022-11-17 ANDROID ANDROID开发 企业微信
1.官方文档Android应用-接口文档-企业微信开发者中心https://developer.work.weixin.qq.com/document/path/911962.创建应用登录企业微信管理后台,选择企业应用,选择“企业微信授权登录”,在设置界面填写Android的App的签名&amp;包名,设置完成后系统自动生成应用程序schema。3.下载企业微信终端开发工具包lib_wwapi-2...

重写、覆盖、重载、多态几个概念的区别分析

发布时间:2017-04-08 重载 ANDROID
override-&gt;重写(=覆盖)、overload-&gt;重载、polymorphism-&gt;多态 override是重写(覆盖)了一个方法,以实现不同的功能。一般是用于子类在继承父类时,重写(重新实现)父类中的方法。重写(覆盖)的规则:  1、重写方法的参数列表必须完全与被重写的方法的相同,否则不能称其为重写而是重载.  2、重写方法的访问修饰符一定要大于被重写方法的访问修饰符...

Design:Widgets

发布时间:2013-06-06 ANDROID
WidgetsWidgets是主屏定制化的精髓。可以认为它们是应用程序最后总要数据和功能在主屏上的一瞥。用户可以在主屏上移动widget。还可以按用户喜好裁减widget的大小。Widgettypes按功能而言,典型的widget分为以下四类:InformationwidgetsInformationwidgets显示重要的和按时改变的信息。一个典型的例子是weatherwidgets,clock...

java进阶之并发编程一ReentrantLock的实际应用和线程中断EXAMPLE

引言:继上一篇ReentrantLock的介绍来做俩个小demo。实现3个线程分别打印指定数字和线程死锁进行线程中断。上一篇:&lt;&lt;java进阶之并发编程一ReentrantLock同步锁的学习和syncthronized的区别&gt;&gt;**demo1:**ReentrantLock搭配三个线程分别打印指定的数字,直接上代码图片比较直观了。demo2:ReentrantLock模仿...

ClassLoader 类加载器模型

发布时间:2021-10-22 ANDROID JVM TOMCAT JAVA SPRING
一、类加载器ClassLoader即常说的类加载器,其功能是用于从Class文件加载所需的类,主要场景用于热部署、代码热替换等场景。系统提供3种类加载器:BootstrapClassLoader、ExtensionClassLoader、ApplicationClassLoader1.1BootstrapClassLoader启动类加载器,一般由C++实现,是虚拟机的一部分。该类加载器主要职责是将...

【WLAN】如何从显示角度调试WFD问题

发布时间:2022-11-16 ANDROID WIFI
连接WFD时,有时会出现闪烁问题、空白屏幕问题、显示混乱问题等。首先,请从视频角度检查yuv编码器数据和ts转储是否正常。如果yuv编码器数据正常,但ts转储异常,需要与视频团队或wifi团队合作进行调试。如果yuv编码器数据异常,需要转储虚拟显示输入层和虚拟显示输出层,以从显示角度进行检查。如何dumpvirtualdisplaylayer从displayperspective:1:Disabl...

CISP-PTE真题演示

周末帮好兄弟做PTE的真题,觉得确实挺有意思的,于是就有了这篇文章,侵删侵删哈第一阶段基础题目一:SQL注入所谓SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。开始答题这道题的有趣的地方就是,你先注册用户,不同于以往的360的PTE真题,并不是以admin用户去发表文章就能看到key,按照往常的先注册一个用户吧可以看见,...

从CES预测2011年的热点

发布时间:2011-01-06 微软 ANDROID MEEGO 程序开发 手机 终端
对于终端设备来说CES的热点是什么? 抛去纷繁的厂商和外表,可以提炼出这么几个:android3.0,ARM双核,4G,xpad。其他的如intel的新处理器都是浮云 MOTO推出携带android3.0的平板电脑可以支持4G,同时还推出配置强大的4G手机。这两个无疑将是2011年的趋势,更会加速的革掉笔记本/上网本和功能手机的命。在这场变革中,诺基亚注定是个悲剧,intel也存在着慢慢失去主导的...

类微信的门户页面框架设计(2)

发布时间:2022-10-21 ANDROID STUDIO 微信 ANDROID AS类微信界面
ps:这回衔接的是老师给的代码,和之前作业关系不大一、主要内容1.在“联系人”栏用recycleview实现滚动列表2.点击列表进入详情界面二、核心代码(1)layout中新增编写RecyclerView滚动列表中的行样式文件item.xml&lt;?xmlversion="1.0"encoding="utf-8"?&gt;&lt;LinearLayoutxmlns:android="http:/...

Android Studio 类微信界面的制作

发布时间:2022-09-30 ANDROID STUDIO ANDROID IDE
设计目标使用AndroidStudio完成类微信的门户页面框架设计,APP包含4个tab页面。框架设计使用fragment,activity。功能说明界面的样式和微信打开后的界面相似1点击底部导航栏的4个"按钮"切换Fragment来展示不同的界面2点击"按钮",按钮会变色3初步使用RecyclerView展示不同界面的内容运行效果在后面有截图代码解析1首先创建一个新的project,选择Empt...

Mysql子查询关键字的使用(exists)

1.all1.1格式:1.2特点:all:与子查询返回的所有值比较为true则返回trueall可以与=,&gt;=,&gt;,&lt;,&lt;=,&lt;&gt;结合使用,分别表示等于,大于等于,大于,小于,小于等于,不等于其中的所有数据大于all表示指定列中的值必须要大于子查询集中的每一个值,即必须要大于子查询集的最大值;如果是小于即小于子查询中的最小值。1.3操作:2.any(some)1...

PendingIntent

发布时间:2022-11-15 JAVA ANDROID 算法
PendingIntent可以看作是对Intent的一个封装,但它不是立即执行某个行为,而是满足某些条件或者触发某些事件后才执行指定的行为。获取PendingIntent的方法有以下三种,分别是通过Activity、Service、BroadcastReceiver获取:通过getActivity系列方法从系统中获取一个用于启动Activity的PendingIntent对象;通过getServi...

一个优化if-else的例子

发布时间:2014-02-13 JAVA 优化IF ELSE ANDROID
publicclasshello{ publicenumMyEnum { Name(0),Age(1),Address(2); publicintmEnum; MyEnum(intiEnum) { mEnum=iEnum; } } publicstaticvoidmain(String[]args) { intmyEnum=MyEnum.valueOf("Address").mE...

Android Animation动画(很详细)

发布时间:2015-07-31 ANDROID ANDROID开发
AndroidAnimationContents:AnimationsTweenAnimationsAnimationSetInterpolatorFrame-By-FrameAnimationsLayoutAnimationsControllerAnimationListener  Animations一、Animations介绍Animations是一个实现androidUI界面动画效果的AP...

andriod 学习 step by step (五) 分析 Foursquared 思考Android 应用程序框架

发布时间:2010-11-11 程序开发 ANDROID 框架 APPLICATION
Android应用程序开发中,有的时候我们在应用程序的任何一个地方都需要访问一个全局变量,也就是在任何一个Activity中都可以访问的变量。它不会因为Activity的生命周期结束而消失。要实现应用程序级的变量,我们可以通过Application这个类来实现。1 public class HelloApplication extends Application {2         privat...

分析 Foursquared 思考Android 应用程序框架

发布时间:2013-05-13 ANDROID
Android应用程序开发中,有的时候我们在应用程序的任何一个地方都需要访问一个全局变量,也就是在任何一个Activity中都可以访问的变量。它不会因为Activity的生命周期结束而消失。要实现应用程序级的变量,我们可以通过Application这个类来实现。1 public class HelloApplication extends Application {2         privat...

Android UI-实现底部切换标签(fragment

发布时间:2018-11-08 ANDROID UI
分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!               AndroidUI-实现底部切换标签(fragment) 前言 本篇博客要分享的一个UI效果——实现底部切换标签,想必大家在一些应用上面遇到过这种效果了,最典型的就是微信了,可以左右...

ANDROID L——Material Design详解(UI控件)

发布时间:2015-07-09 ANDROID
http://doc.okbase.net/a396901990/archive/113540.html转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持!AndroidL:Google已经确认AndroidL就是AndroidLollipop(5.0)。前几天发现Android5.0正式版的sdk已经可以下载了,而且首次搭载AndroidL...

Android 图像变换Matrix的原理应用

发布时间:2016-09-12 MATRIX ANDROID 数学
第一部分Matrix的数学原理在Android中,如果你用Matrix进行过图像处理,那么一定知道Matrix这个类。Android中的Matrix是一个3x3的矩阵,其内容如下: Matrix的对图像的处理可分为四类基本变换:Translate          平移变换Rotate               旋转变换Scale                 缩放变换Skew        ...

在线客服在教育行业应用--在线老师

发布时间:2018-02-27 ANDROID C# JAVA PHP IOS
传统的在线客服,在教育领域的体现和普通网站其实没什么区别。一般包含以下几点: 在线客服,对学员主动发起咨询   访客登陆培训机构网站,无非是想在培训机构的网络平台了解所需的相关信息。快商通在线客服可以监控访客轨迹,从访客浏览轨迹里分析出访客的具体需求,相关客服可以对访客主动发出对话邀请进行沟通,进行精准的营销推广,引导访客入学培训。   微信在线客服,整合微信营销资源   把教育机构微信公众服务号...

云输入法离我们还有多远?那就是2010年!!!

前段时间搜狗发布了云输入法,一时间云输入法成为了网络的焦点。我试用了一下也发表了自己的看法,见http://blog.csdn.net/yzsind/archive/2009/11/15/4813947.aspx有说好的,有说差的,但我认为这是输入法发展的方向。     今天收到邀请思考一下云输入法的相关问题,于是想了许多,最后得出结论是云输入法将会在2010年全面应用,搜狗、QQ、Google、...

Android 一个简单的登陆窗口的实现(文件的保存与读取)

发布时间:2019-02-07 ANDROID 读取
               好久没写过代码了,现在又开始重操旧业了。想了想,还是回到CSDN博客来吧,原本都是在Github上面自己弄Pages写的,但是还是挺舍不得CSDN的。在这里继续开写吧,反正都是一些菜鸟的流水账的东西。记录Mark一下。今天写的是一个简单的登陆界面,主要功能就是实现记住密码这个简单的功能,说白了就是在Android中实现文件的保存与再次的读取。首先是布局文件,很简单的线...

Android studio svn使用方法

发布时间:2018-05-10 ANDROID ANDROID-STUDIO
步骤一、设置忽略文件(可参考工程目录下的.gitignore文件)*.iml.DS_Store.externalNativeBuildlocal.properties.idea/libraries.idea/modules.xml.idea/workspace.xml.gradlecapturesbuild目录和子目录下所有的build目录操作步骤:1).打开设置对话框Ctrl+Alt+s或者Fi...

java进阶学习 --java网络编程一(转)

Java-网络编程完全总结(基础介绍)本文主要是在网络编程方面的学习总结,先主要介绍计算机网络方面的相关内容,包括计算机网络基础,OSI参考模型,TCP/IP协议簇,常见的网络协议等等,在此基础上,主要介绍Java中的网络编程。目录一、概述二、计算机网络1.网络协议2.网络体系结构三、OSI参考模型四、TCP/IP参考模型五、常见网络协议1.TCP协议2.UDP协议3.HTTP协议六、计网常见问题...

foursquared 1

http://zhanwc.iteye.com/blog/834772&#13;外国人真具有共产主义精神,Foursquare都拿出开源了,不像国内某些公司。Foursquare下载地址主页地址http://code.google.com/p/foursquared/。下载方式hgclonehttps://foursquared.googlecode.com/hg/foursquared,在lin...

从60道经典音视频面试题的角度去看:如何从零开始

前言随着互联网的高速发展,抖音等音视频的发酵,加之5G落地,催生出大量音视频需求,国内技术人才少,岗位多,出现公司抢人还难招聘的情况。想成为资深音视频开发?面临着:音视频资料非常少不知道从何学习又渴望掌握更高级的技术也有不少人吐槽去面试音视频开发,不知道会问到那些问题,今天就从60道经典音视频的角度来分析如何从0出去进阶音视频。🤣首先来看一下60道经典音视频面试题(含答案)1.为什么巨大的原始视频...

Java 反射方法使用记录

发布时间:2022-09-28 开发语言 JAVA ANDROID JAVA基础
//获取ClassClassc1=Student.class;Studentstudent=newStudent();Classc2=student.getClass();Classc3=Class.forName("org.example.Student");//获取修饰符0默认不写1public2private4protect8static16final32synchronizintresul...

我也玩android了

发布时间:2010-09-13 工作 ANDROID 游戏 鸟哥的生活
因为一些原因没做完张三丰2--RPG游戏,最后做完植物僵尸总碰缘--对对碰休闲游戏,j2me的工作暂时就宣告结束了,今天开始在另一个大公司搞android了。为什么转android,这问题太复杂了,问移动去。以后得向更多的android高手学习了。当然j2me鸟哥一样有空就玩,一样要学习的。期待与大家android,j2me一起进步!...

Trime同文输入法

发布时间:2022-11-23 ANDROID 输入法
一、trime同文输入法介绍trime同文输入法是基于rime输入法引擎面向Android平台的一款开源高度可定制化输入法应用,提到trime就不得不先了解下rime。1.1rime与trimerime并不是一个输入法应用,而是经过巧妙设计能够满足丰富定制化的输入法引擎。大约是2009年由佛振大佬发起项目,起初是在linux平台开发使用,配套的前端为ibus-rime(中州韵),后经多人共同开发维...