面试季,覆盖70%-80%的面经基础题(java及安卓)-------安卓篇
- 一般
- 内存泄漏及处理
- 场景
- 解决
- 进程层次
- Handler
- 组成
- 机制
- sendmessage和postmessage的区别
- postDelayed()的实现
- 事件传递机制
- Binder机制
- Activity、Window、View三者的差别
- Android的数据存储方式。
- 什么是ANR?如何避免它?
- listview和recyclerview的区别
- 缓存机制
- 局部刷新
- 动画
- recyclerview怎么实现滑动功能
- 为什么要设计出Bundle而不是直接使用HashMap来进行数据传递
- 接收到广播的intent之后创子线程下载内容是否合适
- 为什么Looper.loop()死循环不会导致ANR
- mainThread的Looper和其他looper区别
- 内存泄漏及处理
- Activity(四大组件)
- 生命周期
- 各生命周期状态说明
- Activity启动模式
- 生命周期
- Service(四大组件)
- 生命周期
- startService()
- bindService()
- Service的两种启动方法
- 通过startService()启动
- 通过bindService()启动
- 如何保证Service不被杀死?
- Intent Service
- Service 与 IntentService 的区别
- 生命周期
- Broadcast Receiver(四大组件)
- 普通广播
- 有序广播
- 本地广播
- 粘性广播
- Content Provider(四大组件)
- 在哪个线程里运行?
- 视图
- View的绘制流程
- Measure
- Layout
- Draw
- View的绘制流程
- Fragment
- 生命周期
- 与Activity通信
- Activity传Fragment
- Fragment传Activity
- Fragment间通信
一般
内存泄漏及处理
场景
-
static引用大对象
static Activity activity;
这是最简单粗暴的持有一个activity的引用,这样这个activity退出之后对象并没有被销毁。
static View view;
一个View初始化时会用到context,我们在自定义View,重写构造方法时就知道这个了。因此如果一个View也像这样被持有,那个context也不会被释放。
-
内部类
内部类有个特性,是他会持有一个外部类的引用。如果内部类的实例一直存活,那么外部类activity的实例也就一直在。
比如持有一个static的内部类引用;或者用asynctask时喜欢搞一个匿名内部类执行异步任务,那当我们activity退出后这个异步任务还在执行的话,就会泄露了;还有自己开个匿名线程;还有在使用handler时,如果用了匿名handler,那么这个handler会带着activity的引用藏到消息队列中。消息没有被处理,就会造成内存泄漏。
解决
- 我们在代码中能不用static变量持有contxt就不用,非要用就用weak引用。
- 对于内部类,尽量用静态内部类,这样就不会持有外部类引用。如果需要外部类引用做一些事,就手动赋给一个weak引用。
- 对于匿名内部类,不要图简单方便,实在不行就乖乖的写成外部类。
- 异步操作,尽量用可以方便管理的,比如rxJava,而不是用老古董AsyncTask了。非要用也最好加一个终止条件,在退出Activity时就该结束了。在用rx时,可以在subscribe()的时候获取到Subscripeion,在不用的时候手动unSubscribe(),或者直接bind()到Activity的生命周期上,比如使用RxActivity管理。
- 在使用handler时,记得在activity的onDestroy()中加上remove()
- 在获取到某些资源时,使用完记得释放
- 在用到一些大对象比如Bitmap啊什么的,要记得回收
- 最后,在使用各种第三方库或者系统服务的时候还要记得有注册或绑定就要有解除注册、解绑定。
进程层次
-
前端进程。顾名思义,前端进程就是目前显示在屏幕上和用户交互的进程,在系统中前端进程数量很少,而这种进程是对用户体验的影响最大,只有系统的内存稀少到不足以维持和用户的基本交互时才会销毁前端进程。因此这种进程重要性是最高的。
-
可见进程。可见进程也拥有一个可视化的界面,只是目前不是最上层界面(最上层界面在前端进程里面),可见进程一般调用了OnPause(),可见进程比前端进程重要性低,但是在交互方面影响还是很大,因为用户可能随时切换过去,所以系统不会轻易销毁它。
-
服务进程。一个服务进程就是一个Service,它调用了startService(),就是UNIX中说的守护进程,对用户不可见,但是保证了一些重要的事件被监听或者维持着某些状态,比如网络数据传输、后台音乐播放,这类进程在内存不足且为了保证前端交互的顺利进行的时候被销毁。
-
后台进程。这里叫后台进程可能会和一般意义上的后台进程混淆,要说明的是,android里的后台进程是调用了OnStop()的,可以理解成用户暂时没有和这个进程交互的愿望,所以这里后台进程有点“待销毁”的意思。
-
空进程。这是一种系统缓存机制,其实就是个进程的外壳,当有新进程创建的时候,这个空进程可以加快进程创建速度,当系统内存不足的时候,首先销毁空进程。
活动在子线程里面,服务在子进程里面
Handler
Handler是android提供用于更新UI的一套机制,也是消息处理机制
Handler的主要作用有两个:
-
在新启动的线程中发送消息
-
在主线程中获取,处理消息。
组成
-
Message
Message是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间交换数据。使用Message的arg1和arg2便可携带int数据,使用obj便可携带Object类型数据。
-
Handler
Handler顾名思义就是处理者的意思,它主要用于在子线程发送消息对象Message,在UI线程处理消息对象Message,在子线程调用sendMessage方法发送消息对象Message,而发送的消息经过一系列地辗转之后最终会被传递到Handler的handleMessage方法中,最终在handleMessage方法中消息对象Message被处理。
-
MessageQueue
MessageQueue就是消息队列的意思,它只要用于存放所有通过Handler发送过来的消息。这部分消息会一直存放于消息队列当中,等待被处理。每个线程中只会有一个MessageQueue对象,请牢记这句话。其实从字面上就可以看出,MessageQueue底层数据结构是队列,而且这个队列只存放Message对象。
-
Looper
Looper是每个线程中的MessageQueue的管家,调用Looper的loop()方法后,就会进入到一个无限循环当中,然后每当MesssageQueue中存在一条消息,Looper就会将这条消息取出,并将它传递到Handler的handleMessage()方法中。每个线程只有一个Looper对象。
机制
Handler将Message发送到Looper的消息队列中,即MessageQueue,等待Looper的循环读取Message,处理Message,然后调用Message的target,即附属的Handler的dispatchMessage()方法,将该消息回调到handleMessage()方法中,然后完成更新UI操作。
sendmessage和postmessage的区别
- sendmessage是阻塞方法,postmessage是非阻塞方法
- 返回时间不同
PostMessage发送消息后就立即返回;SendMessage发送消息后,等待消息处理函数处理完后才返回。 - 返回值不同
从函数定义上来看,PostMessage的返回值是BOOL,意思是返回非0值,消息执行成功,返回0,执行不成功。SendMessage的返回值是LRESULT,返回的是消息处理函数后的返回值。
postDelayed()的实现
postDelayed()首先通过getPostMessage()将传入的Runnable对象封装成一个Message,调用sendMessageDelayed(),而sendMessageDelayed()增加了一个delay时间参数的健壮性检查,然后转化成绝对时间,调用sendMessageAtTime()。
事件传递机制
-
Android事件分发机制的本质是要解决:点击事件由哪个对象发出,经过哪些对象,最终达到哪个对象并最终得到处理。这里的对象是指Activity、ViewGroup、View.
-
Android中事件分发顺序:Activity(Window) -> ViewGroup -> View.
-
事件分发过程由dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()三个方法协助完成
Binder机制
Binder是android中一种实现进程间通信(IPC)的方式之一。
首先,Binder分为Client和Server两个进程。
注意,Client和Server是相对的。谁发消息,谁就是Client,谁接收消息,谁就是Server。
举个例子,两个进程A和B之间使用Binder通信,进程A发消息给进程B,那么这时候A是Binder Client,B是Binder Server;进程B发消息给进程A,那么这时候B是Binder Client,A是Binder Server。
组成
各类Server注册到ServiceManager中,当Client想与Server通讯,Binder在ServiceManager中查询Server的地址,再充当二者之间的通讯渠道
过程
Binder在ServiceManager中查询Server的地址,返回对应对象的代理给Client,Client通过代理调用对应Server中的方法
Activity、Window、View三者的差别
Activity像一个工匠(控制单元),Window像窗户(承载模型),View像窗花(显示视图)
Android的数据存储方式。
- SharedPreferences
- 文件存储
- SQLite数据库
- 内容提供器(Content provider)
- 网络存储
什么是ANR?如何避免它?
ANR:Application Not Responding
在Android中,活动管理器和窗口管理器这两个系统服务负责监视应用程序的响应。当出现下列情况时,Android就会显示ANR对话框了:
-
对输入事件(如按键、触摸屏事件)的响应超过5秒
-
意向接受器(intentReceiver)超过10秒钟仍未执行完毕
Android应用程序完全运行在一个独立的线程中(例如main)。这就意味着,任何在主线程中运行的,需要消耗大量时间的操作都会引发ANR。 因为此时,你的应用程序已经没有机会去响应输入事件和意向广播(Intent broadcast)。
因此,任何运行在主线程中的方法,都要尽可能的只做少量的工作。特别是活动生命周期中的重要方法如onCreate()和 onResume()等更应如此。 潜在的比较耗时的操作,如访问网络和数据库;或者是开销很大的计算,比如改变位图的大小,需要在一个单独的子线程中完成 (或者是使用异步请求,如数据库操作)。 但这并不意味着你的主线程需要进入阻塞状态已等待子线程结束 – 也不需要调用Therad.wait()或者Thread.sleep()方法。 取而代之的是,主线程为子线程提供一个句柄(Handler),让子线程在即将结束的时候调用它。
listview和recyclerview的区别
缓存机制
-
层级
RecyclerView比ListView多两级缓存,支持多个ItemView缓存,支持开发者自定义缓存处理逻辑,支持所有RecyclerView共用同一个RecyclerViewPool(缓存池)。
-
缓存对象
RecyclerView缓存RecyclerView.ViewHolder,ListView缓存View
局部刷新
RecyclerView更大的亮点在于提供了局部刷新的接口,通过局部刷新,就能避免调用许多无用的bindView。ListView和RecyclerView最大的区别在于数据源改变时的缓存的处理逻辑,ListView是"一锅端",将所有的mActiveViews都移入了二级缓存mScrapViews,而RecyclerView则是更加灵活地对每个View修改标志位,区分是否重新bindView。
动画
Listview中删除或添加item时,item是无法产生动画效果的,在RecyclerView中添加、删除或移动item时有两种默认的效果可以选择SimpleItemAnimator(简单条目动画) 和 DefaultItemAnimator(原样的条目动画)。
recyclerview怎么实现滑动功能
如果你想实现 RecyClerView 横向滑动功能只需要,使用 RecyClerView 的线性布局管理器 LinearLayoutManager ,方法调用 setOrientation() , 来控制使用什么样的布局样式,如果你想实现横向滑动的话就使用setOrientation(LinearLayoutManager.HORIZONTAL);。
为什么要设计出Bundle而不是直接使用HashMap来进行数据传递
-
Bundle内部是由ArrayMap实现的,ArrayMap的内部实现是两个数组,一个int数组是存储对象数据对应下标,一个对象数组保存key和value,内部使用二分法对key进行排序,所以在添加、删除、查找数据的时候,都会使用二分法查找,只适合于小数据量操作,如果在数据量比较大的情况下,那么它的性能将退化。
而HashMap内部则是数组+链表结构,所以在数据量较少的时候,HashMap的Entry Array比ArrayMap占用更多的内存。因为使用Bundle的场景大多数为小数据量,所以相比之下,在这种情况下使用ArrayMap保存数据,在操作速度和内存占用上都具有优势,因此使用Bundle来传递数据,可以保证更快的速度和更少的内存占用。
-
另外一个原因,则是在Android中如果使用Intent来携带数据的话,需要数据是基本类型或者是可序列化类型,HashMap使用Serializable进行序列化,而Bundle则是使用Parcelable进行序列化。而在Android平台中,更推荐使用Parcelable实现序列化,虽然写法复杂,但是开销更小,所以为了更加快速的进行数据的序列化和反序列化,系统封装了Bundle类,方便我们进行数据的传输。
接收到广播的intent之后创子线程下载内容是否合适
不合适,BroadcastReceiver的生命周期很短,在接收到广播的时候创建,onReceive()方法结束之后销毁,如果创建子线程做耗时的工作,若因为BR被销毁后进程就成为了空进程,很容易被系统杀掉
为什么Looper.loop()死循环不会导致ANR
Android 的是由事件驱动的,looper.loop() 不断地接收事件、处理事件,每一个点击触摸或者说Activity的生命周期都是运行在 Looper.loop() 的控制之下,如果它停止了,应用也就停止了。只能是某一个消息或者说对消息的处理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞它。
也就说我们的代码其实就是在这个循环里面去执行的,当然不会阻塞了。
而且主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此loop的循环并不会对CPU性能有过多的消耗。
mainThread的Looper和其他looper区别
application运行在mainThread的looper中,其他looper运行在子线程中
Activity(四大组件)
生命周期
一个Activity从本质上讲拥有4种状态:
- Started:如果当前的activity在前台界面上时(堆栈顶端)。
- Paused:如果activity被另一个非全屏活动强占焦点并覆盖时(如弹窗dialog),它将会暂停。一个暂停的活动也是完全活跃的(它的所有的状态和成员信息将会保留,但activity本身将不会再依附于WindowsManager了),在内存极度缺乏的状态会被系统杀死。
- Stopped:如果activity完全被另一个全屏活动遮挡住时,它将会停止。该活动也仍保留全部的状态和成员信息,但将会被隐藏起来不再展示给用户,并且当内存在其他地方被需要时该活动就将会被系统杀死。
- Resumed:如果activity处于暂停或者停止状态,系统将会在内存中终止该活动无论是结束活动或者杀死进程。当它再一次展示给用户时,它必须是完全重启并且恢复到之前的状态。
各生命周期状态说明
方法 | 描述 | 用途(以当前界面播放视频为例) | 下一个方法 |
---|---|---|---|
onCreate() | 当Activity第一次创建时调用。该方法(如果有)会提供给你一个包含之前活动的冻结状态信息bundle包。 | 进行一系列初始化操作,如:创建View,加载视频数据等。 | onStart() |
onRestart() | 当Activity被停止后调用,在重新开始之前 | 当活动停止后重新启动该活动时调用(不常用),针对停止后重启操作。 | onStart() |
onStart() | 当Activity被展示在用户眼前时调用。如果活动出现在前台紧接着是onResume(),如果活动直接隐藏则紧接着是onStop()。 | 该方法也不常用。 | onResume() or onStop() |
onResume() | 当Activity将开始与用户进行交互时调用。在这个时间点你的活动将会在活动堆栈的顶端,用户输入将会访问它。 | 暂停后恢复我们会在该方法中进行一些操作,例如视频继续播放。 | onPause() |
onPause() | 当系统将要恢复一个之前的活动。这是一个有代表性的常常用于提交未被存储的改动信息为持久数据,停止动画和消耗CPU的东西等。实现该方法必须要特别的迅速,因为在此方法返回之前,下一个活动将不会恢复。如果活动将返回到前台则接下来调用onResume(),如果要隐藏到用户看不见的地方时,则调用onStop(); | 该方法十分重要,用来做信息持久化存储操作以及停止消耗CPU资源操作,如记录视频播放进度时间,以及暂停视频播放操作等。 | onResume or onStop() |
onStop() | 当另一个活动被恢复且完全覆盖该活动,而该Activity将不在展示给用户时调用。这种情况将发生在一个新的活动将被开始,一个退出的活动将被恢复,又或者该活动将要被销毁。如果该活动将恢复与用户交互则调用onRestart(),如果该活动将被销毁则调用onDestory()。 | 界面将会隐藏或销毁,做一些重要信息或未被存储的信息的存储操作。但也不要太耗时。如存储用户信息等操作,以及用户此次观看的视频地址以及时间,便于下次打开该界面时继续播放。 | onRestart() or onResume() or onDestroy() |
onDestory() | Activity被销毁钱最后一个被调用的方法。这个方法将会发生因为活动将会结束(在活动中调用finish()方法,或者系统临时销毁该实例节约空间。你可以使用isFinishing()方法区别这两种场景)。 | 界面将要销毁,释放一些实例节约空间,如置空List集合等。 |
Activity启动模式
-
Standard
该启动模式为Android默认启动模式,每当启动一个Activity就会在任务栈中创建一个Activity,这种模式默认的但是非常的浪费空间,但是可以有效的保存之前启动的Activity。用于保证之前页面不丢失的时候。
-
Single Top
该启动模式是在查看任务栈顶和你将要启动的Activity是否是一个Activity,是一个就直接复用,否则就新创一个实例。这个经常用于类似聊天界面,当有人给你发消息时更新Activity。
-
Single Task
该启动模式是在任务栈中看是否有和你一样的Activity,有则直接把该Activity之上的Activity全部弹出使之置于栈顶,获得焦点。常用于一个程序的入口处。
-
Single Instance
该启动模式是再创建一个任务栈把Activity放进去,新的任务栈只有新的Activity一个实例。如果已经创建过目标Activity实例,则不会创建新的任务栈,而是将以前创建过的Activity唤醒(对应Task设为Foreground状态)。此模式用于不同应用调用一个activity时,用于程序和界面分开的时候。
Service(四大组件)
生命周期
startService()
- onCreate():服务第一次被创建时调用
- onStartCommand():服务启动时调用
- onDestroy():服务停止时调用
bindService()
- onCreate():服务第一次被创建时调用
- onBind():服务被绑定时调用
- onUnBind():服务被解绑时调用
- onDestroy():服务停止时调用
Service的两种启动方法
通过startService()启动
通过startService启动后,service会一直无限期运行下去,只有外部调用了stopService()或stopSelf()方法时,该Service才会停止运行并销毁。
要创建一个这样的Service,你需要让该类继承Service类,然后重写以下方法:
- onCreate()
- 如果service没被创建过,调用startService()后会执行onCreate()回调;
- 如果service已处于运行中,调用startService()不会执行onCreate()方法。
也就是说,onCreate()只会在第一次创建service时候调用,多次执行startService()不会重复调用onCreate(),此方法适合完成一些初始化工作。
- onStartCommand()
如果多次执行了Context的startService()方法,那么Service的onStartCommand()方法也会相应的多次调用。onStartCommand()方法很重要,我们在该方法中根据传入的Intent参数进行实际的操作,比如会在此处创建一个线程用于下载数据或播放音乐等。 - onBind()
Service中的onBind()方法是抽象方法,Service类本身就是抽象类,所以onBind()方法是必须重写的,即使我们用不到。 - onDestory()
在销毁的时候会执行Service该方法。
这几个方法都是回调方法,且在主线程中执行,由android操作系统在合适的时机调用。
通过bindService()启动
bindService启动服务特点:
- bindService启动的服务和调用者之间是典型的client-server模式。调用者是client,service则是server端。service只有一个,但绑定到service上面的client可以有一个或很多个。这里所提到的client指的是组件,比如某个Activity。
- client可以通过IBinder接口获取Service实例,从而实现在client端直接调用Service中的方法以实现灵活交互,这在通过startService方法启动中是无法实现的。
- bindService启动服务的生命周期与其绑定的client息息相关。当client销毁时,client会自动与Service解除绑定。当然,client也可以明确调用Context的unbindService()方法与Service解除绑定。当没有任何client与Service绑定时,Service会自行销毁。
如何保证Service不被杀死?
-
onStartCommand方式中,返回START_STICKY
调用Context.startService方式启动Service时,如果Android面临内存匮乏,可能会销毁当前运行的Service,待内存充足时可以重建Service。而Service被Android系统强制销毁并再次重建的行为依赖于Service的onStartCommand()方法的返回值。
-
START_NOT_STICKY
如果返回START_NOT_STICKY,表示当Service运行的进程被Android系统强制杀掉之后,不会重新创建该Service。当然如果在其被杀掉之后一段时间又调用了startService,那么该Service又将被实例化。
那什么情境下返回该值比较恰当呢?如果我们某个Service执行的工作被中断几次无关紧要或者对Android内存紧张的情况下需要被杀掉且不会立即重新创建这种行为也可接受,那么我们便可将 onStartCommand的返回值设置为START_NOT_STICKY。
-
START_STICKY
如果返回START_STICKY,表示Service运行的进程被Android系统强制杀掉之后,Android系统会将该Service依然设置为started状态(即运行状态),但是不再保存onStartCommand方法传入的intent对象,然后Android系统会尝试再次重新创建该Service,并执行onStartCommand回调方法,但是onStartCommand回调方法的Intent参数为null,也就是onStartCommand方法虽然会执行但是获取不到intent信息。
如果你的Service可以在任意时刻运行或结束都没什么问题,而且不需要intent信息,那么就可以在onStartCommand()方法中返回START_STICKY,比如一个用来播放背景音乐功能的Service就适合返回该值。
-
START_REDELIVER_INTENT
如果返回START_REDELIVER_INTENT,表示Service运行的进程被Android系统强制杀掉之后,与返回START_STICKY的情况类似,Android系统会将再次重新创建该Service,并执行onStartCommand回调方法
但是不同的是,Android系统会再次将Service在被杀掉之前最后一次传入onStartCommand方法中的Intent再次保留下来并再次传入到重新创建后的Service的onStartCommand方法中,这样我们就能读取到intent参数。只要返回START_REDELIVER_INTENT,那么onStartCommand重的intent一定不是null。
如果我们的Service需要依赖具体的Intent才能运行(需要从Intent中读取相关数据信息等),并且在强制销毁后有必要重新创建运行,那么这样的Service就适合返回START_REDELIVER_INTENT。
-
-
提高Service的优先级
在AndroidManifest.xml文件中对于intent-filter可以通过android:priority = "1000"
这个属性设置最高优先级,1000是最高值,如果数字越小则优先级越低,同时适用于广播。 -
提升Service进程的优先级
当系统进程空间紧张时,会依照优先级自动进行进程的回收。可以使用startForeground将service放到前台状态,这样低内存时,被杀死的概率会低一些。Android将进程分为6个等级,按照优先级由高到低依次为:
- 前台进程foreground_app
- 可视进程visible_app
- 次要服务进程secondary_server
- 后台进程hiddena_app
- 内容供应节点content_provider
- 空进程empty_app
-
在onDestroy()方法里重启Service
当service走到onDestroy()时,发送一个自定义广播,当收到广播时,重新启动service。
-
系统广播监听Service状态
-
将APK安装到/system/app,变身为系统级应用
Intent Service
-
会创建独立的worker线程来处理所有的Intent请求;
-
会创建独立的worker线程来处理onHandleIntent()方法实现的代码,无需处理多线程问题;
-
所有请求处理完成后,IntentService会自动停止,无需调用stopSelf()方法停止Service;
-
为Service的onBind()提供默认实现,返回null;
-
为Service的onStartCommand提供默认实现,将请求Intent添加到队列中;
Service 与 IntentService 的区别
Service 是长期运行在后台的应用程序组件。Service 不是一个单独的进程,它和应用程序在同一个进程中,Service 也不是一个线程,它和线程没有任何关系,所以它不能直接处理耗时操作。如果直接把耗时操作放在 Service 的 onStartCommand() 中,很容易引起 ANR,如果有耗时操作就必须开启一个单独的线程来处理
IntentService 是继承于 Service 并处理异步请求的一个类,在 IntentService 内有一个工作线程来处理耗时操作,启动 IntentService 的方式和启动传统 Service 一样,同时,当任务执行完后,IntentService 会自动停止,而不需要我们去手动控制。另外,可以启动 IntentService 多次,而每一个耗时操作会以工作队列的方式在IntentService 的 onHandleIntent 回调方法中执行,并且,每次只会执行一个工作线程,执行完第一个再执行第二个,以此类推。而且,所有请求都在一个单线程中,不会阻塞应用程序的主线程(UI Thread),同一时间只处理一个请求。 那么,用 IntentService 有什么好处呢?首先,我们省去了在 Service 中手动开线程的麻烦,第二,当操作完成时,IntentService会自己停止
Broadcast Receiver(四大组件)
Broadcast(广播)是一种广泛应用在应用程序之间传输信息的机制,而BroadcastReceiver(广播接收器)则是用于接收来自系统和应用的广播对并对其进行响应的组件。Android提供了一套完整的API,允许应用程序自由地发送和接收广播,其中又用到可以传递信息的Intent。
普通广播
普通广播是一种完全异步执行的广播,在广播发出之后,所有的广播接收器几乎都会在同一时刻接收到这条广播消息,因此它们接收的先后是随机的。另外,接收器不能截断普通广播。
有序广播
有序广播是一种同步执行的广播,在广播发出之后,同一时刻只会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑执行完毕后,广播才会继续传递,所以此时的广播接收器是有先后顺序的,且优先级(priority)高的广播接收器会先收到广播消息。有序广播可以被接收器截断使得后面的接收器无法收到它。
本地广播
学到的的广播都属于系统全局广播,即发出的广播可被其他应用程序接收到,且我们也可接收到其他任何应用程序发送的广播。为了能够简单地解决全局广播可能带来的安全性问题,Android引入了一套本地广播机制,使用这个机制发出的广播只能够在应用程序的内部进行传递,并且广播接收器也只能接收本应用程序发出的广播。
粘性广播
通过**Context.sendStickyBroadcast()方法可发送粘性(sticky)广播,这种广播会一直滞留,当有匹配该广播的接收器被注册后,该接收器就会收到此条广播。sendStickyBroadcast()只保留最后一条广播,并且一直保留下去,这样即使已经有广播接收器处理了该广播,一旦又有匹配的广播接收器被注册,该粘性广播仍会被接收。如果只想处理一遍该广播,可通过removeStickyBroadcast()**方法来实现。
Content Provider(四大组件)
使一个应用程序的指定数据集提供给其他应用程序。其他应用可以通过ContentResolver类从该内容提供者中获取或存入数据,其最重要的作用就在于提供了一种跨进程获取数据的方式,provider组件不仅可以自己的进程使用,还可以提供给其他的进程使用,大大方便了不同进程之间的数据交换。
在哪个线程里运行?
ContentProvider可以在AndroidManifest.xml中配置一个叫做android:multiprocess的属性,默认值是false,表示ContentProvider是单例的,无论哪个客户端应用的访问都将是同一个ContentProvider对象,如果设为true,系统会为每一个访问该ContentProvider的进程创建一个实例。
对于content provider的操作:
- 当ContentProvider和调用者在同一个进程,ContentProvider的方法(query/insert/update/delete等)和调用者在同一线程中
- 当ContentProvider和调用者在不同的进程,ContentProvider的方法会运行在它自身所在进程的一个Binder线程中
视图
View的绘制流程
当一个应用启动时,会启动一个主 Activity,Android 系统会根据 Activity 的布局来对它进行绘制。绘制会从根视图 ViewRoot 的 performTraversals() 方法开始,从上到下遍历整个视图树,每个 View 控制负责绘制自己,而 ViewGroup 还需要负责通知自己的子 View 进行绘制操作。
performTraversals 方法在类 ViewRootImpl 内,其核心代码如下。
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); ... // 测量 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); ... // 布局 performLayout(lp, mWidth, mHeight); ... // 绘制 performDraw();
MeasureSpec
MeasureSpec 表示的是一个 32 位的整数值,它的高 2 位表示测量模式 SpecMode,低 30 位表示某种测量模式下的规格大小 SpecSize。MeasureSpec 是 View 类的一个静态内部类,用来说明应该如何测量这个View。
三种测量模式。
- UNSPECIFIED:不指定测量模式,父视图没有限制子视图的大小,子视图可以是想要的任何尺寸,通常用于系统内部,应用开发中很少使用到。
- EXACTLY:精确测量模式,当该视图的 layout_width 或者 layout_height 指定为具体数值或者 match_parent 时生效,表示父视图已经决定了子视图的精确大小,这种模式下 View 的测量值就是 SpecSize 的值。
- AT_MOST:最大值模式,当前视图的 layout_width 或者 layout_height 指定为 wrap_content 时生效,此时子视图的尺寸可以是不超过父视图运行的最大尺寸的任何尺寸。
对 DecorView 而言,它的 MeasureSpec 由窗口尺寸和其自身的 LayoutParams 共同决定;对于普通的 View,它的 MeasureSpec 由父视图的 MeasureSpec 和其本身的 LayoutParams 共同决定。
视图操作的过程可以分为三个步骤,分别是测量(Measure)、布局(Layout)和绘制(Draw)。
Measure
Measure 用来计算 View 的实际大小。页面的测量流程从 performMeasure 方法开始。
具体操作是分发给 ViewGroup 的,由 ViewGroup 在它的 measureChild 方法中传递给子 View。ViewGroup 通过遍历自身所有的子 View,并逐个调用子 View 的 measure 方法实现测量操作。
Layout
Layout 过程用来确定 View 在父容器的布局位置,他是父容器获取子 View 的位置参数后,调用子 View 的 layout 方法并将位置参数传入实现的。
Draw
Draw 操作用来将控件绘制出来,绘制的流程从 performDraw 方法开始,performDraw 方法在类 ViewRootImpl 内。
Fragment
Fragment是一种可以嵌入在活动中的UI片段,能够让程序更加合理和充分地利用大屏幕的空间,出现的初衷是为了适应大屏幕的平板电脑,可以将其看成一个小型Activity,又称作Activity片段。
使用Fragment可以把屏幕划分成几块,然后进行分组,进行一个模块化管理。Fragment不能够单独使用,需要嵌套在Activity中使用,其生命周期也受到宿主Activity的生命周期的影响
-
Fragment是依赖于Activity的,不能独立存在的。
-
一个Activity里可以有多个Fragment。
-
一个Fragment可以被多个Activity重用。
-
Fragment有自己的生命周期,并能接收输入事件。
-
我们能在Activity运行时动态地添加或删除Fragment。
生命周期
onAttach()
:Fragment和Activity相关联时调用。可以通过该方法获取Activity引用,还可以通过getArguments()获取参数。(只回调一次)onCreate()
:Fragment被创建时调用(只回调一次)onCreateView()
:每次创建,绘制View组件时回调,返回显示的ViewonActivityCreated()
:当Activity完成onCreate()时调用onStart()
:当Fragment可见时调用。onResume()
:当Fragment可见且可交互时调用onPause()
:当Fragment不可交互但可见时调用。onStop()
:当Fragment不可见时调用。onDestroyView()
:当Fragment的UI从视图结构中移除时调用。onDestroy()
:销毁Fragment时调用。onDetach()
:当Fragment和Activity解除关联时调用。
Fragment生命周期会经历:运行、暂停、停止、销毁。
- 运行状态:碎片可见时,关联活动处于运行状态,其也为运行状态
- 暂停状态:活动进入暂停状态,相关联可见碎片就会进入暂停状态
- 停止状态:活动进入停止状态,相关联碎片就会进入停止状态,或者通过FragmentTransaction的
remove()
、replace()
方法将碎片从从活动中移除,但如果在事务提交之前调用addToBackStack()
方法,这时的碎片也会进入到停止状态。 - 销毁状态:当活动被销毁,相关联碎片进入销毁状态。或者调用FragmentTransaction的
remove()
、replace()
方法将碎片从活动中移除,但在事务提交之前并没有调用addToBackStack()
方法,碎片也会进入到销毁状态。
与Activity通信
-
如果Activity中包含自己管理的Fragment的引用,可以通过引用直接访问所有的Fragment的public方法
-
如果Activity中未保存任何Fragment的引用,那么没关系,每个Fragment都有一个唯一的TAG或者ID,可以通过
getFragmentManager.findFragmentByTag()
或者findFragmentById()
获得任何Fragment实例,然后进行操作 -
在Fragment中可以通过
getActivity
得到当前绑定的Activity的实例,然后进行操作。
Activity传Fragment
-
在Activity中创建Bundle数据包,调用Fragment实例的
setArguments()
,将Bundle数据包传给Fragment -
Fragment调用
getArguments()
获得Bundle对象,然后进行解析就可以
Fragment传Activity
- 在Fragment中定义一个内部回调接口,再让包含该Fragment的Activity实现该回调接口
- Fragment通过回调接口传数据
Fragment间通信
setArguments()
- 在Fragment B中新建一个函数:newInstance()接收传过来的参数
- 在Fragment B的onCreateView中获取参数
- 在Fragment A中,调用Fragment B时,通过newInstance函数获取实例并传递参数