Android--精通Activity

美女图集01
初识Activity:

我们都知道android中有四大组件(Activity活动,Service服务,Content Provider内容提供者,BroadcastReceiver广播接收者),Activity是我们用的最多的,也是最为基本的组件,因为应用的所有操作都与用户相关,Activity提供窗口来和用户进行交互。
官方文档这么说:

An activity is a single, focused thing that the user can do. Almost all activities interact with the user, so the Activity class takes care of creating a window for you in which you can place your UI with setContentView(View).
大概意思:activity是一个独立的,能够单独处理用户操作的组件,几乎所有的activity都是用来和用户做交互的,所以activity类会创建一个窗口,开发者可以通过setContent(View)的接口方法把UI放到activity组件上。

Android中activity全都归属于task管理。task:是一个activity的集合,这些activity按照启动的顺序排队存入一个栈(即"back stack")。android默认会为每一个App维持一个task来存放该app的所有的activity,task的默认name为该app的packagename。

Activity的内部调用过程:

上面已经讲过了,系统通过堆栈来管理activity,当一个新的activity开始时,它被放置在堆栈的顶部和成为运行活动,以前的activity始终保持低于它在堆栈的位置(也就是说当前活动的activity位于栈顶,以前使用过的activity位于栈低)而不会再一次到达前台,直到新的活动退出。

activity生命周期

下面分析Activity的生命周期过程:

  • ** 启动Activity:** onCreate() -> onstart() -> onResume(),Activity进入运行状态。
  • Activity退居后台:当前Activity转到新的Activity界面或者按手机Home键回到主屏:onPause() -> onStop(),进入停滞状态。
  • Activity从后台返回前台:onRestart() -> onStart() -> onResume(),再次回到运行状态。
  • Activity退居后台,且系统内存不足时,系统会杀掉这个后台转态的Activity(此时这个Activity引用仍然处在任务栈中,只是这个时候引用指向的对象已经为null),若再次回到这个Activity,则会走onCreate() -> onStart() -> onResume()(将重新走一次Activity的初始化生命周期)。
  • 锁屏:onPause() -> onStop()
  • 解锁:onStart() -> onResume()
  • Activity的完整生存期会在onCreate()调用和onDestroy()调用之间发生。
  • Activity的可见生存期会在onStart()调用和onStop()调用之间发生。系统会在activity的整个生存期内多次调用onStart()和onStop(),因为activity可能会在显示和隐藏之间不断的来回切换。
  • Activity的前后台切换会在onResume()调用和onPause()调用之间发生。因为这个状态可能会经常发生转换,为了避免切换迟缓引起的用户等待,这两个方法中的代码应该相当地轻量化。
启动模式是什么?为什么要定义启动模式呢?

简单的说启动模式就是定义activity实例与task的关联方式。
定义启动模式是为了实现一些默认启动(standard)模式之外的需求:
1.让某个activity启动一个崭新的task(而不是被放入到当前的task中)
2.让activity启动时只是调出已有的某个实例(而不是在back stack栈顶创建一个崭新的实例)
3.或者,你想在用户离开task时,只保留根activity,而back stack中的其他activity都要清空。

以上的这些需求,就是启动模式,所起到的作用。

怎样定义启动模式?
定义启动模式的方法有两种:
1.在manifest 文件中,通过Acitivty的xml标签来改变任务栈的默认行为(启动模式):

  • 使用android:launchMode="standard|singleInstance|singleTask|singleTop"来控制Activity任务栈。在 manifest 文件中activity声明时,利用 activity 元素的 launchMode 属性来设定 activity 与 task 的关系。
    ** 任务栈 是一种后进先出的结构。位于栈顶的Activity处于焦点状态,当按下back按钮的时候,栈内的Activity会一个一个的出栈,并且调用其onDestory()方法。如果栈内没有Activity,那么系统就会回收这个栈,每个App默认只有一个栈,以App的包名来命名。
    ** <1>.standard :
    标准模式,每次启动Activity都会创建一个新的Activity实例,并且将其压入任务栈栈顶,而不管这个Activity是否已经存在。Activity的启动三回调(onCreate() -> onStart() -> onResume()都会执行)。
    ** <2>.singleTop :** 栈顶复用模式,这种模式下,如果新Activity已经位于任务栈的栈顶,那么此Activity不会被重新创建,所以它的启动三回调就不会执行,同时Activity的onNewIntent()方法会被回调,如果Activity已经存在但是不在栈顶,那么作用与standard模式一样。
    ** <3>.singleTask:** 栈内复用模式,创建这样的Activity的时候,系统会先确认它所需任务栈已经创建,否则先创建任务栈,然后放入Activity,如果栈中已经有一个Activity实例,那么这个Activity就会被调到栈顶,onNewIntent(),并且singleTask会清理在当前Activity尚明的所有Activity。(clear top)
    ** <4>.singleInstance :**加强版的singleTask模式,这种模式的Activity只能单独位于一个任务栈内,由于栈内复用的特性,后续请求均不会创建新的Activity,除非这个独特的任务栈被系统销毁了。

