安卓-四大组件-Activity

Activity

4 种状态

  • Active
  • Paused
  • Stopped
  • Killed

生命周期

Activity正常生命周期

Activity正常生命周期图.png

onCreate() 首次创建
onStart() 用户可见
onResume() 用户交互
onPause() 另一个应用开启,该应用暂停
onStop() 用户不可见(还在内存)–》onRestart()–》onStart()
​ 或者process is killed on–》onStart()
onDestroy() 销毁命令:当内存不足原先不用的会自动销毁
onRestart() 重新启动
onRestart() 后会调用onNewIntent()(即,onNewIntentonCreate是不会被同时调用)

onNewIntent()被调用两次

1
2
3
4
5
Intent intent = new Intent(WebTestActivity.this, MainActivity.class); 
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
Uri uri = Uri.parse(url);
intent.setData(uri);
startActivity(intent);

以上面的方式经过onRestart()启动的话onNewIntent()会被调用两次。
解决:只设置intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);

onNewIntent的生命周期

onNewIntent的生命周期

  1. 只对singleTop , singleTask , singlelnstance有效,因为standard每次都是新建,所以不存在onNewlntent ;
  2. 只对startActivity有效,对于从Navigation切换回来的恢复无效;

异常情况下的生命周期

  • onSaveInstanceState
  • onRestoreInstanceState

一些特殊情况下的生命周期

  • Activity的横竖屏切换
  • 什么时候单独走 onPause()不走 onStop()
    Activity被另一个透明或者Dialog样式的Activity覆盖时走onPause()不走onStop()。 此时它依然和WindowManager保持连接,系统继续维护其内部状态,所以它仍然可见,但它已失去焦点不能与用户交互。
  • 什么情况下导致 onDestory()不执行
    • 极端情况下:系统内存不足时被杀死是不会调onDestroy()
    • MainActivity中调System.exit(0);MainActivity不会调用onDestroy

IdleHandler比onResume精确

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

Looper.getMainLooper().getQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
Log.e("DBL", "queueIdle");
// UI第一帧绘制完成(可以理解为页面可见)
return false; // 返回false表示MessageQueue在执行完这段代码后将该IdleHandler删除,反之不删除,下一次继续执行
}
});
}

@Override
protected void onResume() {
super.onResume();
Log.e("DBL", "onResume");
}

上面例子将IdleHandler添加到主线程MessageQueue中,queueIdle()方法回调,说明UI第一帧绘制完成,可以理解为UI首次可见,这个比onResume精确的多,因为onResume回调的时候界面还没有开始绘制,此时界面是不可见的。

IdleHandler应用场景

  1. 在应用启动时我们可能希望把一些优先级没那么高的操作延迟一点处理,一般会使用 Handler.postDelayed(Runnable r, long delayMillis)来实现,但是又不知道该延迟多少时间比较合适,因为手机性能不同,有的性能较差可能需要延迟较多,有的性能较好可以允许较少的延迟时间。所以在做项目性能优化的时候可以使用 IdleHandler,它在主线程空闲时执行任务,而不影响其他任务的执行。
  2. 想要在一个 View 绘制完成之后添加其他依赖于这个 View 的 View,当然这个用View.post()也能实现,区别就是前者会在消息队列空闲时执行
  3. 发送一个返回 true 的 IdleHandler,在里面让某个 View 不停闪烁,这样当用户发呆时就可以诱导用户点击这个View,这也是种很酷的操作

    IdelHandler注意事项

  4. MessageQueue 提供了add/remove IdleHandler方法,但是我们不一定需要成对使用它们,因为IdleHandler.queueIdle() 的返回值返回 false 的时候可以移除 IdleHanlder。
  5. 不要将一些不重要的启动服务放到 IdleHandler 中去管理,因为它的处理时机不可控,如果 MessageQueue 一直有待处理的消息,那么它的执行时机会很靠后。
  6. 当 mIdleHanders 一直不为空时,为什么不会进入死循环?
    • 只有在 pendingIdleHandlerCount 为 -1 时,才会尝试执行 mIdleHander;
    • pendingIdlehanderCount 在 next() 中初始时为 -1,执行一遍后被置为 0,所以不会重复执行;

