处理Android程序运行时的配置变化

本篇文章翻译自Android官方文档Handling Runtime Changes,有翻译错误请留言告知,多谢。



Android程序在运行期间设备的配置是可能发生改变的(例如屏幕的方向,键盘可用性,和语言等)。当这些配置发生变化时,Android会重启正在运行的Activity(先调用onDestory(),紧接着调用onCreate())。这个设计是为了让你的程序在配置发生变化时,使用不同的资源自动去适配新的配置机器。

正确的处理重启,一件很重要的事就是通过Activity正常的生命周期去恢复之前状态,你可以在Android销毁你的Activity前调用onSaveInstanceState()来保存程序的状态。然后你就可以在onCreate()或onRestoreInstanceState()调用期间恢复程序之前的状态了。

如果你想测试你程序重启之后的状态完整性,你可以在程序开着不同线程处理事务时主动改变配置(例如改变屏幕的方向)。你的程序应该在任何时候重启都不会丢失用户的状态或数据,例如配置改变或用户突然收到来电后程序的进程被销毁。你可以阅读关于Activity的生命周期学习怎样保存你的Activity状态。

不过,你可能遭遇过程序重启时需要恢复大量的数据导致而糟糕的用户体验。发生这种情况,你还有下面这两种方法:

  1. 配置改变时保留一个对象

    允许你的Activity在配置改变时可以重启,然后在新建一个Activity时携带一个之前所有状态数据的类

  2. 你自己处理配置改变

    配置改变时你不必重启你的Activity,而是收到一个回调方法,这样你就可以更新Activity需要更新的资源

配置改变时保存一个对象

如果重启你的activity需要恢复大量的数据、重建网络连接、或展示其他紧急的事务,那么重启将会非常慢。同时,你不可能在系统回调onSaveInstanceState()时保存整个activity的状态数据进Bundle里,它也不是设计来携带需要序列化和反序列化的大数据量对象(如bitmap对象)的,那需要开销大量的内存并使配置变化变慢。在这种情况下,你可以在保存一个Fragment来缓解因为配置改变导致activity重启的初始化负担。这个fragment可以持有你想保存的引用到一个状态对象里。

当Android系统因为配置改变销毁你的activity时,activity里那个你想要来保存数据的fragment并不会销毁。你可以增加这样的fragment在activity里来保护状态对象。

在配置改变期间保存一个状态对象的步骤:

  1. 继承Fragment类并声明你的状态引用
  2. 当fragment创建时调用setRetainInstance(boolean)方法
  3. 将上面的fragment放到你的activity里
  4. 当activity重启时用FragmentManager去保存这个fragment

例如下面这个例子:

public class RetainedFragment extends Fragment { 

    //我们想要的保存的数据对象
    private MyDataObject data;

    // 这个方法在fragment里只被调用一次
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 保存这个fragment
        setRetainInstance(true);
    } 

    public void setData(MyDataObject data) {
        this.data = data;
    } 

    public MyDataObject getData() {
        return data;
    }
 }

慎重:当你保存任何对象时,最好不要保存那些跟Activity绑定的对象,例如Drawable, Adapter, View或者任何一个需要和Context绑定才能使用的对象。如果你保存了,意味着你会泄漏原来activity实体里的所有views和资源。(泄漏资源的意思是,你的程序持有这些对象将使它们没办法被垃圾回收,会造成内存泄漏。)

接着用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”);

        // 第一次创建fragment和数据
        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();
        // 将数据保存在这个fragment里
        dataFragment.setData(collectMyLoadedData());
    }
}

在上面例子,onCreate()里创建一个fragment对象并在里面保存数据对象。然后在onDestory()方法内更新fragment里的数据对象。

你自己处理配置改变

如果你的程序在一些配置改变期间不需要更新资源,并且你有一些特殊的限制使你避免重启activity,那么就可以声明你自己来处理配置变化,系统也就不会重启你的activity。

