IME SoftInputWindow窗口添加

2023-11-15

android12-release1


1、时序图

输入法应用继承InputMethodService

frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java
frameworks/base/core/java/android/view/ImeFocusController.java
frameworks/base/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
frameworks/base/core/java/android/inputmethodservice/InputMethodService.java
frameworks/base/core/java/android/inputmethodservice/SoftInputWindow.java
android/app/Dialog.java

在这里插入图片描述

2、InputMethodService#onCreate()

  1. SoftInputWindow界面实质是Dialog
  2. 界面属性Type 为 WindowManager.LayoutParams.TYPE_INPUT_METHOD, WindowToken 标识 mCurToken = new Binder()
  3. 界面主要层级
    在这里插入图片描述
@Override public void onCreate() {
    Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.onCreate");
    mTheme = Resources.selectSystemTheme(mTheme,
            getApplicationInfo().targetSdkVersion,
            android.R.style.Theme_InputMethod,
            android.R.style.Theme_Holo_InputMethod,
            android.R.style.Theme_DeviceDefault_InputMethod,
            android.R.style.Theme_DeviceDefault_InputMethod);
    super.setTheme(mTheme);
    super.onCreate();
    mImm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE);
    mSettingsObserver = SettingsObserver.createAndRegister(this);
    // cache preference so we don't have to read ContentProvider when IME is requested to be
    // shown the first time (cold start).
    mSettingsObserver.shouldShowImeWithHardKeyboard();

    mIsAutomotive = isAutomotive();
    mAutomotiveHideNavBarForKeyboard = getApplicationContext().getResources().getBoolean(
            com.android.internal.R.bool.config_automotiveHideNavBarForKeyboard);

    // TODO(b/111364446) Need to address context lifecycle issue if need to re-create
    // for update resources & configuration correctly when show soft input
    // in non-default display.
    mInflater = (LayoutInflater)getSystemService(
            Context.LAYOUT_INFLATER_SERVICE);
    Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.initSoftInputWindow");
    mWindow = new SoftInputWindow(this, "InputMethod", mTheme, null, null, mDispatcherState,
            WindowManager.LayoutParams.TYPE_INPUT_METHOD, Gravity.BOTTOM, false);
    mWindow.getWindow().getAttributes().setFitInsetsTypes(statusBars() | navigationBars());
    mWindow.getWindow().getAttributes().setFitInsetsSides(Side.all() & ~Side.BOTTOM);
    mWindow.getWindow().getAttributes().receiveInsetsIgnoringZOrder = true;

    // Automotive devices may request the navigation bar to be hidden when the IME shows up
    // (controlled via config_automotiveHideNavBarForKeyboard) in order to maximize the visible
    // screen real estate. When this happens, the IME window should animate from the bottom of
    // the screen to reduce the jank that happens from the lack of synchronization between the
    // bottom system window and the IME window.
    if (mIsAutomotive && mAutomotiveHideNavBarForKeyboard) {
        mWindow.getWindow().setDecorFitsSystemWindows(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();
    mWindow.getWindow().setLayout(MATCH_PARENT, WRAP_CONTENT);
    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);

    mInlineSuggestionSessionController = new InlineSuggestionSessionController(
            this::onCreateInlineSuggestionsRequest, this::getHostInputToken,
            this::onInlineSuggestionsResponse);
    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}

void initViews() {
    Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.initViews");
    mInitialized = false;
    mViewsCreated = false;
    mShowInputRequested = false;
    mShowInputFlags = 0;

    mThemeAttrs = obtainStyledAttributes(android.R.styleable.InputMethodService);
    mRootView = mInflater.inflate(
            com.android.internal.R.layout.input_method, null);
    mWindow.setContentView(mRootView);
    mRootView.getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsComputer);
    mFullscreenArea = mRootView.findViewById(com.android.internal.R.id.fullscreenArea);
    mExtractViewHidden = false;
    mExtractFrame = mRootView.findViewById(android.R.id.extractArea);
    mExtractView = null;
    mExtractEditText = null;
    mExtractAccessories = null;
    mExtractAction = null;
    mFullscreenApplied = false;

    mCandidatesFrame = mRootView.findViewById(android.R.id.candidatesArea);
    mInputFrame = mRootView.findViewById(android.R.id.inputArea);
    mInputView = null;
    mIsInputViewShown = false;

    mExtractFrame.setVisibility(View.GONE);
    mCandidatesVisibility = getCandidatesHiddenVisibility();
    mCandidatesFrame.setVisibility(mCandidatesVisibility);
    mInputFrame.setVisibility(View.GONE);
    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}