启动模式

Activity 的任务栈

AndroidManifest.xmlactivity标签中加taskAffinity="xxx",不同taskAffinity创建不同栈

使用Intent标志

  • FLAG_ACTIVITY_NEW_TASKsingleTask
  • FLAG_ACTIVITY_SINGLE_TOPsingleTop
  • FLAG_ACTIVITY_CLEAR_TOP
    如果正在启动的activity已在当前task中运行,则不会启动该activity的新实例,而是销毁其上的activity,并调用其onNewIntent()

启动模式的类型和特性

standard

系统在启动它的任务中创建activity的新实例

singleTop

如果activity的实例已存在于当前任务的顶部,则系统通过调用其onNewIntent()

singleTask

系统创建新task并在task的根目录下实例化activity。但如果activity的实例已存在于单独的任务中,则调用其onNewIntent()方法。一次只能存在一个activity实例

singleInstance

相同”singleTask”,activity始终是其task的唯一成员; 任何由此开始的activity都在一个单独的task中打开

Activity 组件之间的通信

Activity–》Activity

  • Intent/Bundle
  • 类的静态变量
  • 全局变量

Activity–》Service

  • 绑定服务,利用 ServiceConnection类
  • Intent
  • Callback+Handler,监听Service 进程变化

Activity–》Fragment

  • Bundle
  • 直接调用方法

Activity之间通过Intent的通信

Intent有两个最重要的部分:动作和动作对应的数据

  • 典型的动作类型:
    • MAIN(activity的门户)
    • VIEW
    • PICK
    • EDIT
  • 动作对应的数据则以URI的形式进行表示。例如:要查看一个人的联系方式,需要创建一个动作类型为VIEWintent,以及一个表示这个人的URI
  • 与之有关系的一个类IntentFilter,用于描述一个activity(或者IntentReceiver)能够操作哪些intent

Scheme跳转协议

AndroidManifest.xml清单文件中定义:

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
...
<activity
android:name=".launcher.activity.LauncherActivity"
android:configChanges="orientation|screenSize|keyboardHidden"
android:exported="true"
android:launchMode="singleTask"
android:screenOrientation="portrait"
android:theme="@style/WithBgTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="597.com"
android:scheme="im597" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" /> <!-- 显示数据 -->
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <!-- 定义成浏览器类型,有URL需要处理时会过滤 -->
<data android:scheme="com597" /> <!-- 打开以com597协议的URL,这个自己随便定义。 -->
</intent-filter>
</activity>
...

跳转链接:

  • 简历页面 https://m.597.com/download/app/?act=resume&rid=d5de934017675&userType=2
  • 职位管理页面 https://m.597.com/download/app/?act=jobManage&userType=2
    代码中处理:
    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
    @Override  
    protected void onCreate(Bundle savedInstanceState) {
    //...
    if (BaseAppCache.getLauncherIntent() != null) {
    doIntent(BaseAppCache.getLauncherIntent());
    } else {
    doIntent(getIntent());
    }
    }

    @Override
    protected void onRestart() {
    super.onRestart();
    AppActivityMgr.INST.finishOtherActivity(this);
    if (BaseAppCache.getLauncherIntent() != null) {
    doIntent(BaseAppCache.getLauncherIntent());
    } else {
    doIntent(getIntent());
    }
    }

    /**
    * 启动页 的广告页面点击事件
    */
    public void doIntent(Intent intent) {
    if (intent != null) {
    if (intent.hasExtra(NimIntent.EXTRA_NOTIFY_CONTENT)) {
    //...
    } else if (intent.getData() != null) {
    checkOutLinkIntent(intent);
    }
    }
    BaseAppCache.setLauncherIntent(null);
    }

    /**
    * * @param intent
    */
    private void checkOutLinkIntent(Intent intent) {
    Uri data = intent.getData();
    if (data != null) {
    String act = data.getQueryParameter("act");
    if (TextUtils.equals(act, "jobManage")) {
    //...
    }else if (TextUtils.equals(act, "resume")){
    String rid = data.getQueryParameter("rid");
    //...
    }
    }
    }

