开始活动后立即调用OnPause和OnStop()方法

47

我有一个需要在启动时打开屏幕(如果关闭了)的活动。

因此,在onCreate中,我有:

this.getWindow().setFlags(
            WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                    | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                    | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON,
            WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                    | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                    | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);

通过在广播接收器中使用wakelock,我能够使我的活动在从广播接收器启动时显示。

但是问题非常奇怪,活动生命周期在这种方式下调用,即在启动活动后立即调用onPause()和onResume()。

  1. onCreate
  2. onStart
  3. onResume
  4. onPause
  5. onStop
  6. onStart
  7. onResume

因此,在启动和恢复时调用两次,同时还调用了onStop,我想在onStop()中实现一些逻辑,但是由于这种行为,应用程序将无法正常工作。

编辑

我发现问题只出现在FLAG_SHOW_WHEN_LOCKED标志下,当设备被锁定时会发生。并且仅在启动活动之前锁定设备时才会发生。

PS:我正在使用带有广播接收器的闹钟管理器,然后从广播接收器启动活动。


很棒的问题,我也有同样的标志FLAG_SHOW_WHEN_LOCKED,已投赞成。 - Mohammad Ersan
我通过在一个处理程序中使用一点延迟来解决了这个问题,该处理程序在onStop中进行检查。 - Mohammad Ersan
这可能是一种方法,但不是正确的解决方案。 - Haris
这个答案救了我:https://dev59.com/-Ws05IYBdhLWcg3wQvuq#14053686 它是关于检测屏幕是否开启的。 - Manza
你的代码可行吗?我也遇到了同样的问题,虽然我已经使用了唤醒锁和禁用键盘锁,但它仍然显示键盘并暂停我的活动。 - Sweety Bertilla
7个回答

52
  • 让我们了解为什么生命周期方法会被多次调用。

这是在ActivityThread中记录的重要代码注释,它负责执行应用程序进程的活动。

我们通过正常的启动过程来实现这一点(因为活动希望在其窗口显示之前首次运行通过 onResume()),然后将其暂停。

onResume之后,活动窗口附加到窗口管理器,并调用onAttachedtoWindow。如果屏幕处于打开状态,则活动窗口将获得焦点,并使用true参数调用onWindowFocusChanged。从文档中可知:

请记住,onResume并不是您的活动对用户可见的最佳指标; 系统窗口(例如键盘保护)可能在前面。使用onWindowFocusChanged(boolean)可以确定您的活动对用户可见

在报告的问题中,屏幕关闭。 因此,活动窗口将无法获得焦点,这会导致调用活动的onPause方法,然后是onStop方法,因为活动窗口不可见。

由于在活动窗口上设置了WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON标志,因此窗口管理器服务使用电源管理器API打开屏幕。 下面是WindowManagerService代码:

public int relayoutWindow(...) {
    ...
    toBeDisplayed = !win.isVisibleLw();
    ...
    if (toBeDisplayed) {
        ...
        if ((win.mAttrs.flags
            & WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON) != 0) {
            if (DEBUG_VISIBILITY) Slog.v(TAG,
                "Relayout window turning screen on: " + win);
                win.mTurnOnScreen = true;
            }
        ...
        if (mTurnOnScreen) {
            if (DEBUG_VISIBILITY) Slog.v(TAG, "Turning screen on after layout!");
            mPowerManager.wakeUp(SystemClock.uptimeMillis());
            mTurnOnScreen = false;
        }
        ...
}

屏幕打开后,onStartonPause将再次被调用。

因此:onCreate - onStart - onResume - onPause - onStop - onStart - onPause

可以通过锁定设备并使用adb命令或eclipse启动活动来验证此操作。

  • Ideal Solution

如果您在onCreate中启动任务,则需要在onDestory中停止它(如果任务仍处于挂起状态)。对于onStart,应该是onStop,对于onResume,应该是onPause

  • Workaround

如果无法遵循上述协议,则可以在onPause方法中使用hasWindowFocus检查活动窗口焦点的状态。通常,在onPause中,活动窗口焦点状态将为true。在屏幕关闭或显示键保护的情况下,活动窗口焦点将在onPause中为false。

boolean mFocusDuringOnPause;

public void onPause() {
    super.onPause;

    mFocusDuringOnPause = hasWindowFocus();    
}

public void onStop() {
    super.onStop();

    if(mFocusDuringOnPause) {
        // normal scenario
    } else {
        // activity was started when screen was off / screen was on with keygaurd displayed
    }
}

解释得很好,但是解决方法不起作用,我宁愿在onWindowFocusChanged中设置一个标志值,然后在窗口被聚焦后处理onPause()和onStop()。 - Haris
为什么你的解决方法不使用 onWindowFocusChanged?表面上看,这个事件听起来很完美。 - Corey Ogburn
只是补充一下,onRestart 在 onStop 之后也会被调用。如果有人在 onStart 中执行某些逻辑,请检查活动是否已重新启动,然后避免在 onStart 中再次运行逻辑。 - Prashant

2
在您的Manifest中的Activity添加android:configChanges="keyboardHidden|orientation|screenSize"。这可能会解决您的问题。

2
那怎么解决问题呢?监听配置更改只会防止活动自动处理更改。这与所描述的屏幕开/关情况无关。 - milosmns

2

Manish Mulimani提供的解决方法对我有用,不过我首先检查窗口是否获得焦点,然后再调用super.onPause():

public void onPause() {
    mFocusDuringOnPause = hasWindowFocus();    
    super.onPause();
}

public void onStop() {
    super.onStop();
    if (mFocusDuringOnPause) {
        // normal scenario
    } else {
        // activity was started when screen was off or screen was on with keyguard displayed
    }
}

1
您说您想在onStop()中实现一些逻辑,但这样的行为将会导致应用程序无法正确工作。您没有向我们展示您在onStop()中具体有什么内容,但我认为您的逻辑可能会重新启动活动... 在这种情况下,您可以像这样在onStop()中实现您的逻辑:
@Override
protected void onStop(){
    super.onStop();

         //implement your code only if !STATE_OFF - to prevent infinite loop onResume <-> onStop  while screen is off                      
        DisplayManager dm = (DisplayManager) this.getSystemService(Context.DISPLAY_SERVICE);
        for (Display display : dm.getDisplays()){
               if(display.getState() != Display.STATE_OFF){

                 //implement your code only if device is not in "locked"  state     
                KeyguardManager myKM = (KeyguardManager) this.getSystemService(Context.KEYGUARD_SERVICE);
                if( !myKM.inKeyguardRestrictedInputMode())      
                    //If needed you can call finish() (call onDestroy()) and your logic will restart your activity only once.             
                    finish();                  
                    // implement your logic here (your logic probably restarts the activity)
                }
            }
        }
    }
  1. other solution that may help you will be to avoid onStop() and use onWindowFocusChanged with bool hasFocus

      /**
      * Called when the current {@link Window} of the activity gains or loses
      * focus.  This is the best indicator of whether this activity is visible
      * to the user.
      */
        @Override
           public void onWindowFocusChanged(boolean hasFocus){
             super.onWindowFocusChanged(hasFocus);
             if( ! hasFocus  ){
             }
             else{
             }
           }
    

0

试试这个。我用过它,效果很好。

PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);

        _wakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP,
            POWER_SERVICE);
    _wakeLock.acquire();

1
PowerManager.FULL_WAKE_LOCK已被弃用。 - Dpedrinha

0
我注意到在AndroidManifest.xml中有一个名为activity的属性,称为android:showOnLockScreen="true|false" 例如:
   <activity android:name="AlarmAlertFullScreen"
                android:excludeFromRecents="true"
                android:theme="@style/AlarmAlertFullScreenTheme"
                android:showOnLockScreen="true"
                android:screenOrientation="nosensor"
                android:configChanges="orientation|screenSize|keyboardHidden|keyboard|navigation"/>

我在网上搜索了它的文档,但没有运气,不过从它的名称来看,它应该像Window标志WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED一样工作。

我找到的唯一文件

指定一个 Activity 应该在锁屏界面上显示,并且在多用户环境中跨越所有用户的窗口 [布尔值]


编辑

请在调用super.onCreate(...)之前尝试调用您的标志代码。

public class BaseActivity extends Activity {

    @Override
    protected void onCreate(Bundle bundle) {
    this.getWindow().setFlags(
            WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                    | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                    | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON,
            WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                    | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                    | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
        //then call super
        super.onCreate(bundle);
.
.
.
  }
}

我相信这背后的原因是键盘锁屏幕在第一次停止了活动,然后活动再次恢复。 - Mohammad Ersan

-1
使用WakeLocker类。它具有唤醒屏幕的方法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接