android 接入USB输入设备后应用重跑onCreate

简介: android 接入USB输入设备后应用重跑onCreate

平台:


RK3288 + Android7.12


问题:


打开测试应用, 并在各个生命周期中加入LOG, 当接入USB外设后, 会导致Activity重走了一次生命周期.


LOG如下:
2017-01-02 11:35:20.189com.test.app I/EntranceActivity: ALog > LIEF[ -> onCreate]
2017-01-02 11:35:20.246com.test.app I/EntranceActivity: ALog > LIEF[onCreate -> onStart]
2017-01-02 11:35:20.249com.test.app I/EntranceActivity: ALog > LIEF[onStart -> onResume]
接上USB外设
2017-01-02 11:35:59.621com.test.app I/EntranceActivity: ALog > LIEF[onResume -> onPause]
2017-01-02 11:35:59.622com.test.app I/EntranceActivity: ALog > LIEF[onPause -> onSaveInstanceState]
2017-01-02 11:35:59.636com.test.app I/EntranceActivity: ALog > LIEF[onSaveInstanceState -> onStop]
2017-01-02 11:35:59.636com.test.app I/EntranceActivity: ALog > LIEF[onStop -> onDestroy]
2017-01-02 11:35:59.686com.test.app I/EntranceActivity: ALog > LIEF[ -> onCreate]
2017-01-02 11:35:59.704com.test.app I/EntranceActivity: ALog > LIEF[onCreate -> onStart]
2017-01-02 11:35:59.706com.test.app I/EntranceActivity: ALog > LIEF[onStart -> onRestoreInstanceState]
2017-01-02 11:35:59.708com.test.app I/EntranceActivity: ALog > LIEF[onRestoreInstanceState -> onResume]


解决方案:


在AndroidManifest.xml中, 指定activity的定义加上:


android:configChanges="screenSize|keyboard|keyboardHidden|navigation"


在对应的Activity中, 新增:


@Override
    public void onConfigurationChanged(Configuration newConfig) {
  //USB 拔插动作, 这个方法都会被调用.
        super.onConfigurationChanged(newConfig);
}


修改后LOG:
2017-01-01 20:10:36.561com.test.app I/EntranceActivity: ALog > LIFE[ -> onCreate]
2017-01-01 20:10:36.627com.test.app I/EntranceActivity: ALog > LIFE[onCreate -> onStart]
2017-01-01 20:10:36.630com.test.app I/EntranceActivity: ALog > LIFE[onStart -> onResume]
拔插USB:
2017-01-01 20:13:15.329com.test.app I/EntranceActivity: ALog > LIFE[onResume -> onConfigurationChanged]


代码跟踪:


|–frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp


mInputManager = new InputManager(eventHub, this, this);


|-- frameworks/native/services/inputflinger/InputDispatcher.cpp


void InputDispatcher::doNotifyConfigurationChangedInterruptible(
        CommandEntry* commandEntry) {
    mLock.unlock();
#ifndef INPUT_BOX
  //-----------mPolicy来自 new InputManager(eventHub, this, this);---------
    mPolicy->notifyConfigurationChanged(commandEntry->eventTime);
#endif
    mLock.lock();
}


|–frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp


void NativeInputManager::notifyConfigurationChanged(nsecs_t when) {
#if DEBUG_INPUT_DISPATCHER_POLICY
    ALOGD("notifyConfigurationChanged - when=%lld", when);
#endif
    JNIEnv* env = jniEnv();
    env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyConfigurationChanged, when);
    checkAndClearExceptionFromCallback(env, "notifyConfigurationChanged");
}


|–frameworks/base/services/core/java/com/android/server/input/InputManagerService.java


// Native callback.
    private void notifyConfigurationChanged(long whenNanos) {
        mWindowManagerCallbacks.notifyConfigurationChanged();
    }


|–frameworks/base/services/core/java/com/android/server/wm/InputMonitor.java


/* Notifies that the input device configuration has changed. */
    @Override
    public void notifyConfigurationChanged() {
        mService.sendNewConfiguration();
        synchronized (mInputDevicesReadyMonitor) {
            if (!mInputDevicesReady) {
                mInputDevicesReady = true;
                mInputDevicesReadyMonitor.notifyAll();
            }
        }
    }