源码解读 startActivity 都做了什么

Android启动过程

Activity生命周期解析

Activity:直译为“活动”,翻译成“界面”会更好理解。

典型情况下的生命周期分析

onCreate:Activity正被创建,生命周期第一个方法。可做些初始化工作:setContentView、初始化Activity所需数据。
onRestart:Activity正被重新启动。一般从当前Activity不可见重新变为可见时触发。如:用户按Home切换到桌面再回来。
onStart:Activity正被启动,Activity可见了,未出现在前台,还无法与用户交互。
onResume:Activity可见了,出现在前台并开始活动,可与用户交互了。
onPause:Activity正在停止,正常情况会紧接着调用onStop。特殊情况,快速再回到当前Activity(用户很难重现这一场景)。可做些存储数据、停止动画等工作,但不能太耗时(因为onPause走完才执行新Activity的onResume。太耗时会影响新Activity的显示)
onStop:Activity即将停止。可做些稍微重量级的回收工作,不能太耗时。
onDestory:Activity即将被销毁,Activity生命周期的最后一个回调。可做些回收工作和最终的资源释放。

Activity生命周期的切换过程

正常情况下启动

  • Activity A 启动另一个Activity B,回调如下:
    Activity AonPause()Activity BonCreate()onStart()onResume()Activity AonStop();如果B是透明主题又或则是个DialogActivity ,则不会回调A的onStop
  • 使用onSaveInstanceState()保存简单,轻量级的UI状态
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    lateinit var textView: TextView
    var gameState: String? = null

    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    gameState = savedInstanceState?.getString(GAME_STATE_KEY)
    setContentView(R.layout.activity_main)
    textView = findViewById(R.id.text_view)
    }

    override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
    textView.text = savedInstanceState?.getString(TEXT_VIEW_KEY)
    }

    override fun onSaveInstanceState(outState: Bundle?) {
    outState?.run {
    putString(GAME_STATE_KEY, gameState)
    putString(TEXT_VIEW_KEY, textView.text.toString())
    }
    super.onSaveInstanceState(outState)
    }

几种情况:

  1. 针对一个特定的Activity,第一次启动,回调:onCreate–>onStart–>onResume
  2. 当打开新Activity或切换到桌面时,回调:onPause–>onStop。若新Activity采用==透明主题(或是个DialogActivity)==,那么当前Activity不会回调onStop
  3. 当再次回到原Activity时,回调:onStart–>onStart–>onResume
  4. 当按back回退时,回调:onPause–>onStop–>onDestory
  5. Activity被系统回收后再次打开,生命周期与1一样。注意,只是生命周期方法一样,不代表所有过程都一样
  6. 从整个生命周期看onCreateonDestory是配对的,只调一次。从Activity是否可见看,onStartonStop是配对的,随用户的操作或设备息屏亮屏会调用多次。从Activity是否在前台看,onResumeonPause是配对的,随用户操作和息屏亮屏会调用多次。

两个问题

  1. onStartonResumeonPauseonStop从描述上来看差不多,对我们来说有什么实质的不同呢?
  2. 假设当前Activity为A,如果这是用户打开一个新ActivityB,那么B的onResume和A的onPause哪个先执行呢?

答案:

  1. 第一个问题:实际使用我们甚至只保留其中一对。从设计层面分析,onStartonStop应用于Activity是否可见这个角度;onResumeonPause应用于Activity是否位于前台这个角度。
  2. 第二个问题:Activity启动的请求会由Instrumentation来处理,然后它通过BinderActivityManagerServie(简称AMS)发请求,AMS内部维护一个ActivityStack并负责栈内的Activity的状态同步,AMS通过ActivityThread去同步Activity的状态从而完成生命周期方法的调用。

异常情况下的生命周期分析

