一些设备的参数会在运行时改变(如屏幕方向,键盘是可用性,语言等),当这样的变化发生Android会重新启动当前正在运行的Activity(调用onDestroy()再调用onCreate())。重启动的设计是为了帮助你的程序通过自动加载匹配新的设备参数的资源文件来适应参数的变化。要恰当的处理重启动,需要确保你的activity能够通过一般的生命周期(通过在销毁activity之前调用onSaveInstanceState()保存相关数据)恢复它之前的状态。这样你就能在onCreate()或者onRestoreInstanceState()方法中恢复activity之前的状态。
为了测试你的app是否能毫发无损(数据不会丢失)的重启动,你应该在执行多种任务时让设备参数变化(如改变屏幕的方向)。为了处理诸如参数改变、用户接听电话后回到你的app(此时你的app进程可能已经被系统销毁)等事件,你的app必须能在任何时候重启动,而且不能丢失用户数据或者状态。学习如何恢复你Activity状态,请阅读Activity lifecycle。
然而你可能会遇到重启动app并且恢复重要数据需要耗费较多的系统资源,从而导致用户体验变差的情况。在这样的情况下,你有两个选择。
a在设备参数改变期间保持一个对象
允许你的activity在参数改变时重启动,但是携带一个有状态的对象给你的activity的新实例。
B自己处理设备参数的改变
阻止系统在特定的参数改变的时候重启动你的activity,但是在设备参数改变时通过回调方式使你能够根据需要手动的更新你的activity
Retaining an Object During a Configuration Change
如果重启动你的activity需要你恢复大量的数据,如重新建立一个网络连接,或者执行其他耗时较大的操作时,那么一个因参数改变而完全地重启动会使用户体验变慢。而且,你想要用Bundle(系统通过onSaveInstanceState()回调函数帮你存储)完全恢复你的activity数据也不太可能,因为Bundle不是为了携带大量数据(如bitmaps)而设计的。而且在Bundle中的数据必须经过序列化和反序列化操作,这通常会耗费大量的存储空间并且使app的反应变慢。在这样的情况下,可以通过在你的activity由于设备参数改变而重启时,保持一个Fragment减少activity重新初始化的负担。这个fragment可以包括你想保持的状态性的对象的引用。
当Android系统因为设备参数改变而关闭你的activity时,activity中你标记要保持的的fragment不会被销毁。你可以为你的activity添加这样的fragment来保存状态性的对象。
为了在运行参数变化时在fragment保持状态性的东西:
1继承fragment类并声明对你的状态性的东西的引用。
2当fragment被创建时,调用setRetainInstance(boolean)
3把fragment添加到你的activity
4用FragmentManager来在activity重启动时重新获得fragment
作为例子,像下面这样定义你的fragment
public class RetainedFragment extends Fragment {
// data objectwe want to retain
private MyDataObject data;
// this methodis only called once for this fragment
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
// retain this fragment
setRetainInstance(true);
}
public void setData(MyDataObject data){
this.data= data;
}
public MyDataObject getData(){
return data;
}
}
注意:当你存储任何对象时,不要传递跟activity相关联的对象,如Drawble,Adapter,View或者其他与Context有联系的对象。不然,它会泄露原始activity实例的所有views和资源(泄露资源意味着你的app一直保持着他们,就不能被垃圾回收,这样会导致大量的空间浪费)。
然后使用FragmentManager来添加fragment到activity。当activity在参数改变时再次启动时,你可以从fragment中获取数据对象。例如,按照以下方式定义你的activity
public class MyActivity extends Activity { private RetainedFragment dataFragment; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // find the retained fragment on activity restarts FragmentManager fm = getFragmentManager(); dataFragment = (DataFragment) fm.findFragmentByTag(“data”); // create the fragment and data the first time if (dataFragment == null) { // add the fragment dataFragment = new DataFragment(); fm.beginTransaction().add(dataFragment, “data”).commit(); // load the data from the web dataFragment.setData(loadMyData()); } // the data is available in dataFragment.getData() ... } @Override public void onDestroy() { super.onDestroy(); // store the data in the fragment dataFragment.setData(collectMyLoadedData()); } }
在这个例子中,onCreate()添加fragment或者回复fragment的引用,onCreate()也可以在fragment实例中存储状态性的对象。onDestroy()在保持的fragment实例中更新状态性对象。
<activity android:name=".MyActivity" android:configChanges="orientation|keyboardHidden" android:label="@string/app_name">现在,当这些设置过的参数改变时,MyActivity不会重启动。当这些参数改变时,MyActivity的onConfigurationChanged()会被调用,这个函数传递一个包含新参数的Configuration对象。通过读取Configuration对象中域的值,你可以获取新的参数并自己通过更新资源来做适当的改变。在这个方法被调用时,你的acitivity的资源对象已经根据新的参数数更新了资源并将其返回,这样你就可以容易的重新设置你界面中的ui元素而不用系统重启动你的activity。
@Override 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(); } }Configuration对象拥有所有当前设备的参数,而不是仅仅包括那些改变的参数。在大多数时候,你不会关心参数怎样改变,。例如因为Resources对象现在给更新,你可以通过setImageResources设置任何的ImageView,而且适配新参数的恰当的资源被使用。
android:configChanges
documentation and the
Configuration
class.