Android屏幕旋转导致活动重启

1459
在我的Android应用中,当我旋转设备(推出键盘)时,我的Activity会被重新启动(调用onCreate)。现在,这可能是预期的行为,但我在onCreate方法中进行了很多初始设置,因此我需要:
  1. 将所有初始设置放在另一个函数中,以便设备旋转时不会丢失它们。
  2. 使onCreate不再被调用,仅调整布局。
  3. 将应用限制为仅支持竖屏模式,这样就不会调用onCreate

4
这篇博客文章中详细解释了如何在活动组件配置更改时保留长时间运行的异步任务,点击此处阅读。 - Adrian Monk
3
这不是一个直接的答案,因为其他人已经回答了,但我邀请您查看LogLifeCycle,以了解您的Android应用程序在生命周期方面发生了什么。 - Snicolas
34个回答

994

使用Application类

根据您在初始化过程中所做的操作,您可以考虑创建一个扩展了Application类的新类,并将初始化代码移动到该类中重写的onCreate方法中。

Kotlin版本

class MyApplicationClass: Application {
    @override fun onCreate() {
        super.onCreate()
        // Put your application initialization code here
    }
}

Java版本

public class MyApplicationClass extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        // Put your application initialization code here
    }
}

应用程序类中的onCreate方法只在整个应用程序创建时调用,因此方向或键盘可见性更改导致的活动重新启动不会触发它。

最好将此类的实例公开为单例并使用getter和setter公开正在初始化的应用程序变量。

注意:您需要在清单中指定新应用程序类的名称以进行注册和使用:

<application
    android:name="com.you.yourapp.MyApplicationClass"

响应配置更改

更新:自API 13起已弃用;请参阅推荐的替代方法

作为另一种选择,您可以让应用程序监听会导致重启的事件,例如方向和键盘可见性更改,并在Activity内处理它们。

首先,在Activity的清单节点中添加android:configChanges节点:

 <activity android:name=".MyActivity"
      android:configChanges="orientation|keyboardHidden"
      android:label="@string/app_name">

或者对于Android 3.2(API级别13)及更高版本

<activity android:name=".MyActivity"
      android:configChanges="keyboardHidden|orientation|screenSize"
      android:label="@string/app_name">

然后在Activity中重写onConfigurationChanged方法,并调用setContentView来强制重新绘制新方向的GUI布局。

Kotlin版本

@override fun onConfigurationChanged(newConfig: Configuration) {
    super.onConfigurationChanged(newConfig)
    setContentView(R.layout.myLayout)
}

Java版本

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    setContentView(R.layout.myLayout);
}

19
我认为第二种方法行不通。我尝试了一下,创建了一个带有EditText的Activity。我在那里写了一些文本,改变方向后发现文本被清空/重置了。 - Ted
238
希望未来能看到onRotate()方法。需要担心这样的事情,实在是令人沮丧。请期待更新。 - Kelly Sutton
84
请注意,Android 开发指南 警告不要经常使用 android:configChanges。它应该只作为最后的手段来使用。请参阅 "处理运行时更改" 以获取有关如何正确处理由于配置更改而重新启动的详细信息。相反,为了在旋转事件中保留数据,他们似乎更喜欢使用 onSaveInstanceState Bundle。或者就像 @Jon-O 提到的,使用 onRetainNonConfigurationInstance - Goffredo
16
我认为你应该将这个3.2版本的更新添加到你的答案中,它相当重要(我刚遇到了这个问题),而且可能会被忽视。 - bigstones
9
使用android:configChanges能够让我省下很多工作量,所以我不喜欢谷歌告诉我只有在万不得已的情况下才使用它,而不解释为什么。请给我一个理由不去省下大量的工作量。 - Nilzor
显示剩余4条评论

191

Android 3.2及以上版本更新提示:

