从显示 Tap 位置的原理窥探 Android Input 系统

简介: 从显示 Tap 位置的原理窥探 Android Input 系统

原以为显示 Tap 位置是 ViewRootImpl 里依据 Touch 位置显示的 PopupWindow ,实际不是、而且要复杂得多。

开发者选项画面里的 “Show taps” 选项,开发者一定不陌生。开启之后,截屏或录屏里可以直观地展示点击过的位置,非常方便。

20200308234636925.jpg类似的选项还有显示 Touch 参数的 “Pointer location”,原理差不多。本次我们聚焦 “Show taps” 的功能,查阅 Android 12 的源码,将开启和显示流程分析清楚。

借此也窥探一下 Android 最重要的 Input 系统。

1. Settings 写入设置

首先是 Settings App 提供的开发者选项画面响应点击,将 “Show taps” 选项对应的设置 Key SHOW_TOUCHES 的 ON 值通过 android.provder.Settings 接口写入到保存系统设置数据的 SettingsProvier 中。

// packages/apps/Settings/src/com/android/settings/development/ShowTapsPreferenceController.java
public class ShowTapsPreferenceController extends DeveloperOptionsPreferenceController ... {
    ...
    @Override
    public boolean onPreferenceChange(Preference preference, Object newValue) {
        final boolean isEnabled = (Boolean) newValue;
        Settings.System.putInt(mContext.getContentResolver(),
                Settings.System.SHOW_TOUCHES, isEnabled ? SETTING_VALUE_ON : SETTING_VALUE_OFF);
        return true;
    }
    ...
}

2. IMS 监听和反映设置

负责管理输入的系统服务 InputManagerService 在启动之际,会注册监听 SHOW_TOUCHES Key 的观察者,在设置产生变化的时候调用 JNI 开始反映设置。

// frameworks/base/services/core/java/com/android/server/input/InputManagerService.java
public class InputManagerService extends IInputManager.Stub... {
    ...
    public void start() {
        registerShowTouchesSettingObserver();
        ...
    }
    private void registerShowTouchesSettingObserver() {
        mContext.getContentResolver().registerContentObserver(
                Settings.System.getUriFor(Settings.System.SHOW_TOUCHES), true,
                new ContentObserver(mHandler) {
                    @Override
                    public void onChange(boolean selfChange) {
                        updateShowTouchesFromSettings();
                    }
                }, UserHandle.USER_ALL);
    }
    private void updateShowTouchesFromSettings() {
        int setting = getShowTouchesSetting(0);
        nativeSetShowTouches(mPtr, setting != 0);
    }
    ...
}

JNI 端的 NativeInputManager 持有 InputFlinger ,向其中负责读取事件的 InputReader 发出更新配置的请求,配置变更的 Type 为 CHANGE_SHOW_TOUCHES。


在此之前需要先更新管理配置信息的 mLocked 结构体中的 showTouches 成员,InputFlinger 在刷新配置的时候需要验证。

// frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
static void nativeSetShowTouches(JNIEnv* /* env */,
        jclass /* clazz */, jlong ptr, jboolean enabled) {
    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
    im->setShowTouches(enabled);
}
void NativeInputManager::setShowTouches(bool enabled) {
    { // acquire lock
        ...
        mLocked.showTouches = enabled;
    } // release lock
    mInputManager->getReader()->requestRefreshConfiguration(
            InputReaderConfiguration::CHANGE_SHOW_TOUCHES);
}

3. 通过 InputReader 请求刷新配置

InputReader 接收到配置变化的 Type 之后,会根据记录待刷新配置的变量 mConfigurationChangesToRefresh 判断当前是否已经在刷新过程中。


如果尚未处于刷新中,则标记需要 wake 事件源头 EventHub,之后会将该变化添加该变量到中,最后就是通知 EventHub 唤醒。

// frameworks/native/services/inputflinger/reader/InputReader.cpp
void InputReader::requestRefreshConfiguration(uint32_t changes) {
    std::scoped_lock _l(mLock);
    if (changes) {
        bool needWake = !mConfigurationChangesToRefresh;
        mConfigurationChangesToRefresh |= changes;
        if (needWake) {
            mEventHub->wake();
        }
    }
}

4. EventHub 唤醒 InputReader 线程

IMS 过来的刷新请求最终需要 InputReader 线程来处理。

