这part主要探讨的是Android的四大组件。
1.Activity
标准生命周期
说到Activity,想必第一时间想到生命周期这玩意。
一般来说,Activity启动经过以下几个步骤:
onCreate()
----> onStart()
----> onResume()
----> Activity is running.
Activity正常结束掉会经过以下几个步骤:
onPause()
----> onStop()
----> onDestroy()
----> Activity is sht down.
也就是说,假如正常标准的走完整个流程,那么就是以下这种情况:
onCreate()
---->onStart()
---->onResume()
---->onPause()
---->onStop()
---->onDestroy()
那么这几个方法调用的实际时机又是怎么样呢?
-
onCreate()
":Activity创建的时候,以及当Activity被onPause()
或者onStop()
的时候,假如memory不足,被kill掉(kill掉的是那个在后台的Activity),用户重新回到这个Activity,那么就会再次调用onCreate()
。
-
onStart()
:在onCreate()
之后被调用,以及当被onStop()
之后,再次回到这个Activity时,会先调用生命周期之外的onRestart()
,随后被调用。onStart()
可以理解为是当前的Activity “被看见了!”
-
onResume()
:情况较多。- 在
onStart()
之后,这里有两种情况,一种是从onCreate()
之后的onStart()
,一种是onRestart()
之后的onStart()
。 - Activity被覆盖,然后回到前台。(包括Dialog风格的Activity)
- 解锁屏幕的时候。
- 总的来说,就是假如Activity回到前台的时候,
onResume()
是一定会被调用的。
- 在
-
onPause()
:实际上是跟onResume()
成对来活动的。- Activity被覆盖的时候。
- Activity退居后台的时候。(退居后台的操作比如有跳转到新的Activity,按Home键回到桌面等)
- 锁屏的时候。
-
onPause()
实际代表的就是当前的Activity "暂停了" ,也就意味着Activity被短暂的遮挡住了。而且需要注意的是,只有当上一个Activity的onPause()
执行完毕之后,下一个新的Activity的onResume()
才会被调用,也就是上一个Activity的onPause()
是影响到下一个Activity的刷新时间的!
-
onStop()
:有三种情况。- 第一种就是当当前的Activity跳转到新Activity的时候。
- Home键等操作让Activity退居到后台的时候。
- 在
onPause()
之后。 - 其实
onStop()
代表的是当前Activity "看不着了!" 注意一下 “被覆盖” 是不会调用这个方法的。
-
onDestroy()
:有以下几种情况。- finish掉的时候。
- 正常生命周期的
onStop()
之后。 - 出现意外情况当前的Activity被Kill掉的时候。(内存不足啊,横竖屏啊之类的)。
-
onRestart()
:只会在被onStop()
之后才会被调用,上面也说过了,onStop()
之后代表的是这个Activity “看不见了!” ,也就是说从后台被推到前台的时候才会被调用。值得注意的是假如这个Activity被 “Kill掉了!” 然后再回到前台的时候,这个方法是不会被调用的, 只有被Stop而没被Kill掉的情况下才会走到这个方法! 。
看完上面这段,现在看这个图应该很清楚了:
特殊情况
一旦Activity因为某种原因被kill掉,而这时候用户又需要到这个Activity,再次被推回前台的时候,这个时候会产生特殊情况。
一般来说,引起特殊情况的原因有以下两种:
- 系统配置发生了变化,比如横竖屏的切换。
- 退居到后台的Activity因内存不足而被kill掉。
这两种都会走onStop()
,onDestroy()
,等于完全被销毁掉了。
然后这个时候,假如这个Activity又再次被推到前台使用的话,就会从onCreate()
开始重新走一遍生命流程。
这涉及到两个特殊的方法:
-
onSaveInstanceState
onRestoreInstanceState
被销毁时,会调用onSaveInstanceState
,目的是希望能够用Bundle
来进行当前页面状态的储存。
销毁之后,再次恢复的话,就会走onCreate
然后就是onRestoreInstanceState
用来恢复其状态。
像EditextView这种,官方已经实现了上面两个方法,也就是说假如遇到了这种意外被销毁的情况,将会被自动恢复。如果你是自定义的View,也想对被销毁的视图进行恢复的话,也需要实现这两个方法。
关于调用时机的情况,onSaveInstanceState
的源码是如此描述的:
this method will occur before {@link #onStop} and there are no guarantees about whether it will occur before or after {@link #onPause}.
也就是说,会在onStop
之前调用,但不确定是否会在onPause
之前或之后调用。
一段简单的数据恢复演示:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//值得注意的是,在onCreate()中进行数据恢复的话,需要判断是否为空,因为当onCreate时正常启动而非意外销毁的情况下,为null
if (savedInstanceState != null) {
String save_str = savedInstanceState.getString("save_str");
Log.d(TAG, "onCreate,restore data:" + save_str);
}
}
@Override
public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
super.onSaveInstanceState(outState, outPersistentState);
outState.putString("save_str", "test_data");
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
//在这里不需要判空,因为只要走了这个方法的,说明savedInstanceState肯定有值。
String save_str = savedInstanceState.getString("save_str");
Log.d(TAG, "onRestoreInstanceState,restore date:" + save_str);
}
当然我们也可以让阻止它销毁重建,比如帮activity
设置个属性,android:configChanges="orientation"
。
这个配置属性除了这个以外,还有很多个,常用的是locale
,orientation
,keyboardHidden
这三个,可以适当记一记。
启动模式
在说启动模式之前,先要简单明白任务栈的概念。
任务栈
跟内存一样,“后进先出” 是栈的基本理念。
这是我基于自己理解的灵魂手绘图:
在Android中,Activity所处的栈是跟包名挂钩的,而且可以通过修改AndroidMenifest
中
标签中的android:taskAffinity=xxx.xx.xx
来修改启动的栈名。(注意这个taskAffinity属性的值为字符串,且中间必须含有包名分隔符“.”),默认情况下是本应用的包名,所以必须要是非本包包名这个值才会起作用。
栈分为 前台任务栈 和 后台任务栈 ,后台任务栈处于暂停状态,二者可以通过操作互相切换。
四种启动模式
启动模式有四种,分别为:
standard
,singleTop
,singleTask
,singleInstance
-
standard
:标准模式,也是默认的启动模式。
- 不管是否存在,一旦启动就重新创建一个实例。
- 会创建在启动的栈中,也就意味着多个栈中可以存在多个实例。
-
singleTop
:栈顶复用模式。
- 如果需要启动的新Activity已经位于栈顶,那么就不会重新创建。
- 如果需要启动的新Activity已存在但是不位于栈顶,那么还是会进行重新创建。
这里做个小测试,假如当前有三个Activity在栈中,ThirdActivity
在栈顶:
Running activities (most recent first):
TaskRecord{1300817 #6 A=com.jormun.myandroidstudy U=0 StackId=1 sz=3}
Run #2: ActivityRecord{ebe0b66 u0 com.jormun.myandroidstudy/.activity.ThirdActivity t6}
Run #1: ActivityRecord{7b723d7 u0 com.jormun.myandroidstudy/.activity.SecondActivity t6}
Run #0: ActivityRecord{87d7a27 u0 com.jormun.myandroidstudy/.MainActivity t6}
随后再跳转到SecondActivity,singTop模式,再来看看adb的日志怎么说:
Running activities (most recent first):
TaskRecord{1300817 #6 A=com.jormun.myandroidstudy U=0 StackId=1 sz=4}
Run #3: ActivityRecord{d104a67 u0 com.jormun.myandroidstudy/.activity.SecondActivity t6}
Run #2: ActivityRecord{ebe0b66 u0 com.jormun.myandroidstudy/.activity.ThirdActivity t6}
Run #1: ActivityRecord{7b723d7 u0 com.jormun.myandroidstudy/.activity.SecondActivity t6}
Run #0: ActivityRecord{87d7a27 u0 com.jormun.myandroidstudy/.MainActivity t6}
可以看到,即便是SecondActivity
存在了,一旦不在栈顶,那么还是会重新创建的。
-
singleTask
:栈内复用模式
- 假如栈中存在,那么就会调到栈顶进行复用。会执行
onNewIntent
,不存在的话就会新建一个实例放到栈中。 - 这种模式是跟需要启动的栈直接相关的,假如需要启动的栈中不存在该实例,那么会新建一个栈来放进去。上面也说了,栈是跟你的设置有关。
- 该启动模式,假如栈中已存在但不在栈顶,那么会把上方所有实例全部清栈,也就是
clearTop
效果。
针对第三点,我们来做个小测试看看,当前栈中有四个实例:
Running activities (most recent first):
TaskRecord{77265d2 #8 A=com.jormun.myandroidstudy U=0 StackId=1 sz=4}
Run #3: ActivityRecord{529f01f u0 com.jormun.myandroidstudy/.activity.FourthActivity t8}
Run #2: ActivityRecord{2f09d4b u0 com.jormun.myandroidstudy/.activity.ThirdActivity t8}
Run #1: ActivityRecord{59ade37 u0 com.jormun.myandroidstudy/.activity.SecondActivity t8}
Run #0: ActivityRecord{a4ced8b u0 com.jormun.myandroidstudy/.MainActivity t8}
这时我们返回到SecondActivity
,看看adb又怎么说:
Running activities (most recent first):
TaskRecord{77265d2 #8 A=com.jormun.myandroidstudy U=0 StackId=1 sz=2}
Run #1: ActivityRecord{59ade37 u0 com.jormun.myandroidstudy/.activity.SecondActivity t8}
Run #0: ActivityRecord{a4ced8b u0 com.jormun.myandroidstudy/.MainActivity t8}
很明显的看到,直接把上面的实例全部干掉了,而不是简单的把SecondActivity
放到最上面去使用。
-
singleInstance
: 单实例模式。
- 这种模式继承了
singleTask
的所有特性,属于加强版的singleTask
- 一个Activity存在于一个栈中,启动一个创建一个,因此可以复用。
说完启动模式之后,我们来说说怎么指定启动模式。
-
AndroidMenifest
中
标签。最基本的方法,例子:
- 代码中指定,相信经常想通过
Fragment
来跳转Activity
的人都不会陌生,例子:
Intent intent = new Intent(MainActivity.this,SecondActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
需要注意的一些地方:
- 假如两种的存在,以第二种为准。
-
singleInstance
不适用于第二种。 - 第一种无法指定
FLAG_ACTIVITY_CLEAR_TOP
标识。
隐式调用
隐式调用需要用intentFilter来进行过滤,对应AndroidMenifest
的
标签中的
一个
可以有多个
,一个Intent
只需要对应到一组
就可以启动。
通过隐式调用的时候,可以用PackageManager
或者Intent
的resolveActivity
方法,也可以用PackageManager
的queryIntentActivities
方法进行判断,不返回null
就可以成功启动Activity
。
一个
含有以下三种信息:
action
,category
,data
下面来逐一看看这3个东西。
-
action
:字符串类型。
- 必须存在一个
action
在Intent
中。 - 一个
中可以有多个action
,Intent
中设置的action
匹配到任何一个即可。 - 严格区分大小写。
-
category
:字符串类型。
-
Intent
中有默认的category
值:android.intent.category.DEFAULT
。 - 因为有默认值的存在,因此
category
可有可无,但是一旦有,无论多少都要与
中定义的相同。
-
data
:其实就是个URI。
-
Scheme
:URI的模式,比如http、file、content等等,这个不定义,整个URI都不生效。 -
Host
:URI的主机名,同上,这个不定义则整个都不生效。 -
Port
:URI中的端口号。 -
Path
:完整的路径信息。 -
PathPattern
:同上。 -
pathPrefix
:路径的前缀信息。 -
mimeType
:媒体类型,如图片的image/jpeg,视频的audio/mpeg4-generic、vedio/* 这些
除去mimeType
,其实就是以下这样的格式:
:// : /[ | | ]
写成这样就一目了然了:
http://www.baidu.com:80/search/info
关于data
有以下一些需要注意:
- URI部分,默认值为
content
和file
。 - 如需要为
Intent
指定完整的data
,必须要调用setDataAndType
。
2.Service
简单介绍一下Service吧。
个人理解:
Service是一个运行在后台的组件,不需要和用户交互,但可以跟其它组件进行交互。
Service适用于一些不需要依赖于UI而一直运行的任务,比如后台下载,检测链接状态(心跳)等等。
因为是依赖于进程的,所以只要进程还存在,那么就可以一直跑下去。
实现Service的方式
Service:
最简单的方式是创建一个继承自Service的类,然后重写里面的方法,再在有需要的地方启动一下即可。
示例:
public class MyTestService extends Service {
private static final String KEY_SERVICE_TAG = "SERVICE_TAG";
@Override
public void onCreate() {
super.onCreate();
//打印create的状态
Log.e(KEY_SERVICE_TAG, "service_create");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//打印startCommand状态
Log.e(KEY_SERVICE_TAG, "service_start");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
//打印Destroy的状态
Log.e(KEY_SERVICE_TAG, "service_stop");
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
千万注意要在清单文件注册一下:AndroidMenifest
中
一下就好。
启动也很简单,还是用Intent:
Intent serviceStartIntent = new Intent(this, MyTestService.class);
//注意一下这里是startService,别跟StartActivity给混了
startService(serviceStartIntent);
停止就这样:
Intent serviceStopIntent = new Intent(this, MyTestService.class);
stopService(serviceStopIntent);
或者在Service中自己停掉自己:
stopSelf();
这种方式的优缺点:
- 优点:经典做法,可以简单的实现到
Service
的启停。 - 缺点:一旦忘记stop就会一直运行,无法拿到
IBinder
进行交互。
IntentService:
用以上的方式当然可以简单的实现,不过假如你在某处start了一个Service
,但是你忘记关了那就蛋疼了,所以官方也考虑到这种情况,提供了一个执行完任务就自动关闭的Service
给我们用,叫 IntentService
。
用法也很简单,继承IntentService来重写方法即可,示例:
未完待续......