注意:自己处理配置变化在使用其他的资源时会变得更困难,因为系统不会自动为你处理。这种技术你应该在万不得已时才用,大部分程序是不推荐使用的。

你可以在mainifest文件相应的标签里,声明android:configChanges属性来配置你想要处理的事件。adnroid:configChanges文档里列出了所有能够配置的属性(大多数人会用到”orientation”来避免因屏幕方向变化导致activity重启,和用”keyboardHidden”来防止键盘的可用性变化导致重启)。你可以用’|’字符隔开不同属性来声明多个配置。

例如,下面的配置是会使MyActivity检测到屏幕方向变化和键盘可用性变化:

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

现在,当上面那两个配置发生改变,MyActivity将不会重启。不过,MyActivity的onConfigurationChanged()方法会被调用。这个方法会通过一个Configuration对象来指定新设备的参数。通过读取Configuration的属性值,你可以确定新的配置并且用你的接口更新到合适的资源里。在这个方法调用时,你activity的资源对象将以新的配置为基础做更新,这样你就可以很容易的重置你的用户视图而不必重启activity。

注意:从Andorid 3.2(API level 13)开始,当设备的屏幕在垂直和水平方向切换时,屏幕尺寸(screen size)也会随之改变。因此从API level 13起(在mainifest里有声明minSdkVersion和targetSdkVersion属性),如果你想要避免activity在运行时重启,就需要在configChanges属性中,除了声明”orientation”外同时加上”screenSize”,如android:configChanges=”orientation|screenSize”。不过,如果你的程序的目标API在12或以下,你的activity也可以用这两个属性来避免重启(这个两个属性可以避免所有Android版本的因屏幕方向变化导致的重启)。

例如,下面的onConfigurationChanged()方法可以用来检测当前设备屏幕变化:

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

    // 检查屏幕的方向
    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对象持有所有当前设备的配置,不仅仅是那些变化的。通常,你不用关心配置是怎样改变的,你只需从configuration里获取配置并重新分配你的所有资源文件就得了。比如,因为资源对象现在已经更新,你就可以用setImageResource()方法来重置你所有ImageView控件(资源文件可参考Providing Resources)。

需要注意的是,Configuration对象里的参数都是整形,它们和Cofiguration类里的常量相匹配。你可以从Configuration文档中找到可用的配置属性。

请记住:当你自己声明处理配置变化,你就有责任去更新那些有替代品的元素。如果你声明了你的activity去处理屏幕方向变化事件,且你有一些图片是需要在横竖屏切换时改变,那么你就必须在onConfiguration()方法调用期间重置那些控件资源。

如果你不需要再配置变化时更新你的程序,你也可以不用实现onConfigurationChanged()方法。在这种情况下,程序在配置变化后还会继续引用之前的资源,只是不重启你的activity。不过,你的程序最好可以被销毁和带着之前的状态重启,你不应该用这种技术在正常的生命周期里保持状态溜走。不止是因为你无法避免那些你没处理的变化会导致重启你的程序,且应该处理用户离开你程序的事件和在用户返回之前销毁它。

如果你想知道activity里有多少配置变化可以处理,看看android:configChangesConfiguration类。

我发现CSDN对于Markdown编写的文章排版不是很好,如果你想获得更好的阅读体验,可以访问这个链接阅读。

时间: 2024-10-05 04:34:10

处理Android程序运行时的配置变化的相关文章

Android程序运行时权限与文件系统权限的区别

apk程序是运行在虚拟机上的,对应的是Android独特的权限机制,只有体现到文件系统上时才使用linux的权限设置. (1)Android中的apk必须签名 (2)基于UserID的进程级别的安全机制  (3)默认apk生成的数据对外是不可见的  (4)AndroidManifest.xml中的显式权限声明  Android程序运行时权限与文件系统权限的区别

获取java程序运行时内存信息