可是 InputReader 线程处在从 EventHub 中读取事件和没有事件时便调用 epoll_wait 进入等待状态的循环当中。

所以为了让其即刻处理配置变化,需要 EventHub 的手动唤醒。

// frameworks/native/services/inputflinger/reader/EventHub.cpp
void EventHub::wake() {
    ALOGV("wake() called");
    ssize_t nWrite;
    do {
        nWrite = write(mWakeWritePipeFd, "W", 1);
    } while (nWrite == -1 && errno == EINTR);
    if (nWrite != 1 && errno != EAGAIN) {
        ALOGW("Could not write wake signal: %s", strerror(errno));
    }
}
size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
    ...
    for (;;) {
        ...
        int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);
        ...
    }
    ...
}

5. InputReader 线程刷新配置

EventHub 唤醒后处于等待状态的 getEvents() 会结束,之后 InputReader 线程会进入下次循环即 loopOnce()。


其首先将检查是否存在待刷新的配置变化 changes,存在的话调用 refreshConfigurationLocked() 让 InputDevice 去重新配置这项变化。

void InputReader::loopOnce() {
    ...
    std::vector<InputDeviceInfo> inputDevices;
    { // acquire lock
        ...
        uint32_t changes = mConfigurationChangesToRefresh;
        if (changes) {
            mConfigurationChangesToRefresh = 0;
            timeoutMillis = 0;
            refreshConfigurationLocked(changes);
        } else if (mNextTimeout != LLONG_MAX) {
            nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
            timeoutMillis = toMillisecondTimeoutDelay(now, mNextTimeout);
        }
    } // release lock
    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
    ...
}

需要留意,refreshConfigurationLocked() 在调用 InputDevice 进一步处理之前需要先从 JNI 获取配置(getReaderConfiguration())的变化放入 mConfig 中。

void InputReader::refreshConfigurationLocked(uint32_t changes) {
    mPolicy->getReaderConfiguration(&mConfig);
    ...
    if (changes & InputReaderConfiguration::CHANGE_MUST_REOPEN) {
        mEventHub->requestReopenDevices();
    } else {
        for (auto& devicePair : mDevices) {
            std::shared_ptr<InputDevice>& device = devicePair.second;
            device->configure(now, &mConfig, changes);
        }
    }
    ...
}

InputDevice 配置变化

InputDevice 的 configure() 需要处理很多配置变化,比如键盘布局、麦克风等。对于 Show taps 的变化关注调用 InputMapper 的 congfigure() 即可。

// frameworks/native/services/inputflinger/reader/InputDevice.cpp
void InputDevice::configure(nsecs_t when, const InputReaderConfiguration* config,
                            uint32_t changes) {
    ...
    if (!isIgnored()) {
        ...
        for_each_mapper([this, when, config, changes](InputMapper& mapper) {
            mapper.configure(when, config, changes);
            mSources |= mapper.getSources();
        });
        ...
    }
}

TouchInputMapper 进一步处理

众多输入事件的物理数据需要对应的 InputMapper 来转化为上层能识别的事件类型。比如识别键盘输入的 KeyboardInputMapper、识别震动的 VibratorInputMapper 等等。


现在的触摸屏都支持多点触控,所以是 MultiTouchInputMapper 来处理的。可 MultiTouchInputMapper 没有复写 configure(),而是沿用由父类 TouchInputMapper的共通处理。

// frameworks/native/services/inputflinger/reader/mapper/TouchInputMapper.cpp
void TouchInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config,
                                 uint32_t changes) {
    ...
    bool resetNeeded = false;
    if (!changes ||
        (changes &
         (InputReaderConfiguration::CHANGE_DISPLAY_INFO |
          InputReaderConfiguration::CHANGE_POINTER_CAPTURE |
          InputReaderConfiguration::CHANGE_POINTER_GESTURE_ENABLEMENT |
          InputReaderConfiguration::CHANGE_SHOW_TOUCHES |
          InputReaderConfiguration::CHANGE_EXTERNAL_STYLUS_PRESENCE))) {
        // Configure device sources, surface dimensions, orientation and
        // scaling factors.
        configureSurface(when, &resetNeeded);
    }
    ...
}

TouchInputMapper 会依据 changes 的类型进行对应处理,对于 SHOW_TOUCHES 的变化需要调用 configureSurface() 进一步处理。

6. 创建和初始化 PointerController

