在Android上旋转活动重启

Activity restart on rotation Android

在我的Android应用程序中,当我旋转设备(滑出键盘)时,我的Activity会重新启动(onCreate)。现在,这可能是应该的,但我在onCreate方法中做了很多初始设置,所以我需要:

  • 将所有的初始设置置于另一个函数中,这样就不会在设备旋转或
  • 使其不再调用onCreate,并且布局只是调整或
  • 将应用程序限制为仅纵向,以便不调用onCreate

  • 使用应用程序类

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

    1
    2
    3
    4
    5
    6
    7
    public class MyApplicationClass extends Application {
      @Override
      public void onCreate() {
        super.onCreate();
        // TODO Put your application initialization code here.
      }
    }

    应用程序类中的onCreate仅在创建整个应用程序时调用,因此活动将在方向上重新启动,否则键盘可见性更改不会触发它。

    将这个类的实例作为单例公开,并使用getter和setter公开正在初始化的应用程序变量,这是一个很好的实践。

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

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

    对配置更改作出反应[更新:自API 13以来,此项已弃用;请参阅建议的替代项]

    作为另一种选择,您可以让应用程序监听可能导致重新启动的事件(如方向和键盘可见性更改),并在活动中处理这些事件。

    首先将android:configChanges节点添加到活动的清单节点

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

    或Android 3.2(API 13级)及更高版本:

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

    然后在活动中覆盖onConfigurationChanged方法并调用setContentView以强制在新方向上重新执行GUI布局。

    1
    2
    3
    4
    5
    @Override
    public void onConfigurationChanged(Configuration newConfig) {
      super.onConfigurationChanged(newConfig);
      setContentView(R.layout.myLayout);
    }


    Android 3.2及更高版本的更新:

    Caution: Beginning with Android 3.2 (API level 13), the"screen size" also changes when the device switches between portrait and landscape orientation. Thus, if you want to prevent runtime restarts due to orientation change when developing for API level 13 or higher (as declared by the minSdkVersion and targetSdkVersion attributes), you must include the "screenSize" value in addition to the "orientation" value. That is, you must declare android:configChanges="orientation|screenSize". However, if your application targets API level 12 or lower, then your activity always handles this configuration change itself (this configuration change does not restart your activity, even when running on an Android 3.2 or higher device).


    与其试图阻止onCreate()被完全解雇,不如尝试检查BundlesavedInstanceState是否被传递到事件中,以查看它是否为空。

    例如,如果我有一些逻辑应该在真正创建Activity时运行,而不是在每个方向更改时,我只在onCreate()中运行该逻辑,前提是savedInstanceState为空。

    否则,我仍然希望布局为方向重新正确绘制。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public void onCreate(Bundle savedInstanceState) {

            super.onCreate(savedInstanceState);

            setContentView(R.layout.activity_game_list);

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

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


    我做了什么…

    在清单的"活动"部分中,添加了:

    1
    android:configChanges="keyboardHidden|orientation"

    在活动的代码中,实现了:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    //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();
    }


    您所描述的是默认行为。您必须自己检测和处理这些事件,方法是添加:

    1
    android:configChanges

    到您的清单,然后是您想要处理的更改。因此,对于方向,您将使用:

    1
    android:configChanges="orientation"

    对于正在打开或关闭的键盘,您将使用:

    1
    android:configChanges="keyboardHidden"

    如果要同时处理这两个问题,可以使用pipe命令将它们分开,如下所示:

    1
    android:configChanges="keyboardHidden|orientation"

    这将在您调用的任何活动中触发onconfigurationChanged方法。如果重写方法,则可以传入新值。

    希望这有帮助。


    我刚刚发现了这个传说:

    为了通过方向更改保持活动的活动,并通过onConfigurationChanged进行处理,上面的文档和代码示例在清单文件中建议这样做:

    1
    android:configChanges="keyboardHidden|orientation"

    这有一个额外的好处,它总是有效的。

    另外一个传说是,省略keyboardHidden可能看起来很合乎逻辑,但它会导致模拟器(至少对于android 2.1)出现故障:仅指定orientation会使模拟器有时同时调用onCreateonConfigurationChanged,而其他时候只调用onCreate

    我没有在设备上看到失败,但是我听说模拟器在其他设备上失败了。所以值得记录。


    您还可以考虑使用Android平台的方法来在方向变化中保持数据:onRetainNonConfigurationInstance()getLastNonConfigurationInstance()

    这允许您跨配置更改持久保存数据,例如从服务器获取的信息或在onCreate或之后计算的其他信息,同时允许Android使用XML文件为当前使用的方向重新布局Activity

    看这里或这里。

    应该注意的是,这些方法现在已被弃用(尽管仍然比处理方向改变更灵活,正如上述大多数解决方案所建议的那样),建议每个人切换到Fragments,而不是在您想要保留的每个Fragment上使用setRetainInstance(true)


    该方法很有用,但在使用片段时不完整。

    片段通常在配置更改时重新创建。如果您不希望发生这种情况,请使用

    片段构造器中的setRetainInstance(true);

    这将导致在配置更改期间保留片段。

    http://developer.android.com/reference/android/app/fragment.html setretaininstance(布尔值)


    我只是简单地加了一句

    1
         android:configChanges="keyboard|keyboardHidden|orientation"

    在清单文件中,没有在我的活动中添加任何onConfigurationChanged方法。

    所以每次键盘滑出或滑入时都不会发生任何事情。


    将下面的代码放入Manifest.xml中的标记中:

    1
    android:configChanges="screenLayout|screenSize|orientation"

    即使更改了android的orientation,仍然调用onCreate方法。因此,将所有重要的功能移到这个方法并不能帮助您


    很简单,只需执行以下步骤:

    1
    2
    3
    4
    5
    <activity
        android:name=".Test"
        android:configChanges="orientation|screenSize"
        android:screenOrientation="landscape">
    </activity>

    这对我很有用:

    注意:方向取决于您的需求


    1
    2
    3
     onConfigurationChanged is called when the screen rotates.
     (onCreate is no longer called when screen rotates due to manifest, see:  
     android:configChanges)

    舱单的哪个部分告诉它"不要叫onCreate()"?

    也,谷歌的文档说要避免使用android:configChanges(除了作为最后手段)。但是他们建议的其他方法都使用android:configChanges

    根据我的经验,仿真器在旋转时总是调用onCreate()。但是我运行相同代码的1-2设备…不要。(不知道为什么会有什么不同。)


    将此行添加到清单中:

    1
    android:configChanges="orientation|keyboard|keyboardHidden|screenSize|screenLayout|uiMode"

    活动的这段代码:

    1
    2
    3
    4
    5
    6
    @Override
        public void onConfigurationChanged(Configuration newConfig) {
            super.onConfigurationChanged(newConfig);
            getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                    WindowManager.LayoutParams.FLAG_FULLSCREEN);
        }


    要在Android清单中进行的更改包括:

    1
    android:configChanges="keyboardHidden|orientation"

    要在活动中添加的内容包括:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);

        // Checks the orientation of the screen
        if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
            Toast.makeText(this,"landscape", Toast.LENGTH_SHORT).show();
        } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
            Toast.makeText(this,"portrait", Toast.LENGTH_SHORT).show();
        }
    }

    有几种方法可以做到这一点:

    保存活动状态

    您可以在onSaveInstanceState中保存活动状态。

    1
    2
    3
    4
    5
    6
    @Override
    public void onSaveInstanceState(Bundle outState) {
        /*Save your data to be restored here
        Example : outState.putLong("time_state", time); , time is a long variable*/
        super.onSaveInstanceState(outState);
    }

    然后使用bundle恢复状态。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if(savedInstanceState!= null){
           /*When rotation occurs
            Example : time = savedInstanceState.getLong("time_state", 0); */
        } else {
          //When onCreate is called for the first time
        }
    }

    自己处理方向更改

    另一种选择是自己处理方向更改。但这并不是一个好的做法。

    将此添加到清单文件。

    1
    android:configChanges="keyboardHidden|orientation"

    对于Android 3.2及更高版本:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    android:configChanges="keyboardHidden|orientation|screenSize"

    @Override
    public void onConfigurationChanged(Configuration config) {
        super.onConfigurationChanged(config);

    if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
            //Handle rotation from landscape to portarit mode here
        } else if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE){
            //Handle rotation from portrait to landscape mode here
        }
    }

    限制旋转

    您还可以将活动限制在纵向或横向模式,以避免旋转。

    将此添加到清单文件中的活动标记:

    1
            android:screenOrientation="portrait"

    或者在活动中以编程方式实现:

    1
    2
    3
    4
    5
    @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
    }

    我发现这样做的方法是使用onRestoreInstanceStateonSaveInstanceState事件在Bundle中保存一些内容(即使不需要保存任何变量,也只需在其中放置一些内容,这样Bundle就不会是空的)。然后,在onCreate方法上,检查Bundle是否为空,如果为空,则进行初始化;如果不是,则进行初始化。


    尽管这不是"安卓方式",但我通过自己处理方向更改并简单地在视图中重新定位小部件以将更改的方向考虑在内,获得了非常好的结果。这比任何其他方法都快,因为您的视图不需要保存和恢复。它还为用户提供了更无缝的体验,因为重新定位的小部件完全相同,只是移动和/或调整了大小。这样不仅可以保留模型状态,还可以保留视图状态。

    对于一个需要不时调整自身方向的观点,RelativeLayout有时是一个很好的选择。您只需为每个子窗口小部件提供一组纵向布局参数和一组景观布局参数,每个参数上都有不同的相对定位规则。然后,在您的onConfigurationChanged()方法中,您将适当的方法传递给每个孩子的setLayoutParams()调用。如果任何子控件本身需要在内部重新定向,只需对该子控件调用一个方法来执行重新定向。该子控件同样调用需要内部重新定向的任何子控件的方法,等等。


    注意:如果将来有人和我面临同样的问题,我会发布这个答案。对我来说,下面这句话并不新鲜:

    1
    android:configChanges="orientation"

    当我旋转屏幕时,没有调用方法"onconfigurationchanged(configuration newconfig)"。

    解决方案:即使问题与方向有关,我也必须添加"屏幕大小"。因此,在androidmanifest.xml文件中,添加以下内容:

    1
    android:configChanges="keyboardHidden|orientation|screenSize"

    然后实现方法onConfigurationChanged(Configuration newConfig)


    您需要使用onsavedinstanceState方法来存储其参数is has(即bundle)的所有值。

    1
    2
    3
    4
    5
    @Override
        public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
            super.onSaveInstanceState(outState, outPersistentState);
            outPersistentState.putBoolean("key",value);
        }

    使用

    1
    2
    3
    4
    5
    @Override
        protected void onRestoreInstanceState(Bundle savedInstanceState) {
            super.onRestoreInstanceState(savedInstanceState);
            savedInstanceState.getBoolean("key");
        }

    检索并设置值以查看对象它将处理屏幕旋转


    每次旋转屏幕时,打开的活动都会完成,并再次调用onCreate()。

    1。您可以做一件事,即在屏幕旋转时保存活动状态,以便在再次调用活动的onCreate()时恢复所有旧内容。参考此链接

    2。如果要阻止重新启动活动,只需在manifest.xml文件中放置以下行。

    1
    2
      <activity android:name=".Youractivity"
      android:configChanges="orientation|screenSize"/>

    manifest的活动部分,增加:

    1
    android:configChanges="keyboardHidden|orientation"

    在舱单中加这一行:android:configChanges="orientation|screenSize"


    人们说你应该使用

    1
    android:configChanges="keyboardHidden|orientation"

    但在Android中处理旋转的最佳和最专业的方法是使用loader类。它不是一个著名的类(我不知道为什么),但它比AsyncTask要好得多。有关更多信息,您可以阅读Udacity的Android课程中的Android教程。

    当然,作为另一种方法,您可以使用OnSaveInstanceState存储值或视图,并使用OnRestoreInstanceState读取它们。这真的取决于你。


    把下面的代码放到你的EDOCX1中(10),在EDOCX1中(26)。

    1
    android:configChanges="orientation"

    当你改变方向时,这不会重新开始你的活动。


    使用orientation监听器在不同的方向上执行不同的任务。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @Override
    public void onConfigurationChanged(Configuration myConfig)
    {
        super.onConfigurationChanged(myConfig);
        int orient = getResources().getConfiguration().orientation;
        switch(orient)
        {
           case Configuration.ORIENTATION_LANDSCAPE:
              setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
                        break;
           case Configuration.ORIENTATION_PORTRAIT:
              setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
                        break;
           default:
              setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
        }
    }

    经过一段时间的反复尝试,我找到了一个最适合我需要的解决方案。代码如下:

    清单配置:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
              package="com.pepperonas.myapplication">

        <application
            android:name=".App"
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:supportsRtl="true"
            android:theme="@style/AppTheme">
            <activity
                android:name=".MainActivity"
                android:configChanges="orientation|keyboardHidden|screenSize">
                <intent-filter>
                   

                    <category android:name="android.intent.category.LAUNCHER"/>
                </intent-filter>
            </activity>
        </application>

    </manifest>

    MainActivity:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    import android.content.res.Configuration;
    import android.os.Bundle;
    import android.support.v4.app.Fragment;
    import android.support.v4.app.FragmentManager;
    import android.support.v4.app.FragmentTransaction;
    import android.support.v7.app.AppCompatActivity;
    import android.util.Log;
    import android.view.View;
    import android.widget.Button;

    public class MainActivity extends AppCompatActivity implements View.OnClickListener {

        private static final String TAG ="MainActivity";

        private Fragment mFragment;

        private int mSelected = -1;


        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            Log.d(TAG,"onCreate " +"");

            // null check not realy needed - but just in case...
            if (savedInstanceState == null) {

                initUi();

                // get an instance of FragmentTransaction from your Activity
                FragmentManager fragmentManager = getSupportFragmentManager();
                FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

                /*IMPORTANT: Do the INITIAL(!) transaction only once!
                * If we call this everytime the layout changes orientation,
                * we will end with a messy, half-working UI.
                * */
                mFragment = FragmentOne.newInstance(mSelected = 0);
                fragmentTransaction.add(R.id.frame, mFragment);
                fragmentTransaction.commit();
            }
        }


        @Override
        public void onConfigurationChanged(Configuration newConfig) {
            super.onConfigurationChanged(newConfig);
            Log.d(TAG,"onConfigurationChanged " +
                       (newConfig.orientation
                        == Configuration.ORIENTATION_LANDSCAPE
                        ?"landscape" :"portrait"));

            initUi();

            Log.i(TAG,"onConfigurationChanged - last selected:" + mSelected);
            makeFragmentTransaction(mSelected);
        }


        /**
         * Called from {@link #onCreate} and {@link #onConfigurationChanged}
         */
        private void initUi() {
            setContentView(R.layout.activity_main);
            Log.d(TAG,"onCreate  instanceState == null / reinitializing..." +"");
            Button btnFragmentOne = (Button) findViewById(R.id.btn_fragment_one);
            Button btnFragmentTwo = (Button) findViewById(R.id.btn_fragment_two);
            btnFragmentOne.setOnClickListener(this);
            btnFragmentTwo.setOnClickListener(this);
        }


        /**
         * Not invoked (just for testing)...
         */
        @Override
        protected void onSaveInstanceState(Bundle outState) {
            super.onSaveInstanceState(outState);
            Log.d(TAG,"onSaveInstanceState " +"YOU WON'T SEE ME!!!");
        }


        /**
         * Not invoked (just for testing)...
         */
        @Override
        protected void onRestoreInstanceState(Bundle savedInstanceState) {
            super.onRestoreInstanceState(savedInstanceState);
            Log.d(TAG,"onSaveInstanceState " +"YOU WON'T SEE ME, AS WELL!!!");
        }


        @Override
        protected void onResume() {
            super.onResume();
            Log.d(TAG,"onResume " +"");
        }


        @Override
        protected void onPause() {
            super.onPause();
            Log.d(TAG,"onPause " +"");
        }


        @Override
        protected void onDestroy() {
            super.onDestroy();
            Log.d(TAG,"onDestroy " +"");
        }


        @Override
        public void onClick(View v) {

            switch (v.getId()) {
                case R.id.btn_fragment_one:
                    Log.d(TAG,"onClick btn_fragment_one" +"");
                    makeFragmentTransaction(0);
                    break;

                case R.id.btn_fragment_two:
                    Log.d(TAG,"onClick btn_fragment_two" +"");
                    makeFragmentTransaction(1);
                    break;

                default:
                    Log.d(TAG,"onClick  null - wtf?!" +"");
            }
        }


        /**
         * We replace the current Fragment with the selected one.
         * Note: It's called from {@link #onConfigurationChanged} as well.
         */
        private void makeFragmentTransaction(int selection) {

            switch (selection) {
                case 0:
                    mFragment = FragmentOne.newInstance(mSelected = 0);
                    break;
                case 1:
                    mFragment = FragmentTwo.newInstance(mSelected = 1);
                    break;
            }

            // Create new transaction
            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

            // Replace whatever is in the fragment_container view with this fragment,
            // and add the transaction to the back stack
            transaction.replace(R.id.frame, mFragment);

            /*This would add the Fragment to the backstack...
            * But right now we comment it out.*/
            //        transaction.addToBackStack(null);

            // Commit the transaction
            transaction.commit();
        }

    }

    样本片段:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    import android.os.Bundle;
    import android.support.v4.app.Fragment;
    import android.util.Log;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;

    /**
     * @author Martin Pfeffer (pepperonas)
     */
    public class FragmentOne extends Fragment {

        private static final String TAG ="FragmentOne";


        public static Fragment newInstance(int i) {
            Fragment fragment = new FragmentOne();
            Bundle args = new Bundle();
            args.putInt("the_id", i);
            fragment.setArguments(args);
            return fragment;
        }


        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            Log.d(TAG,"onCreateView " +"");
            return inflater.inflate(R.layout.fragment_one, container, false);
        }

    }

    可以在Github上找到。


    固定AndroidManifest.xml中的屏幕方向(横向或纵向)

    android:screenOrientation="portrait"android:screenOrientation="landscape"

    为此,不调用您的onResume()方法。


    谷歌推出的Android架构的最佳组件之一,将满足您的所有要求,即ViewModel。

    它旨在以生命周期的方式存储和管理与用户界面相关的数据,并且允许数据在屏幕旋转时存活。

    1
    class MyViewModel : ViewModel() {

    请参考:https://developer.android.com/topic/libraries/architecture/viewmodel


    您可以在活动中使用ViewModel对象。

    在配置更改期间,ViewModel对象会自动保留,以便它们保存的数据立即可用于下一个活动或片段实例。阅读更多:

    https://developer.android.com/topic/libraries/architecture/viewmodel(https://developer.android.com/topic/libraries/architecture/viewmodel)


    您可以使用此代码锁定到屏幕的当前方向…

    1
    2
    3
    4
    5
    6
    int currentOrientation =context.getResources().getConfiguration().orientation;
            if (currentOrientation == Configuration.ORIENTATION_PORTRAIT) {
                ((Activity) context).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
            } else {
                ((Activity) context). setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
            }