3、Dialog添加到WMS

参考 WMS侧添加界面WindowManagerImpl.java#addView -> WindowManagerGlobal.java#addView -> ViewRootImpl.java#setView -> Session extends IWindowSession.Stub#addToDisplayAsUser -> WindowManagerService.java#addWindow

注意界面TYPE_INPUT_METHOD判断
注意WindowToken判断,发生BadTokenException异常WindowManagerGlobal.ADD_BAD_APP_TOKEN
调用DisplayContent.java中setInputMethodWindowLockedcomputeImeTarget 确定 IME 目标的窗口,以便对 IME 窗口进行分层。

public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
        int displayId, int requestUserId, InsetsState requestedVisibility,
        InputChannel outInputChannel, InsetsState outInsetsState,
        InsetsSourceControl[] outActiveControls) {
    Arrays.fill(outActiveControls, null);
    int[] appOp = new int[1];
    final boolean isRoundedCornerOverlay = (attrs.privateFlags
            & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0;
    int res = mPolicy.checkAddPermission(attrs.type, isRoundedCornerOverlay, attrs.packageName,
            appOp);
    if (res != ADD_OKAY) {
        return res;
    }

    WindowState parentWindow = null;
    final int callingUid = Binder.getCallingUid();
    final int callingPid = Binder.getCallingPid();
    final long origId = Binder.clearCallingIdentity();
    final int type = attrs.type;

    synchronized (mGlobalLock) {
        if (!mDisplayReady) {
            throw new IllegalStateException("Display has not been initialialized");
        }

        final DisplayContent displayContent = getDisplayContentOrCreate(displayId, attrs.token);

        if (displayContent == null) {
            ProtoLog.w(WM_ERROR, "Attempted to add window to a display that does "
                    + "not exist: %d. Aborting.", displayId);
            return WindowManagerGlobal.ADD_INVALID_DISPLAY;
        }
        if (!displayContent.hasAccess(session.mUid)) {
            ProtoLog.w(WM_ERROR,
                    "Attempted to add window to a display for which the application "
                            + "does not have access: %d.  Aborting.",
                    displayContent.getDisplayId());
            return WindowManagerGlobal.ADD_INVALID_DISPLAY;
        }

        if (mWindowMap.containsKey(client.asBinder())) {
            ProtoLog.w(WM_ERROR, "Window %s is already added", client);
            return WindowManagerGlobal.ADD_DUPLICATE_ADD;
        }

        if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
            parentWindow = windowForClientLocked(null, attrs.token, false);
            if (parentWindow == null) {
                ProtoLog.w(WM_ERROR, "Attempted to add window with token that is not a window: "
                        + "%s.  Aborting.", attrs.token);
                return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
            }
            if (parentWindow.mAttrs.type >= FIRST_SUB_WINDOW
                    && parentWindow.mAttrs.type <= LAST_SUB_WINDOW) {
                ProtoLog.w(WM_ERROR, "Attempted to add window with token that is a sub-window: "
                        + "%s.  Aborting.", attrs.token);
                return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
            }
        }

        if (type == TYPE_PRIVATE_PRESENTATION && !displayContent.isPrivate()) {
            ProtoLog.w(WM_ERROR,
                    "Attempted to add private presentation window to a non-private display.  "
                            + "Aborting.");
            return WindowManagerGlobal.ADD_PERMISSION_DENIED;
        }

        if (type == TYPE_PRESENTATION && !displayContent.getDisplay().isPublicPresentation()) {
            ProtoLog.w(WM_ERROR,
                    "Attempted to add presentation window to a non-suitable display.  "
                            + "Aborting.");
            return WindowManagerGlobal.ADD_INVALID_DISPLAY;
        }

        int userId = UserHandle.getUserId(session.mUid);
        if (requestUserId != userId) {
            try {
                mAmInternal.handleIncomingUser(callingPid, callingUid, requestUserId,
                        false /*allowAll*/, ALLOW_NON_FULL, null, null);
            } catch (Exception exp) {
                ProtoLog.w(WM_ERROR, "Trying to add window with invalid user=%d",
                        requestUserId);
                return WindowManagerGlobal.ADD_INVALID_USER;
            }
            // It's fine to use this userId
            userId = requestUserId;
        }

        ActivityRecord activity = null;
        final boolean hasParent = parentWindow != null;
        // Use existing parent window token for child windows since they go in the same token
        // as there parent window so we can apply the same policy on them.
        WindowToken token = displayContent.getWindowToken(
                hasParent ? parentWindow.mAttrs.token : attrs.token);
        // If this is a child window, we want to apply the same type checking rules as the
        // parent window type.
        final int rootType = hasParent ? parentWindow.mAttrs.type : type;

        boolean addToastWindowRequiresToken = false;

        final IBinder windowContextToken = attrs.mWindowContextToken;

        if (token == null) {
            if (!unprivilegedAppCanCreateTokenWith(parentWindow, callingUid, type,
                    rootType, attrs.token, attrs.packageName)) {
                return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
            }
            if (hasParent) {
                // Use existing parent window token for child windows.
                token = parentWindow.mToken;
            } else if (mWindowContextListenerController.hasListener(windowContextToken)) {
                // Respect the window context token if the user provided it.
                final IBinder binder = attrs.token != null ? attrs.token : windowContextToken;
                final Bundle options = mWindowContextListenerController
                        .getOptions(windowContextToken);
                token = new WindowToken.Builder(this, binder, type)
                        .setDisplayContent(displayContent)
                        .setOwnerCanManageAppTokens(session.mCanAddInternalSystemWindow)
                        .setRoundedCornerOverlay(isRoundedCornerOverlay)
                        .setFromClientToken(true)
                        .setOptions(options)
                        .build();
            } else {
                final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
                token = new WindowToken.Builder(this, binder, type)
                        .setDisplayContent(displayContent)
                        .setOwnerCanManageAppTokens(session.mCanAddInternalSystemWindow)
                        .setRoundedCornerOverlay(isRoundedCornerOverlay)
                        .build();
            }
        } else if (rootType >= FIRST_APPLICATION_WINDOW
                && rootType <= LAST_APPLICATION_WINDOW) {
            activity = token.asActivityRecord();
            if (activity == null) {
                ProtoLog.w(WM_ERROR, "Attempted to add window with non-application token "
                        + ".%s Aborting.", token);
                return WindowManagerGlobal.ADD_NOT_APP_TOKEN;
            } else if (activity.getParent() == null) {
                ProtoLog.w(WM_ERROR, "Attempted to add window with exiting application token "
                        + ".%s Aborting.", token);
                return WindowManagerGlobal.ADD_APP_EXITING;
            } else if (type == TYPE_APPLICATION_STARTING) {
                if (activity.mStartingWindow != null) {
                    ProtoLog.w(WM_ERROR, "Attempted to add starting window to "
                            + "token with already existing starting window");
                    return WindowManagerGlobal.ADD_DUPLICATE_ADD;
                }
                if (activity.mStartingData == null) {
                    ProtoLog.w(WM_ERROR, "Attempted to add starting window to "
                            + "token but already cleaned");
                    return WindowManagerGlobal.ADD_DUPLICATE_ADD;
                }
            }
        } else if (rootType == TYPE_INPUT_METHOD) {
            if (token.windowType != TYPE_INPUT_METHOD) {
                ProtoLog.w(WM_ERROR, "Attempted to add input method window with bad token "
                        + "%s.  Aborting.", attrs.token);
                return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
            }
        } else if (rootType == TYPE_VOICE_INTERACTION) {
            if (token.windowType != TYPE_VOICE_INTERACTION) {
                ProtoLog.w(WM_ERROR, "Attempted to add voice interaction window with bad token "
                        + "%s.  Aborting.", attrs.token);
                return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
            }
        } else if (rootType == TYPE_WALLPAPER) {
            if (token.windowType != TYPE_WALLPAPER) {
                ProtoLog.w(WM_ERROR, "Attempted to add wallpaper window with bad token "
                        + "%s.  Aborting.", attrs.token);
                return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
            }
        } else if (rootType == TYPE_ACCESSIBILITY_OVERLAY) {
            if (token.windowType != TYPE_ACCESSIBILITY_OVERLAY) {
                ProtoLog.w(WM_ERROR,
                        "Attempted to add Accessibility overlay window with bad token "
                                + "%s.  Aborting.", attrs.token);
                return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
            }
        } else if (type == TYPE_TOAST) {
            // Apps targeting SDK above N MR1 cannot arbitrary add toast windows.
            addToastWindowRequiresToken = doesAddToastWindowRequireToken(attrs.packageName,
                    callingUid, parentWindow);
            if (addToastWindowRequiresToken && token.windowType != TYPE_TOAST) {
                ProtoLog.w(WM_ERROR, "Attempted to add a toast window with bad token "
                        + "%s.  Aborting.", attrs.token);
                return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
            }
        } else if (type == TYPE_QS_DIALOG) {
            if (token.windowType != TYPE_QS_DIALOG) {
                ProtoLog.w(WM_ERROR, "Attempted to add QS dialog window with bad token "
                        + "%s.  Aborting.", attrs.token);
                return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
            }
        } else if (token.asActivityRecord() != null) {
            ProtoLog.w(WM_ERROR, "Non-null activity for system window of rootType=%d",
                    rootType);
            // It is not valid to use an app token with other system types; we will
            // instead make a new token for it (as if null had been passed in for the token).
            attrs.token = null;
            token = new WindowToken.Builder(this, client.asBinder(), type)
                    .setDisplayContent(displayContent)
                    .setOwnerCanManageAppTokens(session.mCanAddInternalSystemWindow)
                    .build();
        }

        final WindowState win = new WindowState(this, session, client, token, parentWindow,
                appOp[0], attrs, viewVisibility, session.mUid, userId,
                session.mCanAddInternalSystemWindow);
        if (win.mDeathRecipient == null) {
            // Client has apparently died, so there is no reason to
            // continue.
            ProtoLog.w(WM_ERROR, "Adding window client %s"
                    + " that is dead, aborting.", client.asBinder());
            return WindowManagerGlobal.ADD_APP_EXITING;
        }

        if (win.getDisplayContent() == null) {
            ProtoLog.w(WM_ERROR, "Adding window to Display that has been removed.");
            return WindowManagerGlobal.ADD_INVALID_DISPLAY;
        }

        final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
        displayPolicy.adjustWindowParamsLw(win, win.mAttrs);
        win.updateRequestedVisibility(requestedVisibility);

        res = displayPolicy.validateAddingWindowLw(attrs, callingPid, callingUid);
        if (res != ADD_OKAY) {
            return res;
        }

        final boolean openInputChannels = (outInputChannel != null
                && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
        if  (openInputChannels) {
            win.openInputChannel(outInputChannel);
        }

        // If adding a toast requires a token for this app we always schedule hiding
        // toast windows to make sure they don't stick around longer then necessary.
        // We hide instead of remove such windows as apps aren't prepared to handle
        // windows being removed under them.
        //
        // If the app is older it can add toasts without a token and hence overlay
        // other apps. To be maximally compatible with these apps we will hide the
        // window after the toast timeout only if the focused window is from another
        // UID, otherwise we allow unlimited duration. When a UID looses focus we
        // schedule hiding all of its toast windows.
        if (type == TYPE_TOAST) {
            if (!displayContent.canAddToastWindowForUid(callingUid)) {
                ProtoLog.w(WM_ERROR, "Adding more than one toast window for UID at a time.");
                return WindowManagerGlobal.ADD_DUPLICATE_ADD;
            }
            // Make sure this happens before we moved focus as one can make the
            // toast focusable to force it not being hidden after the timeout.
            // Focusable toasts are always timed out to prevent a focused app to
            // show a focusable toasts while it has focus which will be kept on
            // the screen after the activity goes away.
            if (addToastWindowRequiresToken
                    || (attrs.flags & FLAG_NOT_FOCUSABLE) == 0
                    || displayContent.mCurrentFocus == null
                    || displayContent.mCurrentFocus.mOwnerUid != callingUid) {
                mH.sendMessageDelayed(
                        mH.obtainMessage(H.WINDOW_HIDE_TIMEOUT, win),
                        win.mAttrs.hideTimeoutMilliseconds);
            }
        }

        // Switch to listen to the {@link WindowToken token}'s configuration changes when
        // adding a window to the window context. Filter sub window type here because the sub
        // window must be attached to the parent window, which is attached to the window context
        // created window token.
        if (!win.isChildWindow()
                && mWindowContextListenerController.hasListener(windowContextToken)) {
            final int windowContextType = mWindowContextListenerController
                    .getWindowType(windowContextToken);
            if (type != windowContextType) {
                ProtoLog.w(WM_ERROR, "Window types in WindowContext and"
                        + " LayoutParams.type should match! Type from LayoutParams is %d,"
                        + " but type from WindowContext is %d", type, windowContextType);
                return WindowManagerGlobal.ADD_INVALID_TYPE;
            }
            final Bundle options = mWindowContextListenerController
                    .getOptions(windowContextToken);
            mWindowContextListenerController.registerWindowContainerListener(
                    windowContextToken, token, callingUid, type, options);
        }

        // From now on, no exceptions or errors allowed!

        res = ADD_OKAY;

        if (mUseBLAST) {
            res |= WindowManagerGlobal.ADD_FLAG_USE_BLAST;
        }

        if (displayContent.mCurrentFocus == null) {
            displayContent.mWinAddedSinceNullFocus.add(win);
        }

        if (excludeWindowTypeFromTapOutTask(type)) {
            displayContent.mTapExcludedWindows.add(win);
        }

        win.attach();
        mWindowMap.put(client.asBinder(), win);
        win.initAppOpsState();

        final boolean suspended = mPmInternal.isPackageSuspended(win.getOwningPackage(),
                UserHandle.getUserId(win.getOwningUid()));
        win.setHiddenWhileSuspended(suspended);

        final boolean hideSystemAlertWindows = !mHidingNonSystemOverlayWindows.isEmpty();
        win.setForceHideNonSystemOverlayWindowIfNeeded(hideSystemAlertWindows);

        final ActivityRecord tokenActivity = token.asActivityRecord();
        if (type == TYPE_APPLICATION_STARTING && tokenActivity != null) {
            tokenActivity.mStartingWindow = win;
            ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "addWindow: %s startingWindow=%s",
                    activity, win);
        }

        boolean imMayMove = true;

        win.mToken.addWindow(win);
        displayPolicy.addWindowLw(win, attrs);
        if (type == TYPE_INPUT_METHOD) {
            displayContent.setInputMethodWindowLocked(win);
            imMayMove = false;
        } else if (type == TYPE_INPUT_METHOD_DIALOG) {
            displayContent.computeImeTarget(true /* updateImeTarget */);
            imMayMove = false;
        } else {
            if (type == TYPE_WALLPAPER) {
                displayContent.mWallpaperController.clearLastWallpaperTimeoutTime();
                displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
            } else if (win.hasWallpaper()) {
                displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
            } else if (displayContent.mWallpaperController.isBelowWallpaperTarget(win)) {
                // If there is currently a wallpaper being shown, and
                // the base layer of the new window is below the current
                // layer of the target window, then adjust the wallpaper.
                // This is to avoid a new window being placed between the
                // wallpaper and its target.
                displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
            }
        }

        final WindowStateAnimator winAnimator = win.mWinAnimator;
        winAnimator.mEnterAnimationPending = true;
        winAnimator.mEnteringAnimation = true;
        // Check if we need to prepare a transition for replacing window first.
        if (activity != null && activity.isVisible()
                && !prepareWindowReplacementTransition(activity)) {
            // If not, check if need to set up a dummy transition during display freeze
            // so that the unfreeze wait for the apps to draw. This might be needed if
            // the app is relaunching.
            prepareNoneTransitionForRelaunching(activity);
        }

        if (displayPolicy.getLayoutHint(win.mAttrs, token, outInsetsState,
                win.isClientLocal())) {
            res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS;
        }

        if (mInTouchMode) {
            res |= WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE;
        }
        if (win.mActivityRecord == null || win.mActivityRecord.isClientVisible()) {
            res |= WindowManagerGlobal.ADD_FLAG_APP_VISIBLE;
        }

        displayContent.getInputMonitor().setUpdateInputWindowsNeededLw();

        boolean focusChanged = false;
        if (win.canReceiveKeys()) {
            focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS,
                    false /*updateInputWindows*/);
            if (focusChanged) {
                imMayMove = false;
            }
        }

        if (imMayMove) {
            displayContent.computeImeTarget(true /* updateImeTarget */);
        }

        // Don't do layout here, the window must call
        // relayout to be displayed, so we'll do it there.
        win.getParent().assignChildLayers();

        if (focusChanged) {
            displayContent.getInputMonitor().setInputFocusLw(displayContent.mCurrentFocus,
                    false /*updateInputWindows*/);
        }
        displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/);

        ProtoLog.v(WM_DEBUG_ADD_REMOVE, "addWindow: New client %s"
                + ": window=%s Callers=%s", client.asBinder(), win, Debug.getCallers(5));

        if (win.isVisibleOrAdding() && displayContent.updateOrientation()) {
            displayContent.sendNewConfiguration();
        }

        // This window doesn't have a frame yet. Don't let this window cause the insets change.
        displayContent.getInsetsStateController().updateAboveInsetsState(
                win, false /* notifyInsetsChanged */);

        getInsetsSourceControls(win, outActiveControls);
    }

    Binder.restoreCallingIdentity(origId);

    return res;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

