android 沉淀 - 四大组件
Activity
大家最熟悉的吧,但是熟悉并不代表了解一切,但往往越是被熟知的越是被问及的越深,面试时看=有的面试官可以喜欢在这里难住面试者的
1. 生命周期
- onCreate - Activity 被创建,一些初始化工作在这里(加载布局资源,初始化所需要的数据等),如果有耗时任务需开异步线程
- onStart - Activity 启动中,个状态下 Activity 还在加载其他资源,用户还无法看到,不能交互
- onResume - Activity创建完成,用户可看见界面,可交互
- onPause - Activity 正在暂停,正常情况下接着会执行 onStop(),这时可以做数据的存储、动画停止的操作,尤其是不能太耗时,新的 Activity 必须要等当前 Activity 的 onPause 执行完才能开始创建的
- onStop - Activity 即将停止,这时可以做一些回收工作,一样不能太耗时
- onDestory - Activity即将被销毁,可以做一些工作和资源的回收,Service、BroadCastReceiver、Map、Bitmap、动画、handle、rxjava 回收
- onRestart - Activity 正在重新启动,一般时当前 Activity 从不可见到可见状态时会执行这个方法,例如:用户按下 Home 键(锁屏)或者打开新 Activity 再返回这个 Activity
以下的操作会触发 Activity 的生命周期函数:
- 启动 Activity
- 按 Home 键回到主屏
- 再次点击 App 图标回到 Activity
- AActivity 中打开 BActivity
- Back 键返回
- 熄屏,亮屏,解锁
- 系统配置改变(横竖屏切换)
- 内存不足导致低优先级的 Activity 被杀死
2. 启动模式
启动模式这个一定会问,但是必会的
- standard - 标准默认启动模式,Activity 可以有多个实例,每次启动 Activity,无论任务栈中是否已经有这个Activity 的实例,系统都会创建一个新的 Activity 实例
- singleTop - 栈顶复用模式,当一个 singleTop 模式的 Activity 已经位于任务栈的栈顶,再去启动它时,不会再创建新的实例,如果不位于栈顶,就会创建新的实例
- SingleTask - 栈复用模式,整个 Activity 栈只能有一个 SingleTask 的 Activity 实例存在,若是再启动相同的 Activity,会把栈中的该 Activity 实例置于栈顶,简单说就是该 Activity 实例上面的所有 Activity都会出栈,也就是退出了,之后该 Activity 实例自然就在栈顶了,注意栈的操作
- singleInstance - Activity 实例复用模式,singleInstance 的 Activity 会自动生成一个 Activity 任务栈,该栈中只能切只有该 Activity 一个Activity,整个 app 的进程中只能使用这一个 Activity 实例,类似于 static 的概念
除了 standard 之外其他3个模式,复用所在 Activity 时都会触发 onNewIntent 方法,这个方法接受一个 intent 参数,可以拿到用户传递过来的数据
调用顺序如下:
- onNewIntent() -> onRestart() -> onStart() -> onResume()
需要特别注意的是, 如果在 onNewIntent(Intent) 中,不调用 setIntent(Intent) 方法对 Intent 进行更新的话,那么之后在调用 getIntent() 方法时得到的依然是最初的值
3. 数据保存与恢复
2 个方法:
- onSaveInstanceState - 保存数据
- onRestoreInstanceState - 恢复数据
调用时机 : 注意是 Activity 容易被销毁的时候调用, 是容易被销毁, 但是也可能没有销毁就调用了
- 按下Home键 - Activity 进入了后台, 此时会调用该方法
- 按下电源键 - 屏幕关闭, Activity 进入后台
- 启动其它 Activity - Activity 被压入了任务栈的栈底
- 横竖屏切换 - 会销毁当前 Activity 并重新创建
onSaveInstanceState 方法总是在 onStop 之前调用,但是不去确定是在 onPause 之前或之后调用。另外 onSaveInstanceState 保存的数据有有效期和 app 进程相同,app 进程要是被销毁了那么保存的数据也就没了
4. 系统 kill 时的生命周期
非常郁闷的是当系统因为内存不足要回收 Activity 占用的资源时,有时 Activity 在 onPause() 之后就会被销毁,onStop(),onDestory() 根本不会执行,所以很多时候我们要在 onResume()注册监听,onPause() 注销监听也是没办法的事,尤其是对于广播接收器来说,这可能造成内存泄露问题
Service
Service 既服务,被认为是没有 UI 的 Activity,非常相似
Service 的特点(可能被问及的点):
- Service 同 Activity 一样都是运行在主线程上的
- Service 的作用就是用来在可不见的后台默默执行一些任务
- Service 有2种启动模式:StartService / bindService
- StartService 启动的 Service,无法与外置直接通信(如传递接口),只能通过广播,handle,evenBus 等间接通讯方式。声明周期同 app 进程,比 app 本身要长,即便 app 退出了,该 Service 也会一直运行,直到内存不足时被系统 kill,并根据 Service 中 onStartCommand 方法的返回值采取不同的策略(比如重新启动该 Service )。可以多次启动,在该 Service 已经启动的情况下只会触发 onStartCommand 方法,可以在外部和 Service 内部关闭 Service
- bindService 启动的 Service,可以与外界直接通信,可以通过 IBinder 接口获取 Service 实例,bindService 的生命周期和启动 Service 的 UI 页面等同,UI 页面关闭时,bindService 也会同步销毁,当然也可以在 UI 内主动解绑销毁 Service,当没有人与 bindService 捆绑时,bindService 也会自行销毁
- 当前最常见的 Service 绑定方式:先 StartService 启动该 Service,在需要的时候 bindService,这样即可以让 Service 服务一直运行,也可以和 Service 进行通讯。
- 设置为前台模式的 Service 会一直存活,理论上系统不会主动销毁该服务,除非用户手动 kill 该进程
2种 Service 启动方式分别对应的生命周期:
如何保证Service不被杀死:
- onStartCommand方式中,返回START_STICKY,这样即便被系统 kill 也有东山再起的机会
- 提高Service的优先级,AndroidManifest.xml 中可以通过 android:priority = "1000" 这个属性设置最高优先级,1000是最高值,如果数字越小则优先级越低,同时适用于广播
- 提升Service进程的优先级,比如前台进程
- 在onDestroy方法里重启Service,onDestroy() 时发送一个自定义广播,重新启动service
- 系统广播监听 Service 状态,利用时间广播 Intent.ACTION_TIME_TICK 该广播每分钟发送一次,如果已经被结束了,就重新启动 Service
- 将APK安装到/system/app,变身为系统级应用
更多更详细请看:
广播
1. 静态注册参数
<receiver
android:enabled=["true" | "false"]
//此broadcastReceiver能否接收其他App的发出的广播
//默认值是由receiver中有无intent-filter决定的:如果有intent-filter,默认值为true,否则为false
android:exported=["true" | "false"]
android:icon="drawable resource"
android:label="string resource"
//继承BroadcastReceiver子类的类名
android:name=".mBroadcastReceiver"
//具有相应权限的广播发送者发送的广播才能被此BroadcastReceiver所接收;
android:permission="string"
//BroadcastReceiver运行所处的进程
//默认为app的进程,可以指定独立的进程
//注:Android四大基本组件都可以通过此属性指定自己的独立进程
android:process="string" >
//用于指定此广播接收器将接收的广播类型
//本示例中给出的是用于接收网络状态改变时发出的广播
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>
2. 继承 BroadcastReceiver
BroadcastReceiver 是官方组件,自然是要像 Activity 一样去继承的
public class SdCardBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction ();
if ("android.intent.action.MEDIA_MOUNTED".equals(action)) {
System.out.println("sd卡已挂载");
} else if ("android.intent.action.MEDIA_UNMOUNTED".equals(action)) {
System.out.println("sd卡已卸载");
}
String data = intent.getStringExtra("key");
}
}
从 intent 中可以拿到 action 和 bundle 传递的数据
3. 广播的类型
- 普通广播(Normal Broadcast)
- 系统广播(System Broadcast)
- 有序广播(Ordered Broadcast)
- 粘性广播(Sticky Broadcast)
- App应用内广播(Local Broadcast)
3.1 发送无序广播
public void startBroadcast(View view){
//开启广播
//创建一个意图对象
Intent intent = new Intent();
//指定发送广播的频道
intent.setAction("com.example.BROADCAST");
//发送广播的数据
intent.putExtra("key", "发送无序广播,顺便传递的数据");
//发送
sendBroadcast(intent);
}
3.2 发送有序广播
public void sendOrderedBroad(View view) {
Intent intent = new Intent();
intent.setAction("com.example.ORDERED");
// 发送无序广播
sendOrderedBroadcast(intent,//意图动作,指定action动作
null, //receiverPermission,接收这条广播具备什么权限
new FinalReceiver(),//resultReceiver,最终的广播接受者,广播一定会传给他
null, //scheduler,handler对象处理广播的分发
0,//initialCode,初始代码
"每人发10斤大米,不得有误!", //initialData,初始数据
null//initialExtras,额外的数据,如果觉得初始数据不够,可以通过bundle来指定其他数据
);
}
有序广播的特点是按照 xml 中的优先级从高到底开始接受,先接受的 recriver 可以把数据处理之后再交给下一级或者不在传递,很想网络里的拦截器
priority 越大优先级越高
public class ShengReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent)
// 获取广播的数据
String data = getResultData();
// 修改之后再给下一级
setResultData("中央下达福利,每人5斤大米");
// 终止广播,权限小的接收者就接收不到广播了
abortBroadcast();
}
}
4. 动态注册方法
注意广播注册注销的最佳时机在 onResume、onPause,因为系统回收 Activity 时后面的生命周期可能就不会走了
上面在 AndroidManifest 中声明的都叫做静态广播,动态注册的广播就是不在AndroidManifest 中声明,用代码启动,解绑,但是要注意自 8.0 开始,不在允许静态注册的广播,必须手动代码注册
// 选择在Activity生命周期方法中的onResume()中注册
@Override
protected void onResume(){
super.onResume();
// 手动注册广播
var intentFilter = IntentFilter()
intentFilter.addAction("AAA")
registerReceiver(receiver, intentFilter)
// 发送给广播
var intent = Intent("AAA")
sendBroadcast(intent)
}
@Override
protected void onPause() {
super.onPause();
// 解绑广播
unregisterReceiver(mBroadcastReceiver);
}
}
重复注册、重复注销也不允许,不注销会有内存泄露问题的
5. 不同注册方式的广播回调 OnReceive
对于不同注册方式的广播接收器回调 OnReceive(Context context,Intent intent)中的 context 返回值是不一样的:
- 对于静态注册(全局+应用内广播),回调onReceive(context, intent)中的context返回值是:ReceiverRestrictedContext
- 对于全局广播的动态注册,回调onReceive(context, intent)中的context返回值是:Activity Context
- 对于应用内广播的动态注册(LocalBroadcastManager方式),回调onReceive(context, intent)中的context返回值是:Application Context
- 对于应用内广播的动态注册(非LocalBroadcastManager方式),回调onReceive(context, intent)中的context返回值是:Activity Context
进程
android 的进程主要了解有优先级,用来配合 Service 保活策略
android 进程优先级5个档:
- 前台进程
- 可见进程
- 服务进程
- 后台进程
- 空进程
怎么理解呢,简单来说:
- 前台进程 - 需要指定设置;
- 可见进程 - Activity 可见时就是
- 服务进程 - Service 启动时就是
- 后台进程 - app 按 home 就是
- 空进程 - 所有的页面都退出了,但 app 所在的进程不会第一时间就回收,为了放置用户短时间内再启动有一个缓冲,但是非常容易被回收
上面是简单理解,详细请看:
但是和理论不同的是,Service 优先级首先参考的不是自己的优先级,而是首先参考所在 app 进程的页面活动
- 比如我们在一个页面中启动一个 Service 播放音乐,我们按 home 切到后台,按照理论此时 app 所在进程是服务进程的优先级,属于不容易被回收的那种,但是实际不是,app 进程是后台进程,优先级比服务进程第一个档次,属于容易被回收的进程,只有 Service 设置为前台时除外
- 若是这个 Service 单独运行在一个进程中,那么就和理论情况一样
具体可以参考: