湖畔镇

Activity相关

本文介绍一些与Activity相关的小知识

启动模式

Activity可以设置android:launchMode指定启动模式

  • standard
    标准

  • singleTop
    唯一栈顶,启动的Activity如果在栈顶,将不再新建,而是调用原Activity的onNewIntent()
    比如点击搜索按钮进入搜索结果页面,一般而言还是会有搜索框可以再次搜索,如果反复生成新搜索页面,回退的时候非常麻烦而且很怪异,这个时候适合用这个

  • singleTask
    如果栈内已经有一个标记为singleTask的Activity A,再次启动一个这样的Activity时将不会新建,而是会残忍的销毁A之上的所有Activity,使A显示在栈顶,并调用它的onNewIntent()
    文档里提到然而并不是这样,如果想达到这样的效果即新开一个Task,这个Activity作为根,你需要配置清单的android:taskAffinity属性为空

The system creates a new task and instantiates the activity at the root of the new task

  • singleInstance
    栈内只有这么一个Activity,再次启动一个这样的Acitivity会启动一个新的Task

标志位

比较常用的有下面这些

  • FLAG_ACTIVITY_SINGLE_TOP
    唯一栈顶,启动的Activity如果在栈顶,将不再新建,而是调用原Activity的onNewIntent()

  • FLAG_ACTIVITY_NEW_TASK
    启动一个新的Task,通常用于表现启动器行为,提供用户一组可以做的事,然后运行在完全独立的Task里

  • FLAG_ACTIVITY_MULTIPLE_TASK
    用来启动一个新的Task,通常与FLAG_ACTIVITY_NEW_TASKFLAG_ACTIVITY_NEW_DOCUMENT一起使用(不一起使用该标记会被忽略),因为单独使用这些标记时,如果能在现存的Task里找到匹配的,就不会再新建,加上这个标记则会跳过搜索匹配Task的过程,无条件的创建一个新的Task

  • FLAG_ACTIVITY_CLEAR_TOP
    可以和FLAG_ACTIVITY_NEW_TASK很好的搭配,用来启动一个Task的根Activity,将把那个Task当前正在运行的任何实例都调到前台,然后清除到根,在从通知里启动Activity这种场景尤其有用

  • FLAG_ACTIVITY_REORDER_TO_FRONT
    如果已经运行,就调到栈顶,如果FLAG_ACTIVITY_CLEAR_TOP也被设置了会被忽略

  • FLAG_ACTIVITY_CLEAR_TASK
    清空Task,Activity成为根

生命周期

Activity有三种重要状态:

  • resumed
    Activity在前台,拥有用户焦点
  • paused
    别的Activity在前台并且拥有焦点,但是这个Activity仍然可见,内存中保存有Activity对象,维持着所有状态,保持附着于WindowManager,在极度低内存时可能被销毁
  • stopped
    完全被其他Activity覆盖,处于后台不可见,内存中保存有Activity对象,维持着所有状态,但是未附着于WindowManager,当其他地方需要内存时可能被销毁

当Activity是pausedstopped时,系统可能调用finish()或直接杀死进程来回收内存,当Activity再次被打开时,必须被重新创建

生命周期

  • 全部阶段
    onCreate()onDestroy(),在前者初始化,在后者释放所有资源,比如停止后台下载线程
  • 可见阶段
    onStart()onStop(),可以在前者注册BroadcastReceiver监视UI变化,在后者注销
  • 前台阶段
    onResume()onPause(),获得焦点用户可交互,可能频繁切换,所以其中的代码需要轻量,onPause()是Activity可能被销毁之前调用的最后方法,所以要在这里做关键数据保存

假设A启动B,执行顺序是A.onPause() -> B.onCreate() -> B.onStart() -> B.onResume() -> A.onStop(),所以如果A需要写入数据供B读取,应该发生在onPause()

配置变化

如果配置变化,UI都需要更新,Activity有特殊的支持处理配置变化
除非特别指定,配置变化将会导致当前Activity被销毁,如果Activity当前可见,之后会重新实例化,使用savedInstanceState,这是之前实例通过onSaveInstanceState()保存的

某些特殊情况,你可能想跳过重建,通过清单里的android:configChanges配置,对于你要处理的任意配置变化类型,将会调用onConfigurationChanged()而不是重启,如果一个配置变化你不处理,仍然会重建而且这个函数不会被调用

要做以下几点:

  1. 添加权限<uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
  2. 指定android:configChanges属性,用|连接多个变化项
  3. 重写onConfigurationChanged()

从API13后转屏时screenSize也会改变,这时仅写orientation是不会生效的,而要写orientation|srceenSize,小于API13则不需要,此为一坑

保存状态

Activity可能被销毁,重建时需要之前的信息,可以通过onSaveInstanceState()保存信息,在onCreate()onRestoreInstanceState()中获得信息恢复

onSaveInstanceState()是不保证被调用的,因为当用户主动关闭Activity时没有必要保存状态,如果系统调用这个方法,会在onStop()之前调用,可能在onPause()之前

然而即使你没有实现onSaveInstanceState(),一些Activity状态仍然被默认实现保存了,默认会调用所有视图的onSaveInstanceState(),保存所有视图的状态,几乎所有视图都实现了这个方法,所以能保存和恢复状态,不过你需要提供一个android:id,否则不会保存状态