IME SoftInputWindow窗口添加 的相关文章

  • 如何为 Android 创建我们自己的 PDF 查看器?

    我想构建一个可在我的 Android 应用程序中使用的 PDF 阅读器 查看器 但我无法使用 Google 文档来阅读我的内容 我无法使用我的设备中已安装的任何 PDF 阅读器 它应该位于我的应用程序内 并且不会通过互联网公开我的安全内容
  • 什么是适合 Android 的 html 解析器?

    什么是适合 Android 的 html 解析器 这取决于你想做什么 如果你真的想处理 html Java 中有很多 html 解析器可以使用 这里有一些 https stackoverflow com questions 2168610
  • 当活动被破坏时如何保存状态

    public class Talk extends Activity private ProgressDialog progDialog int typeBar TextView text1 EditText edit Button res
  • 在 Android 上生成 FileDescriptor 而不先打开文件

    在Android中 是否可以直接从字节数组生成FileDescriptor 而不必先打开文件 在 Android 2 2 中 我动态生成 MIDI 文件 然后使用 MediaPlayer 进行播放 我在下面包含了成功执行此操作的 Main
  • 单击另一个项目/小部件时展开/打开微调器?

    当用户单击另一个按钮时 我试图展开微调器 例如 我有一个带有值的微调器和一个 确定 按钮 当用户单击 确定 按钮而不从微调器中选择任何值时 微调器会自行扩展 是否可以在无需用户与微调器交互的情况下获得扩展微调器的事件 只需致电Spinner
  • 禁用 com.google.android.maps.MapView 中的平移/缩放

    如何禁用 MapView 的平移 缩放功能 不是缩放控件 我想要一个完全静态的地图 我还注意到触摸地图似乎不会触发 MapView onClickListener 有人可以详细说明为什么吗 对于 Android 版 Google Maps
  • 具有自定义厚度的虚线分隔符

    我有一个虚线分隔符
  • 如何检查 Android 设备是否在线

    我的服务器不断检查我的 Android 应用程序是否在线 请问我可以在我的android应用程序上执行哪些操作 创建一个名为 isNetworkAvailable 的帮助器方法 该方法将根据网络是否可用返回 true 或 false 它看起
  • Android 中是否可以使用滑动视图导航的刻度线?

    我想在 Android 应用程序中创建一组页面 这些页面可以水平滑动并使用刻度线来指示当前页面在我拥有的 12 个页面集中的位置 Android 设计文档中提到了像这样使用刻度线的设计模式 http developer android co
  • 使用全局变量从内部函数获取空字符串

    请帮助我解决一些小问题 我确信你能做到 D 我试图在 firestore 文档 user cases information 上设置一个字段 其中包含一个字段 case number 首先我声明这个全局变量 private String c
  • RecyclerView 单击将数据传递到新活动

    我正在制作一个包含三个选项卡的应用程序 每个选项卡都有一个带有两个文本的 recyclerview 我希望用户能够单击任何 recyclerview 项目 并且该项目中的文本应传递到新活动 这是应用程序外观的图像 https i stack
  • FileObserver 不适用于 Android 6.0 Marshmallow (API 23) 中的外部存储

    我有一个应用程序可以观察外部存储上的公共目录FileObserver 它运行良好Lollipop设备 我想添加对Marshmallow 所以我用它设置了一台 Nexus 9 平板电脑 在 Marshmallow 设备上 它失败 在 Loll
  • eglCodecCommon:setVertexArrayObject:设置vao调试消息

    我的 Android Emulator API 28 logcat 被这样的消息搞得晕头转向 D eglCodecCommon setVertexArrayObject set vao to 1 1 0 0 D eglCodecCommon
  • Android - 在图像/缩略图上覆盖播放按钮的最佳方式

    我有一个 Android 应用程序 可以播放音频 视频并显示图片 对于视频 我想在预览图像顶部以及列表视图中叠加一个播放按钮 现在我的做法是使用 xml 中的 ImageView 然后可绘制对象是一个图层图层列表 我以编程方式定义它 因为其
  • 如何将AVFrame转换为glTexImage2D使用的纹理?

    如您所知 AVFrame 有 2 个属性 pFrame gt data pFrame gt linesize 当我从视频 sdcard test mp4 android平台 读取帧后 并将其转换为RGB AVFrame副 img conve
  • Firebase:用户注册后如何进行电话号码验证?

    所以我知道我可以使用电子邮件验证或电话号码验证 但我想做的是在用户注册或登录后进行电话号码验证 如何连接这两种身份验证方法 最后 Firebase中是否有一个函数可以检查用户是否通过电话号码验证 谢谢 即使用户已通过身份验证 您仍然可以使用
  • 使用bindService启动IntentService时是否应该调用onHandleIntent?

    我的服务延伸IntentService当它开始时startService onHandleIntent被叫 但是 当服务启动时bindService 我确实需要绑定 onHandleIntent没有被调用 Should onHandleIn
  • Recyclerview项目点击涟漪效果[重复]

    这个问题在这里已经有答案了 我正在尝试添加Ripple影响到RecyclerView的项目 我在网上查了一下 但找不到我需要的东西 我努力了android background归因于RecyclerView本身并将其设置为 android
  • 我想要有条件的登录导航,没有 MAIN 片段或按钮

    我正在使用 Android Jetpack 导航组件 实时数据和 Firebase 我希望工作流程就像用户打开应用程序时一样 然后根据登录 注销状态导航到登录 配置文件片段 而不需要任何主片段或按钮 请 我的应用程序中没有主要片段 用户启动
  • 使用部分字符串匹配进行 Firebase 查询[重复]

    这个问题在这里已经有答案了 假设我有一个简单的 firebase 实时数据库结构 其中关键是username其值为userid 现在我想搜索userid by username 如果用户名匹配 这很容易 但如何获得部分匹配的答案 更清楚地说

