详解Android线程池ThreadPoolExecutor的教程
一、获取线程池的三种方法:
1、ExecutorService pool = Executors.newFixedThreadPool
创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大多数 nThreads 线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之前,池中的线程将一直存在。
2、ExecutorService pool = Executors.newSingleThreadExecutor
创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。(注意,如果因为在关闭前的执行期间出现失败而终止了此单个线程,那么如果需要,一个新线程将代替它执行后续的任务)。可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。与其他等效的 newFixedThreadPool(1) 不同,可保证无需重新配置此方法所返回的执行程序即可使用其他的线程。
3、ExecutorService pool = Executors.newCachedThreadPool
创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能。调用 execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源。注意,可以使用 ThreadPoolExecutor 构造方法创建具有类似属性但细节不同(例如超时参数)的线程池。
二、线程池类 java.util.concurrent.ThreadPoolExecutor讲解
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime, TimeUnit unit,BlockingQueue workQueue,RejectedExecutionHandler handler)
参数说明
corePoolSize: 线程池维护线程的最少数量
maximumPoolSize:线程池维护线程的最大数量
keepAliveTime: 线程池维护线程所允许的空闲时间
unit: 线程池维护线程所允许的空闲时间的单位
workQueue: 线程池所使用的缓冲队列
handler: 线程池对拒绝任务的处理策略
线程创建规则
一个任务通过 execute(Runnable)方法被添加到线程池,任务就是一个 Runnable类型的对象,任务的执行方法就是 Runnable类型对象的run()方法。
当一个任务通过execute(Runnable)方法欲添加到线程池时:
l 如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
2 如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。
3 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。
4 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。也就是:处理任务的优先级为:核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。
5 当线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。
核心和最大池大小
ThreadPoolExecutor 将根据 corePoolSize(参见 getCorePoolSize())和 maximumPoolSize(参见 getMaximumPoolSize())设置的边界自动调整池大小。当新任务在方法execute(java.lang.Runnable) 中提交时,如果运行的线程少于 corePoolSize,则创建新线程来处理请求,即使其他辅助线程是空闲的。如果运行的线程多于 corePoolSize 而少于 maximumPoolSize,则仅当队列满时才创建新线程。如果设置的 corePoolSize 和 maximumPoolSize 相同,则创建了固定大小的线程池。如果将 maximumPoolSize 设置为基本的无界值(如 Integer.MAX_VALUE),则允许池适应任意数量的并发任务。在大多数情况下,核心和最大池大小仅基于构造来设置,不过也可以使用setCorePoolSize(int) 和 setMaximumPoolSize(int) 进行动态更改。
创建新线程
使用 ThreadFactory 创建新线程。如果没有另外说明,则在同一个 ThreadGroup 中一律使用 Executors.defaultThreadFactory() 创建线程,并且这些线程具有相同的NORM_PRIORITY 优先级和非守护进程状态。通过提供不同的 ThreadFactory,可以改变线程的名称、线程组、优先级、守护进程状态,等等。如果从 newThread 返回null 时 ThreadFactory 未能创建线程,则执行程序将继续运行,但不能执行任何任务。如果池中当前有多于 corePoolSize 的线程,则这些多出的线程在空闲时间超过 keepAliveTime 时将会终止(参见 getKeepAliveTime(java.util.concurrent.TimeUnit))。这提供了当池处于非活动状态时减少资源消耗的方法。如果池后来变得更为活动,则可以创建新的线程。也可以使用方法 setKeepAliveTime(long, java.util.concurrent.TimeUnit) 动态地更改此参数。使用 Long.MAX_VALUE TimeUnit.NANOSECONDS 的值在关闭前有效地从以前的终止状态禁用空闲线程。
排队
所有 BlockingQueue 都可用于传输和保持提交的任务。可以使用此队列与池大小进行交互:
A. 如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。
B. 如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。
C. 如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。
execute()执行
execute()方法中,调用了三个私有方法
addIfUnderCorePoolSize():在线程池大小小于核心线程池大小的情况下,扩展线程池
addIfUnderMaximumPoolSize():在线程池大小小于线程池大小上限的情况下,扩展线程池
ensureQueuedTaskHandled():保证在线程池关闭的情况下,新加入队列的线程也能正确处理
线程池的停止和关闭
ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:
shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
一、背景
在开发Android应用程序的实现,有时候需要引入第三方so lib库,但第三方so库比较大,例如开源第三方播放组件ffmpeg库, 如果直接打包的apk包里面, 整个应用程序会大很多.经过查阅资料和实验,发现通过远程下载so文件,然后再动态注册so文件时可行的。主要需要解决下载so文件存放位置以及文件读写权限问题。
二、主要思路
1、首先把so放到网络上面,比如测试放到:http://codestudy.sinaapp.com//lib/test.so
2、应用启动时,启动异步线程下载so文件,并写入到/data/data/packageName/app_libs目录下面
3、调用System.load 注册so文件。因路径必须有执行权限,我们不能加载SD卡上的so,但可以通过调用context.getDir("libs", Context.MODE_PRIVATE)把so文件写入到应用程序的私有目录/data/data/packageName/app_libs。
三、代码实现
1、网络下载so文件,并写入到应用程序的私有目录/data/data/PackageName/app_libs
代码如下 | 复制代码 |
/** * 下载文件到/data/data/PackageName/app_libs下面 * @param context * @param url * @param fileName * @return */ public static File downloadHttpFileToLib(Context context, String url, String fileName) { long start = System.currentTimeMillis(); FileOutputStream outStream = null; InputStream is = null; File soFile = null; try { HttpClient client = new DefaultHttpClient(); HttpGet get = new HttpGet(url); HttpResponse response = client.execute(get); HttpEntity entity = response.getEntity(); File dir = context.getDir("libs", Context.MODE_PRIVATE); soFile = new File(dir, fileName); outStream = new FileOutputStream(soFile); is = entity.getContent(); if (is != null) { byte[] buf = new byte[1024]; int ch = -1; while ((ch = is.read(buf)) > 0) { outStream.write(buf, 0, ch); //Log.d(">>>httpDownloadFile:", "download 进行中...."); } } outStream.flush(); long end = System.currentTimeMillis(); Log.d(">>>httpDownloadFile cost time:", (end-start)/1000 + "s"); Log.d(">>>httpDownloadFile:", "download success"); return soFile; } catch (IOException e) { Log.d(">>>httpDownloadFile:", "download failed" + e.toString()); return null; } finally { if (outStream != null) { try { outStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (is != null) { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } } } |
2、调用System.load 注册so文件
代码如下 | 复制代码 |
new Thread(new Runnable() { @Override public void run() { File soFile = FileUtils.downloadHttpFileToLib(getApplicationContext(), "http://codestudy.sinaapp.com//lib/test.so", "test.so"); if (soFile != null) { try { Log.d(">>>loadAppFile load path:", soFile.getAbsolutePath()); System.load(soFile.getAbsolutePath()); } catch (Exception e) { Log.e(">>>loadAppFile load error:", "so load failed:" + e.toString()); } } } }).start(); |
四、需要解决的问题
1、so文件下载以及注册时机。测试发现libffmpeg.so 8M的文件单线程下载需要10-13s左右
2、so下载失败或者注册失败该怎么处理。例如so播放组件是否尝试采用android系统原生MediaPlayer进行播放
3、当初次so还没有下载完注册成功时,进入播放页面时,需要友好提示用户,比如loading 视频正在加载等等
4、无网络情况等等情况
五、说明
上面的demo经过3(2.3/4.2/4.4)实际机型测试可以正常使用,然后根据第四点列举问题完善以下,即可使用。
临时接受支付宝支付任务,最初研究旧版本,后来发现新版本更简单明了优化,使用最新版的,看见旧版的写出来的人多,新版的少,咱这最精炼的通过实践滴,与大家共同进步。
1.下载移动支付接口SDK2.0标准版,解压取出:
(1)从客户端alipay-sdk-common文件夹中取出alipaysdk.jar 、alipaysecsdk.jar 、alipayutdid.jar放入新建项目libs中,android4.0之后只要放入免手动导入,低于这个版本的按旧方法手动导入。
(2)从客户端Demo中取出Base64.java 、Result.java、SignUtils.java放在src中,对应支持的。
2.权限开通:
代码如下 | 复制代码 |
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.WRITE_APN_SETTINGS" /> |
3.支付接口调用:
代码如下 | 复制代码 |
/** * 通过支付宝支付订单 *void * @exception * @since 1.0.0 */ public void pay(final String orderInfo, final String sign){ threadManager.startTaskThread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub // 构造PayTask 对象 PayTask alipay = new PayTask(activity); //拼接成完整支付信息(订单+签名) final String payInfo = orderInfo + "&sign="" + sign + """ + "&" + getSignType(); // 调用支付接口 String result = alipay.pay(payInfo); payResult = Base64.encode(result.getBytes()); Result rtl = new Result(result); //解析支付结果 //TextUtils.equals(resultStatus, "9000") //支付结果错误码: /*9000:订单支付成功 *8000:正在处理中("支付结果确认中") 代表支付结果因为支付渠道原因或者系统原因还在等待支付结果确认,最终交易是否成功以服务端异步通知为准(小概率状态) *4000:订单支付失败 *6001:用户中途取消 *6002:网络连接出错 */ final String resultStatus = rtl.resultStatus; //支付错误码 GoloLog.d(ALIPAY_PAY_KEY, "get alipay result status: "+ resultStatus); if (activity != null) { activity.runOnUiThread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub if (TextUtils.equals(resultStatus, "9000")) { //9000:订单支付成功 Toast.makeText(activity, "支付成功", Toast.LENGTH_SHORT).show(); }else { // 判断resultStatus 为非“9000”则代表可能支付失败 // “8000” 代表支付结果因为支付渠道原因或者系统原因还在等待支付结果确认,最终交易是否成功以服务端异步通知为准(小概率状态) if (TextUtils.equals(resultStatus, "8000")) { Toast.makeText(activity, "支付结果确认中", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(activity, "支付失败", Toast.LENGTH_SHORT).show(); } } } }); } } }); } |
4.个人界面调用:
orderInfo 订单内容
strsign 签名
代码如下 | 复制代码 |
//支付宝支付 private void toAlipay() { // TODO Auto-generated method stub String orderInfo = OrderBean.getOrderInfo(); String strsign = OrderBean.getOrderSign(); alipayPayHandler.pay(orderInfo, strsign); } |
项目需要,在ListView中显示多张图片,用到了GridView,不过如果使用普通的GridView,Item仅仅只是显示一部分,超出第一行以后的都无法显示了,这个很无语,所以又得继承下GridView重写onMeasure方法去测量子控件的宽高了..
这里只是贴出自定义GridView的代码,直接在xml中使用,ListView的Adapter中调用即可:
代码如下 | 复制代码 |
public class GridViewForListView extends GridView { public GridViewForListView(Context context) { super(context); } public GridViewForListView(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); super.onMeasure(widthMeasureSpec, expandSpec); } } |
概述及基本概念
**EventBus**是一个Android端优化的publish/subscribe消息总线,简化了应用程序内各组件间、组件与后台线程间 的通信。比如请求网络,等网络返回时通过Handler或Broadcast通知UI,两个Fragment之间需要通过Listener通信,这些需求 都可以通过**EventBus**实现。
作为一个消息总线,有三个主要的元素:
Event:事件
Subscriber:事件订阅者,接收特定的事件
Publisher:事件发布者,用于通知Subscriber有事件发生
Event
**Event**可以是任意类型的对象。
Subscriber
在EventBus中,使用约定来指定事件订阅者以简化使用。即所有事件订阅都都是以onEvent开头的函数,具体来说,函数的名字是 onEvent,onEventMainThread,onEventBackgroundThread,onEventAsync这四个,这个和 ThreadMode有关,后面再说。
Publisher
可以在任意线程任意位置发送事件,直接调用EventBus的`post(Object)`方法,可以自己实例化EventBus对象,但一般使用 默认的单例就好了:`EventBus.getDefault()`,根据post函数参数的类型,会自动调用订阅相应类型事件的函数。
ThreadMode
前面说了,Subscriber函数的名字只能是那4个,因为每个事件订阅函数都是和一个`ThreadMode`相关联的,ThreadMode指定了会调用的函数。有以下四个ThreadMode:
PostThread:事件的处理在和事件的发送在相同的进程,所以事件处理时间不应太长,不然影响事件的发送线程,而这个线程可能是UI线程。对应的函数名是onEvent。
MainThread: 事件的处理会在UI线程中执行。事件处理时间不能太长,这个不用说的,长了会ANR的,对应的函数名是onEventMainThread。
BackgroundThread:事件的处理会在一个后台线程中执行,对应的函数名是onEventBackgroundThread,虽然名 字是BackgroundThread,事件处理是在后台线程,但事件处理时间还是不应该太长,因为如果发送事件的线程是后台线程,会直接执行事件,如果 当前线程是UI线程,事件会被加到一个队列中,由一个线程依次处理这些事件,如果某个事件处理时间太长,会阻塞后面的事件的派发或处理。
Async:事件处理会在单独的线程中执行,主要用于在后台线程中执行耗时操作,每个事件会开启一个线程(有线程池),但最好限制线程的数目。
根据事件订阅都函数名称的不同,会使用不同的ThreadMode,比如果在后台线程加载了数据想在UI线程显示,订阅者只需把函数命名为onEventMainThread。
简单使用
基本的使用步骤就是如下4步,点击此链接查看例子及介绍。
定义事件类型:
`public class MyEvent {}`
定义事件处理方法:
`public void onEventMainThread`
注册订阅者:
`EventBus.getDefault().register(this)`
发送事件:
`EventBus.getDefault().post(new MyEvent())`
实现
**EventBus**使用方法很简单,但用一个东西,如果不了解它的实现用起来心里总是没底,万一出问题咋办都不知道,所以还是研究一下它的实 现,肯定要Read the fucking Code。其实主要是`EventBus`这一个类,在看看Code时需要了解几个概念与成员,了解了这些后实现就很好理解了。
EventType:onEvent*函数中的参数,表示事件的类型
Subscriber:订阅源,即调用register注册的对象,这个对象内包含onEvent*函数
SubscribMethod:`Subscriber`内某一特定的onEvent*方法,内部成员包含一个`Method`类型的 method成员表示这个onEvent*方法,一个`ThreadMode`成员threadMode表示事件的处理线程,一个 `Class<?>`类型的eventType成员表示事件的类型`EventType`。
Subscription,表示一个订阅对象,包含订阅源`Subscriber`,订阅源中的某一特定方法`SubscribMethod`,这个订阅的优先级`priopity`
了解了以上几个概念后就可以看`EventBus`中的几个重要成员了
代码如下 | 复制代码 |
// EventType -> List<Subscription>,事件到订阅对象之间的映射 private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType; // Subscriber -> List<EventType>,订阅源到它订阅的的所有事件类型的映射 private final Map<Object, List<Class<?>>> typesBySubscriber; // stickEvent事件,后面会看到 private final Map<Class<?>, Object> stickyEvents; // EventType -> List<? extends EventType>,事件到它的父事件列表的映射。即缓存一个类的所有父类 private static final Map<Class<?>, List<Class<?>>> eventTypesCache = new HashMap<Class<?>, List<Class<?>>>(); |
注册事件:Register
通过`EventBus.getDefault().register`方法可以向`EventBus`注册来订阅事件,`register`有很 多种重载形式,但大都被标记为`Deprecated`了,所以还是不用为好,前面说了事件处理方法都是以*onEvent*开头,其实是可以通过 register方法修改的,但相应的方法被废弃了,还是不要用了,就用默认的*onEvent*,除下废弃的register方法,还有以下4 个**public**的`register`方法
代码如下 | 复制代码 |
public void register(Object subscriber) { register(subscriber, defaultMethodName, false, 0); } public void register(Object subscriber, int priority) { register(subscriber, defaultMethodName, false, priority); } public void registerSticky(Object subscriber) { register(subscriber, defaultMethodName, true, 0); } public void registerSticky(Object subscriber, int priority) { register(subscriber, defaultMethodName, true, priority); } |
可以看到,这4个方法都调用了同一个方法:
代码如下 | 复制代码 |
private synchronized void register(Object subscriber, String methodName, boolean sticky, int priority) { List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass(), methodName); for (SubscriberMethod subscriberMethod : subscriberMethods) { subscribe(subscriber, subscriberMethod, sticky, priority); } } |
第一个参数就是订阅源,第二个参数就是用到指定方法名约定的,默认为*onEvent*开头,说默认是其实是可以通过参数修改的,但前面说了,方法已被废弃,最好不要用。第三个参数表示是否是*Sticky Event*,第4个参数是优先级,这两个后面再说。
在上面这个方法中,使用了一个叫`SubscriberMethodFinder`的类,通过其`findSubscriberMethods`方 法找到了一个`SubscriberMethod`列表,前面知道了`SubscriberMethod`表示Subcriber内一个 onEvent*方法,可以看出来`SubscriberMethodFinder`类的作用是在Subscriber中找到所有以 methodName(即默认的onEvent)开头的方法,每个找到的方法被表示为一个`SubscriberMethod`对象。
`SubscriberMethodFinder`就不再分析了,但有两点需要知道:
所有事件处理方法**必需是`public void`类型**的,并且只有一个参数表示*EventType*。
`findSubscriberMethods`不只查找*Subscriber*内的事件处理方法,**同时还会查到它的继承体系中的所有基类中的事件处理方法**。
找到*Subscriber*中的所有事件处理方法后,会对每个找到的方法(表示为`SubscriberMethod`对象)调用`subscribe`方法注册。`subscribe`方法干了三件事:
根据`SubscriberMethod`中的*EventType*类型将`Subscribtion`对象存放在`subscriptionsByEventType`中。建立*EventType*到*Subscription*的映射,每个事件可以有多个订阅者。
根据`Subscriber`将`EventType`存放在`typesBySubscriber`中,建立*Subscriber*到*EventType*的映射,每个Subscriber可以订阅多个事件。
如果是*Sticky*类型的订阅者,直接向它发送上个保存的事件(如果有的话)。
通过*Subscriber*到*EventType*的映射,我们就可以很方便地使一个Subscriber取消接收事件,通过*EventType*到*Sucscribtion*的映射,可以方便地将相应的事件发送到它的每一个订阅者。
Post事件
直接调用`EventBus.getDefault().post(Event)就可以发送事件,根据Event的类型就可以发送到相应事件的订阅者。
代码如下 | 复制代码 |
public void post(Object event) { PostingThreadState postingState = currentPostingThreadState.get(); List<Object> eventQueue = postingState.eventQueue; eventQueue.add(event); if (postingState.isPosting) { return; } else { postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper(); postingState.isPosting = true; if (postingState.canceled) { throw new EventBusException("Internal error. Abort state was not reset"); } try { while (!eventQueue.isEmpty()) { postSingleEvent(eventQueue.remove(0), postingState); } } finally { postingState.isPosting = false; postingState.isMainThread = false; } } } |
可以看到post内使用了`PostingThreadState`的对象,并且是`ThreadLocal`,来看`PostingThreadState`的定义:
代码如下 | 复制代码 |
final static class PostingThreadState { List<Object> eventQueue = new ArrayList<Object>(); boolean isPosting; boolean isMainThread; Subscription subscription; Object event; boolean canceled; } |
主要是有个成员`eventQueue`,由于是ThreadLocal,所以 结果就是,每个线程有一个`PostingThreadState`对象,这个对象内部有一个事件的队列,并且有一个成员`isPosting`表示现在 是否正在派发事件,当发送事件开始时,会依次取出队列中的事件发送出去,如果正在派发事件,那么post直接把事件加入队列后返回,还有个成员 `isMainThread`,这个成员在实际派发事件时会用到,在`postSingleEvent`中会用到。
代码如下 | 复制代码 |
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error { Class<? extends Object> eventClass = event.getClass(); List<Class<?>> eventTypes = findEventTypes(eventClass); // 1 boolean subscriptionFound = false; int countTypes = eventTypes.size(); for (int h = 0; h < countTypes; h++) { // 2 Class<?> clazz = eventTypes.get(h); CopyOnWriteArrayList<Subscription> subscriptions; synchronized (this) { subscriptions = subscriptionsByEventType.get(clazz); } if (subscriptions != null && !subscriptions.isEmpty()) { // 3 for (Subscription subscription : subscriptions) { postingState.event = event; postingState.subscription = subscription; boolean aborted = false; try { postToSubscription(subscription, event, postingState.isMainThread); // 4 aborted = postingState.canceled; } finally { postingState.event = null; postingState.subscription = null; postingState.canceled = false; } if (aborted) { break; } } subscriptionFound = true; } } if (!subscriptionFound) { Log.d(TAG, "No subscribers registered for event " + eventClass); if (eventClass != NoSubscriberEvent.class && eventClass != SubscriberExceptionEvent.class) { post(new NoSubscriberEvent(this, event)); } } } |
来看一下`postSingleEvent`这个函数,首先看第一点,调用了`findEventTypes`这个函数,代码不帖了,这个函数的应用就是,把这个类的类对象、实现的接口及父类的类对象存到一个List中返回.
接下来进入第二步,遍历第一步中得到的List,对List中的每个类对象(即事件类型)执行第三步操作,即找到这个事件类型的所有订阅者向其发送 事件。可以看到,**当我们Post一个事件时,这个事件的父事件(事件类的父类的事件)也会被Post,所以如果有个事件订阅者接收Object类型的 事件,那么它就可以接收到所有的事件**。
还可以看到,实际是通过第四步中的`postToSubscription`来发送事件的,在发送前把事件及订阅者存入了`postingState`中。再来看`postToSubscription`
代码如下 | 复制代码 |
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) { switch (subscription.subscriberMethod.threadMode) { case PostThread: invokeSubscriber(subscription, event); break; case MainThread: if (isMainThread) { invokeSubscriber(subscription, event); } else { mainThreadPoster.enqueue(subscription, event); } break; case BackgroundThread: if (isMainThread) { backgroundPoster.enqueue(subscription, event); } else { invokeSubscriber(subscription, event); } break; case Async: asyncPoster.enqueue(subscription, event); break; default: throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode); } } |
这里就用到`ThreadMode`了:
如果是PostThread,直接执行
如果是MainThread,判断当前线程,如果本来就是UI线程就直接执行,否则加入`mainThreadPoster`队列
如果是后台线程,如果当前是UI线程,加入`backgroundPoster`队列,否则直接执行
如果是Async,加入`asyncPoster`队列
BackgroundPoster
代码如下 | 复制代码 |
private final PendingPostQueue queue; public void enqueue(Subscription subscription, Object event) { PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event); synchronized (this) { queue.enqueue(pendingPost); if (!executorRunning) { executorRunning = true; EventBus.executorService.execute(this); } } } |
代码比较简单,其实就是,待发送的事件被封装成了`PendingPost`对 象,`PendingPostQueue`是一个`PendingPost`对象的队列,当`enqueue`时就把这个事件放到队列 中,`BackgroundPoster`其实就是一个Runnable对象,当`enqueue`时,如果这个Runnable对象当前没被执行,就将 `BackgroundPoster`加入EventBus中的一个线程池中,当`BackgroundPoster`被执行时,会依次取出队列中的事件 进行派发。当长时间无事件时`BackgroundPoster`所属的线程被会销毁,下次再Post事件时再创建新的线程。
HandlerPoster
`mainThreadPoster`是一个`HandlerPoster`对象,`HandlerPoster`继承自`Handler`,构造 函数中接收一个`Looper`对象,当向`HandlerPoster` enqueue事件时,会像`BackgroundPoster`一样把这个事件加入队列中, 只是如果当前没在派发消息就向自身发送Message
代码如下 | 复制代码 |
void enqueue(Subscription subscription, Object event) { PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event); synchronized (this) { queue.enqueue(pendingPost); if (!handlerActive) { handlerActive = true; if (!sendMessage(obtainMessage())) { throw new EventBusException("Could not send handler message"); } } } } |
在`handleMessage`中会依次取出队列中的消息交由 `EventBus`直接调用事件处理函数,而`handleMessage`执行所在的线程就是构造函数中传进来的`Looper`所属的线程,在 `EventBus`中构造`mainThreadPoster`时传进来的是MainLooper,所以会在UI线程中执行。
AsyncPoster
`AsyncPoster`就简单了,把每个事件都加入线程池中处理
代码如下 | 复制代码 |
public void enqueue(Subscription subscription, Object event) { PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event); queue.enqueue(pendingPost); EventBus.executorService.execute(this); } |
Stick Event
通过`registerSticky`可以注册Stick事件处理函数,前面我们知道了,无论是`register`还是`registerSticky`最后都会调用`Subscribe`函数,在`Subscribe`中有这么一段代码:
也就是会根据事件类型从`stickyEvents`中查找是否有对应的事件,如果有,直接发送这个事件到这个订阅者。而这个事件是什么时候存起来 的呢,同`register`与`registerSticky`一样,和`post`一起的还有一个`postSticky`函数:
代码如下 | 复制代码 |
if (sticky) { Object stickyEvent; synchronized (stickyEvents) { stickyEvent = stickyEvents.get(eventType); } if (stickyEvent != null) { // If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state) // --> Strange corner case, which we don't take care of here. postToSubscription(newSubscription, stickyEvent, Looper.getMainLooper() == Looper.myLooper()); } } |
当通过`postSticky`发送一个事件时,这个类型的事件的最后一次事件会被缓存起来,当有订阅者通过`registerSticky`注册时,会把之前缓存起来的这个事件直接发送给它。
事件优先级Priority
`register`的函数重载中有一个可以指定订阅者的优先级,我们知道`EventBus`中有一个事件类型到 List<Subscription>的映射,在这个映射中,所有的Subscription是按priority排序的,这样当post事 件时,优先级高的会先得到机会处理事件。
优先级的一个应用就事,高优先级的事件处理函数可以终于事件的传递,通过`cancelEventDelivery`方法,但有一点需要注意,`这个事件的ThreadMode必须是PostThread`,并且只能终于它在处理的事件。
# 缺点
无法进程间通信,如果一个应用内有多个进程的话就没办法了
# 注意事项及要点
同一个onEvent函数不能被注册两次,所以不能在一个类中注册同时还在父类中注册
当Post一个事件时,这个事件类的父类的事件也会被Post。
Post的事件无Subscriber处理时会Post `NoSubscriberEvent`事件,当调用Subscriber失败时会Post `SubscriberExceptionEvent`事件。
其他
`EventBus`中还有个Util包,主要作用是可以通过`AsyncExecutor`执行一个Runnable,通过内部的 RunnableEx(可以搜索异常的Runnable)当Runnable抛出异常时通过`EventBus`发消息显示错误对话框。没太大兴趣,不作 分析
项目主页:https://github.com/greenrobot/EventBus
一个很简单的Demo,Activity中包含列表和详情两个Fragment,Activity启动时加载一个列表,点击列表后更新详情数据:EventBusDemo
相关文章
- 今天小编在这里就来给Painter的这一款软件的使用者们来说一说绘制红衣喝酒男水粉画效果的教程,各位想知道具体绘制步骤的使用者,那么下面就快来跟着小编一起看一看教程...2016-09-14
iPhone6怎么激活?两种苹果iPhone6激活教程图文详解
iPhone6新机需要激活后才可以正常使用,那么对于小白用户来说,iPhone6如何激活使用呢?针对此问题,本文就为大家分别介绍Wifi无线网络激活以及iPhone6连接电脑激活这两种有效的方法,希望本文能够帮助到大家...2022-09-14- 今天小编在这里就来给各位photoshop的这一款软件的使用者们来说下制作雨中野外孤独行走的一头牛海报的教程,各位想知道具体制作方法的使用者们,大家就快来看一看小编给...2016-09-14
- 今天小编在这里就来给Painter的这一款软件的使用者们来说一下绘制帅气卡通魔法王子漫画的具体教程,各位想知道绘制步骤的使用者,那么下面就快来跟着小编一起看一看教程...2016-09-14
- 今天小编在这里就来给各位Illustrator的这一款软件的使用者们来说说鼠绘堆雪人的孩童矢量插画的教程,各位想知道具体绘制方法的使用者们,那么各位就快来跟着小编来看看...2016-09-14
基于springcloud异步线程池、高并发请求feign的解决方案
这篇文章主要介绍了基于springcloud异步线程池、高并发请求feign的解决方案,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-02-25- 支付宝支付在国内算是大家了,我们到处都可以使用支付宝了,下文整理介绍的是在安卓app应用中使用支付宝进行支付的开发例子。 之前讲了一篇博客关与支付宝集成获取...2016-09-20
- 今天小编在这里就来给llustrator的这一款软件的使用者们来说一说绘制扁平化风格卡通警察护士空姐肖像的教程,各位想知道具体绘制步骤的使用者们,那么下面就快来跟着小编...2016-09-14
- 今天小编在这里就来给美图秀秀的这一款软件的使用者们来说下究竟该怎么给照片天空加蓝天白云的教程,各位想知道具体制作步骤的,那么下面就来跟着小编一起看看吧。 ...2016-09-14
- 今天小编在这里就来给Illustrator的这一款软件的使用者们来说一下绘制一个方形的录音机图标的教程,各位想知道具体绘制方法的使用者们,那么下面就来看一下小编给大家分...2016-09-14
- 今天小编在这里就来给photoshop的这一款软件的使用者们来说一说简单制作一个搞笑的换脸表情包的教程,各位想知道具体制作方法的使用者们,那么大家就快来看一看教程吧。...2016-09-14
- 今天小编在这里就来给美图秀秀的这一款软件的使用者们来说一下让你胸丰满起来的处理教程,各位想知道具体处理步骤的,那么下面就快来跟着小编一起看一下教程吧。 给...2016-09-14
- 今天小编在这里就来给各位photoshop的这一款软件的使用者们来说说给手绘画调色变换场景的后期教程,各位想知道具体后期处理步骤的使用者们,那么大家就快来跟着小编来看...2016-10-02
- 下面我们来看一篇关于Android子控件超出父控件的范围显示出来方法,希望这篇文章能够帮助到各位朋友,有碰到此问题的朋友可以进来看看哦。 <RelativeLayout xmlns:an...2016-10-02
- 今天小编在这里就来给Painter的这一款软件的使用者们来说一下绘制雷神传插画的教程,各位想知道具体绘制步骤的使用者,那么下面就快来跟着小编一起看看绘制方法吧。 ...2016-09-14
Android开发中findViewById()函数用法与简化
findViewById方法在android开发中是获取页面控件的值了,有没有发现我们一个页面控件多了会反复研究写findViewById呢,下面我们一起来看它的简化方法。 Android中Fin...2016-09-20- 如果我们的项目需要做来电及短信的功能,那么我们就得在Android模拟器开发这些功能,本来就来告诉我们如何在Android模拟器上模拟来电及来短信的功能。 在Android模拟...2016-09-20
- 联合索引又叫复合索引。对于复合索引:Mysql从左到右的使用索引中的字段,一个查询可以只使用索引中的一部份,但只能是最左侧部分。例如索引是key index (a,b,c). 可以支持a | a,b| a,b,c 3种组合进行查找,但不支持 b,c进...2015-11-24
- 夜神android模拟器如何设置代理呢?对于这个问题其实操作起来是非常的简单,下面小编来为各位详细介绍夜神android模拟器设置代理的方法,希望例子能够帮助到各位。 app...2016-09-20
- 为了增强android应用的用户体验,我们可以在一些Button按钮上自定义动态的设置一些样式,比如交互时改变字体、颜色、背景图等。 今天来看一个通过重写Button来动态实...2016-09-20