|-- frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java


/*
     * Instruct the Activity Manager to fetch the current configuration and broadcast
     * that to config-changed listeners if appropriate.
     */
    void sendNewConfiguration() {
        try {
            mActivityManager.updateConfiguration(null);
        } catch (RemoteException e) {
        }
    }
  public Configuration computeNewConfiguration() {
        synchronized (mWindowMap) {
            return computeNewConfigurationLocked();
        }
    }


|-- frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

public void updateConfiguration(Configuration values) {
        enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
                "updateConfiguration()");
        synchronized(this) {
            if (values == null && mWindowManager != null) {
                // sentinel: fetch the current configuration from the window manager
                values = mWindowManager.computeNewConfiguration();
            }
            if (mWindowManager != null) {
                mProcessList.applyDisplaySize(mWindowManager);
            }
            final long origId = Binder.clearCallingIdentity();
            if (values != null) {
                Settings.System.clearConfiguration(values);
            }
            updateConfigurationLocked(values, null, false);
            Binder.restoreCallingIdentity(origId);
        }
    }


|-- frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java


public Configuration computeNewConfiguration() {
        synchronized (mWindowMap) {
            return computeNewConfigurationLocked();
        }
    }
    private Configuration computeNewConfigurationLocked() {
        if (!mDisplayReady) {
            return null;
        }
        Configuration config = new Configuration();
        config.fontScale = 0;
        computeScreenConfigurationLocked(config);
        return config;
    }
    /** Do not call if mDisplayReady == false */
    void computeScreenConfigurationLocked(Configuration config) {
        final DisplayInfo displayInfo = updateDisplayAndOrientationLocked(
                config.uiMode);
        final int dw = displayInfo.logicalWidth;
        final int dh = displayInfo.logicalHeight;
        config.orientation = (dw <= dh) ? Configuration.ORIENTATION_PORTRAIT :
                Configuration.ORIENTATION_LANDSCAPE;
        config.screenWidthDp =
                (int)(mPolicy.getConfigDisplayWidth(dw, dh, mRotation, config.uiMode) /
                        mDisplayMetrics.density);
        config.screenHeightDp =
                (int)(mPolicy.getConfigDisplayHeight(dw, dh, mRotation, config.uiMode) /
                        mDisplayMetrics.density);
        final boolean rotated = (mRotation == Surface.ROTATION_90
                || mRotation == Surface.ROTATION_270);
        computeSizeRangesAndScreenLayout(displayInfo, rotated, config.uiMode, dw, dh,
                mDisplayMetrics.density, config);
        config.screenLayout = (config.screenLayout & ~Configuration.SCREENLAYOUT_ROUND_MASK)
                | ((displayInfo.flags & Display.FLAG_ROUND) != 0
                        ? Configuration.SCREENLAYOUT_ROUND_YES
                        : Configuration.SCREENLAYOUT_ROUND_NO);
        config.compatScreenWidthDp = (int)(config.screenWidthDp / mCompatibleScreenScale);
        config.compatScreenHeightDp = (int)(config.screenHeightDp / mCompatibleScreenScale);
        config.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, config.uiMode,
                mDisplayMetrics, dw, dh);
        config.densityDpi = displayInfo.logicalDensityDpi;
        // Update the configuration based on available input devices, lid switch,
        // and platform configuration.
        config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
        config.keyboard = Configuration.KEYBOARD_NOKEYS;
        config.navigation = Configuration.NAVIGATION_NONAV;
        int keyboardPresence = 0;
        int navigationPresence = 0;
        final InputDevice[] devices = mInputManager.getInputDevices();
        final int len = devices.length;
        for (int i = 0; i < len; i++) {
            InputDevice device = devices[i];
            if (!device.isVirtual()) {
                final int sources = device.getSources();
                final int presenceFlag = device.isExternal() ?
                        WindowManagerPolicy.PRESENCE_EXTERNAL :
                                WindowManagerPolicy.PRESENCE_INTERNAL;
                if (mIsTouchDevice) {
                    if ((sources & InputDevice.SOURCE_TOUCHSCREEN) ==
                            InputDevice.SOURCE_TOUCHSCREEN) {
                        config.touchscreen = Configuration.TOUCHSCREEN_FINGER;
                    }
                } else {
                    config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
                }
                if ((sources & InputDevice.SOURCE_TRACKBALL) == InputDevice.SOURCE_TRACKBALL) {
                    config.navigation = Configuration.NAVIGATION_TRACKBALL;
                    navigationPresence |= presenceFlag;
                } else if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD
                        && config.navigation == Configuration.NAVIGATION_NONAV) {
                    config.navigation = Configuration.NAVIGATION_DPAD;
                    navigationPresence |= presenceFlag;
                }
                if (device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
                    config.keyboard = Configuration.KEYBOARD_QWERTY;
                    keyboardPresence |= presenceFlag;
                }
            }
        }
        if (config.navigation == Configuration.NAVIGATION_NONAV && mHasPermanentDpad) {
            config.navigation = Configuration.NAVIGATION_DPAD;
            navigationPresence |= WindowManagerPolicy.PRESENCE_INTERNAL;
        }
        // Determine whether a hard keyboard is available and enabled.
        boolean hardKeyboardAvailable = config.keyboard != Configuration.KEYBOARD_NOKEYS;
        if (hardKeyboardAvailable != mHardKeyboardAvailable) {
            mHardKeyboardAvailable = hardKeyboardAvailable;
            mH.removeMessages(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE);
            mH.sendEmptyMessage(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE);
        }
        boolean dualscreenconfig = Settings.System.getInt(mContext.getContentResolver(),Settings.DUAL_SCREEN_MODE,0) != 0;
        config.dualscreenflag= dualscreenconfig ? Configuration.ENABLE_DUAL_SCREEN:Configuration.DISABLE_DUAL_SCREEN;
        // Let the policy update hidden states.
        config.keyboardHidden = Configuration.KEYBOARDHIDDEN_NO;
        config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_YES;
        config.navigationHidden = Configuration.NAVIGATIONHIDDEN_NO;
        mPolicy.adjustConfigurationLw(config, keyboardPresence, navigationPresence);
    }


