一、系统架构
1,四大组件
Activity、Service、broadcastReceiver、ContentProvider
1.1 activity 生命周期 、 启动模式
1.1.1 生命周期
https://www.jianshu.com/p/b1ff03a7bb1f
纠正:onStart()时处于可见不可交互状态。onResume()处于可见可交互状态
1.1.1.1 当前Activity为A,此时用户打开ActivityB后,那么A的onPause()和B的onResume()哪个方法先执行?
答:先 A的onPause() ,再B的onResume()
Activity的启动过程:由ActivityManagerService(AMS)对栈内的Activity状态进行同步管理 & 规定:新Activity启动前,栈顶的Activity必须先onPause(),才能启动新的Activity(执行onResume())
注:为了让新的Activity尽快切换到前台,在 onPause()尽量不要做耗时 / 重量级操作
1.1.1.2 常见场景的生命周期调用
在清单文件中指定了屏幕方向,则Activity在锁屏和开启屏幕的时候执行的方法和顺序是:MainActivity onPause--->MainActivity onStop--->MainActivity onRestart--->MainActivity onStart--->MainActivity onResume
如果在清单文件中没有对屏幕进行设置,则Activity在锁屏时候执行的方法和顺序是:MainActivity onPause--->MainActivity onStop--->MainActivity onDestory--->MainActivity onCreate--->MainActivity onStart--->MainActivity onResume--->MainActivity onPause销毁之后又新建。
在开启屏幕的时候,Activity执行的方法及顺序是:MainActivity onResume--->MainActivity onPause--->MainActivity onStop--->MainActivity onDestory--->MainActivity onCreate--->MainActivity onStart--->MainActivity onResume。对于这种,锁屏后再次开启屏幕会销毁两次,重建两次。
第二中情况的解决办法:在清单文件里activity标签下配置 android:configChanges="orientation|screenSize"。
1.1.1.3 android:configChanges
用于捕获手机状态的改变。在当所指定属性(Configuration Changes)发生改变时,通知程序调用onConfigurationChanged()函数。
(1)、不设置Activity的android:configChanges时,切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行两次
(2)、设置Activity的android:configChanges="orientation"时,切屏还是会重新调用各个生命周期,切横、竖屏时只会执行一次
(3)、设置Activity的android:configChanges="orientation|keyboardHidden"时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法
但是,自从Android 3.2(API 13),在设置Activity的android:configChanges="orientation|keyboardHidden"后,还是一样 会重新调用各个生命周期的。
因为screen size也开始跟着设备的横竖切换而改变。所以,在AndroidManifest.xml里设置的MiniSdkVersion和 TargetSdkVersion属性大于等于13的情况下,
如果你想阻止程序在运行时重新加载Activity,除了设置"orientation", 你还必须设置"ScreenSize"。
解决方法:
AndroidManifest.xml中设置android:configChanges="orientation|screenSize“
1.1.1.4 finish() onDestory()
finish() 是结束一个activity的生命周期,而onDestory() 则是activity的一个生命周期
finish()调用后,将此activity移出栈,并未及时调用onDestory()方法,释放资源。但因为已出栈,点击back键时,找不到此activity
1.1.2 启动模式
1.1.2.1 知识
-
任务栈:管理activity,后进先出
-
可在 manifest中设置,launchMode。也可通过Intent设置标志位
intent.addFlags(FLAG_ACTIVITY_NEW_TASK)
intent 设置优先级更高
intent 无法设置单例模式
标记位属性 含义
FLAG_ACTIVITY_SINGLE_TOP 指定启动模式为栈顶复用模式(SingleTop)
FLAG_ACTIVITY_NEW_TASK 指定启动模式为栈内复用模式(SingleTask)
FLAG_ACTIVITY_CLEAR_TOP 所有位于其上层的Activity都要移除,SingleTask模式默认具有此标记效果
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS 具有该标记的Activity不会出现在历史Activity的列表中,即无法通过历史列表回到该Activity上
1.2 fragment
1.2.1 生命周期
创建时
onAttach() onCreate() onCreateCiew() onActivityCreated()可见时
onStart() onResume()进入后台时
onPause() onStop()fragment或activity被销毁
onPause() onStop() onDestoryView() onDestory() onDetach()屏幕熄灭/回到桌面
onPause() onSaveInstanceState() onStop()屏幕解锁/回到应用
onStart() onResume()
Activity中调用replace()方法和addToBackStack()方法时的生命周期
新替换的Fragment(没有在BackStack中):
onAttach > onCreate > onCreateView > onViewCreated > onActivityCreated > onStart > onResume
新替换的Fragment(已经在BackStack中):
onCreateView > onViewCreated > onActivityCreated > onStart > onResume
被替换的Fragment:onPause > onStop > onDestroyView--> onDestroy --> onDetach
Fragment以下4个生命周期方法将跟随所属的Activity一起被调用:
onPause > onStop > onStart > onResume
1.2.2 fragment 和 activity 通信
- Handler
- 回调
- 广播或eventbus
- 持有对象
- bundle
- findFrgamentById/Tag
1.2.3 fragment 间通信
- 通过activity操作
- 回调、广播、eventbus
1.2.4 使用
静态添加: xml中作为view直接引用
动态添加步骤:
- 获取fragmentManager对象
- 开启一个事务 beginTransaction
- add remove replace
- 可以调用addToBackStack(),加入回退栈,管控此fragment
- 提交事务 commit()
1.2.5 fragment 在viewpager容器的 resume 刷新
setUserVisibleHint(true)
- 在onResume的时候,如果getUserVisibleHint的值是false,不一定不是当前显示,如果为true就一定是当前显示的Fragment
- setUserVisibleHint()在Fragment实例化时会先调用一次,并且默认值是false,当选中当前显示的Fragment时还会再调用一次。
- setUserVisibleHint()可能会在Fragment的生命周期之外被调用,也就是可能在view创建前就被调用,也可能在destroyView后被调用,所以如果涉及到一些控件的操作的话,可能会报 null 异常,因为控件还没初始化,或者已经摧毁了。
- 控制viewpager 加载数量 mPager .setOffscreenPageLimit(2);
1.3 broadcastReceiver
https://www.jianshu.com/p/ca3d87a4cdf3
使用了观察者模式,将发送者和接收者解耦
1.3.1 应用场景
- 不同组件简通信(应用内、应用间)
- 多线程通信
- 系统通信 【电话呼入,网络可用】
1.3.2 细解
- 广播接收器运行在UI线程,因此 onReceive()不可执行耗时操作,否则ANR
1.3.3 使用
静态注册:manifest中通过receiver标签声明
//用于指定此广播接收器将接收的广播类型
//本示例中给出的是用于接收网络状态改变时发出的广播
- 当 app首启时,系统会自动实例化此 mBroadcastReceiver 类,并注册
动态注册:代码中调用 .registerReceiver()方法
- 需记得在对应位置【onPause】销毁广播
- 当此 activity 实例化时,注册此广播。当activity销毁时,动态注册的广播将不再接收消息
-
建议在 onResume()注册广播,onPause()注销广播,而非 onCreate() & onDestory() , 是因为当系统内存不足,需要回收资源时,activity执行完onPause()之后就会被销毁,此时还未执行 onStop() onDestory(),即广播还未注销,导致内存泄漏
1.3.4 分类
普通广播、系统广播、有序广播、粘性广播、local broadcast、粘性广播
普通广播:开发者自定义 intent ,匹配接受者的intentFilter的action
系统广播:仅需注册广播接收器,接收系统广播的action
有序广播:优先级别高的广播接收器先接收,接收完了没有丢弃/修改(abortBroadcast()),传给次一级的广播接收器
- 优先级相同的情况下,动态注册的广播优先
粘性广播:发送之后一直存在于消息处理器中,等待对应的接收器去处理。如果接收器被销毁,则会在下次重建时自动接收消息数据
- 需要 权限 BROADCAST_STICKY
- 没有10秒限制,10秒限制是指普通的广播如果onReceive方法执行时间太长,超过10秒的时候系统会将这个广播置为可以干掉的candidate,一旦系统资源不够的时候,就会干掉这个广播而让它不执行。
- 5.0后已 不建议使用
应用内广播
存在原因:
- 其他App发出与当前app广播接收器相匹配的广播,导致当前app不断接收广播信息
- 其他app注册与当前app一直的接收器,接收当前app的广播,出现安全问题
应用内广播保证发送者和接受者属于同一个app
LocalBroadcastManager。接受者只能动态注册,不能静态注册
全局广播 -> 应用内广播 :
- exported=false 使得非本app发送的广播不被接收
- 增加permission
- 发送广播时,指定包名 intent.setPackage(包名)
1.3.5 onReceive()参数context上下文区别
不同注册方式的广播接收器回调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;
1.4 service 使用和生命周期、跨进程通信
1.5 contentProvider
https://www.jianshu.com/p/ea8bc4aaf057
结合消息模块message,存储数据库交互一起看
作用:进程间数据共享和交互
原理: binder机制,分析见后文
1.5.1 contentProvider 使用
URI:统一资源标识符,外部线程通过URI找到对应的ContentProvider和其中的数据,并对数据进行操作
分为系统预置 和 自定义URI
Mime:指定数据类型,及打开的程序 。 = 类型+子类型
text/html
application/pdf
ContentProvider 以表格的形式,组织和管理数据【增删改查】
用于外部进程对数据进行操作的 实现。但不会直接和外部进程交互。
ContentProvider.getType(uri)
- 需注意线程同步
- 若配合SQLite使用,则不需要考虑同步,因为SQLite内部实现了线程同步。 但使用多个SQLite时仍需考虑同步
- 若存储数据到内存等,需手动实现线程同步
ContentResolver:通过Uri操作ContentProvider的数据
外部进程,通过ContentResolver , 与 ContentProvider 交互
为何不允许直接交互?
一款应用若使用多个contentProvider,直接交互需了解每个contentProvider的不同实现,成本高
提供了增删改查四个外部类
提供了三个辅助工具类: ContentUris、UriMatcher、ContentObserver
1.5.2 ContentResolver 辅助工具类
ContentUris : 操作URI
parseId() withAppendedId()
UriMatcher:
向ContentProvider注册URI : matcher.addURI()
根据URI匹配ContentProvider中对应的数据表:matcher.match()
ContentObserver:内容观察者。观察指定URI引起的ContentProvider的数据变化,通知外界【即ContentObserver注册者】。即当ContentProvider的数据发生变化时,会触发ContentObserver的监听
getContentResolver().registerContentObserver(URI)
@Override onChange()