0%

Activity

启动流程

使用 Context 启动活动的方法如下

1
void startActivity(Intent intent);

其实际流程如图俄罗斯瓷娃娃一样,分别经过ContextImpl,Instrumentation和AMS的处理。

1。首先是 ContextImpl 类,它委托给了Instrumentation 类,自己仅仅检查线程和 flag 参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public void startActivity(Intent intent, Bundle options) {
warnIfCallingFromSystemProcess();
if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
throw new AndroidRuntimeException(
"Calling startActivity() from outside of an Activity "
+ " context requires the FLAG_ACTIVITY_NEW_TASK flag."
+ " Is this really what you want?");
}
mMainThread.getInstrumentation().execStartActivity(
getOuterContext(), mMainThread.getApplicationThread(), null,
(Activity) null, intent, -1, options);
}

这里应该注意如果不是从 Activity 中启动活动,则需要添加 FLAG_ACTIVITY_NEW_TASK 标记,否则将抛出异常。

活动启动的结果用 Instrumentation.ActivityResult 类表示,其定义如下

1
2
private final int mResultCode;
private final Intent mResultData;

而后将对该结果进行检测处理,采用主线程 Handler 机制进行,发生消息为 H.SEND_RESULT ,响应方法为

1
void handleSendResult(ResultData res)

该方法中将分发处理结果( dispatchActivityResult ),并调用 onActivityResult(requestCode, resultCode, data) 方法。

2。其次是 Instrumentation 类,它委托给AMS来完成,自己只检查下结果,验证活动是否能够正确启动。

1
2
3
4
5
ActivityResult execStartActivity(...){
IApplicationThread whoThread = (IApplicationThread) contextThread;
int result = ActivityManagerNative.getDefault().startActivity(...);
checkStartActivityResult(result, intent);
}

如结果是 ActivityManager.START_CLASS_NOT_FOUND 会爆出常见异常

1
Unable to find explicit activity class ; have you declared this activity in your AndroidManifest.xml?"

3。最后是AMS ,启动活动会经过一系列复杂的流转,涉及到活动栈的处理等,但最终会回到 ActivityThread 类中来。

在 ActivityThread的 main 方法中,启动了主线程的 Looper 循环。启动活动将发送一个 LAUNCH_ACTIVITY 消息,而后使用内部类 H 来处理该消息。

1
2
ActivityClientRecord r = (ActivityClientRecord) msg.obj;
handleLaunchActivity(r, null);

handleLaunchActivity 方法将完成 WindowManager 初始化,并利用反射创建活动对象

1
2
3
4
5
6
7
8
9
10
void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {	
//1. WindowManager初始化
WindowManagerGlobal.initialize();
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
handleResumeActivity(r.token, false, r.isForward);
} else {
ActivityManagerNative.getDefault().finishActivity(r.tokene);
}
}

performLaunchActivity 方法创建活动,

1
2
3
4
5
6
7
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent){	
Activity activity = mInstrumentation.newActivity(...);
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
Context appContext = createBaseContextForActivity(r, activity);
Configuration config = new Configuration(mCompatConfiguration);
activity.attach(...);
}

其中 attach 方法创建了 Window 的实例。

1
2
3
4
5
6
void attach(Context context){
mWindow = new PhoneWindow(this);
mWindow.setCallback(this);
mWindow.setUiOptions(info.uiOptions);
mWindow.setWindowManager()
}

如果活动创建失败,则结束;如果成功,handleResumeActivity 方法会将 DecorView 对象渲染到窗口 PhoneWindow 上去,但注意此时该控件是不可见的。如果启动成功,在处理结果的时候后将可见性改为可见。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void handleResumeActivity(IBinder token, ...){	
ActivityClientRecord r = performResumeActivity(token, clearHide);
Activity a = r.activity;
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
wm.addView(decor, l);
}
}

因为 AMS 是单例实现,Hook 掉 AMS 非常容易,详情见weishu的文章

1
2
3
4
5
6
7
8
9
10
11
12
13
Class amsClass = Class.forName("android.app.ActivityManagerNative");
Method getDefaultMethod = amsClass.getDeclaredMethod("getDefault");
Object iActivityManager = getDefaultMethod.invoke(null);
Field gDefaultField = amsClass.getDeclaredField("gDefault");
gDefaultField.setAccessible(true);
Object gDefaultSingleton = gDefaultField.get(null);
Class<?> singletonClass = Class.forName("android.util.Singleton");
Field mInstanceField = singletonClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
Class<?> iActivityManagerInterface = Class.forName("android.app.IActivityManager");
Object mInstance = Proxy.newProxyInstance(gDefaultSingleton.getClass().getClassLoader(), new Class[]{
iActivityManagerInterface},new ActivityManagerHandler(iActivityManager));
mInstanceField.set(gDefaultSingleton, mInstance);