未接入:I/ActivityManager: Config changes=60 {1.0 dualscreenflag=DISABLE ?mcc?mnc [zh_CN] ldltr sw1080dp w1920dp h1000dp 160dpi xlrg long land finger qwerty/v/h -nav/h s.9}
接入后:I/ActivityManager: Config changes=60 {1.0 dualscreenflag=DISABLE ?mcc?mnc [zh_CN] ldltr sw1080dp w1920dp h1000dp 160dpi xlrg long land finger qwerty/v/h dpad/v s.8}
ActivityManager: Configuration changes for ActivityRecord{2f76229 u0com.test.app/.EntranceActivity t46} ; taskChanges={}, allChanges={CONFIG_KEYBOARD_HIDDEN, CONFIG_NAVIGATION}


|-- frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java


private boolean updateConfigurationLocked(Configuration values, ActivityRecord starting,
            boolean initLocale, boolean persistent, int userId, boolean deferResume) {
        int changes = 0;
  ...
                for (int i=mLruProcesses.size()-1; i>=0; i--) {
                    ProcessRecord app = mLruProcesses.get(i);
                    try {
                        if (app.thread != null) {
                            if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Sending to proc "
                                    + app.processName + " new config " + mConfiguration);
                            app.thread.scheduleConfigurationChanged(configCopy);
                        }
                    } catch (Exception e) {
                    }
                }
  ...
  final ActivityStack mainStack = mStackSupervisor.getFocusedStack();
        // mainStack is null during startup.
        if (mainStack != null) {
            if (changes != 0 && starting == null) {
                // If the configuration changed, and the caller is not already
                // in the process of starting an activity, then find the top
                // activity to check if its configuration needs to change.
                starting = mainStack.topRunningActivityLocked();
            }
            if (starting != null) {
                kept = mainStack.ensureActivityConfigurationLocked(starting, changes, false);
                // And we need to make sure at this point that all other activities
                // are made visible with the correct configuration.
                mStackSupervisor.ensureActivitiesVisibleLocked(starting, changes,
                        !PRESERVE_WINDOWS);
            }
        }
  ...
  }


调用 onConfigurationChanged


|-- frameworks/base/core/java/android/app/ActivityThread.java