configureSurface() 进行多个参数的测量和配置,其中和 “Show taps” 相关的是 PointerController 的创建,该类是 Mouse、Taps、Pointer location 等系统 Touch 显示的专用类。


需要留意的是创建之前需要验证从 JNI 里取得的 mConfig.showTouches 变量。

void TouchInputMapper::configureSurface(nsecs_t when, bool* outResetNeeded) {
    ...
    // Create pointer controller if needed, and keep it around if Pointer Capture is enabled to
    // preserve the cursor position.
    if (mDeviceMode == DeviceMode::POINTER ||
        (mDeviceMode == DeviceMode::DIRECT && mConfig.showTouches) ||
        (mParameters.deviceType == Parameters::DeviceType::POINTER && mConfig.pointerCapture)) {
        if (mPointerController == nullptr) {
            mPointerController = getContext()->getPointerController(getDeviceId());
        }
        if (mConfig.pointerCapture) {
            mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
        }
    } else {
        mPointerController.reset();
    }
    ...
}

getPointerController() 会回调到 InputReader 开启 PointerController 的创建和初始化。

std::shared_ptr<PointerControllerInterface> InputReader::getPointerControllerLocked(
        int32_t deviceId) {
    std::shared_ptr<PointerControllerInterface> controller = mPointerController.lock();
    if (controller == nullptr) {
        controller = mPolicy->obtainPointerController(deviceId);
        mPointerController = controller;
        updatePointerDisplayLocked();
    }
    return controller;
}

用于创建的 obtainPointerController() 的实现在 JNI 里,调用 PointerController 的静态方法 create() 开始构建实例。

std::shared_ptr<PointerControllerInterface> NativeInputManager::obtainPointerController(
        int32_t /* deviceId */) {
    ...
    std::shared_ptr<PointerController> controller = mLocked.pointerController.lock();
    if (controller == nullptr) {
        ensureSpriteControllerLocked();
        controller = PointerController::create(this, mLooper, mLocked.spriteController);
        mLocked.pointerController = controller;
        updateInactivityTimeoutLocked();
    }
    return controller;
}

PointerController 构建的同时需要构建持有的 MouseCursorController

// frameworks/base/libs/input/PointerController.cpp
std::shared_ptr<PointerController> PointerController::create( ... ) {
    std::shared_ptr<PointerController> controller = std::shared_ptr<PointerController>(
            new PointerController(policy, looper, spriteController));
    ...
    return controller;
}
PointerController::PointerController( ... )
      : mContext(policy, looper, spriteController, *this), mCursorController(mContext) {
    std::scoped_lock lock(mLock);
    mLocked.presentation = Presentation::SPOT;
}

obtainPointerController() 执行完之后调用 updatePointerDisplayLocked() 执行 PointerController 的初始化。

初始化 PointerController

调用 PointerController 的 setDisplayViewport() 传入显示用的 DisplayViewPort

void InputReader::updatePointerDisplayLocked() {
    ...
    std::optional<DisplayViewport> viewport =
            mConfig.getDisplayViewportById(mConfig.defaultPointerDisplayId);
    if (!viewport) {
        ...
        viewport = mConfig.getDisplayViewportById(ADISPLAY_ID_DEFAULT);
    }
    ...
    controller->setDisplayViewport(*viewport);
}

setDisplayViewport() 需要持有的 MouseCursorController 进一步初始化。

void PointerController::setDisplayViewport(const DisplayViewport& viewport) {
    ...
    mCursorController.setDisplayViewport(viewport, getAdditionalMouseResources);
}

MouseCursorController 需要获取 Display 相关的参数,并执行两个重要步骤:

  1. loadResourcesLocked()
  2. updatePointerLocked()
// frameworks/base/libs/input/MouseCursorController.cpp
void MouseCursorController::setDisplayViewport(const DisplayViewport& viewport,
                                               bool getAdditionalMouseResources) {
    ...
    // Reset cursor position to center if size or display changed.
    if (oldViewport.displayId != viewport.displayId || oldDisplayWidth != newDisplayWidth ||
        oldDisplayHeight != newDisplayHeight) {
        float minX, minY, maxX, maxY;
        if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
            mLocked.pointerX = (minX + maxX) * 0.5f;
            mLocked.pointerY = (minY + maxY) * 0.5f;
            // Reload icon resources for density may be changed.
            loadResourcesLocked(getAdditionalMouseResources);
        ...
        }
    } else if (oldViewport.orientation != viewport.orientation) {
        ...
    }
    updatePointerLocked();
}

