由于直播项目的推流SDK,拉流SDK已经稳定。所以这段时间一直开发业务层新需求。业务层的核心灵魂就是层层的消息传递,今天就来总结一下andorid的应用层的各种消息传递。
1.线程间通讯 ——— Handler,HandlerThread等。
2.组件间通信 ——— BroadcastReceiver,接口回调等。
3. 第三方通信 ——— EventBus,rxBus
4.进程间通信 ——— Content Provider ,Broadcast ,AIDL等。
5.长连接推送 ——— WebSocket,XMPP等。
Handler
一个Android应用程序被创建的时候都会创建一个UI主线程,但是有时我们会有一些比较耗时的操作,为了防止阻塞UI主线程,我们会将耗时的操作放到子线程中进行处理,处理完之后操作UI,但是Android不允许子线程操作UI,违背了Android单线程模型的原则(即 Android UI操作并不是线程安全的并且这些操作必须在UI线程中执行),所以Android通过Handler消息机制来实现线程之间的通讯。
Handler机制主要角色
Message:消息,其中包含了消息ID,消息处理对象以及处理的数据等,由MessageQueue统一列队,终由Handler处理。
Handler:处理者,负责Message的发送及处理。使用Handler时,需要实现handleMessage(Message msg)方法来对特定的Message进行处理,例如更新UI等。
MessageQueue:消息队列,用来存放Handler发送过来的消息,并按照FIFO规则执行。当然,存放Message并非实际意义的保存,而是将Message以链表的方式串联起来的,等待Looper的抽取。
Looper:消息泵,不断地从MessageQueue中抽取Message执行。因此,一个MessageQueue需要一个Looper。
Thread:线程,负责调度整个消息循环,即消息循环的执行场所。
Handler机制主要运用
sendEmptyMessage(int);//发送一个空的消息
sendMessage(Message);//发送消息,消息中可以携带参数
sendMessageAtTime(Message, long);//未来某一时间点发送消息
sendMessageDelayed(Message, long);//延时Nms发送消息
post(Runnable);//提交计划任务马上执行
postAtTime(Runnable, long);//提交计划任务在未来的时间点执行
postDelayed(Runnable, long);//提交计划任务延时Nms执行
主线程定义Handler
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 0:
//完成主界面更新,拿到数据
String data = (String) msg.obj;
textView.setText(data);
break;
default:
break;
}
}
};
子线程执行耗时操作然后发消息,通知Handler完成UI更新
private void getDataFromNet() {
new Thread(new Runnable() {
@Override
public void run() {
//需要数据传递,用下面方法;
Message msg = new Message();
msg.obj = "网络数据";//可以是基本类型,可以是对象,可以是List、map等;
mHandler.sendMessage(msg);
}
}).start();
}
Handler机制扩展:
Activity.runOnUiThread(Runnable)
View.post(Runnable)
以上也可以从子线程切换到主线程。
HandlerThread:
HandlerThread本质上就是一个普通Thread,只不过内部建立了Looper.
HandlerThread用法实例
//创建一个线程,线程名字:handler-thread
myHandlerThread = new HandlerThread( "handler-thread") ;
//开启一个线程
myHandlerThread.start();
//在这个线程中创建一个handler对象 主要这个handler是在子线程中循环接受消息的
handler = new Handler( myHandlerThread.getLooper() ){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//这个方法是运行在 handler-thread 线程中的 ,可以执行耗时操作
Log.d( "handler " , "消息: " + msg.what + " 线程: " + Thread.currentThread().getName() ) ;
}
};
//在主线程给handler发送消息
handler.sendEmptyMessage( 1 ) ;
new Thread(new Runnable() {
@Override
public void run() {
//在子线程给handler发送数据
handler.sendEmptyMessage( 2 ) ;
}
}).start() ;
}
@Override
protected void onDestroy() {
super.onDestroy();
//释放资源
myHandlerThread.quit() ;
}
Looper的quit方法或quitSafely方法
相同点:
将不在接受新的事件加入消息队列。
不同点
当我们调用Looper的quit方法时,实际上执行了MessageQueue中的removeAllMessagesLocked方法,该方法的作用是把MessageQueue消息池中所有的消息全部清空,无论是延迟消息(延迟消息是指通过sendMessageDelayed或通过postDelayed等方法发送的需要延迟执行的消息)还是非延迟消息。
当我们调用Looper的quitSafely方法时,实际上执行了MessageQueue中的removeAllFutureMessagesLocked方法,通过名字就可以看出,该方法只会清空MessageQueue消息池中所有的延迟消息,并将消息池中所有的非延迟消息派发出去让Handler去处理,quitSafely相比于quit方法安全之处在于清空消息之前会派发所有的非延迟消息。
无论是调用了quit方法还是quitSafely方法只会,Looper就不再接收新的消息。即在调用了Looper的quit或quitSafely方法之后,消息循环就终结了,这时候再通过Handler调用sendMessage或post等方法发送消息时均返回false,表示消息没有成功放入消息队列MessageQueue中,因为消息队列已经退出了。
需要注意的是Looper的quit方法从API Level 1就存在了,但是Looper的quitSafely方法从API Level 18才添加进来。
我在直播推流中应用了HandlerThread,每编码组装出来一帧视频就发送一个handler消息,然后HandlerThread线程接收消息数据并用libRtmp推倒服务器。此时可以监控队列中有多少消息在循环,可以监听进出队列的比例,如果超出一定的范围说明网络不好,需要执行丢帧策略。
BroadcastReceiver与接口回调 是 EventBus,rxBus没出现之前的主要组件间通信方式。
BroadcastReceiver广播就不在介绍了, 广播传递本身是有安全隐患的,需要设置权限,每一个Activity都要定义、注册,解注册广播无形中加大了工作量和维护成本。已经不适应用在组件间通信。
接口回调: 和观察者模式大致一样。
实例:
public class DataSynManager {
private LinkedList autoListeners = new LinkedList();//监听集合
private static DataSynManager mInstance;//单例引用
/**
* 获取单例引用
*
* @return
*/
public static DataSynManager getInstance() {
if (mInstance == null) {
synchronized (DataSynManager.class) {
if (mInstance == null) {
mInstance = new DataSynManager();
}
}
}
return mInstance;
}
/**
* 添加同步数据监听
*/
public void registerDataSynListener(IDataSynListener autoDataListener) {
if (autoListeners == null) {
autoListeners = new LinkedList();
}
if (!autoListeners.contains(autoDataListener)) {
autoListeners.add(autoDataListener);
}
}
/**
* 移除同步数据监听
*/
public void unRegisterDataSynListener(IDataSynListener autoDataListener) {
if (autoListeners == null) {
return;
}
if (autoListeners.contains(autoDataListener)) {
autoListeners.remove(autoDataListener);
}
}
/**
* 执行数据同步
*
* @param count
*/
public void doDataSyn(final int count) {
if (autoListeners == null) {
autoListeners = new LinkedList();
}
new Handler().post(new Runnable() {
@Override
public void run() {
for (IDataSynListener dataSynListener : autoListeners) {
dataSynListener.onDataSyn(count);
}
}
});
}
/**
* 清除所有监听者
*/
public void release() {
if (autoListeners != null) {
autoListeners.clear();
autoListeners = null;
}
}
public interface IDataSynListener {
void onDataSyn(int count);
}
}
使用:
//添加监听
DataSynManager.getInstance().registerDataSynListener(dataSynListener);
//移除监听
DataSynManager.getInstance().unRegisterDataSynListener(dataSynListener//个监听);
DataSynManager.IDataSynListener dataSynListener=new DataSynManager.IDataSynListener() {
@Override
public void onDataSyn(int count) {
//接下来执行同步操作
}
}
};
//发送事件
DataSynManager.getInstance().doDa(5);
EventBus主要角色:
Event 传递的事件对象
Subscriber 事件的订阅者
Publisher 事件的发布者
ThreadMode 定义函数在何种线程中执行
定义一个事件类型
public class DataEvent {
private int count;
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
//订阅
EventBus.getDefault().register(this);//订阅
//解除订阅
EventBus.getDefault().unregister(this);//解除订阅
//发布事件
EventBus.getDefault().post(new DataEvent());
//订阅事件处理
@Subscribe(threadMode = ThreadMode.MAIN) //在ui线程执行
public void onDataEvent(DataEvent event) {
Log.e(TAG, "event---->" + event.getCount());
}
ThreadMode总共四个:
NAIN UI主线程
BACKGROUND 后台线程
POSTING 和发布者处在同一个线程
ASYNC 异步线程
事件的优先级类似广播的优先级,优先级越高优先获得消息
@Subscribe(threadMode = ThreadMode.MAIN,priority = 100) //在ui线程执行 优先级100
public void onDataEvent(DataEvent event) {
Log.e(TAG, "event---->" + event.getCount());
}
终止事件往下传递
发送有序广播可以终止广播的继续往下传递,EventBus也实现了此功能
EventBus.getDefault().cancelEventDelivery(event) ;//优先级高的订阅者可以终止事件往下传递
EventBus黏性事件
何为黏性事件呢?简单讲,就是在发送事件之后再订阅该事件也能收到该事件,跟黏性广播类似。
本身粘性广播用的就比较少,为了方便理解成订阅在发布事件之后,但同样可以收到事件。订阅/解除订阅和普通事件一样,但是处理订阅函数有所不同,需要注解中添加sticky = true
@Subscribe(threadMode = ThreadMode.MAIN,sticky = true) //在ui线程执行
public void onDataEvent(DataEvent event) {
Log.e(TAG, "event---->" + event.getCount());
}
//发送粘性事件
EventBus.getDefault().postSticky(new DataEvent());
//对于粘性广播我们都比较清楚属于常驻广播,对于EventBus粘性事件也类似,我们如果不再需要该粘性事件我们可以移除
EventBus.getDefault().removeStickyEvent(new DataEvent());
//或者调用移除所有粘性事件
EventBus.getDefault().removeAllStickyEvents();
RXBus:如果项目中用了rxjava的话可以参考https://github.com/AndroidKnife/RxBus 自己封装一个。
Content Provider
Android应用程序可以使用文件或SqlLite数据库来存储数据。Content Provider提供了一种在多个应用程序之间数据共享的方式(跨进程共享数据)。应用程序可以利用Content Provider完成下面的工作
1. 查询数据
2. 修改数据
3. 添加数据
4. 删除数据
虽然Content Provider也可以在同一个应用程序中被访问,但这么做并没有什么意义。Content Provider存在的目的向其他应用程序共享数据和允许其他应用程序对数据进行增、删、改操作。
Android系统本身提供了很多Content Provider,例如,音频、视频、联系人信息等等。我们可以通过这些Content Provider获得相关信息的列表。这些列表数据将以Cursor对象返回。因此,从Content Provider返回的数据是二维表的形式。
广播(Broadcast)
广播是一种被动跨进程通讯的方式。当某个程序向系统发送广播时,其他的应用程序只能被动地接收广播数据。这就象电台进行广播一样,听众只能被动地收听,而不能主动与电台进行沟通。
在应用程序中发送广播比较简单。只需要调用sendBroadcast方法即可。该方法需要一个Intent对象。通过Intent对象可以发送需要广播的数据。
AIDL Service
这是我个人比较推崇的方式,因为它相比Broadcast而言,虽然实现上稍微麻烦了一点,但是它的优势就是不会像广播那样在手机中的广播较多时会有明显的时延,甚至有广播发送不成功的情况出现。
注意普通的Service并不能实现跨进程操作,实际上普通的Service和它所在的应用处于同一个进程中,而且它也不会专门开一条新的线程,因此如果在普通的Service中实现在耗时的任务,需要新开线程。
要实现跨进程通信,需要借助AIDL(Android Interface Definition Language)。Android中的跨进程服务其实是采用C/S的架构,因而AIDL的目的就是实现通信接口。
AIDL具体使用可参考http://www.jianshu.com/p/d1fac6ccee98
Websocket
支持客户端和服务器端的双向通信,而且协议的头部又没有HTTP的Header那么大,于是,Websocket就诞生了!
Websocket是应用层第七层上的一个应用层协议,它必须依赖 HTTP 协议进行一次握手 ,握手成功后,数据就直接从 TCP 通道传输,与 HTTP 无关了。
Websocket的数据传输是frame形式传输的,比如会将一条消息分为几个frame,按照先后顺序传输出去。这样做会有几个好处:
1 大数据的传输可以分片传输,不用考虑到数据大小导致的长度标志位不足够的情况。
2 和http的chunk一样,可以边生成数据边传递消息,即提高传输效率。
XMPP中定义了三个角色,客户端,服务器,网关。通信能够在这三者的任意两个之间双向发生。服务器同时承担了客户端信息记录,连接管理和信息的路由功能。网关承担着与异构即时通信系统的互联互通,异构系统可以包括SMS(短信),MSN,ICQ等。基本的网络形式是单客户端通过TCP/IP连接到单服务器,然后在之上传输XML。
QQ是使用类似XMPP协议的UDP协议进行发送和接收消息的。当你的机器安装了QQ以后,实际上,你既是服务端(Server),又是客户端(Client)。当你登录QQ时,你的QQ作为Client连接到腾讯公司的主服务器上,当你看谁在线时,你的QQ又一次作为Client从QQ Server上读取在线网友名单。当你和你的QQ伙伴进行聊天时,如果你和对方的连接比较稳定,你和他的聊天内容都是以UDP的形式,在计算机之间传 送。如果你和对方的连接不是很稳定,QQ服务器将为你们的聊天内容进行中转。其他的即时通信软件原理与此大同小异。
所以基于WebSocke和XMPP都可以开发出IM社交聊天类的app