比如当资源相关的系统配置发生改变以及系统内存不足时,Activity就可能被杀死。下面由这两种情况进行具体分析:

  1. 情况1:资源相关的系统配置发生改变导致Activity被杀死并重新创建
    比如旋转屏幕,由于系统配置发生了改变,默认情况下Activity会被销毁并且重新创建,当然我们也可以阻止系统重新创建我们的Activity
    异常情况下Activity的重建过程
    会调用onPauseonStoponDestroy,其中可能onPause–>onSaveInstanceState–>onStop也可能onSaveInstanceState–>onPause–>onStoponRestoreInstanceStateonStart之后

    Activity被意外终止时,Activity会调用onSaveInstanceState去保存数据,然后Activity会委托Window去保存数据,接着Window再委托它上面的顶级容器(是一个ViewGroup很可能是DecorView)去保存数据。最后顶层容器再去意义通知它的子元素来保存数据。–》典型的委托思想:上层委托下层、父容器委托子元素去处理一件事情,这种思想还应用在View的绘制过程、事件分发等。数据恢复过程也是类似。

    onCreateonRestoreInstanceState中都可以恢复数据,官方推荐用onRestoreInstanceState

  2. 资源内存不足导致低优先级的Activity被杀死

    Activity优先级:

    • 前台Activity–正在和用户交互的Activity,优先级最高
    • 可见但非前台Activity–比如Activity中弹出一个对话框,导致Activity可见但是位于后台无法和用户直接交互
    • 后台Activity–已经被暂停的Activity,比如执行了onStop,优先级最低

    如果一个进行中如果没有四大组件在执行,此进程将很快被系统杀死,故一些后台工作不适合脱离四大组件而独自运行在后台中。较好的方式是放入Service

不想重新创建Activity

Activity指定configChanges属性。比如不想让Activity在旋转时重建 android:configChanges="orientation"

configChanges的项目和含义

我们常用到的configChanges属性有localeorientationkeyboardHidden

注意screenSizesmallestScreenSize笔记特殊,它们的行为与编译选项有关,与运行环境无关。

如:minSdkVersiontargetSdkVersion有一个大于13,在旋转屏幕时不重建Activity要加orientation还要加screenSize,最终调onConfigurationchanged方法

Activity的启动模式

  • standard:标准模式。不管此实例是否存在都会重建一个新的实例。当用ApplicationContext(没有所谓的任务栈)去启动standard模式的Activity会报错“android.util.AndroidRuntimeException:Calling startActivity from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?”。如果加了“FLAG_ACTIVITY_NEW_TASK”其实是以singleTask模式启动的。
  • singleTop:栈顶复用模式。若新Activity已位于栈顶,则不会重建,会回调onNewIntent方法(可以取出当前请求的信息),注意:这个Activity的onCreate、onStart不会被系统调用
  • singleTask:栈内复用模式。若Activity在栈中存在则不会重建,会回调onNewIntent。
    具体例子:
    1. 任务栈S1中有ABC,此时ActivityD以singleTask模式请求启动,其所需要的任务栈为S2,由于S2和D实例都不存在,那么会先创建S2,再创建D并将其入栈到S2
    2. 任务栈S1中有ABC,此时ActivityD以singleTask模式请求启动,其所需要的任务栈为S1,由于S1已存在,那么系统直接创建D并将其入栈到S1
    3. 任务栈S1中有ADBC,此时D不会重建,系统会把D切换到栈顶并调用其onNewIntent,同时singleTask具有clearTop效果。最终S1中是AD
  • singleInstance:单实例模式。一种加强的singleTask模式,具有singleTask所有特性外,加强了一点,此模式的Activity只能单独位于一个任务栈中。由于栈内复用的特性,后续的请求均不会重建新Activity,除非此任务栈被系统销毁了。
    特殊任务栈1
    特殊任务栈2