加载 Pointer 相关资源

void MouseCursorController::loadResourcesLocked(bool getAdditionalMouseResources) REQUIRES(mLock) {
    ...
    policy->loadPointerResources(&mResources, mLocked.viewport.displayId);
    policy->loadPointerIcon(&mLocked.pointerIcon, mLocked.viewport.displayId);
    ...
}

省略诸多细节,loadPointerResources() 将通过 IMS 的 JNI 端以及 PointerIcon 的 JNI 端创建 PointerIcon 实例,并读取显示 Taps 的资源。


getSystemIcon() 则是负责的函数,其将读取系统资源里名为 Pointer 的 Style,并读取 Taps 对应的资源 ID。

// frameworks/base/core/java/android/view/PointerIcon.java
    public static PointerIcon getSystemIcon(@NonNull Context context, int type) {
        ...
        int typeIndex = getSystemIconTypeIndex(type);
        if (typeIndex == 0) {
            typeIndex = getSystemIconTypeIndex(TYPE_DEFAULT);
        }
        int defStyle = sUseLargeIcons ?
                com.android.internal.R.style.LargePointer : com.android.internal.R.style.Pointer;
        TypedArray a = context.obtainStyledAttributes(null,
                com.android.internal.R.styleable.Pointer,
                0, defStyle);
        int resourceId = a.getResourceId(typeIndex, -1);
        ...
        icon = new PointerIcon(type);
        if ((resourceId & 0xff000000) == 0x01000000) {
            icon.mSystemIconResourceId = resourceId;
        } else {
            icon.loadResource(context, context.getResources(), resourceId);
        }
        systemIcons.append(type, icon);
        return icon;
    }
    private static int getSystemIconTypeIndex(int type) {
        switch (type) {
            ...
            case TYPE_SPOT_TOUCH:
                return com.android.internal.R.styleable.Pointer_pointerIconSpotTouch;
            ...
            default:
                return 0;
        }
    }

资源 ID 为 pointer_spot_touch_icon。

<!-- frameworks/base/core/res/res/drawable/pointer_spot_touch_icon.xml -->
<?xml version="1.0" encoding="utf-8"?>
<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
    android:bitmap="@drawable/pointer_spot_touch"
    android:hotSpotX="16dp"
    android:hotSpotY="16dp" />

其指向的图片就是如下熟悉的 Spot png:pointer_spot_touch.png。之后的 loadPointerIcon 阶段会将该图片解析成 Bitmap 并被管理在 SpriteIcon 中。

1832b220aa754cd18c504acc7686a560.png

而 SpriteIcon 在 updatePointerLocked() 阶段会被存放到 SpriteController 中,等待显示的调度。

void MouseCursorController::updatePointerLocked() REQUIRES(mLock) {
    if (!mLocked.viewport.isValid()) {
        return;
    }
    sp<SpriteController> spriteController = mContext.getSpriteController();
    spriteController->openTransaction();
    ...
    if (mLocked.updatePointerIcon) {
        if (mLocked.requestedPointerType == mContext.getPolicy()->getDefaultPointerIconId()) {
            mLocked.pointerSprite->setIcon(mLocked.pointerIcon);
        ...
        }
        mLocked.updatePointerIcon = false;
    }
    spriteController->closeTransaction();
}

7. 显示 tap

点击的时候 EventHub#getEvents() 会产生事件,InputReader#loopOnce() 会调用 processEventsLocked() 处理事件。

void InputReader::loopOnce() {
    ...
    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
    { // acquire lock
        ...
        if (count) {
            processEventsLocked(mEventBuffer, count);
        }
        ....
    } // release lock
    ...
}

之后调用 InputMapper 开始加工事件,并在 TouchInputMapper#cookAndDispatch() 的时候调用 updateTouchSpots() 更新 PointerController 的一些参数。

void TouchInputMapper::updateTouchSpots() {
    ...
    mPointerController->setPresentation(PointerControllerInterface::Presentation::SPOT);
    mPointerController->fade(PointerControllerInterface::Transition::GRADUAL);
    mPointerController->setButtonState(mCurrentRawState.buttonState);
    setTouchSpots(mCurrentCookedState.cookedPointerData.pointerCoords,
                  mCurrentCookedState.cookedPointerData.idToIndex,
                  mCurrentCookedState.cookedPointerData.touchingIdBits, mViewport.displayId);
}