生命周期

在理解了启动流程后,更容易理解生命周期,正常的启动和退出流程是

启动(post 方法等为碎片而存在):

onCreate –> onStart –> onPostCreate –> onResume –> onPostResume –> onAttachedToWindow

退出:

onPause –> onStop –> onDestroy –> onDetachedFromWindow

如果中途切换其它 App

onPause –> onSaveInstanceState –> onStop

恢复页面

onRestart –> onStart –> onResume –> onPostResume

使用adb命令回收Activity(adb shell am force-stop [包名]),与正常退出一致。

如果使用下列方法旋转屏幕(可以靠重力感应完成,会销毁和重建活动)

1
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

则生命周期如下,这里调用保存和恢复方法,也可以在onCreate方法中恢复。

onPause –> onSaveInstanceState –> onStop –> onDestroy –> onDetachedFromWindow–>

onCreate –> onStart –> onRestoreInstanceState –> onResume –> onAttachedToWindow

这和使用 recreate()方法效果一样。

做如下配置可以避免重建,只执行 onConfig 方法

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

此外启动栈内已存在的活动将会调用 onNewIntent 方法。

保存和恢复

活动的保存和恢复是向下分发的,碎片和View都受到影响。如 View会构建一个BaseSavedState对象

1
2
3
4
5
6
7
8
9
protected Parcelable onSaveInstanceState() {    
mPrivateFlags |= PFLAG_SAVE_STATE_CALLED;
if (mStartActivityRequestWho != null) {
BaseSavedState state = new BaseSavedState(AbsSavedState.EMPTY_STATE);
state.mStartActivityRequestWhoSaved = mStartActivityRequestWho;
return state;
}
return BaseSavedState.EMPTY_STATE;
}

使用时获取并扩展该对象,写入数据即可。

1
2
3
public static class SavedState extends BaseSavedState {    
CharSequence text;
}

任务栈

Activity 的启动跳转关系与Task(任务)和 Back Stack(回退栈)紧密相连,Task 是一个Framework层概念,专指在程序运行时一组相互关联的activity的集合,负责控制界面的跳转和返回Back Stack则是实现 Task 所用的数据结构,拥有栈的特点.

下面三个属性会影响TaskBack Stack的状态。

  • 活动的启动模式
  • taskAffinity属性
  • intent的flag属性
1
2
3
android:launchMode="standard"
android:taskAffinity="con.incredible"
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

简单说来

  • 启动模式为standardsingleTop时,Activity 一般在同一个栈中。
  • 启动模式为singleTasksingleInstance时,一般会产生新的任务栈。

Task和Back Stack

默认情况下,某个Application中所有activity都处在同一个Task中。但二者并没有严格的约束条件,这里有两种情况:

  • 两个Application中的activity可以处在同一个Task中。即 task 是可以跨应用的,这正是task存在的一个重要原因
  • 一个Application中的activity也可以处于多个Task中。

例如在应用中的一个Activity A中使用系统邮件,会启动系统邮件程序的一个Activity B。这两个activity是存在于不同app中的,但是被系统放在一个task中,这样能保证程序回退到原有程序中。

设备Home界面是大多数tasks的起始点。当用户点击程序在Home上的启动图标时,这个程序的task就转入前台。如果程序没有打开,那么之前不存在task,一个新的task将被创建并且程序的”main” 活动将被打开并被推入栈中;如果程序已经打开,则已经存在Task,就恢复显示栈顶的活动。

当目前的activity启动另外一个activity时,新的activity被压入栈中作为栈顶并且获取到了focus。前面的那个activity则进入stopped状态,此时系统会保留它的UI状态以便恢复。当用户点击back按钮时,栈顶的activity从栈顶退出并被destroyed,之前处于stopped状态的activity则进入resume状态(其保存的UI状态得到恢复).

1

如果用户持续点击back按钮,那么在栈中的每一个activity都会做退栈并显示之前activity的动作, 直到用户退回到Home界面(或者是用户开始task的地方)。当所有的activities都从栈中被移除之后,这个task也就消失了。

一个task是一个紧密结合的单元,处于后台的task中的所有活动都处于stopped状态,只有task转移到前台,栈顶的活动显示。众多tasks都可以一并在后台被Hold住,然而系统为了恢复内存而有可能销毁这些栈中的activities。

此时activity的状态信息则会丢失. 但系统仍然为那个activity在back stack中保留了位置, 但是当这个activity成为栈顶activity时, 系统必须recreate它(而不是resume它),这时需要使用者主动实现onSaveInstanceState()回调方法来保存恢复所需的信息。