重写这些方法的时候不要忘了调用父类方法

onSaveInstanceState()不保证被调用,所以应该只用来保存UI状态,不要用它保存数据,这应该在onPause()中做

进程生命周期

同一个应用的所有组件都在同一个进程下,可以通过组件的android:process属性指定不同的进程,也可以指定application的这个属性为所有组件指定默认的进程

Android系统尽可能保持应用进程的运行,当内存很低时也会移除旧的进程,有五种进程状态,按重要程度排列:

  • 前台进程
  1. 进程中包含处于前台的正与用户交互的Activity
  2. 进程中包含与前台Activity绑定的Service
  3. 进程中包含调用了startForeground()方法的Service
  4. 进程中包含正在执行onCreate(), onStart(), 或onDestroy()方法的Service
  5. 进程中包含正在执行onReceive()方法的BroadcastReceiver
    系统中前台进程的数量很少, 前台进程几乎不会被杀死,只有当内存低到无法保证所有的前台进程同时运行时才会选择杀死某个前台进程(系统也差不多挂了)
  • 可见进程
  1. 进程中包含未处于前台但仍然可见的Activity(调用了onPause()方法, 但没有调用onStop()方法),典型的情况是运行Activity时弹出对话框, 此时的Activity虽然不是前台Activity, 但其仍然可见
  2. 进程中包含与可见Activity绑定的Service
  • 服务进程
    包含已启动的Service

  • 后台进程
    不可见,暂停

  • 空进程
    不持有Activity和其他组件,当系统内存低时会被很快杀掉,空进程存在的唯一理由是为了缓存一些启动数据,以便下次可以更快的启动

由于服务进程的优先级高于后台进程,因此如果Activity需要执行耗时操作,最好还是启动一个Service来完成,当然在Activity中启动子线程完成耗时操作也可以,但是这样做的缺点在于,一旦Activity不再可见,Activity所在的进程成为后台进程,而内存不足时后台进程随时都有可能被系统杀死。基于同样的考虑,在BroadcastReceiver中也不应该执行耗时操作,而应该启动Service来完成(当然, BroadcastReceiver的生命周期过于短暂,也决定了不能在其中执行耗时操作)

有用的方法

1
public boolean isFinishing();

检查Activity是不是结束了,可能是你手动调用了finish(),也可能是其他地方请求结束
经常在onPause()中检查Acitivity只是暂停还是彻底结束

1
public boolean moveTaskToBack (boolean nonRoot)

把包含此Activity的Task移动到Activity栈的后面,Task内的Activity顺序不变,nonRoottrue对所有Activity有效,false只对根Activity有效

测试:比如A1->A2->A3,A3调用moveTaskToBack(),参数为true时”退出”应用,实际栈维持不变,再回到应用还是一样的,参数为false时在这个页面无效,只在A有效

1
public boolean navigateUpTo (Intent upIntent)

类似目标Activity有singleTask的效果,在复杂导航的APP里可能有用
应该用于同Task里的向上操作,如果跨Task,参见shouldUpRecreateTask()

测试:比如A1->A2->A3->A4,A4调用navigateUpTo()到A1,A2/A3/A4都会出栈,A1再按后退键就会退出应用

1
public boolean shouldUpRecreateTask (Intent targetIntent)

当向上导航时是否要重建Task

1
public PendingIntent createPendingResult (int requestCode, Intent data, int flags)

创建PendingIntent,你能够传给其他组件,它们可以用来发送数据回来到onActivityResult(),可以是一次性的也可以是多次使用的

这个方法可以用来Activity与Service交互,也可以应用于跨Activity传递数据,比如A、B、C三个Activity,A打开B,B打开C,然后B自动关掉,A接收C的结果,就可以使用这种方式传递PendingIntent实现

1
2
3
4
PendingIntent pendingResult = createPendingResult(100, new Intent(), 0);  
Intent i = new Intent(this, MyService.class);
i.putExtra(PENDING__RESULT, pendingResult);
startService(i);

先启动一个service,PendingIntent也传过去

1
2
3
4
5
6
PendingIntent pendingIntent = intent.getParcelableExtra(MainActivity.PENDING__RESULT);  
try {
pendingIntent.send(MyService.this, 101, new Intent().putExtra("data", "some data"));
} catch (CanceledException e) {
e.printStackTrace();
}

调用PendingIntentsend()方法,发送结果给调用的Activity,让其onActivityResult()响应

1
public void finishAffinity ()

可以关闭当前Activity所属的Activity栈中所有的Activity,如果所有Activity都保存在默认栈中,则使用该方法会直接退出程序

如果要回到程序的主界面,则可以在清单文件中将主界面的Activity设置为单独的栈保存,设置android:taskAffinity=":[name]

清空任务栈

用户离开Task很长时间,系统清理Task里的除了根Activity以外的所有Activity,当用户回来,只有根Activity会恢复
有一些Activity属性可以用来改变这种行为

  • alwaysRetainTaskState
    如果根Activity的这个属性是true,则总是保留Activity
  • clearTaskOnLaunch
    如果根Activity的这个属性是true,则总是清空Activity
  • finishOnTaskLaunch
    类似clearTaskOnLaunch,但是不是对整个Task,而是对单个Activity
分享