注意:从Android 3.2(API level 13)开始,当设备在纵横屏方向之间切换时,"屏幕尺寸(screen size)"也会发生改变。因此,如果您想要防止在开发API级别为13或更高(由minSdkVersion和targetSdkVersion属性声明)的应用程序时,由于方向更改而导致运行时重新启动,则必须除了包含"orientation"值外,还要包含"screenSize"值。也就是说,您必须声明android:configChanges="orientation|screenSize"。然而,如果您的应用程序目标是API级别12或更低,则您的活动始终会自己处理此配置更改(即使在运行Android 3.2或更高版本的设备上,此配置更改也不会重新启动您的活动)。

参考资料:http://web.archive.org/web/20120805085007/http://developer.android.com/guide/topics/resources/runtime-changes.html


1
感谢您的澄清,因为上面的评论几乎让我去查看它。我目前正在针对API 8进行开发,我的代码在configChanges上没有screenSize,并且可以确认它在我拥有的运行ICS的设备上正常工作(无需重新定向)。 - Carl
谢谢指出,我只设置了android:configChanges =“orientation | screenSize”,而方向切换重新创建了我的Activity,我真的找不出原因! - Christopher Perry
5
android:configChanges 应该只作为最后的选择。考虑使用 FragmentssetRetainInstance 代替。 - Simon Forsberg
关键点是 screenSize 适用于 Android 3.2 及更高版本,这解决了我的问题,谢谢! - fantouch

137

不要试图完全停止触发 onCreate(),而是可以尝试检查传递给该事件的 Bundle savedInstanceState 是否为null。例如,如果我有一些逻辑只应在 Activity 真正创建时运行,而不是在每次方向更改时运行,则只有在 savedInstanceState 为null时,我才在 onCreate() 中运行该逻辑。

否则,我仍希望布局能够正确地重绘以适应方向的更改。

public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_game_list);

        if(savedInstanceState == null){
            setupCloudMessaging();
        }
}

我不确定这是否是最终答案,但它对我有用。


6
你实际上在哪里保存状态? - Ewoks
6
这种方法对我来说似乎行得通,并且似乎是最简单的方法。我注意到你只得到了4个upvotes(包括我的),而在关于子类化Application的想法中,你只得到了373个upvote,这对我来说似乎更加复杂。这种方法有什么缺点吗? - steveh
4
这个解决方案对我非常有效。我能够使用Intent serverintent = new Intent(MainActivity.this, MessageListener.class);startService(serverintent);创建一个serverSocket = new ServerSocket(0xcff2);,然后使用BufferedReader(new InputStreamReader(client.getInputStream()))接受Socket client = serverSocket.accept();,即使旋转了我的Android设备也能保持客户端/服务器连接处于活动状态,同时保持GUI旋转。根据手册,当最后一个活动被关闭时savedInstanceState会被初始化。 - Fred F
3
我不明白,有什么陷阱吗?这个方法很好用,并且比其他解决方案更简单易懂。 - RTF
3
这是在Android中正确的做法。其他通过configChanges及其它方式来捕捉旋转的方法都很笨重、复杂且不必要。 - LukeWaggoner

104

我所做的是...

在清单文件中,添加到活动部分:

android:configChanges="keyboardHidden|orientation"

在实现活动的代码中:

//used in onCreate() and onConfigurationChanged() to set up the UI elements
public void InitializeUI()
{
    //get views from ID's
    this.textViewHeaderMainMessage = (TextView) this.findViewById(R.id.TextViewHeaderMainMessage);

    //etc... hook up click listeners, whatever you need from the Views
}

//Called when the activity is first created.
@Override
public void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    InitializeUI();
}

//this is called when the screen rotates.
// (onCreate is no longer called when screen rotates due to manifest, see: android:configChanges)
@Override
public void onConfigurationChanged(Configuration newConfig)
{
    super.onConfigurationChanged(newConfig);
    setContentView(R.layout.main);

    InitializeUI();
}