LaunchMode 说明
standard 系统在启动它的任务中创建activity的新实例
singleTop 如果activity的实例已存在于当前任务的顶部,则系统通过调用其onNewIntent()
singleTask 系统创建新task并在task的根目录下实例化activity。但如果activity的实例已存在于单独的任务中,则调用其onNewIntent()方法。一次只能存在一个activity实例
singleInstance 相同”singleTask”,activity始终是其task的唯一成员; 任何由此开始的activity都在一个单独的task中打开
使用Intent标志 说明
FLAG_ACTIVITY_NEW_TASK 同singleTask
FLAG_ACTIVITY_SINGLE_TOP 同singleTop
FLAG_ACTIVITY_CLEAR_TOP 如果正在启动的activity已在当前task中运行,则不会启动该activity的新实例,而是销毁其上的activity,并调用其onNewIntent()

注意:

  1. 上面说的不同栈,要在AndroidManifest.xml的activity中加 taskAffinity=”xxx”,不同taskAffinity会创建不同栈。(注意其中xxx要直接写,不能写@string/xxx)
  2. 打印taskId的代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @Override
    protected void onResume() {
    super.onResume();
    getActivityTaskInfo();
    }

    protected void getActivityTaskInfo() {
    ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
    ActivityManager.RunningTaskInfo runningTaskInfo = manager.getRunningTasks(1).get(0);
    //栈内activity数量
    int numActivities = runningTaskInfo.numActivities;
    //taskId
    int id = runningTaskInfo.id;
    ComponentName topActivity = runningTaskInfo.topActivity;
    //栈顶activity信息
    String className = topActivity.getClassName();
    Log.e("activityTask", "id == " + id + "\n" + "numActivity == " + numActivities + "\n" + "className == " + className);
    }

IntentFilter的匹配规则

启动Activity:显式调用、隐式调用。理论上两者不共存,若共存则以显式调用为主。
隐式调用需Intent能匹配目标组件的IntentFilter中所设置的过滤信息。IntentFilter过滤信息有action、category、data
举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<activity
android:name="com.ab.chapter_1.ThirdActivity"
android:configChanges="screenLayout"
android:label="@string/app_name"
android:launcherMode="singleTask"
android:taskAffinity="com.ab.task1">
<intent-filter>
<action android:name="com.ab.chapter_1.c"/>
<action android:name="com.ab.chapter_1.d"/>
<category andorid:name="com.ab.category.c"/>
<category andorid:name="com.ab.category.d"/>
<data android:mimeType="text/plain"/>
</intent-filter>
</activity>