其中比较关键的 setTouchSpots() 是显示 Taps 的关键步骤,准备 x、y 坐标和压力值。

在 Reader 而不是 Dispatch、更不是 ViewRootImpl 的时候处理的原因在于:Read 到事件即显示可以更早地响,同时不用占用 App 进程。

void TouchInputMapper::setTouchSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
                                     BitSet32 spotIdBits, int32_t displayId) {
    std::array<PointerCoords, MAX_POINTERS> outSpotCoords{};
    for (BitSet32 idBits(spotIdBits); !idBits.isEmpty();) {
        const uint32_t index = spotIdToIndex[idBits.clearFirstMarkedBit()];
        float x = spotCoords[index].getX();
        float y = spotCoords[index].getY();
        float pressure = spotCoords[index].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE);
        ...
    }
    mPointerController->setSpots(outSpotCoords.data(), spotIdToIndex, spotIdBits, displayId);
}

其后 PointerController 会通过 TouchSpotController 创建 Spot 实例向其发送 updateSprite() 请求。最后回调 SpriteController 调用 setIcon 处理。

// frameworks/base/libs/input/TouchSpotController.cpp
void TouchSpotController::Spot::updateSprite(const SpriteIcon* icon, float x, float y,
                                             int32_t displayId) {
    sprite->setLayer(Sprite::BASE_LAYER_SPOT + id);
    ...
    if (icon != mLastIcon) {
        mLastIcon = icon;
        if (icon) {
            sprite->setIcon(*icon);
            sprite->setVisible(true);
        } else {
            sprite->setVisible(false);
        }
    }
}
// frameworks/base/libs/input/SpriteController.cpp
void SpriteController::SpriteImpl::setIcon(const SpriteIcon& icon) {
    AutoMutex _l(mController->mLock);
    ...
    invalidateLocked(dirty);
}
void SpriteController::SpriteImpl::invalidateLocked(uint32_t dirty) {
    ...
    if (!wasDirty) {
        mController->invalidateSpriteLocked(this);
    }
}
void SpriteController::invalidateSpriteLocked(const sp<SpriteImpl>& sprite) {
    bool wasEmpty = mLocked.invalidatedSprites.isEmpty();
    mLocked.invalidatedSprites.push(sprite);
    if (wasEmpty) {
        if (mLocked.transactionNestingCount != 0) {
            mLocked.deferredSpriteUpdate = true;
        } else {
            mLooper->sendMessage(mHandler, Message(MSG_UPDATE_SPRITES));
        }
    }
}

MSG_UPDATE_SPRITES 经过 Handler 回调 doUpdateSprites(),将取出封装在 SpriteUpdate 中的 SpriteIcon 并执行 draw

void SpriteController::doUpdateSprites() {
    ...
    for (size_t i = 0; i < numSprites; i++) {
        SpriteUpdate& update = updates.editItemAt(i);
        if ((update.state.dirty & DIRTY_BITMAP) && update.state.surfaceDrawn) {
            update.state.surfaceDrawn = false;
            update.surfaceChanged = surfaceChanged = true;
        }
        if (update.state.surfaceControl != NULL && !update.state.surfaceDrawn
                && update.state.wantSurfaceVisible()) {
            sp<Surface> surface = update.state.surfaceControl->getSurface();
            if (update.state.icon.draw(surface)) {
                update.state.surfaceDrawn = true;
                update.surfaceChanged = surfaceChanged = true;
            }
        }
    }
    ...
    updates.clear();
}

最后,SpriteIcon 将取出 Bitmap 描画到 Surface 的 Canvas 上去。

bool SpriteIcon::draw(sp<Surface> surface) const {
    ...
    graphics::Paint paint;
    paint.setBlendMode(ABLEND_MODE_SRC);
    graphics::Canvas canvas(outBuffer, (int32_t)surface->getBuffersDataSpace());
    canvas.drawBitmap(bitmap, 0, 0, &paint);
    ...
    status = surface->unlockAndPost();
    if (status) {
        ALOGE("Error %d unlocking and posting sprite surface after drawing.", status);
    }
    return !status;
}

总体流程

通过一个框图简单回顾一下整个流程。

可以看到,简简单单的 Show taps 功能,从设置、配置、刷新再到显示,经历了多个进程、多个模块的协力。

1832b220aa754cd18c504acc7686a560.png

通过这个过程的梳理,可以一探 InputManagerService 和 InputFinger 的大体原理。但篇幅有限省略了很多细节,有兴趣彻底贯通 Android Input 系统的话,没有捷径、只得一字一句地逐个啃!


相关文章
|
28天前
|
缓存 Java Shell
Android 系统缓存扫描与清理方法分析
Android 系统缓存从原理探索到实现。
52 15
Android 系统缓存扫描与清理方法分析
|
19天前
|
算法 JavaScript Android开发
|
21天前
|
安全 搜索推荐 Android开发
揭秘安卓与iOS系统的差异:技术深度对比
【10月更文挑战第27天】 本文深入探讨了安卓(Android)与iOS两大移动操作系统的技术特点和用户体验差异。通过对比两者的系统架构、应用生态、用户界面、安全性等方面,揭示了为何这两种系统能够在市场中各占一席之地,并为用户提供不同的选择。文章旨在为读者提供一个全面的视角,理解两种系统的优势与局限,从而更好地根据自己的需求做出选择。
56 2
|
27天前
|
缓存 Java 数据库
Android的ANR原理
【10月更文挑战第18天】了解 ANR 的原理对于开发高质量的 Android 应用至关重要。通过合理的设计和优化,可以有效避免 ANR 的发生,提升应用的性能和用户体验。
54 8
|
30天前
|
安全 搜索推荐 Android开发
深入探索安卓与iOS系统的差异及其对用户体验的影响
在当今的智能手机市场中,安卓和iOS是两大主流操作系统。它们各自拥有独特的特性和优势,为用户提供了不同的使用体验。本文将深入探讨安卓与iOS系统之间的主要差异,包括它们的设计理念、用户界面、应用生态以及安全性等方面,并分析这些差异如何影响用户的使用体验。
|
29天前
|
安全 搜索推荐 Android开发
揭秘iOS与Android系统的差异:一场技术与哲学的较量
在当今数字化时代,智能手机操作系统的选择成为了用户个性化表达和技术偏好的重要标志。iOS和Android,作为市场上两大主流操作系统,它们之间的竞争不仅仅是技术的比拼,更是设计理念、用户体验和生态系统构建的全面较量。本文将深入探讨iOS与Android在系统架构、应用生态、用户界面及安全性等方面的本质区别,揭示这两种系统背后的哲学思想和市场策略,帮助读者更全面地理解两者的优劣,从而做出更适合自己的选择。
|
20天前
|
安全 搜索推荐 程序员
深入探索Android系统的碎片化问题及其解决方案
在移动操作系统的世界中,Android以其开放性和灵活性赢得了广泛的市场份额。然而,这种开放性也带来了一个众所周知的问题——系统碎片化。本文旨在探讨Android系统碎片化的现状、成因以及可能的解决方案,为开发者和用户提供一种全新的视角来理解这一现象。通过分析不同版本的Android系统分布、硬件多样性以及更新机制的影响,我们提出了一系列针对性的策略,旨在减少碎片化带来的影响,提升用户体验。
|
20天前
|
安全 Android开发 iOS开发
深入探索iOS与Android系统的差异性及优化策略
在当今数字化时代,移动操作系统的竞争尤为激烈,其中iOS和Android作为市场上的两大巨头,各自拥有庞大的用户基础和独特的技术特点。本文旨在通过对比分析iOS与Android的核心差异,探讨各自的优势与局限,并提出针对性的优化策略,以期为用户提供更优质的使用体验和为开发者提供有价值的参考。
|
1月前
|
XML 前端开发 Android开发
Android View的绘制流程和原理详细解说
Android View的绘制流程和原理详细解说
39 3
|
22天前
|
安全 Android开发 iOS开发
安卓系统与iOS系统的比较####
【10月更文挑战第26天】 本文将深入探讨安卓(Android)和iOS这两大主流移动操作系统的各自特点、优势与不足。通过对比分析,帮助读者更好地理解两者在用户体验、应用生态、系统安全等方面的差异,从而为消费者在选择智能手机时提供参考依据。无论你是技术爱好者还是普通用户,这篇文章都将为你揭示两大系统背后的故事和技术细节。 ####
40 0