3
澄清一下:通过我的实现,您现在可以在onCreate()中进行变量初始化,并且onConfigurationChanged()仅用于屏幕旋转时的调用。 您的变量现在与屏幕旋转无关,很好地达到了隔离的效果;-)非常方便和简单。 - Someone Somewhere
2
我按照这里描述的一切做了,但在方向更改后尝试按按钮时,我收到NullPointerException。可能出了什么问题? - Finnboy11
5
请记住,我的回答是三年前的,Android正在不断发展...... Simon-你有样例代码的链接吗?这才是人们需要的。 - Someone Somewhere
3
当警告android:configChanges时,@SimonAndréForsberg实际上只是转述了Android文档处理运行时更改中提供了更详细的替代方案信息(包括示例代码)。 - Leif Arne Storset

71

你所描述的是默认行为。你需要通过添加以下代码来检测和处理这些事件:

android:configChanges

将你想要处理的更改添加到清单中。因此,对于定位,您将使用:

android:configChanges="orientation"

如果要检测键盘是否打开或关闭,可以使用以下代码:

android:configChanges="keyboardHidden"

如果你想同时处理它们,你可以用管道命令将它们分开,例如:

android:configChanges="keyboardHidden|orientation"

这将触发调用的 Activity 中的 onConfigurationChanged 方法。如果你重写了该方法,你可以传入新的值。

希望对你有所帮助。


3
@GregD 我知道,这就是为什么现在是更新它以反映今天情况的好时机。考虑到这个问题获得的点赞数,仍然会被其他SO问题引用。 - Simon Forsberg

52

我刚刚发现了这个技巧:

为了在屏幕方向改变时保持 Activity 的生命周期并通过 onConfigurationChanged 来处理它,文档上面的代码示例 建议在 Manifest 文件中添加以下内容:

<activity android:name=".MyActivity"
      android:configChanges="orientation|keyboardHidden"
      android:label="@string/app_name">

使用这种方式的附加好处是它总是有效的。

额外的秘诀是,省略keyboardHidden可能看起来很合理,但它会导致模拟器出现故障(至少对于Android 2.1):仅指定orientation有时会导致模拟器调用OnCreateonConfigurationChanged,而其他时候仅调用OnCreate

我没有在设备上看到过这种故障,但我听说其他人在模拟器上也遇到了类似问题。因此,值得记录。


15
注意:从Android 3.2(API级别13)开始,当设备在纵向和横向方向之间切换时,“屏幕大小”也会发生变化。因此,如果您想要在开发针对API级别13或更高版本的应用程序时防止由于方向更改而导致运行时重启: android:configChanges="orientation|keyboardHidden|screenSize" - Geltrude

40

您也可以考虑使用Android平台跨屏幕旋转持久化数据的方法:onRetainNonConfigurationInstance()getLastNonConfigurationInstance()

这可以让您在屏幕方向改变时持久保存数据,比如从服务器获取的信息或者在onCreate或之后计算出来的数据,同时允许Android使用当前屏幕方向的xml文件重新布局Activity

请参见这里这里

需要注意的是,尽管这些方法仍然比大多数以上提到的解决方案更加灵活,但它们现已被弃用,推荐每个想要保留的Fragment都使用setRetainInstance(true)并切换到使用Fragments来处理屏幕旋转。


4
我认为Fragments和setRetainInstance是最好的方式(也是Google推荐的方式)来做这件事,给你点赞,其他人点踩。添加android:configChanges应该只在万不得已时使用。 - Simon Forsberg

34

9
同意。使用最新的Android API,似乎Fragments是处理这个问题的正确方法。我自己还没有尝试过,但根据我阅读此页面所了解到的,你基本上将以前在Activity中实现的99%内容移动到Fragment的子类中,然后将该Fragment添加到Activity中。虽然在屏幕旋转时仍会销毁并重新创建Activity,但你可以使用@Abdo提到的setRetainInstance()方法明确告诉Android不要销毁Fragment。 - brianmearns

27

我只是简单地添加了:

android:configChanges="keyboard|keyboardHidden|orientation"

我在AndroidManifest.xml文件中没有添加onConfigurationChanged方法到我的活动中。

所以每次键盘滑出或滑入时都不会发生任何事情!还可以查看这篇关于此问题的文章


21

onCreate 方法在 Android 设备改变 屏幕方向 时仍然被调用。因此,把所有重要的功能都移动到这个方法中并不能帮助你。


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