可以有多个action、category、data。一个Intent只有同时匹配action、category、data才能进行跳转。
Intent必须要有一个action与过滤规则中的某个action相同。
Intent可以没有category。
若过滤规则中定义了data(两部分组成:mimeType和URI),那么Intent中必须也要定义可匹配的data。
​ mimeType指媒体类型比如image/jpeg、audio/mpeg4-generic、video/*等。
​ 多个data的内容可以写到同一个data内

Intent-filter的匹配规则对于Service和BroadcastReceiver也是同样的道理,不过对于Service建议使用显式调用方式来启动服务。

隐式方式启动Activity时还需要加判断:PackageManager的resolveActivity()或Intent的resolveActivity()(返回最佳匹配的Activity),否则找不到匹配的Activity会返回null。PackageManager的queryIntentActivitites会返回所有成功匹配的Activity信息。

Activity启动过程

Activity启动过程

Activity启动过程

ActivityThread.java

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
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
ActivityInfo aInfo = r.activityInfo;
if (r.packageInfo == null) {
//step 1: 创建LoadedApk对象
r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
Context.CONTEXT_INCLUDE_CODE);
}
... //component初始化过程

java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
//step 2: 创建Activity对象
Activity activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
...

//step 3: 创建Application对象
Application app = r.packageInfo.makeApplication(false, mInstrumentation);

if (activity != null) {
//step 4: 创建ContextImpl对象
Context appContext = createBaseContextForActivity(r, activity);
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
//step5: 将Application/ContextImpl都attach到Activity对象 [见小节4.1]
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor);

...
int theme = r.activityInfo.getThemeResource();
if (theme != 0) {
activity.setTheme(theme);
}

activity.mCalled = false;
if (r.isPersistable()) {
//step 6: 执行回调onCreate
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}

r.activity = activity;
r.stopped = true;
if (!r.activity.mFinished) {
activity.performStart(); //执行回调onStart
r.stopped = false;
}
if (!r.activity.mFinished) {
//执行回调onRestoreInstanceState
if (r.isPersistable()) {
if (r.state != null || r.persistentState != null) {
mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
r.persistentState);
}
} else if (r.state != null) {
mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
}
}
...
r.paused = true;
mActivities.put(r.token, r);
}

return activity;
}

Activity的其他笔记

android属性之clearTaskOnLaunch

需求:每次从桌面进入app都是打开根Activity
要求:在AndroidManifest.xml中,在根Activity中添加 clearTaskOnLaunch 属性

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
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.administrator.myapplication">

<application
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:clearTaskOnLaunch="true">

<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".ActivityTest">
<intent-filter>
<action android:name="yayun" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
</manifest>

android属性之alwaysRetainTaskState

常用于浏览器,用来保存浏览状态(打开很多tab),用户再次启动浏览器还能看到之前的状态
原文链接

  1. android:allowTaskReparenting 这个属性用来标记一个Activity实例在当前应用退居后台后,是否能从启动它的那个task移动到有共同affinity的task,“true”表示可以移动,“false”表示它必须呆在当前应用的task中,默认值为false。如果一个这个Activity的元素没有设定此属性,设定在上的此属性会对此Activity起作用。例如在一个应用中要查看一个web页面,在启动系统浏览器Activity后,这个Activity实例和当前应用处于同一个task,当我们的应用退居后台之后用户再次从主选单中启动应用,此时这个Activity实例将会重新宿主到Browser应用的task内,在我们的应用中将不会再看到这个Activity实例,而如果此时启动Browser应用,就会发现,第一个界面就是我们刚才打开的web页面,证明了这个Activity实例确实是宿主到了Browser应用的task内。
  2. android:alwaysRetainTaskState 这个属性用来标记应用的task是否保持原来的状态,“true”表示总是保持,“false”表示不能够保证,默认为“false”。此属性只对task的根Activity起作用,其他的Activity都会被忽略。 默认情况下,如果一个应用在后台呆的太久例如30分钟,用户从主选单再次选择该应用时,系统就会对该应用的task进行清理,除了根Activity,其他Activity都会被清除出栈,但是如果在根Activity中设置了此属性之后,用户再次启动应用时,仍然可以看到上一次操作的界面。 这个属性对于一些应用非常有用,例如Browser应用程序,有很多状态,比如打开很多的tab,用户不想丢失这些状态,使用这个属性就极为恰当。
  3. android:clearTaskOnLaunch 这个属性用来标记是否从task清除除根Activity之外的所有的Activity,“true”表示清除,“false”表示不清除,默认为“false”。同样,这个属性也只对根Activity起作用,其他的Activity都会被忽略。 如果设置了这个属性为“true”,每次用户重新启动这个应用时,都只会看到根Activity,task中的其他Activity都会被清除出栈。如果我们的应用中引用到了其他应用的Activity,这些Activity设置了allowTaskReparenting属性为“true”,则它们会被重新宿主到有共同affinity的task中。
  4. android:finishOnTaskLaunch 这个属性和android:allowReparenting属性相似,不同之处在于allowReparenting属性是重新宿主到有共同affinity的task中,而finishOnTaskLaunch属性是销毁实例。如果这个属性和android:allowReparenting都设定为“true”,则这个属性好些。

任务Task(hencoder)

任务的概念

任务其实就是activity 的栈它由一个或多个Activity组成的共同完成一个完整的用户体验, 换句话说任务就是 “应用程序” (可以是一个也可以是多个,比如假设你想让用户看到某个地方的街道地图。而已经存在一个具有此功能的activity 了,那么你的activity 所需要做的工作就是把请求信息放到一个Intent 对象里面,并把它传递给startActivity()。于是地图浏览器就会显示那个地图。而当用户按下BACK 键的时候,你的activity 又会再一次的显示在屏幕上,此时任务是由2个应用程序中的相关activity组成的)栈底的是启动整个任务的Activity,栈顶的是当前运行的用户可以交互的Activity,当一个activity 启动另外一个的时候,新的activity 就被压入栈,并成为当前运行的activity。而前一个activity 仍保持在栈之中。当用户按下BACK 键的时候,当前activity 出栈,而前一个恢复为当前运行的activity。栈中保存的其实是对象,栈中的Activity 永远不会重排,只会压入或弹出,所以如果发生了诸如需要多个地图浏览器的情况,就会使得一个任务中出现多个同一Activity 子类的实例同时存在。

任务中的所有activity 是作为一个整体进行移动的。整个的任务(即activity 栈)可以移到前台,或退至后台。举个例子说,比如当前任务在栈中存有四个activity──三个在当前activity 之下。当用户按下HOME 键的时候,回到了应用程序加载器,然后选择了一个新的应用程序(也就是一个新任务)。则当前任务遁入后台,而新任务的根activity 显示出来。然后,过了一小会儿,用户再次回到了应用程序加载器而又选择了前一个应用程序(上一个任务)。于是那个任务,带着它栈中所有的四个activity,再一次的到了前台。当用户按下BACK 键的时候,屏幕不会显示出用户刚才离开的activity(上一个任务的根activity)。取而代之,当前任务的栈中最上面的activity 被弹出,而同一任务中的上一个activity 显示了出来。

Activity栈:先进后出规则
安卓/Activity栈

Task工作模型、LaunchMode

观看扔物线《Android面试黑洞–当我按下Home键再切回来,会发生什么?》
视频
文章

Task和回退栈

  • 按方块键查看最近任务,看到的就是一个个Task、任务
  • 在桌面点击App图标时,配置了MAIN+LAUNCHERintent-filterActivity会被启动并放进刚创建的一个Task
  • 每个Task都有一个回退栈,它会按顺序记录用户打开的每个Activity,按回退键时会依次关闭Activity,当最后一个Activity被关闭则此Task声明也就结束了。但不会在最近任务列表里消失,仍会保留一个残影,方便下次切回去(要走App的重新创建流程)。–》“在最近任务里看见的Task未必还活着”
    1
    2
    3
    Standard、SingleTop:针对App
    SingleInstance:用于多App
    SingleTask:兼顾多AppApp

Standard

App2的BActivity是标准模式。

  • App2打开A、B;App1启动App2的BActivity,则App1的回退栈中有M、B(复制过来的一个新实例)。App2的回退栈中有A、B。即,标准模式下的Activity会复制多份,分别放到不同的Task中

allowTaskReparenting

App2的BActivity是Standard,且加属性allowTaskReparenting

  • App1启动App2的BActivity,会在创建BActivity并把它挪到App1的Task栈顶。按下Home键,再切换到App2,此时BActivity会回到App2的Task栈顶。再切回App1看已经看不到BActivity了。
  • 在Android9、10失效;在Android11上已修复。

SingleTask(唯一性:全局只有一个对象)

App2的BActivity是SingleTask。

  • App1启动App2的BActivity,此时BActivity会在App1自己的Task栈顶【此时会有切换App的动画】,而且App2的Task栈会叠加到App1的Task栈上面【Task叠加适用于前台Task】
  • 当App1变成后台时(按方块键查看最近任务、按Home键),叠加的Task会拆开。
    注意:前台Task在最近任务列表显示出来的时候就已经进入后台,而不是在切换到其他应用之后
  • App1启动App2的BActivity,若BActivity已经存在【复用】【调用onNewIntent】,则会把App2的Task栈叠加到App1的Task栈上,而且App2会移除BActivity上面的Activity,让BActivity处于栈顶

SingleInstance(唯一性,独占性)

App2的BActivitySingleInstance

  • 具有singleTask所有特性,而且BActivity独占一个Task
  • App1启动App2的BActivity,此时BActivity会独占一个Task,也会叠加到App1的Task上
  • 当App1变成后台时(按方块键查看最近任务、按Home键),叠加的Task会拆开,当在显示BActivity界面按回退键,再去看最近任务列表这个BActivity在最近任务里不可见了。但是它并没有被杀死,如果再次调起BActivity则走onNewIntent方法。–》”在最近任务里看不见的Task,也未必就死了“
  • singleInstance模式的BActivity被藏起来是因为taskAffinity冲突了。

taskAffinity

  • 在Android中,一个App默认只能有一个Task显示在最近任务列表里【由taskAffinity来甄别】。
  • 每个Activity都有个taskAffinity,它的值取其所在ApplicationtaskAffinity【默认取App的包名】
  • 每个Task都有一个taskAffinity,取值自栈底ActivitytaskAffinity。即,从打开的Activity界面调起个新的Activity【不管新Activity来自哪】,新Activity的taskAffinity会被忽略。但若新ActivitysingleTask,则会和当前Task的taskAffinity比较
    • 若相同,依然正常入栈
    • 若不同,Activity会寻找和它的taskAffinity值相同的Task,然后整个Task入栈。
      • 或者如果找不到,系统会为它创建一个新的Task
  • 最近任务列表中出现的Task(任务)的taskAffinity值是不一样的。相同taskAffinity的Task可以被创建多个,但只会在最近任务列表里显示一个。即上面的singleInstance在最近任务列表里不可见是因为被相同taskAffinity替代了。

SingleTop

standard比较相近,直接在当前Task(任务)上入栈。唯一区别:若要启动的singleTopActivity已经打开,则调用其onNewIntent
A:AActivity(Standard),B:BActivity(SingleTop)

  1. A–>B–>B–>A–>B–>A–>A:栈中有ABABAA

Activity的setContentView过程

Android/setContentView过程

代码:

重启Activity及状态保存

  • 保存状态、重启、取出原状态,主题切换
    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
    public class MainActivity extends FragmentActivity implements OnClickListener {
    private Button btn;
    private int mTheme;
    private String THEME = "theme";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //恢复销毁前状态
    if (savedInstanceState != null) {
    mTheme = savedInstanceState.getInt(THEME);
    switchTheme(mTheme);
    }

    setContentView(R.layout.activity_main);
    btn = (Button) findViewById(R.id.btn);
    btn.setOnClickListener(this);
    }

    @Override
    protected void onSaveInstanceState(Bundle savedInstanceState) {
    super.onSaveInstanceState(savedInstanceState);
    Log.e(MainActivity.class.getName(), "onSaveInstanceState");
    //保存销毁前状态
    savedInstanceState.putInt(THEME, mTheme);
    }

    private void switchTheme(int theme) {
    switch (mTheme) {
    case android.R.style.Theme_Holo_Light:
    mTheme = android.R.style.Theme_Black_NoTitleBar;
    break;
    case android.R.style.Theme_Black_NoTitleBar:
    mTheme = android.R.style.Theme_Holo_Light;
    break;
    default:
    mTheme = android.R.style.Theme_Holo_Light;
    break;
    }
    setTheme(mTheme);
    }

    @SuppressLint("NewApi")
    @Override
    public void onClick(View v) {
    //切换主题
    recreate();
    }
    }

注意:
recreate()方法是在Android3.0引入的,所以如果在3.0之前使用会出现错误

  • 调用
    1
    2
    3
    Intent intent = getIntent();
    finish();
    startActivity(intent);

集中管理Activity

有时候在设计软件的时候布局复杂的话不利于查看跟更改,这时候我们可以在新建几个Activity,然后用ActivityGroup来管理这些Activity (把Activity当成一个View来显示)

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MyMain extends ActivityGroup {
/** Called when the activity is first created. */

LinearLayout layout;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Intent intent = new Intent(this,Activity1.class);
layout=(LinearLayout)this.findViewById(R.id.linearLayout1);
//ActivityGroup管理Activity,Activity转为View
Window subActivity = this.getLocalActivityManager().startActivity("Activity",intent);
View view = subActivity.getDecorView();
layout.addView(view);
}
}

禁用返回键

1
2
3
4
5
6
7
8
9
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK){
//禁用返回键
return true;
} else {
return super.dispatchKeyEvent(event);
}
}

相比于onBackPressed和onKeyDown方法有时候没有效果,这个方法能保证禁用手机的返回键。