在activity中调用 moveTaskToBack (boolean nonRoot)方法即可将activity 退到后台,注意不是finish()退出。

启动模式

每个活动的创建有两步:以下对四种启动模式的分析都从这两步进行

1.判断是否创建新任务栈,即新Task
2.判断是否创建新活动,即新 Activity

活动如何创建主要受两个因素影响:taskAffinity属性启动模式

a.taskAffinity 属性意味着 activity 更倾向归属于哪一个task,可以认为它指定了activity所在的task名称,这一属性是活动处在不同的Task中的必要不充分条件。使用taskAffinity属性的一些原则是:

1.如果不设置某活动的taskAffinity属性,则该属性值与启动它的活动相同。第一个活动的该属性为应用的包名。
2.taskAffinity属性并不能唯一决定活动所在的栈,还要受到启动模式影响。

举例:假设某个APP内的活动启动顺序为:a-b-c-d。如果采用默认配置,则abcd四个活动都将处在名称为包名的 Task 中。

b.启动模式
设置活动和 Task 的关系。一共有四种启动模式:为standardsingleTopsingleTasksingleInstance。

Standard(默认模式)

1.taskAffinity 属性无效。即使设置也并不会创建新 Task ,活动所处的 task 与启动它的活动永远保持一致。

2.活动的创建百无禁忌,不需要任何检查就创建新的活动实例,因为能够重复创建活动实例

SingleTop

  1. taskAffinity 属性同样无效。

2.与标准模式的区别在于第二步, SingleTop 模式对活动的创建做了一个较弱的约束,即不允许创建与栈顶重复的活动实例。会检查活动返回栈的栈顶活动是否是待启动的活动类,如果是则不会创建活动实例。

如当前活动栈是a-b-c-d。如果再启动d,仍是a-b-c-d;启动a,将是a-b-c-d-a

SingleTask

前两种模式中, taskAffinity 属性都是无效的,不会对新活动所处的 task 产生影响。 singleTask 模式下, taskAffinity 属性终于获得用武的机会,将对task产生影响。

1.在新活动c创建时,先查看是否存在与其taskAffinity属性相同的task

  • 如果存在,不会创建新task
  • 如果不存在,则创建以taskAffinity属性为名的新task

如果不设置taskAffinity属性,则该属性值与启动它的活动一致,此时不会产生新的task

2.再在选中的task中查找有无活动c实例。

  • 有则将该task中c实例之上的活动全部出栈,使得该实例处在栈顶;
  • 没有则在栈顶上新建活动c.

SingleInstance

SingleInstance 模式中,taskAffinity 属性再次失效,活动总会在新 Task 中启动。

1.不管怎么设置taskAffinity属性,活动总是会在新的任务task中运行。

2.以 SingleInstance 模式启动的活动在整个系统中是单例的。如果单例 task 中存在了一个实例,那么会把已存在的任务调度到前台,且会调用该Activity的onNewIntent方法

以 SingleInstance 模式启动的 Activity 具有独占性,即它会独自占用一个task被他开启的任何 Activity 都会运行在其他task中, 这一点与 singletask 模式有所区别。

使用技巧:如何退出APP?

性能分析工具 dumpsys可以查看感兴任务栈信息

1
2
3
//1. 列出dumpsys所有支持命令
adb shell dumpsys | grep "DUMP OF SERVICE"
adb shell dumpsys activity //检测Activity任务栈

命令得到的信息很多,可以获取

1
2
3
4
5
6
7
8
ACTIVITY MANAGER RECENT TASKS (dumpsys activity recents)  
Recent tasks:
* Recent #0: TaskRecord{33618f06 #10871 A=com.lxt.toast U=0 sz=2} *
Recent #1: TaskRecord{38898108 #10872 A=com.qihoo360.mobilesafe U=0 sz=0}ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)Display #0 (activities from top to bottom):
Stack #1: Task id #10871 TaskRecord{33618f06 #10871 A=com.lxt.toast U=0 sz=2} Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.lxt.toast/.MainActivity }
Hist #1: ActivityRecord{30b1cfcf u0 com.lxt.toast/.Main2Activity t10871}
Hist #0: ActivityRecord{224a2c28 u0 com.lxt.toast/.MainActivity t10871}
Task id #10868 TaskRecord{32f0d2c6 #10868 A=com.android.settings U=0 sz=1} mFocusedActivity: ActivityRecord{30b1cfcf u0 com.lxt.toast/.Main2Activity t10871} mFocusedStack=ActivityStack{16b5b2d9 stackId=1, 51 tasks} mLastFocusedStack=ActivityStack{16b5b2d9 stackId=1, 51 tasks} mCurTaskId=10872