private class ApplicationThread extends ApplicationThreadNative {
        public void scheduleConfigurationChanged(Configuration config) {
            updatePendingConfiguration(config);
            sendMessage(H.CONFIGURATION_CHANGED, config);
        }
  }
    private class H extends Handler {
           public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {             
                case CONFIGURATION_CHANGED:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged");
                    mCurDefaultDisplayDpi = ((Configuration)msg.obj).densityDpi;
                    mUpdatingSystemConfig = true;
                    handleConfigurationChanged((Configuration)msg.obj, null);
                    mUpdatingSystemConfig = false;
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
    }
    final void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) {
  ...
        if (callbacks != null) {
            final int N = callbacks.size();
            for (int i=0; i<N; i++) {
                ComponentCallbacks2 cb = callbacks.get(i);
                if (cb instanceof Activity) {
                    // If callback is an Activity - call corresponding method to consider override
                    // config and avoid onConfigurationChanged if it hasn't changed.
                    Activity a = (Activity) cb;
                    performConfigurationChangedForActivity(mActivities.get(a.getActivityToken()),
                            config, REPORT_TO_ACTIVITY);
                } else {
                    performConfigurationChanged(cb, null, config, null, REPORT_TO_ACTIVITY);
                }
            }
        }
    }
  private void performConfigurationChangedForActivity(ActivityClientRecord r,
                                                        Configuration newBaseConfig,
                                                        boolean reportToActivity) {
        r.tmpConfig.setTo(newBaseConfig);
        if (r.overrideConfig != null) {
            r.tmpConfig.updateFrom(r.overrideConfig);
        }
        performConfigurationChanged(r.activity, r.token, r.tmpConfig, r.overrideConfig,
                reportToActivity);
        freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));
    }
    private void performConfigurationChanged(ComponentCallbacks2 cb,
                                             IBinder activityToken,
                                             Configuration newConfig,
                                             Configuration amOverrideConfig,
                                             boolean reportToActivity) {
  ...
  boolean shouldChangeConfig = false;
        if ((activity == null) || (activity.mCurrentConfig == null)) {
            shouldChangeConfig = true;
        } else {
            // If the new config is the same as the config this Activity is already
            // running with and the override config also didn't change, then don't
            // bother calling onConfigurationChanged.
            int diff = activity.mCurrentConfig.diff(newConfig);
            if (diff != 0 || !mResourcesManager.isSameResourcesOverrideConfig(activityToken,
                    amOverrideConfig)) {
                // Always send the task-level config changes. For system-level configuration, if
                // this activity doesn't handle any of the config changes, then don't bother
                // calling onConfigurationChanged as we're going to destroy it.
    //---------------若AndroidManifest中, 已定义了对应的配置项, 则不重启, 并交由activity中的onConfigurationChanged去处理------------------------
                if (!mUpdatingSystemConfig
                        || (~activity.mActivityInfo.getRealConfigChanged() & diff) == 0
                        || !reportToActivity) {
                    shouldChangeConfig = true;
                }
            }
        }
        if (shouldChangeConfig) {
    ...
                cb.onConfigurationChanged(configToReport);
    ...
        }
    }


调用重启:


|-- frameworks/base/services/core/java/com/android/server/am/ActivityStack.java


