Android 基础
Activity
1.Activity生命周期
Activity在创建、使用、销毁的时候会回调一下生命周方法,根据调用时机依次为onCreate()、onStart()、onResume()、onPause()、onStop()、onDestory()。
- onCreate()
主要是初始化UI,创建PhoneWindow,此时 DecorView 并没有被绘制,Window 对象也没有被显示到屏幕,Activity 也是不可见的
- onStart()
Activity 对用户可见(其实并看不见),可以在这个回调中申请资源,比如相机
- onResume()
onResume() 是真正进行 UI 绘制以及显示的地方
- 创建 ViewRootImpl对象
- 调用 ViewRootImpl.setView() 方法(其中调用> requestLayout())发起绘制
- 将Window添加屏幕,此时Activity真正可见
- onPause()
非前台,不可交互,但不一定不可见,时间比较短暂
- onStop()
完全的不可见状态,可以做一些资源回收和数据保存工作,但此时的 Activity 仍在内存中,只是没有关联到任何 Window,返回调用onRestart -> onStart
- onDestory()
Activity被销毁,注意在 onDestroy 中释放所有不需要的资源,否则可能导致内存泄露
其他回调方法
- onRestart()
Activity在执行onStop之后onDestory之前,返回该Activity时调用
- onSaveInstanceState()
在onStop之后,Activity有可能被系统回收时调用,比如跳转到其他Activity
- SDK 11 之前,在 onPause() 之前调用
- SDK 28 之前,会在 onStop() 之前调用
- SDK 28 之后,会在 onStop 之后调用
- onRestoreInstanceState()
Activity被系统回收的情况下,再次启动的时候调用
- onNewIntent()
launchMode是singleTop 、 singleTask 、 singleInstance的,符合复用条件的Activity再次startActivity的时候调用
- onPostCreate()
onCreate彻底执行完之后调用
- onPostResume()
onResume彻底执行完之后调用,这个时候可以获取View的宽高
- onUserInteraction()
activity无论分发按键事件、触摸事件或者轨迹球事件都会调用。onUserLeaveHint之前回调onUserInteraction
- onUserLeaveHint
用户手动离开当前activity,会调用该方法。
- onWindowFocusChanged()
onResume/onPause 关注的是 Activity 是否可以交互,onStart/onStop 关注的是 Activity 是否可见。
2.Activity A 启动 Activity B会调用哪些方法?
A:Running -> A:onPause -> B:onCreate -> B:onStart -> B:onResume -> A:onStop
| 步骤 | Activity A | Activity B |
|---|---|---|
| 1 | onPause | |
| 2 | onCreate | |
| 3 | onStart | |
| 4 | onResume | |
| 5 | onStop |
如果B是透明主题或者Dialog A:Running -> A:onPause -> B:onCreate -> B:onStart -> B:onResume
| 步骤 | Activity A | Activity B |
|---|---|---|
| 1 | onPause | |
| 2 | onCreate | |
| 3 | onStart | |
| 4 | onResume |
3.Activity任务栈是什么
launchMode 属性有四种取值 :standard 、 singleTop 、 singleTask 、 singleInstance 。
- standard: 标准启动模式
每次启动 Activity 都会新建一个新的实例。待启动 Activity 会进入源 Activity 所属任务栈。
- singleTop: 栈顶复用模式
待启动 Activity 已经位于源 Activity 所属的任务栈的栈顶时,不会创建新的 Activity,而是直接使用栈顶的 Activity,并回调它的 onNewIntent 方法,onCreate 和 onStart 不会被调用,直接回调 onResume 。
- singleTask:栈内复用模式
全局单实例,首先会寻找要启动的 Activity 想要的任务栈(默认或者 taskAffinity 属性指定),如果没有找到,则创建新的任务栈并将 Activity 实例放入。如果找到了想要的任务栈,这时候要判断栈中是否已经存在该 Activity 的实例,如果已经存在,会将该 Activity 以上的其他 Activity 实例弹出,把自己放到栈顶,同样也是回调 onNewIntent 和 onResume。如果实例不存在,创建新的实例并压入栈中
- singleInstance:单实例模式
全局单实例,首次启动时会创建新的 Activity 实例,并放入一个新的任务栈中,且 这个任务栈中只会有这一个实例。 后续启动不会再新建实例。
taskAffinity
- taskAffinity 的作用 是指定想要的任务栈。但它并不会在任何场景下都会起作用。
- 未显式声明 taskAffinity 的 Activity 都具有默认的任务栈,该任务栈的名称是应用包名
- 当启动模式设置为 standard 或 singleTop 时,它是不起作用的
- 当启动模式设置了 singleTask 或者 singleInstance 时,它就会新建任务栈来存储待启动的 Activity 实例
- 除了 singleTask 和 singleInstance 以外,FLAG_ACTIVITY_NEW_TASK 也会使 taskAffinity 生效
4. flag
- FLAG_ACTIVITY_NEW_TASK
首先,在不设置 taskAffinity 的情况下,单独设置 FLAG_ACTIVITY_NEW_TASK 并没有任何意义,不会创建新的任务栈,每次启动都会创建新的 Activity 实例,不会 栈内复用。
- FLAG_ACTIVITY_CLEAR_TOP
CLEAR_TOP 在单独使用时,如果想要的任务栈中已经存在待启动的 Activity 的实例,则会将该 Activity 实例之上的其他 Activity 弹出,把自己放到栈顶,并回调 onNewIntent 。但这是有前提的,就是待启动的 Activity 的 launchMode 不能是 standard 。 如果是 standard ,则会把自己及之上的所有 Activity 全部弹出,新建一个实例放入。
- FLAG_ACTIVITY_SINGLE_TOP
等同于 singleTop ,栈顶复用。即使待启动的 Activity 是 standard ,如果已经处于栈顶的话,也会复用。
5. Intent传递数据大小限制,数据太大怎么办?
Intent 携带信息的大小其实是受 Binder 限制,Binder 传递缓存有一个限定大小,通常是 1Mb。但同一个进程中所有的传输共享缓存空间,所以实际更低。
改用其他方式
- 静态static
- 单例
- Application
- 数据持久化
Service
1. Service生命周期
- startService() -> onCreate() -> onStartCommand() -> onDestroy()
graph TD
A[startService]-->B{是否已执行onCreate}
B --> |否| C(onCreate)
B --> |是| D(onStartCommand)
C --> D
--> Stop[onDestroy]
- bindService() -> onCreate() -> onBind() ->unbindService() -> onUnbind() -> onDestroy()
graph TD
A[bindService]-->B{是否已执行onCreate}
B --> |否| C(onCreate)
B --> |是| D{是否binding}
C -->D
D --> |否| E(onBind)
D --> |是| F(binding)
E --> F
F --> G(unbindService)
G --> H{是否binding}
H --> |否| Stop[onDestroy]
H --> |是| I(onUnbind)
I --> Stop
2. bindService 和 startService混合调用情况
startService 和 bindService 同时调用的情况,必须同时stopService和unbindService才能停止服务。
3. Service保活
android O 以后,后台service只能存活几分钟,并且后台应用不能通过startService启动服务,否则会抛出异常.
回收优先级
前台进程 < 可视进程 < 服务进程 < 后台进程 < 内容供应根节点 < 空进程
解决方案
- startForegroundService,显示通知notification
- 跳转到设置,添加到手机白名单,不同厂商有不同处理方式
4. onStartCommand相关
参数说明
onStartCommand(Intent intent, int flags, int startId)
- 第一个参数是启动过来的Intent信息,也就是调用者的Intent信息
- 第二个参数flags代表着启动请求的附加参数,由系统传入
- 通过
startService()启动,flags传入0- onStartCommand()返回值为START_STICKY_COMPATIBILITY或者START_STICKY并且服务被强制杀死时重启后,flags传入START_FLAG_REDELIVERY(1)
- onStartCommand()返回值为START_REDELIVER_INTENT并且服务被强制杀死重启后,flags传入START_FLAG_REDELIVERY(2)
- 第三个参数为启动的ID,每次启动都对应不同的ID,与stopSelf()连同停止Service
回值说明
- START_STICKY
默认返回值,表示Service被杀掉后会重新启动,但是不会携带之前的Intent信息
- START_NOT_STICKY
Service被kill后,不会进行重启
- START_REDELIVER_INTENT
Service被杀掉后,会进行重新启动,并且还会携带之前的Intent信息
这里的重启的前提是,有自启权限。
5. IntentService
IntentService是Service的子类, onCreate()方法中,创建了HandlerThread对象,使用HandlerThread的线程处理异步任务。
HandlerThread
HandlerThread是Thread的子类,开启线程后,在run方法中初始化了Looper,我们通过HandlerThread提供的Looper对象新建Handlder对象实现线城。
6. Service和Activity通信
- Binder
Activity通过bindService绑定Service,获取Service中实现了Binder的内部类,Service获取Activity实现的Handler 实现双向通信
- 广播+startService
- 各类事件总线,EventBus、RxBus等
7. 常用系统服务
| 枚举值 | 对应对象 | 说明 |
|---|---|---|
| WINDOW_SERVICE | WindowManager | 管理Window |
| LAYOUT_INFLATER_SERVICE | LayoutInflater | 解析Xml |
| ACTIVITY_SERVICE | ActivityManager | Activity、运行时封装,Process、应用程序/包、Service、Task |
| POWER_SERVICE | PowerManger | 电源的服务 |
| ALARM_SERVICE | AlarmManager | 闹钟的服务 |
| NOTIFICATION_SERVICE | NotificationManager | 状态栏的服务 |
| KEYGUARD_SERVICE | KeyguardManager | 键盘锁的服务 |
| LOCATION_SERVICE | LocationManager | 位置的服务,如GPS |
| SEARCH_SERVICE | SearchManager | 搜索的服务 |
| VIBRATOR_SERVICE | Vibrator | 手机震动的服务 |
| CONNECTIVITY_SERVICE | Connectivity | 网络连接的服务 |
| WIFI_SERVICE | WifiManager | Wi-Fi服务 |
| TELEPHONY_SERVICE | TeleponyManager | 电话服务 |
BroastReceiver
1. 分类
- 无序广播
- 使用Context.sendBroadcast()方法发送
- 完全异步,无优先级
- 效率高
- 有序广播
- 使用Context.sendOrderedBroadcast()方法发送
- 允许接收者设定优先级,并且根据优先级依次传递广播
- 优先级高的接收者,可以对广播数据处理、继续传播、停止传播
- 本地广播
- 只能使用LocalBroadcastManager对象提供的方法注册和发送
- 只在当前App内传播
- 优先级更高
- Sticky广播
- 使用Context.sendStickyBroadcast()方法发送
- 需要BROADCAST_STICKY权限
- Sticky广播发送后不会取消,滞留在系统中,等待接收者出现
- 接收者处理后需要手动调用removeStickyBoradcast取消
- 6.0已废弃(@Depracated)
2. 注册
- 静态注册
编写BroastReceiver子类,在AndroidManifest.xml中注册,通过设置广播优先级和过滤器。
- 动态注册
编写BroastReceiver子类,创建IntentFilter对象,设置广播优先级和过滤器,用Context.registerReceiver(mReceiver,mIntentFilter)等方法注册,使用Context.unregisterReceiver()取消注册。
- 动态广播需要手动取消,不需要可能导致内存泄漏,例如Activity内部类和Context对象等
- 出于安全考虑和效率原因,一些系统广播只能通过动态注册,例如SCREEN_ON、SCREEN_OFF、TIME_TICK等
3. 原理
Android的广播本质上是一种事件的订阅和发布机制。通过registerReceiver或者Manifest文件订阅消息,通过sendBroadcast等方法发送消息。
- 普通广播
普通广播消息由AMS管理,通过registerReceiver或者Manifest文件订阅消息,最后都会走AMS,由AMS统一派发。涉及进程间通信,效率有损耗。
- 本地广播
本地广播消息由LocalBroadcastManager管理,基于观察者模式,注册时记录Action、IntentFilter和Receiver对象,发送时遍历记录,通过Handler异步回调Receiver对象的onReceive方法。
ContentProvider
- 定义
ContentProvider是Android SDK提供的不同进程间实现数据共享的机制。具体实现方式有SQLite、文件、SP等。
- 使用
Context context = .......;
Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; //1. 获取要查询的Uri
ContentResolver contentResolver = context.getContentResolver(); //2. 获取内容解析器ContentResolver
Cursor cursor = contentResolver.query(uri, null,
null, null, null); //3. ContentResolver使用Uri查询数据,返回游标Cursor
if (cursor != null) {
cursor.moveToFirst();
while (cursor.moveToNext()){ //4. 移动Cursor到指定位置或者遍历Cursor,获取数据
/** ..........**/
}
cursor.close();
}
- 权限
| 属性 | 说明 |
|---|---|
| android:grantUriPermssions: | 临时许可标志。 |
| android:permission: | Provider读写权限。 |
| android:readPermission: | Provider的读权限。 |
| android:writePermission: | Provider的写权限。 |
| android:enabled: | 标记允许系统启动Provider。 |
| android:exported: | 标记允许其他应用程序使用这个Provider。 |
| android:multiProcess: | 标记允许系统启动Provider相同的进程中调用客户端。 |
- ContentResolver
ContentResolver通过URI查询ContentProvider中提供的数据。ContentProvider是以类似数据库中表的方式将数据暴露出去,ontentResolver也是采用类似数据库的操作来从ContentProviders中获取数据。
- ContentObserver
内容观察者,观察特定URI指向的ContentProvider数据变化,做出响应。(类似监听器)
- 进程间通信原理
- ContentProvider是通过IBinder实现通信过程的
- getContentResolver获得到的是ApplicationContentResolver(在ContextImpt中实现的)
- Client端ApplicationContentResolver使用ContentProviderProxy作为IBinder的Proxy(ContentProviderNative中实现)
- Provider端通过Transport作为IBinder的实现端(ContentProvider中实现)
- 注意事项
ContentProvider的onCreate方法在Application的onCreate方法之前,耗时操作会影响应用的启动。
- 优点
- 安全,开发了增删改查接口,但是没有直接开放数据库权限,避免数据库毁灭性危机
- 统一数据共享方式,解耦底层数据存储方式
- 更加高效简单的访问系统应用数据
Uri
统一资源标识符,唯一标识ContentProvider的数据。
- 格式
schema + authority + path + id,例如:content://media/external/images/media/61645616
| 组成 | 说明 | 举例 |
|---|---|---|
| schema | Android固定为content:// | content:// |
| authority | 标识ContentProvider的唯一字符串,注册时指定的android:authority | media |
| path | 标识 authority 数据的某些子集 | external/images/media |
| id | 标识 path 子集中的某个记录,不指定是标识全部记录 | 61645616 |