Activity的堆栈管理以ActivityRecord为单位,所有的ActivityRecord都放在一个List里面,可以认为一个ActivityRecord就是一个Activity栈。

2.使用 Intent 标志
在要启动activity时,你可以在传给startActivity()的intent中包含相应标志,以修改activity与task的默认关系。
代码如下:

Intent i = new Intent(this,NewActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(i);```
还可以通过标志修改默认模式,如下所示:
* **FLAG_ACTIVITY_NEW_TASK**
与“singleTask”模式相同,在新的task中启动activity。如果要启动的activity已经运行于某个task中,则那个task将调入前台。
* **FLAG_ACTIVITY_SINGLE_TOP**
与“singleTop”模式相同,如果要启动的activity位于back stack栈顶,系统不会重新创建目标Activity实例,而是直接复用Task栈顶的Activity。
* **FLAG_ACTIVITY_CLEAR_TOP**
此种模式在launchMode中没有对应的属性值。如果要启动的activity已经在当前task中运行,则不再启动一个崭新的实例,且所有在其上面的activity将被销毁。
**关于启动模式的一些建议:**
一般不要改变activity和task默认的工作方式。如果你确定有必要修改默认方式时,请保持谨慎,并确保activity在启动和从其他activity返回时的可用性,多做测试和安全方面的工作。

#####Activity缓存方法:
**情景再现:**有a,b两个Activity,当从a进入b之后一段时间,可能系统会把a回收,这时候按back键,执行的不是a的onRestart(),而是onCreate()方法,a被重新创建一次,这时a种的临时数据和状态可能丢失了。
**解决办法:**可以用Activity的 onSaveInstanceState()回调方法保存临时的数据和状态,这个方法一定会在活动被回收之前调用。方法中有一个Bundle参数,putString(),putInt()等方法需要传入两个参数,一个键一个值。数据保存之后会在onCreate()中恢复,onCreate也有一个Bundle类型的参数。
**实例代码:**
```@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //这里,当Acivity第一次被创建的时候为空
        //所以我们需要判断一下
        if( savedInstanceState != null ){
            savedInstanceState.getString("anAnt");
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString("anAnt","Android");
    }```
* **onSaveInstanceState(Bundle outState)**
当某个activity变得“容易”被系统销毁时,该activity的onSaveInstanceState就会被执行,除非该activity是被用户主动销毁的,例如当用户按Back键的时候。
注意上面的双引号,何为“容易”?言下之意就是该activity还没有被销毁,而仅仅是一种可能性。这种可能性有哪些呢?下面,我们通过重新一个activity的所有的生命周期的onXxx()方法,包括onSaveInstanceState()和onRestoreInstanceSatae()方法,我们可以清楚地知道,当某个activity(假定为activity A)显示在当前task的最上层时,其onSaveInstanceState()方法会在什么时候被执行,有这么几种情况:
**1.当用户按下Home键时。**
这中情况是用户显而易见的操作,系统不知道你按下Home键后要运行多少其他的程序,自然也不知道activity A是否会被销毁,故系统会调用onSaveInstanceState(),让用户有机会保存某些非永久性的数据。以下的几种情况得分析都遵循该原则。
**2.长按Home键,选择运行其他的程序时。**
**3.按下电源键(关闭屏幕显示)时。**
**4.从activity A中启动一个新的activity时。**
**5.屏幕方向切换时,例如从竖屏切换到横屏时。**(如果不指定configchange属性)在屏幕切换之前,系统会销毁activity A,在屏幕切换之后系统又会自动地创建activity A,所以onSaveInstanceState()一定会被执行。
**总而言之,onSaveInstanceState()的调用遵循一个重要原则**,即当系统“未经过许可”时销毁了你的activity,则onSaveInstanceState()会被系统调用,这是系统的责任。因为它必须要提供一个机会让你保存你的数据(当然你不保存那就随你了)。
**另外,需要注意一下几点:**
**<注意点 1>:**布局中的每一个View默认实现了onSaveInstanceState()方法,这样的话,这个UI的任何改变都会自动地存储和在activity重新创建的时候自动地恢复。但是这种情况只有在你的这个UI提供唯一的ID之后才起作用,如果没有提供ID,app将不会存储它的状态。
**<注意点 2>:**由于默认的onSaveInstanceState()方法的实现帮助UI存储它的状态,所以如果你需要覆盖这个方法去存储额外的转态信息,你应该在执行任何代码之前都调用父类的onSaveInstanceState()方法(super.onSaveInstanceState())。既然有现成的可用,那么我们到底还要不要自己实现onSaveInstanceState()?这就得看情况了,如果你自己的派生类中有变量影响到UI,或影响到你的程序的行为,当然就要把这个变量也保存了,那么就需要自己去实现了,否则就不需要了。
**<注意点 3>:**由于onSaveInstanceState()方法调用的不正确性,你应该子使用这个方法去记录activity的瞬间状态(UI状态)。不应该用这个方法去存储持久化数据。当用户离开这个activity的时候应该在onPause()方法中存储持久化数据(例如应该被存储到数据库中的数据)。
**<注意点 4>:**onSaveInsatnceState()如果被调用,这个方法会在onStop()前被触发,但是系统并不保证是否在onPause()之前或者之后触发。

* **onRestoreInstanceState(Bundle outState)**
至于onRestoreInstanceState()方法,需要注意的是,**onSaveInstanceState()方法和onRestoreInstanceState()方法“不一定”是成对被调用的。(通过代码实践得到的结论,并不一定会两个方法成对被调用)**
**onRestoreInstanceState()被调用的前提是**:activity A“确实”被系统销毁了,而如果仅仅是停留在有这种可能性的情况下,则该方法不会被调用,例如,当正在显示的activity A的时候,用户按下Home键回到主界面,然后用户紧接着又返回到activty A,这种情况下activity A一般不会因为内存的原因被系统销毁,故activity A的onRestoreInstanceState()方法不会被执行。
**另外,onRestoreInstanceState()方法的bundle参数也会传递到onCreate()方法中**,你也可以选择在onCreate()方法中做数据的还原。还有onRestoreInstanceState()在onStart()方法之后执行。至于这两个函数的使用,给出示范代码(注:要特别留意自定义代码在调用super的前或后):

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putBoolean("MyBoolean", true);
savedInstanceState.putDouble("myDouble", 1.9);
savedInstanceState.putInt("MyInt", 1);
savedInstanceState.putString("MyString", "Welcome back to Android");
// etc.在super() 前 些自己的自定义的代码
super.onSaveInstanceState(savedInstanceState);
}

@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
// etc.在super() 后 些自己的自定义的代码
boolean myBoolean = savedInstanceState.getBoolean("MyBoolean");
double myDouble = savedInstanceState.getDouble("myDouble");
int myInt = savedInstanceState.getInt("MyInt");
String myString = savedInstanceState.getString("MyString");
}


#####Fragment的生命周期和Activity之间的关系:
先上一张Fragment和Activity生命周期综合图:
![Fragment和Activity生命周期综合图.jpg](http://upload-images.jianshu.io/upload_images/3416944-baf47cfa0f0a4132.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

* **为什么程序要在Service中创建子线程,而不是Activity中创建呢?**
这是因为Activty很难对Thread进行控制,当Activity被销毁之后,就没有任何其他的办法可以再重新获取到之前创建的子线程的实例。而且在一个Activity中创建的子线程,在另一个Activity中是无法对其进行操作的。但是Service就不同了,所有的Activity都可以与Service进行关联,然后可以很方便地操作其中的方法,即使Activity被销毁了,之后只要重新与Service建立关联,就又能够获取到原有的Service中的Binder的实例了。因此,使用Service来处理后台任务,Activity就可以放心的finish(),完全不需要担心无法对后台任务进行控制的情况了。
* **Intent的使用方法,可以传递哪些数据类型。**
通过查询Intent/Bundle的API文档,我们可以获知,Intent/Bundle支持传递基本类型的数据和基本类型的数组数据,以及String/CharSequence类型的数据和String/CharSequence类型的数组数据。而对于其它类型的数据貌似无能为力,其实不然,我们可以在Intent/Bundle的API中看到Intent/Bundle还可以传递Parceable(包裹化,邮包)和Serializable(序列化)类型的数据,以及它们的数据/列表数据。
所以要让非基本类型的和非String/CharSequence类型的数据通过Intent/Bundle来进行传输,我们需要在数据类型中实现Parceable接口或是实现Serializable接口。
[http://blog.csdn.net/kkk0526/article/details/7214247](http://blog.csdn.net/kkk0526/article/details/7214247)

#####解析Intent Filter:
android的3个核心组件---Activity,Services,Braodcast Receiver---是通过Intent传递消息的。Intent消息用于在运行时绑定不同的组件。在Android的AndroidMainfest.xml配置文件中可以通过intent-filter节点为一个Activity指定其Intent Filter,以便于告诉该Activity可以响应什么类型的Intent。
* intent-filter的三大属性:
**Action :**一个Intent Filter可以包含多个Action,Action列表用于标志Activity所能接受的“动作”,它是一个用户自定义的字符串。

<intent-filter >
<action android:name="android.intent.action.MAIN" />
<action android:name="com.wang.app" />
……
</intent-filter>```
也可以在代码中使用以下语句便可以启动该Intent对象:

Intent i=new Intent(); 
i.setAction("com.wang.app");```
Action列表中包含了"com.wang.app"的Activity都将会匹配成功。
**URL :**在inter-filter节点中,通过data节点匹配外部数据,也就是通过URL携带外部数据给目标组件。

<data android:mimeType="mimeType"
android:scheme="scheme"
android:host="host"
android:port="port"
android:path="path"/>```
注意:只有data的所有的属性都匹配成功时,URL数据匹配才会成功。
Category :为组件定义一个类别列表,当Intent中包含这个类别列表的所有项目时,才会匹配成功。

<intent-filter . . . >
      <action android:name="code.android.intent.action.MAIN" />
      <category android:name="code.android.intent.category.LAUNCHER" />
</intent-filter>```
**Activity中Intent Filter的匹配过程:**
(1)加载所有的Intent Filter列表
(2)去掉action匹配失败的Intent Filter
(3)去掉url匹配失败的Intent Filter
(4)去掉Category匹配失败的Intent Filter
(5)判断剩下的Intent Filter数目是否为0,如果为0查找失败返回异常;如果大于0,就按优先级排序,返回最高优先级的Intent Filter。

#####开发中Activity的一些问题:
一般设置Activity为非公开的

<activity
......
android:exported="false" /> ```
注意:非公开的Activity不能设置intent-filter,以免被其他的activity唤醒(如果拥有相同的intent-filter)。

  • 不要指定activity的taskAffinity属性。
  • 不要设置activity的LaunchMode(保持默认),注意Activity的intent最好也不要设定为FLAG_ACTIVITY_NEW_TASK
  • 在匿名内部类中使用this时,加上activity类名(类名.this,不一定是当前的activity)
  • 设置activity全屏:在其onCreate()方法中加入代码:
// 设置全屏模式
 getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 
                       WindowManager.LayoutParams.FLAG_FULLSCREEN); 
 // 去除标题栏
 requestWindowFeature(Window.FEATURE_NO_TITLE);```

**结束语:我害怕,自己在最该努力的时光中,却选择了安逸。   ---王令
QQ:2585085940
邮箱:wang91ling@163.com
欢迎大家光临寒舍。**
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容

  • Activity 一、四种形态 运行状态: 当 Activity 处于栈的顶层,可见,并可与用户进行交互 onRe...
    任教主来也阅读 1,622评论 1 10
  • 转自 1. 什么是Activity? 四大组件之一,一般的,一个用户交互界面对应一个activity setCon...
    joe1632阅读 1,387评论 0 7
  • 旧文 我们,总归要与自己和解,与这个世界,化干戈为玉帛。 今天,你结婚了,我盯着你发出来的结婚证看了很久很久,一遍...
    伶人自悲卿自喜阅读 1,177评论 10 15
  • 愿望清单:1 换一份工作,别想现在这样重复简单的劳动2 有自己的书桌自己的房间自己的书柜√3 换手机,现在的掉电太...
    为了明日_will阅读 193评论 0 0
  • 小时候,父母是我们的保护神,小小的自己仿佛一刻也离不开爸妈。慢慢的,我们有了朋友,和朋友在一起总是玩到不想回家。再...
    胡椒椒的小喵喵呀阅读 409评论 0 0