boolean ensureActivityConfigurationLocked(
            ActivityRecord r, int globalChanges, boolean preserveWindow) {
        if (mConfigWillChange) {
            if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
                    "Skipping config check (will change): " + r);
            return true;
        }
        if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
                "Ensuring correct configuration: " + r);
        // Short circuit: if the two configurations are equal (the common case), then there is
        // nothing to do.
        final Configuration newConfig = mService.mConfiguration;
        r.task.sanitizeOverrideConfiguration(newConfig);
        final Configuration taskConfig = r.task.mOverrideConfig;
        if (r.
相关文章
|
15天前
|
JSON Java Android开发
探索安卓开发之旅:打造你的第一个天气应用
【10月更文挑战第30天】在这个数字时代,掌握移动应用开发技能无疑是进入IT行业的敲门砖。本文将引导你开启安卓开发的奇妙之旅,通过构建一个简易的天气应用来实践你的编程技能。无论你是初学者还是有一定经验的开发者,这篇文章都将成为你宝贵的学习资源。我们将一步步地深入到安卓开发的世界中,从搭建开发环境到实现核心功能,每个环节都充满了发现和创造的乐趣。让我们开始吧,一起在代码的海洋中航行!
|
15天前
|
存储 搜索推荐 Java
打造个性化安卓应用:从设计到实现
【10月更文挑战第30天】在数字化时代,拥有一个个性化的安卓应用不仅能够提升用户体验,还能加强品牌识别度。本文将引导您了解如何从零开始设计和实现一个安卓应用,涵盖用户界面设计、功能开发和性能优化等关键环节。我们将以一个简单的记事本应用为例,展示如何通过Android Studio工具和Java语言实现基本功能,同时确保应用流畅运行。无论您是初学者还是希望提升现有技能的开发者,这篇文章都将为您提供宝贵的见解和实用的技巧。
|
19天前
|
搜索推荐 开发工具 Android开发
打造个性化Android应用:从设计到实现的旅程
【10月更文挑战第26天】在这个数字时代,拥有一个能够脱颖而出的移动应用是成功的关键。本文将引导您了解如何从概念化阶段出发,通过设计、开发直至发布,一步步构建一个既美观又实用的Android应用。我们将探讨用户体验(UX)设计的重要性,介绍Android开发的核心组件,并通过实际案例展示如何克服开发中的挑战。无论您是初学者还是有经验的开发者,这篇文章都将为您提供宝贵的见解和实用的技巧,帮助您在竞争激烈的应用市场中脱颖而出。
|
21天前
|
算法 Java 数据库
Android 应用的主线程在什么情况下会被阻塞?
【10月更文挑战第20天】为了避免主线程阻塞,我们需要合理地设计和优化应用的代码。将耗时操作移到后台线程执行,使用异步任务、线程池等技术来提高应用的并发处理能力。同时,要注意避免出现死循环、不合理的锁使用等问题。通过这些措施,可以确保主线程能够高效地运行,提供流畅的用户体验。
35 2
|
25天前
|
Java API Android开发
安卓应用程序开发的新手指南:从零开始构建你的第一个应用
【10月更文挑战第20天】在这个数字技术不断进步的时代,掌握移动应用开发技能无疑打开了一扇通往创新世界的大门。对于初学者来说,了解并学习如何从无到有构建一个安卓应用是至关重要的第一步。本文将为你提供一份详尽的入门指南,帮助你理解安卓开发的基础知识,并通过实际示例引导你完成第一个简单的应用项目。无论你是编程新手还是希望扩展你的技能集,这份指南都将是你宝贵的资源。
48 5
|
25天前
|
移动开发 Dart 搜索推荐
打造个性化安卓应用:从零开始的Flutter之旅
【10月更文挑战第20天】本文将引导你开启Flutter开发之旅,通过简单易懂的语言和步骤,让你了解如何从零开始构建一个安卓应用。我们将一起探索Flutter的魅力,实现快速开发,并见证代码示例如何生动地转化为用户界面。无论你是编程新手还是希望扩展技能的开发者,这篇文章都将为你提供价值。
|
1月前
|
调度 Android开发 开发者
构建高效Android应用:探究Kotlin多线程优化策略
【10月更文挑战第11天】本文探讨了如何在Kotlin中实现高效的多线程方案,特别是在Android应用开发中。通过介绍Kotlin协程的基础知识、异步数据加载的实际案例,以及合理使用不同调度器的方法,帮助开发者提升应用性能和用户体验。
46 4
|
1月前
|
编解码 Android开发 UED
构建高效Android应用:从内存优化到用户体验
【10月更文挑战第11天】本文探讨了如何通过内存优化和用户体验改进来构建高效的Android应用。介绍了使用弱引用来减少内存占用、懒加载资源以降低启动时内存消耗、利用Kotlin协程进行异步处理以保持UI流畅,以及采用响应式设计适配不同屏幕尺寸等具体技术手段。
49 2
|
1月前
|
XML 数据可视化 Android开发
Android应用界面
Android应用界面中的布局和控件使用,包括相对布局、线性布局、表格布局、帧布局、扁平化布局等,以及AdapterView及其子类如ListView的使用方法和Adapter接口的应用。
23 0
Android应用界面
|
1月前
|
Android开发
Android开发显示头部Bar的需求解决方案--Android应用实战
Android开发显示头部Bar的需求解决方案--Android应用实战
21 0