由于最近想自己动手测试一下String和StringBuffer的效率问题,需要获取程序运行时的内存占中信息,于是上网查了一下,根据查到的资料写了个程序,发现结果有问题,才发现查到的资料是错误的.所以在这里跟大家分享一下获取内存占用的正确方法 错误的方法 //程序开始时:(先调用一下垃圾回收,但是不一定立即执行) Runtime.getRuntime().gc(); long initm=Runtime.getRuntime().freeMemory(); //程序结束时: Runtime.ge

ETCD:运行时重新配置设计

原文地址:the runtime configuration design 运行时重新配置是分布式系统中最难,最容易出错的部分,尤其是在基于共识(像etcd)的系统中. 阅读并学习关于etcd的运行时重新配置命令设计和如何追溯这些错误. 两阶段配置更新保证集群安全 在etcd中,每一次运行时重新配置安全的原因是由于两阶段更新.例如,添加一个成员,首先将新配置通知集群后启动新的成员. 阶段一 通知集群关于新的配置 添加一个成员到etcd集群中,通过API调用请求将一个新成员添加到集群中.这是将新的

VC项目程序运行时设置指定目录读取Dll

方法一: 选择当前工程,右击"Properties" -> "Configuration Properties" -> "Debugging",在"Working Directory"设置dll的路径就可以了 方法二:设置项目的环境变量 方法三: CString strDllPath = GetExePath() + _T("System"); SetDllDirectory(strDllPat

程序运行时三种内存分配策略

按照编译原理的观点,程序运行时的内存分配有三种策略,分别是静态的,栈式的,和堆式的. 静态存储分配是指在编译时就能确定每个数据目标在运行时刻的存储空间需求,因而在编译时就可以给他们分配固定的内存空间.这种分配策略要求程序代码中不允许有可变数据结构(比如可变数组)的存在,也不允许有嵌套或者递归的结构出现,因为它们都会导致编译程序无法计算准确的存储空间需求. 栈式存储分配也可称为动态存储分配,是由一个类似于堆栈的运行栈来实现的.和静态存储分配相反,在栈式存储方案中,程序对数据区的需求在编译时是完全未

c/c++编译时,指定程序运行时查找的动态链接库路径

http://blog.csdn.net/tsxw24/article/details/10220735 c/c++编译时,指定程序运行时查找的动态链接库路径 分类: c/c++ linux 2013-08-23 14:04 1117人阅读 评论(0) 收藏 举报 [plain] view plaincopy $ g++ -Wl,-rpath,/usr/local/lib/ -oevh libevent_http.cpp -levent -Wl,-rpath,  用于指定程序运行时查找动态链接库

Java程序运行时的几个区域

Java运行时涉及到的区域 几个基本概念: 1.Java对象     2.Java方法    3.一个编译好的类,以class文件的形式出现 4.Java的本地方法   5.线程私有和线程共有 一.方法区(永久代) 和 堆(heap) 这两个区域是线程共有的,供所有线程使用.所以,对存放在这两个地方的资源进行操作时,如果是程序是多线程的,那么要考虑同步. 方法区存放的是类的类型信息.类的类型信息有,类的静态变量,其它从class文件中读取到的信息. 当用户访问一个类的静态方法或者类的静态变量,或

程序运行时内存管理

1,管理运行阶段内存空间分配 malloc()/new; int *pn = new int(存储的类型,内存根据此设定相应存储字节的内存) pn是内存地址(所以 当声明一个变量的指针变量时没初始化,声明后再来初始化则pn 接收的应该是变量在内存中的地址 &VariableName); *pn是存储在内存的值 用于给所指向内存中的变量赋值; 为一个数据对象(结构,基本类型)获得并指定分配内存格式 typeName pointer_name = new typeName; 指定需要什么样的内存和用

Android studio运行时乱码 编码问题 閿欒: 缂栫爜UTF-8鐨勪笉鍙槧灏勫瓧绗?

Android studio运行时乱码 编码问题 閿欒: 缂栫爜UTF-8鐨勪笉鍙槧灏勫瓧绗?