九层高塔,起于累土。
Table of Contents
初识活动——Activity
Activity生命周期
Lifecycle | Description |
---|---|
onCreate() | |
onStart() | 表示Activity可见了,但是还没有出现在前台,还无法和用户交互 |
onResume() | |
onPause() | 表示Activity正在停止,此时可以做一些存储数据、停止动画等工作,注意不能太耗时,因为这会影响到新Activity的显示:onPause()必须先执行完,新的Activity的onResume()才会执行 |
onStop() | |
onRestart() | 表示Activity正在重新启动,一般情况下,当前Activity从不可见重新变为可见状态时就会被调用,这种情形一般是用户行为所导致的 |
onDestroy() |
常见生命周期流
- 启动Activity :系统先调用onCreate(),然后调用onStart(),最后调用onResume()方法,Activity进入运行状态。
- 覆盖 或 系统锁屏 :系统先调用onPause()方法,暂停当前Activity的运行,进入暂停状态。
- 回到前台 或 唤醒 :系统调用onResume()方法,再次进入运行状态。
- 跳转 或 Home键 :系统先调用onPause()方法,再调用onStop()方法,Activity退居后台,进入停滞状态。
- 通过 Back键 返回 :系统先调用onRestart(),然后调用onStart(),最后调用onResume()方法,再次进入运行状态。
- 被 清理 后用户退回当前Activity :再次调用onCreate()、onStart()、onResume()方法,进入运行状态。
- 用户 退出当前Activity : 系统先调用onPause(),然后调用onStop(),最后调用onDestroy()方法,结束当前Activity。
注意事项
- 从Activity是否可见来说, onStart() 和 onStop() 是配对的
- 从Activity是否在前台来说, onResume() 和 onPause() 是配对的
- 旧Activity先 onPause ,然后新Activity再启动
- 当弹出Dialog对话框时,不会回调 onPause()
- 当启动Dialog风格的Activity时,会回调 onPause() 函数
异常情况下的生命周期
比如当系统资源配置发生改变或者系统资源不足时,Activity就可能被杀死。
情况1:资源相关的系统配置发生改变导致Activity被杀死并重新创建
比如说当前Activity处于竖屏状态,如果突然旋转屏幕,由于系统配置发生了改变,在默认情况下Activity就会被销毁并且重新创建,当然我们也可以组织系统重新创建我们的Activity。
系统配置发生改变后,Activity会销毁,其 onPause() ,onStop() ,onDestroy() 方法均会被调用。由于是在异常情况下终止的,系统会调用 onSaveInstanceState() 方法来保存当前Activity状态,这个方法在 onStop() 之前调用,与 onPause() 没有既定的时序关系。
当Activity重新创建后,系统会调用 onRestoreInstanceState() ,并把之前保存的 Bundle 对象作为参数同时传递给 onRestoreInstanceState() 和 onCreate() 方法,而 onStart() 方法则在 onRestoreInstanceState() 方法执行之后回调。
在 onSaveInstanceState() 和 onRestoreInstanceState() 方法中,View相关的状态系统都能够默认为我们恢复。
生命周期日志打印如下
情况2:资源内存不足导致低优先级的Activity被杀死
这里的情况和之前情况1中数据存储和恢复是完全一致的。Activity按照优先级从高到低可以分为以下三种:
- 前台Activity——正在和用户交互的Activity,优先级最高
- 可见但非前台的Activity——比如Activity中弹出了一个对话框,导致Activity可见但是位于后台,无法和用户直接交互
- 后台Activity——已经被暂停的Activity,优先级最低
防止重新创建Activity
可为Activity指定configChange
属性来防止系统重新创建Activity
|
|
Activity与Fragment生命周期关系
创建过程
销毁过程
Activity和Menu创建先后顺序
在Activity创建完回调 onResume() 后创建 menu ,回调 onCreateOptionMenu() 。
|
|
Activity启动模式
共有 Standard 、SingleTop 、SingleTask 、SingleInstance 四种启动模式
Type | Description |
---|---|
Standard | 在这种模式下,Activity默认会进入启动它的Activity所属的任务栈中。但是,非Activity类型的Context(如ApplicationContext)并没有所谓的任务栈,所以不能通过ApplicationContext去启动Standard模式的Activity。 |
SingleTop | 栈顶复用模式。如果新Activity位于任务栈栈顶的话,Activity不会被重新创建,同时它的onNewIntent()方法会被回调。不过,这个Activity的onCreate()、onStart()、onResume()方法不会被回调,因为他们并没有发生改变。 |
SingleTask | 栈内复用模式。只要Activity在一个栈中存在,那么多次启动此Activity不会重新创建单例,系统会回调onNewIntent()。比如activityA,系统会首先寻找是否存在A想要的任务栈,如果没有则创建一个新的任务栈,然后把activityA压入栈;如果存在任务栈,然后再看有没有activityA的实例,如果实例存在,那么就会把A调到栈顶并调用它的onNewIntent()方法,如果不存在则把它压入栈。 |
SingleInstance | 单实例模式。这种模式的Activity只能单独地位于一个任务栈中。由于栈内复用特性,后续的请求均不会创建新的Activity实例。 |
默认情况下,所有Activity所需的任务栈的名字为应用的包名,可以通过给Activity指定
TaksAffinity
属性来指定任务栈,该属性不能和包名相同,否则没有意义
探究服务——Service
本地服务(LocalService)
本地服务指的是调用者和Service在同一个进程里,运行在主进程的main线程中,所以不能进行耗时操作,可以采用在Service里创建一个Thread的方法来执行任务。
任何Activity都可以控制同一个Service,而系统也只会创建一个对应Service的实例
通过Start方式开启服务
使用Service的步骤如下
- 定义一个类继承Service
- manifest.xml文件中配置Service
- 使用context的
startService(Intent)
方法启动Service- 不再使用时,调用
stopService(Intent)
方法停止服务
使用start方式启动的生命周期如下
|
|
特点:如果服务已经开启就跟调用者(开启者)没有任何关系了。开启者即便退出了,服务还在后台长期的运行,而开启者不能调用服务里面的方法
通过Bind方式开启服务
使用Service的步骤如下
- 定义一个类继承Service
- 在manifest.xml文件中注册Service
- 使用context的
bindService(Intent,ServiceConnection,int)
方法启动Service- 不再使用时,调用
unbindService(ServiceConnection)
方法停止该服务
使用bind方式启动的生命周期如下
|
|
可以看到的是,绑定服务不会调用 onStart() 或者 onStartCommand() 方法
特点:通过bind方式开启并绑定服务,如果调用者挂了,服务也会跟着挂掉。绑定者可以调用服务里面的方法
代码示例
定义一个类继承Service
|
|
在AndroidManifest.xml中注册Service
|
|
绑定自定义的Service
|
|
startService输出日志
|
|
bindService输出日志
|
|
远程服务
远程服务是指调用者和Service不在同一个进程中,Service在单独的进程中的main线程中,是一种跨进程的通信方式。参考文章
绑定远程服务
- 在服务的内部创建一个内部类,提供一个可以间接调用服务的方法
- 把暴露的接口文件的扩展名改为
.aidl
文件,并去掉访问修饰符- 实现服务的
onbind()
方法,继承Binder并实现aidl定义的接口,提供给外界可调用的方法- 在Activity中使用
bindService()
方法绑定服务- 在服务成功绑定的时候会回调
onServiceConnected()
方法传递一个IBinder
对象- aidl定义的接口
.Stub.asInterface(binder)
调用接口里面的方法
IntentService
IntentService是Service的子类,比普通的Service增加了额外的功能。Service本身存在以下两个问题
- Service不会专门启动一条单独的进程,Service与它所在应用位于同一个进程中
- Service也不是专门一条新线程,因此不应该在Service中直接处理耗时的任务
IntentService有以下几点特征
- 会创建独立的
worker
线程来处理所有的Intent请求- 会创建独立的
worker
线程来处理onHandleIntent()
方法实现的代码,无需处理多线程问题- 所有请求处理完成后,IntentService会自动停止,无需调用
stopSelf()
方法来停止Service- 为Service的
onBind()
提供默认实现,返回null- 为Service的
onStartCommand()
提供默认实现,将请求Intent添加到队列中
广播接收器——BroadcastReceiver
普通广播 Normal Broadcasts
普通广播是完全异步的,理论上可以再同一时刻被所有接收者接收到,消息传递的效率比较高,但缺点是接收者不能将处理结果传递给下一个接收者,并且不发终止广播Intent的传播。
|
|
有序广播 Ordered Broadcasts
有序广播是按照接收者声明的优先级别,按优先级依次接收广播。比如:A > B > C,那么广播先传给A,再传给B,最后传给C。A得到广播后,可以往广播里存入数据,当广播传给B时,B可以从广播中得到A存入的数据。
有序广播的优先级别声明在
intent-filter
元素的android:priority
属性中,数值越大优先级别越高,取值范围从-1000 ~ 1000
,也可以调用IntentFilter
对象的setPriority()
方法进行设置
|
|
例如系统收到短信发出的广播,就属于有序广播。如果想组织用户收到短信,可以通过设置优先级,让自定义的接收者先获取到广播,然后终止广播,这样用户就接收不到短信了。
BroadcastReceiver生命周期
如果一个广播接收器的onReceive方法被调用,那么系统将认定此对象不再是一个活动的对象,进而将其回收。
- 自定义一个类继承自BroadcastReceiver
- 重写onReceive方法
- 在manifest.xml文件中注册
注意!BroadcastReceiver的生命周期很短
如果需要在onReceive方法中完成一些耗时操作,应该考虑在Service中开启一个新线程处理耗时操作,不应该在BroadcastReceiver中开启新线程。
这是因为BroadcastReceiver的生命周期很短,在执行完onReceive方法后就结束了。如果在其中开启新线程,可能出现BroadcastReceiver退出后线程还在的情况。
而如果BroadcastReceiver所在的进程结束了,该线程就会被标记为一个空线程,根据Android的内存管理策略,在系统内存紧张的时候,会按照优先级,结束优先级低的线程。而空线程的优先级无疑是最低的,这样就可能导致BroadcastReceiver启动的子线程不能执行完成。
代码示例
定义BroadcastReceiver定义
|
|
在AndroidManifest.xml中对其注册
|
|
当然,广播也可以动态注册
|
|
注意:动态注册一定要记得加上以下权限
|
|
静态注册和动态注册的区别
- 动态注册广播不是常驻型广播,也就是说广播跟随Activity的生命周期。注意:要在Activity结束前移除BroadcastReceiver
- 静态注册是常驻型,也就是说当应用程序关闭后,如果有信息广播来,程序也会被系统调用自动运行
有序广播和普通广播的区别
当广播为有序广播时
- 优先级高的先接收
- 同优先级的BroadcastReceiver,动态优先于静态
- 同优先级的同类BroadcastReceiver,静态:先扫描的优先于后扫描的,动态:先注册的优先于后注册的
当广播为普通广播时
- 无视优先级,动态BroadcastReceiver优先于静态BroadcastReceiver
- 同优先级的同类BroadcastReceiver,静态:先扫描的优先于后扫描的,动态:先注册的优先于后注册的
BroadcastReceiver小结
- 在Android中如果要发送一个广播必须使用 sendBroadcast() 向系统发送对其感兴趣的BroadcastReceiver中
- 使用广播必须有一个Intent对象,也必须设置其Action动作对象
- 使用广播必须在配置文件中显式的指明该广播对象
- 每次接收广播都会重新生成一个接收广播的对象
- 在BroadcastReceiver中尽量不要处理太多逻辑问题,建议复杂的逻辑交给Activity或者Service去处理
- 静态注册在应用程序关闭后也会收到广播,动态注册在程序关闭后不会收到广播
关于小结第5点,援引如下解释
- 在Android系统中,程序的响应(Resposive)被活动管理器(Activity Manager)和窗口管理器(Window Manager)这两个系统服务监视。当BroadcastReceiver在10秒内没有执行完毕,系统会认为该程序无响应。所以在BroadcastReceiver里不能做一些耗时操作,否则会弹出ANR(Applicaiton No Response)的对话框。如果需要完成耗时操作,应该通过发送Intent给Service的方式,由Service来完成。
内容提供者——ContentProvider
ContentProvider是Android四大组件之一的内容提供器,它的主要作用是将程序内部的数据和外部进行共享,为数据提供外部访问接口。被访问的数据主要存储在数据库中,而且还可以选择共享哪一部分的数据,例如可以选择隐私数据不共享,从而保障了安全性。ContentProvider是Android系统中一种跨程序共享数据的重要组件。
使用系统的ContentProvider
Android系统为我们提供的ContentProvider有很多,如通话记录、短信、通讯录等,都需要和第三方App共享数据。使用ContentProvider的步骤如下:
- 获取 ContentResolver 实例
- 确定 Uri 的内容,并解析为具体的 Uri 实例
- 通过 ContentResolver 实例来调用相应的方法,传递相应的参数。但是第一个参数总是 Uri ,它制定了我们要操作的数据的具体地址
自定义ContentProvider
系统的ContentProvider在与我们进行交互的时候,只接受了一个Uri参数,然后根据我们的操作返回我们所需要的结果。那么Android系统到底是如何根据一个Uri就能够提供给我们准确结果的呢?
和之前的步骤一样,要想自定义程序中的四大组件,就必须重新实现一个类,并重写这个类中的抽象方法,之后在AndroidManifest.xml文件中进行注册,最后才能够正常使用。
重新实现ContentProvider后,发现我们重写了6个重要的抽象方法:
- onCreate
- query
- update
- insert
- delete
- getType
其中除了关于数据库CRUD操作的抽象方法之外,有两个方法需要注意:
- onCreate 是 ContentProvider 创建时所执行的一个回调方法,负责数据库的创建和更新操作。这个方法只有当我们在程序中获取 ContentResolver 实例之后准备访问共享数据的时候,才会被执行
- getType 方法是获取我们通过参数传递进去的 Uri 的 MIME 类型
ContentProvider首先要做的就是将我们传递过来的Uri解析出来,确定其他程序到底想访问哪些数据。Uri的形式一般有以下两种:
- 以路径名为结尾,这种 Uri 请求的是整张表的数据,如
content://com.demo.androiddemo.provider/table1
表示我们要访问 table1 表中的所有数据- 以 id 列的值为结尾,这种 Uri 请求的是该表中和其提供的列值相等的单条数据,如
content://com.demo.androiddemo.provider/table1/1
表示我们要访问 table1 表中 id 列值为1的数据
参考文章
- 《Android四大组件》—— 简书
- 《URI和URL的区别》—— cnblogs
- 《Activity你真的熟悉吗?看了才知道》—— 简书
- 《彻底弄懂彻底弄懂Activity四大启动模式》—— CSDN
- 《Android 多线程之IntentService 完全详解》—— CSDN
- 《安卓面试题:2-关于Service》—— Iwfu
- 《Service要点全解析》—— CSDN
- 《Android中的Service:Binder,Messenger,AIDL》—— CSDN
- 《Android探索之BroadcastReceiver具体使用以及安全性探究》—— 掘金
- 《BroadcastReceiver的工作过程》—— muhao的程序世界
- 《使用ContentProvider跨进程共享数据》—— 简书
- 《Android探索之ContentProvider熟悉而又陌生的组件》—— 掘金