singleTask 这个模式有一个重要应用,即所谓的“优雅的退出APP”。将第一个活动设置为singleTask 模式,则在其他活动中向首活动跳转将会将该栈内部的所有活动出栈。

如果某APP中启动活动顺序是A–>B—>C—>D,要从D中退出整个APP。原理是将活动A设置为singleTask 模式,并在该活动中跳转到启动活动A,则A之上的活动将全部出栈,最终结束A即可。

这么做的思路和原理是正确的,但是必须保证ABCD都处在同一个任务栈下才有效。A只能将和它相同的栈内的活动出栈,而不能将其他任务栈中的活动出栈

更简单的操作是直接使用finishAffinity()方法,注意该方法会销毁栈

Intent

Intent 译作“意图”,表示一个对某类数据待执行的操作,可以用来启动安卓的三大组件活动,服务和广播。因为 Intent 将用在跨进程通信中,所以它是一个 Parcelable 类。

Intent 的基本信息包括 action 和 data。 Action 表示动作类型,Data 指定动作操作的数据 Uri。 例如 ACTION_VIEW 是默认动作,表示展示数据给用户,而展示何种数据由类型决定,展示的内容由 Uri 指定,下例表示展示文本数据。

1
2
3
Intent intent = new Intent(ACTION_VIEW);
intent.setType("text/plain");
startActivity(intent);

发出此 Intent 后,设备内部能够匹配此意图的应用会出现在列表中,即数据展示方式由设备已有的程序决定。隐式 Intent 的解析机制依赖 IntentFilter ,安卓组件通过设置 IntentFilter 确定自己所能匹配的 Intent 。

Intent 的补充信息包括Categories(对动作信息进行补充),Type(指定数据的MIME类型),Extras(提供额外信息),

当发出 Intent 后会对 PackageManager 做查询,在 AndroidManifest.xml 配置文件上寻找能够完成匹配的安卓组件。组件中 IntentFilter 所定义的信息要全部一致才算匹配成功,如果定义了多个 category ,至少要匹配其中一个。

安卓组件也可以定义多个 intent-filter ,此时只要有一个匹配成功就能启动组件。

如果设备内没有能够响应 Intent 的程序,则会抛出异常,因此为安全起见可用如下方法预先判断下

1
List<ResolveInfo> resolveInfos = getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);

PackageManager.MATCH_DEFAULT_ONLY表示只匹配设置了CATEGORY_DEFAULT的活动。

标记位 Flags 控制动作的执行方式,它与具体的组件有关。最常见的标记位多是与活动组件由关的,以FLAG_ACTIVITY开头。

1.FLAG_ACTIVITY_SINGLE_TOP表示SINGLE_TOP启动方式

2.FLAG_ACTIVITY_NO_ANIMATION防止活动启动时使用转场动画。

3.FLAG_ACTIVITY_TASK_ON_HOME 活动将在主屏幕所在栈上新栈,此时点击后退,将回退到主屏幕上。

4.FLAG_ACTIVITY_CLEAR_TASK 此前的活动栈将销毁,所启动的活动将成为新栈的根,需要配合FLAG_ACTIVITY_NEW_TASK使用。

1
2
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);

PendingIntent

PendingIntent表示一个延时意图,亦是Parcelable对象,因为是跨进程实现,即便启动进程销毁,IIntentSender对象依然存在在另一个进程中。这造成一个问题,使用该对象时如果进程已经有一个PendingIntent对象存在,应该如何处理?

1.不管怎样首先要判断两个PendingIntent是否相等?

一个错误是创建多个仅仅”extra”不同的PendingIntent对象,这些实际上相同的。使用下列方法可以进行相等性判断

1
Intent#filterEquals(Intent)

2.发送时如果要创建不同的PendingIntent对象,可以更改getActivity方法中的请求码。

3.发送时如果要创建相同的PendingIntent对象,可以使用FLAG_UPDATE_CURRENT /FLAG_CANCEL_CURRENT标记位。

  • FLAG_ONE_SHOT PendingIntent只使用一次
  • FLAG_NO_CREATE PendingIntent如果不存在,不再创建
  • FLAG_CANCEL_CURRENT 如果PendingIntent存在,取消创建新的 ,如果发送的intent仅仅在extra 数据不同,可以使用该标记位创建新的
  • FLAG_UPDATE_CURRENT 如果PendingIntent存在,替代它的extra data

几个常用的API

关于栈

1
2
3
public int getTaskId()
public boolean isTaskRoot()
public boolean moveTaskToBack(boolean nonRoot) //隐藏栈,实际是是回到桌面

创建和关闭

1
2
3
public void recreate()
public void finish()
public void finishAffinity()//关闭同一个栈内的活动public void finishAfterTransition() //等待动画完成