随机推荐

  • MessageBoxA的用法

    一 函数原型 int fastcall MessageBox const char Text const char Caption int Flags 0x0 Flags表示对话框的按钮组合 取值有 MessageBox Flags def
  • 虚拟地址内存空间

    bss段详解 详细讲解
  • 一个简单的HttpClient使用案例

    HttpClient 是什么 HttpClient 是Apache Jakarta Common 下的子项目 可以用来提供高效的 最新的 功能丰富的支持 HTTP 协议的客户端编程工具包 并且它支持 HTTP 协议最新的版本和建议 该如何使
  • Web3-js的学习(5)-实现合约事件监听

    合约事件监听 latest 监听最新出块事件 pending 监听发布未进块事件 代码很简单 var Web3 require web3 var web3 new Web3 new Web3 providers HttpProvider h
  • 解决在Intellij IDEA中无法创建Servlet类的问题/New中没有Servlet类/创建不了Servlet类

    新手在学习Servlet相关知识的时候 一些课程往往会告知新手去使用IDEA自带的模板来创建Servlet 这样减少了注解等麻烦 降低了工作量 然而 如下图所示 很多人发现在自己的new一栏不存在Servlet类 如下图 网上的解决办法很多
  • 服务 -web服务器及ssh

    web服务器 文件共享 nfs samba 一般用于局域网中 ftp http 一般用于公网 tcp 80 httpd apache 1 完全开源 2 跨平台 3 支持多种编程语言 4 采用模块化的设计 5 安全稳定 IE www taob
  • 笔记/samba搭建

    1 安装 yum y install samba 2 配置 vim etc samba smb conf global 安全模式 user shared domain security user 认证模式 passdb backend td
  • autosar宏定义搜集

    1 AUTOSAR 长函数声明 2 教你如何阅读Autosar代码 1 概述 3 把AUTOSAR函数以及变量等定义的宏用脚本展开以提高可读性 4 Specification of Compiler Abstraction autosar
  • C语言满天星加月亮

    import java awt Color import java awt Graphics import javax swing JFrame import javax swing JPanel public class Stars pu
  • C++关键字 noexcept

    1 关键字noexcept 从C 11开始 我们能看到很多代码当中都有关键字noexcept 比如下面就是std initializer list的默认构造函数 其中使用了noexcept constexpr initializer lis
  • 使用JAVA反射的利与弊

    b color olive size large 在Java的20周年的纪念日的日子里 让我们来重新温习下Java里面的高级知识 Java肯定希望大家了解她 要不然你跟她天天相濡以沫了这么长时间 让她知道你竟然不了解她 不在乎她 那么她该有
  • wsus服务器搭建自动更新

    WSUS服务器搭建 第一次写博客还有点小激动 没有啥过硬的技术手段 简单记录一下日常操作 整一个Windows 2012 Server镜像文件 简单说一下为啥我找的是win 2012 Server吧 微软好像发布了个停止对Windows S
  • SearchView详细使用

    Android SearchView详细使用 布局
  • 在Fedora16中安装Qt

    首先 在http www trolltech com download上下载linux下的qt源文件 我下载时最新版是 qt everywhere opensource src 4 7 4 tar gz 将该文件放到某个目录下 进行解压缩
  • conda常用命令汇总

    conda常用命令汇总 1 创建一个虚拟环境 conda create name pytorch gpu python 3 8 创建一个名为pytorch gpu 可自定义 的虚拟环境 3 8指的是所安装python版本 2 进入一个已经存
  • 【Linux】进程控制1-进程创建、进程终止

    文章目录 进程创建 fork函数 用户空间 内核空间 写实拷贝 fork创建子进程时的一些特性 守护进程 进程终止 正常终止 异常终止 exit和 exit的区别 缓冲方式 进程创建 fork函数 调用fork函数让正在运行的进程创建出来一
  • python出现__init__() got an unexpected keyword argument ‘size‘错误解决!!!

    代码运行出现 init got an unexpected keyword argument size 错误 根据官方文档 将size改为vector size
  • 【git】error: failed to push some refs to ‘https://github.com/biluko/ZJGSU-Exams-in-master-two.git‘

    我在提交代码的时候 遇到了下面的错误 To https github com biluko ZJGSU Exams in master two git rejected master gt master non fast forward e
  • webpack5从入门到精通

    前言 webpack是什么 摘自官网的一段话 webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具 当 webpack 处理应用程序时 它会在内部从一个或多个入口点构建一个 依赖图 dependency gra
  • IME SoftInputWindow窗口添加

    IME SoftInputWindow窗口添加 1 时序图 2 InputMethodService onCreate 3 Dialog添加到WMS android12 release1 1 时序图 输入法应用继承InputMethodSe