写在前面:如果你看到这篇文章,希望你能够自己对照着源码走一遍,直接反射或者使用索引都会详细分析使用的!
EventBus优点和特性
也许你有个疑问: 市面上有很多事件传递框架为何选择EventBus,而不是Otto , Rxbus等 ,相比较他们都可以满足日常开发需求,只是后两者更新基本停滞,而EventBus还在正常更新维护,选择接入人数也是最多的,同时功能也是最全的,如果你选择了使用该框架,自然了解源码的运行对于调试绝对是有帮助的!
我们学知识不能简简单单只是会用而已,要知其然而知其所以然才能进步,提升自己~
优点
简化组件间的通信,解耦事件发送和接受者
很好的应用于Activity,Fragment之间,后台线程之间的通信,避免使用多个Intent传递,handler通信等
避免复杂的,易于出错的依赖和生命周期问题,比如多个Activity跳转后的回调问题
速度响应快,尤其是3.0以后再编译器增加索引
轻量大概50K左右的jar包
被大量的app所使用,在实际中被验证
有很多的高级功能,如线程切换模式,订阅的优先级等
特性
简单易用的注解API : 简单的@Subscribe注解放到订阅方法上即可通过编译期的订阅者索引,app就可以不用在运行期通过注解反射拿到订阅了
支持事件投递到UI线程,不管事件是从哪个线程发送过来的
支持事件和订阅者的继承关系: 如 事件A是事件B的父类,则发送B类型的事件,该事件也会发送给对于事件A感兴趣的订阅者,对于订阅者也存在相似的继承关系,可以通过eventInheritance(false)关闭该功能,只在本类中调用,父类接口不在回调的
即可零设定EventBus直接工作,也可以通过构建者模式调整EventBus的行为,满足你的需求EventBusBuilder.builder()
//通过EventBusBuilder.builder()的方法构建EventBusBuilder对象配置里面的信息以后调用build()方法创建
public EventBus build() { //创建一个EventBus对象
return new EventBus(this);
}
复制代码
注意:通过此方法可以创建多个EventBus对象,且每个配置信息不同,但是每个EventBus的实例都是独立的,也就是说每个EventBus post事件,只要使用该EventBus注册的订阅者才能接收到,其他EventBus注册的是无法接收的即在EventBus.getEventBus(type).regirst(this)的Activity或者Fragment中的方法都注册到了该type返回的EventBus中啦!
EventBus的构建
private final static ExecutorService DEFAULT_EXECUTOR_SERVICE = Executors.newCachedThreadPool();
ExecutorService executorService = DEFAULT_EXECUTOR_SERVICE;
//默认线程池,是一个核心0,最大Max,无缓存队列SynchronousQueue;如果大量事件一起发送可能导致OOM,一般需要更改一下线程池配置
//通过build的方法executorService(executorService)传递进来,主要用于异步和后台事件传递
////在订阅方法中抛出异常时,是否打印日志,在类中注解重写onSubscriberExceptionEvent可以监听到
boolean logSubscriberExceptions = true;
//没有找到订阅方法,是否打印日志
boolean logNoSubscriberMessages = true;
//在非订阅SubscriberExceptionEvent事件方法中抛出异常时, 是否发送SubscriberExceptionEvent事件
boolean sendSubscriberExceptionEvent = true;
//没找到事件订阅方法,是否发送NoSubscriberEvent事件
boolean sendNoSubscriberEvent = true;
//在非订阅SubscriberExceptionEvent事件方法中抛出异常时, 是否抛出EventBusException异常, 默认为false
boolean throwSubscriberException;
boolean eventInheritance = true; //发送子事件,是否发送父事件,默认为true,最好改成false,避免不必要的麻烦
//三个参数用于参照订阅方法
boolean ignoreGeneratedIndex; //是否直接用反射查找订阅方法(就是运行期查找,速度慢耗时,3.0以后优化使用索引的),默认是false
boolean strictMethodVerification; //非注解生成索引时,严格方法验证:当方法不符合格式(public,非abstract, 非static,非桥接方法,只有一个参数)时,是否抛出EventBusException异常,默认false
List subscriberInfoIndexes; //注解生成的索引,在编译器生成,需要通过android-apt三方插件或annotationProcessor生成
List> skipMethodVerificationForClasses; //检查以onEvent开头的方法,基本不用啦,都是使用注解自定义方法了
复制代码
注意: 对EventBus增加索引,必须在第一个EventBus.regirst之前,因此一般操作都是在Application的oncreate方法中添加
//提高性能:第一点是关闭父类以及接口查找分发事件(只有本类调取,父类接口不在回调);
//第二点 添加索引,索引添加的原理就是提前在编译的时候加载好注册类的相关信息。
EventBus.builder().addIndex(new MyEventBusIndex()).eventInheritance(false).installDefaultEventBus();
//EventBus.getDefault() 获得的即为当前installDefaultEventBus方法生成的,不会在重新创建了,但是如果通过builder.build()重新构建的EventBus就无效了!
复制代码
这里我们主要通过Event3.0的源码,毕竟用新不用旧嘛
注解Subscribe
通过注解标记订阅方法:同时可以指定threadMode,代表该订阅方法运行的线程,指定sticky,代表是否是粘性事件,指定priority代表优先级(由高到底依次接收,可以更改设置)
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
ThreadMode threadMode() default ThreadMode.POSTING;
boolean sticky() default false;
int priority() default 0;
}
复制代码
threadMode: 订阅方法运行的线程
ThreadMode.POSTING —— POST线程,订阅方法运行在发布者所在的线程(默认)
ThreadMode.MAIN —— UI主线程,订阅方法运行在主线程,发送如果是UI线程直接运行,如果不是通过Handler回调到UI线程运行
ThreadMode.BACKGROUND —— 后台线程,发布者是主线程,订阅方法运行在新开子线程;发布者是子线程,订阅方法运行在发布者所在的线程;(通过线程池创建子线程)
ThreadMode.ASYNC —— 异步线程,订阅方法运行在新开子线程,无论发布者是在哪个线程(同上共用一个线程池)
sticky: 与Android广播中的sticky概念一致,表示如果当前还未注册,则通过postSticky发送的广播将会保存在内存中,当有注册时就会法将该粘性事件传递给订阅者,即先发送后订阅是可以接收到的!
priority:订阅优先级,事件发送以后根据优先级传递,相同优先级根据订阅顺序传递(添加由大->小,调取方法直接轮询由0 -> size从而达到事件传递性,且事件对象不可变但属性值可变从而可以更改值的传递)
注册register
EventBus通过register注册到Activity或Fragment中
public void register(Object subscriber) { //subscriber表示this
Class subscriberClass = subscriber.getClass(); //获取this代表的类
List subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass); //查找订阅者类中的所有订阅方法
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
//SubscriberMethod类的属性
public class SubscriberMethod {
final Method method; //订阅方法
final ThreadMode threadMode; //订阅方法在哪个线程执行
final Class eventType; //事件类
final int priority; //优先级
final boolean sticky; //是否是粘性
/** Used for efficient comparison */
String methodString;
复制代码
看一下 subscriberMethodFinder.findSubscriberMethods如何查找订阅方法
//首先肯定是subscriberMethodFinder的创建:在EventBus的构造函数
subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
builder.strictMethodVerification, builder.ignoreGeneratedIndex); //注意里面传递的参数: 索引,方法验证是否符合,是否使用反射调用
//引入EventBus的第一个缓存map集合: 键表示当前注册类activity或者Fragment,值表示当前类中的订阅方法的集合
private static final Map, List> METHOD_CACHE = new ConcurrentHashMap<>();
List findSubscriberMethods(Class subscriberClass) {
List subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) { //如果注册类已经缓存过了,直接返回
return subscriberMethods;
}
//ignoreGeneratedIndex表示使用反射也就是忽略注解器编译器生成的MyEventBusIndex类,默认是false
if (ignoreGeneratedIndex) {
subscriberMethods = findUsingReflection(subscriberClass); //通过反射调用,在运行期
} else { //我们都会添加编译器生成的代码逻辑
subscriberMethods = findUsingInfo(subscriberClass);//// 从注解器生成的MyEventBusIndex类中获得订阅类的订阅方法信息
}
if (subscriberMethods.isEmpty()) {
throw new EventBusException("Subscriber " + subscriberClass
+ " and its super classes have no public methods with the @Subscribe annotation");
} else {
METHOD_CACHE.put(subscriberClass, subscriberMethods); //调用完成以后添加到缓存中
return subscriberMethods;
}
}
复制代码
首先我们先看通过反射来获取订阅类中的订阅方法信息findUsingReflection,通过索引获取的稍后解释
private List findUsingReflection(Class subscriberClass) {
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);//从数组长度为4中取值并初始化Findstate
while (findState.clazz != null) {
findUsingReflectionInSingleClass(findState); //找到类中的所有事件响应方法
findState.moveToSuperclass(); //继续寻找当前类父类中注册的事件响应方法
}
return getMethodsAndRelease(findState);
}
复制代码
查找类中的所有事件响应方法
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
try {
// 获取类中的所有方法,包括私有方法
methods = findState.clazz.getDeclaredMethods();
} catch (Throwable th) {
// Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
methods = findState.clazz.getMethods();
findState.skipSuperClasses = true;
}
for (Method method : methods) {
int modifiers = method.getModifiers(); //获取方法的属性,private,static等
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) { //如果方法时public,且不是abstract,static,bridge, synthetic编译器生成的
Class[] parameterTypes = method.getParameterTypes();//获取方法参数,且参数只有1个
if (parameterTypes.length == 1) {
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class); //获取以Subscribe注解的方法
if (subscribeAnnotation != null) {
Class eventType = parameterTypes[0]; //获取事件类
if (findState.checkAdd(method, eventType)) {
ThreadMode threadMode = subscribeAnnotation.threadMode();
findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
subscribeAnnotation.priority(), subscribeAnnotation.sticky())); //将一个类中的所有订阅方法都加入到findstate的集合中
}
}
} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
String methodName = method.getDeclaringClass().getName() + "." + method.getName();
throw new EventBusException("@Subscribe method " + methodName +
"must have exactly 1 parameter but has " + parameterTypes.length);
}
} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
String methodName = method.getDeclaringClass().getName() + "." + method.getName();
throw new EventBusException(methodName +
" is a illegal @Subscribe method: must be public, non-static, and non-abstract");
}
}
}
复制代码
我们看一下FindState类的属性
//FindState是SubscriberMethodFinder的静态内部类,类不长,我们都拿过来吧
static class FindState {
final List subscriberMethods = new ArrayList<>(); //订阅方法信息列表
final Map anyMethodByEventType = new HashMap<>(); //以事件类型为key,方法信息为value的集合
final Map subscriberClassByMethodKey = new HashMap<>(); //以methodkey为key,订阅者类为value集合 (下方的methodkey : method方法所在的类)
final StringBuilder methodKeyBuilder = new StringBuilder(128); //生成methodkey
//methodkey 是由method名字,事件类名称组成
Class subscriberClass; //订阅者类
Class clazz;
boolean skipSuperClasses; //是否跳过父类
SubscriberInfo subscriberInfo;
void initForSubscriber(Class subscriberClass) { //初始化
this.subscriberClass = clazz = subscriberClass;
skipSuperClasses = false;
subscriberInfo = null;
}
void recycle() {
subscriberMethods.clear();
anyMethodByEventType.clear();
subscriberClassByMethodKey.clear();
methodKeyBuilder.setLength(0);
subscriberClass = null;
clazz = null;
skipSuperClasses = false;
subscriberInfo = null;
}
//检查订阅者中注册的事件响应是否可以合法的加入到订阅方法信息中,分为两层检查
boolean checkAdd(Method method, Class eventType) { //method是订阅方法,eventType是事件类
// 2 level check: 1st level with event type only (fast), 2nd level with complete signature when required.
// Usually a subscriber doesn't have methods listening to the same event type.
//第一次检查同一事件类型下是否已有订阅方法信息了
Object existing = anyMethodByEventType.put(eventType, method);
if (existing == null) { //没有就添加成功
return true;
} else {
//如果当前事件类型在同一个类中对应多个方法,则检查他们方法签名是否一致
if (existing instanceof Method) { //existing是相同的键对应的oldValue
if (!checkAddWithMethodSignature((Method) existing, eventType)) {
// Paranoia check
throw new IllegalStateException();
}
// Put any non-Method object to "consume" the existing Method
anyMethodByEventType.put(eventType, this); //方法签名不一致(一个类中多个方法中含有同一个事件类)添加FindState作为value
}
return checkAddWithMethodSignature(method, eventType);
}
}
//使用订阅方法签名检测是否可以加入订阅方法信息列表中,method订阅方法,eventType事件类
private boolean checkAddWithMethodSignature(Method method, Class eventType) {
methodKeyBuilder.setLength(0);
methodKeyBuilder.append(method.getName());
methodKeyBuilder.append('>').append(eventType.getName());
//方法签名methodkey怎么生成的
String methodKey = methodKeyBuilder.toString();
Class methodClass = method.getDeclaringClass(); //方法所在的类
Class methodClassOld = subscriberClassByMethodKey.put(methodKey, methodClass);
if (methodClassOld == null || methodClassOld.isAssignableFrom(methodClass)) //methodClass是MethodClassOld的子类或者子接口
{
//如果不存在同样方法签名的订阅方法或 之前保存的订阅方法所在的类为当前将要添加的订阅方法所在的类的子类(目前不存在此情况,因为只会从子类向父类查找),则可以合法添加此订阅方法信息
return true;
} else {
// 存在同样方法签名,且methodOld是子类
//subscriberClassByMethodKey只保存父子继承关系的最下层子类,目的是为了在子类注册监听事件时,如果父类中有相同的事件响应方法,应该调用子类的覆写方法
subscriberClassByMethodKey.put(methodKey, methodClassOld);
return false;
}
}
void moveToSuperclass() {
if (skipSuperClasses) {
clazz = null;
} else {
clazz = clazz.getSuperclass();
String clazzName = clazz.getName();
/** Skip system classes, this just degrades performance. */
if (clazzName.startsWith("java.") || clazzName.startsWith("javax.") || clazzName.startsWith("android.")) {
clazz = null;
}
}
}
}
复制代码
以上对于FindState中的步骤为:
通过反射获取class的所有方法
过滤掉不是public和是abstract,static,bridge,synthetic的方法,且参数只有一个的
找出别subScribe注解修饰的方法
将method和事件类型添加到findState中
将所有的method订阅方法封装成SubscribeMethod添加到findState中集合类中
这就是查询订阅方法的步骤,下面接着上文,继续注册步骤!
我们目光从新回到注册逻辑上面来
public void register(Object subscriber) {
Class subscriberClass = subscriber.getClass();
List subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass); //上面已经具体分析了,如何去找到该类中的所有订阅方法
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod); //遍历方法调取
}
}
}
复制代码
我们看subscribe(subscriber, subscriberMethod) 参数: 当前订阅类, 订阅类中的订阅方法
// Must be called in synchronized block
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
Class eventType = subscriberMethod.eventType; //订阅方法的事件类
Subscription newSubscription = new Subscription(subscriber, subscriberMethod); //创建一个订阅类的封装,注意参数
CopyOnWriteArrayList subscriptions = subscriptionsByEventType.get(eventType); //从缓存中根据事件类获取订阅类的封装集合
if (subscriptions == null) { //从中查找,如果不存在就添加进入,如果存在集合且集合中已经添加过了抛出异常,不能添加多次
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
if (subscriptions.contains(newSubscription)) { //如果已经存在
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
}
}
int size = subscriptions.size(); //遍历已经存在的集合,根据他们的优先级排序添加
for (int i = 0; i <= size; i++) {
if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) { //优先级由大到小排序
subscriptions.add(i, newSubscription);
break;
}
}
//typesBySubscriber缓存的是key:当前订阅类activity/fragment, value:当前类中的订阅方法的事件集合
List> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType); //在当前订阅方法集合中加入当前事件
if (subscriberMethod.sticky) { //如果是粘性
if (eventInheritance) { //是否查找父类方法
// Existing sticky events of all subclasses of eventType have to be considered.
// Note: Iterating over all events may be inefficient with lots of sticky events,
// thus data structure should be changed to allow a more efficient lookup
// (e.g. an additional map storing sub classes of super classes: Class -> List).
Set, Object>> entries = stickyEvents.entrySet(); //查找当前是当前事件是缓存事件的父类则也会调用它的子类
for (Map.Entry, Object> entry : entries) {
Class candidateEventType = entry.getKey();
if (eventType.isAssignableFrom(candidateEventType)) {
Object stickyEvent = entry.getValue();
checkPostStickyEventToSubscription(newSubscription, stickyEvent); //在注册的时候就开始调用订阅方法了,如果缓存队列中存在
}
}
} else {
Object stickyEvent = stickyEvents.get(eventType);
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
}
//checkPostStickyEventToSubscription
private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
if (stickyEvent != null) {
// 一会儿post的时候还要研究这个方法
postToSubscription(newSubscription, stickyEvent, Looper.getMainLooper() == Looper.myLooper());
}
}
//new的一个订阅类 Subscription 属性包括
final class Subscription {
final Object subscriber; // 订阅者对象
final SubscriberMethod subscriberMethod; // 订阅方法
/**
* Becomes false as soon as {@link EventBus#unregister(Object)} is called, which is checked by queued event delivery
* {@link EventBus#invokeSubscriber(PendingPost)} to prevent race conditions.
*/
volatile boolean active; // 是否处于激活状态.如果不激活就不运行哦
Subscription(Object subscriber, SubscriberMethod subscriberMethod) {
this.subscriber = subscriber;
this.subscriberMethod = subscriberMethod;
active = true; //新建的肯定是激活状态
}
@Override
public boolean equals(Object other) {
if (other instanceof Subscription) {
Subscription otherSubscription = (Subscription) other;
return subscriber == otherSubscription.subscriber
&& subscriberMethod.equals(otherSubscription.subscriberMethod);
} else {
return false;
}
}
@Override
public int hashCode() {
return subscriber.hashCode() + subscriberMethod.methodString.hashCode();
}
}
复制代码
总结订阅方法: 由于上方已经总结了对于一个订阅类查找到它之中的所有订阅方法以后循环调用subscribe继续注册
创建一个订阅类,判断是否存在以当前事件为key的List<订阅类>的集合,就是将所有相同的事件的订阅类统一管理,这样发送一个事件,只需要遍历该集合即可知道回调那些类的哪些方法
注意加入到List集合时根据优先级排序,由大到小,调用顺序也是如此即可实现优先级发送事件啦
对于订阅类通过typesBySubscriber缓存: key当前订阅类,value:当前订阅类中所有事件类的集合
如果是粘性事件,判断是否需要调取事件父类或接口对应的订阅方法,如果true且缓存中存在该事件的post,则直接执行订阅方法,否则只执行当前类中的订阅方法(达到了先发送,后订阅,且订阅即运行的目的,可以在Fragment中不通过intent对重新打开的Activity传递信息)
反注册unregister
有订阅对应的就有反注册,否则会内存泄漏且多次注册相同信息源码中报错的
我们先思考一下,上方注册的时候主要有两个map集合,
ypesBySubscriber表示key:订阅类,value:订阅类中的所有的事件类型集合;
subscriptionsByEventType表示key: 事件类型 , value: 所有注册该事件的订阅方法集合
举个例子,朋友圈A和B是好友,如果A要取关B,则A中有map(A , List好友们),在A中查找List好友将B删除,同时在B中找到他的好友列表将A删除即可啦
同样的,对于上方两个集合来说:我们也是反注册时通过this订阅类在ypesBySubscriber集合中找到该订阅类中的所有事件类型的集合后,遍历在subscriptionsByEventType中找到以当前事件为key的value,订阅方法集合中删除该类对应的订阅方法即可啦
public synchronized void unregister(Object subscriber) {
List> subscribedTypes = typesBySubscriber.get(subscriber); //通过订阅类获取他当中的所有订阅事件类,我们加入的时候时加入事件类的class
if (subscribedTypes != null) {
for (Class eventType : subscribedTypes) { //遍历订阅事件,开始解绑另外一个集合中的数据:以eventType为key中删除
unsubscribeByEventType(subscriber, eventType);
}
typesBySubscriber.remove(subscriber); //从当前订阅类为key的map移除
} else {
Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
}
}
private void unsubscribeByEventType(Object subscriber, Class eventType) {
List subscriptions = subscriptionsByEventType.get(eventType); //获取以当前事件为key的所有订阅方法集合
if (subscriptions != null) {
int size = subscriptions.size();
for (int i = 0; i < size; i++) {
Subscription subscription = subscriptions.get(i);
if (subscription.subscriber == subscriber) { //遍历如果事件是在当前类中订阅的,则从订阅方法集合中删除,因为这个类就要over啦,自然也就不用再接受订阅事件啦
subscription.active = false; //标记不激活
subscriptions.remove(i); //从订阅方法集合中删除
i--;
size--;
}
}
}
}
复制代码
post发送事件
通过上面的注册和反注册已经将事件,订阅方法都添加到集合中了,我们就可以发送一个事件开始响应方法啦!
小知识点: ThreadLocal是线程隔离的只有同一线程才能获取值,通过get访问,其他线程获取不到的,同一个线程中才能获取,且同一个线程获取的是同一个对象
//ThreadLocal 的get方法
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); //如果已经存在map则返回map中的值
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
//调用initialValue创建一个对象值,存储到map中,下次直接通过get即可获取
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
//上面简单分析了ThreadLocal的源码,下面开始真正的post操作
private final ThreadLocal currentPostingThreadState = new ThreadLocal() {
@Override
protected PostingThreadState initialValue() { //如果调用get方法没有value值通过这个方法新建一个返回的,下次改线程在调用,已经存在直接返回了,达到了对象的线程唯一性
return new PostingThreadState();
}
};
public void post(Object event) {
//代码很短小,第一行就蒙蔽了,不急,就是上面我们说了get函数
PostingThreadState postingState = currentPostingThreadState.get(); //不同线程对应不同的postingState,相同的线程之间遵循happended-before
List eventQueue = postingState.eventQueue; //postingState中的eventQueue队列
eventQueue.add(event); //将事件添加到队列中:每个post线程的队列都是相互独立的
//isPosting默认false,可以进去
if (!postingState.isPosting) { //相同线程是遵循happended-before规则的,是安全的
//是否是主进程
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;
}
}
}
//静态内部类:每个线程独一份
final static class PostingThreadState {
//事件队列
final List eventQueue = new ArrayList();
boolean isPosting;
boolean isMainThread;
Subscription subscription;
Object event;
boolean canceled;
}
复制代码
post流程
首先通过ThreadLocal获取当前线程中状态PostingState,注意不同的线程拥有不同的状态PostingState
将发送的event加入到PostingState类中的队列中
循环遍历事件队列,发送单个事件调用
发送单个Event的方法postSingleEvent
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
Class eventClass = event.getClass(); //获取事件类型
boolean subscriptionFound = false;
if (eventInheritance) { //如果允许事件继承,默认允许
//找到eventClass的所有父类和实现接口
List> eventTypes = lookupAllEventTypes(eventClass);
int countTypes = eventTypes.size();
for (int h = 0; h < countTypes; h++) {
Class clazz = eventTypes.get(h);
// 依次向 eventClass 的父类或接口的订阅方法发送事件
// 只要有一个事件发送成功,返回 true ,那么 subscriptionFound 就为 true
subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
}
} else {
//不允许继承,那么只发送该事件
subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
}
if (!subscriptionFound) { //如果当前没有订阅者订阅
if (logNoSubscriberMessages) {
Log.d(TAG, "No subscribers registered for event " + eventClass);
}
//默认允许的
if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
eventClass != SubscriberExceptionEvent.class) {
post(new NoSubscriberEvent(this, event)); //发送NosubSribeEvent事件,可以重写订阅方法接受到从而做自己的处理逻辑
}
}
}
//postSingleEventForEventType(event, postingState, eventClass)参数: 事件对象里面可能有值, 当前线程的状态postingState, 事件类型class
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class eventClass) {
CopyOnWriteArrayList subscriptions;
synchronized (this) {
subscriptions = subscriptionsByEventType.get(eventClass); //获取以当前事件为key的订阅类的集合(属性: 订阅类,订阅方法,是否激活)
}
if (subscriptions != null && !subscriptions.isEmpty()) {
for (Subscription subscription : subscriptions) { //还记得添加由大->小,这里取值是由0->size实现优先级,同时event对象不可变,但是每个共用同一个event,里面的属性值是可以更改的
postingState.event = event;
postingState.subscription = subscription;
boolean aborted = false;
try {
//发送事件,参数subscription订阅类信息,event发送事件对象, 是否是主线程
postToSubscription(subscription, event, postingState.isMainThread);
//是否被取消了
aborted = postingState.canceled;
} finally {
postingState.event = null; //回复默认值
postingState.subscription = null;
postingState.canceled = false;
}
if (aborted) { // 如果被取消,则跳出循环
break;
}
}
return true; //有一个发送成功即可,对应上面的判断
}
return false;
}
//postToSubscription: isMainThread 表示发送方法是否在主线程中
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
switch (subscription.subscriberMethod.threadMode) { //接受事件的订阅方法所在的线程
case POSTING: //那个线程发送就在那个线程接受
invokeSubscriber(subscription, event);
break;
case MAIN:
if (isMainThread) {
invokeSubscriber(subscription, event); //在主线程中接受
} else { //其他线程向主线程发送添加到队列(链表)后通过Handler一次发送队列中的值给UI线程最终调用的还是invokeSubscriber(subscription, event)方法
//PendingPost添加队列获取是通过PendingPost对象池,同Message相同的
mainThreadPoster.enqueue(subscription, event);
}
break;
case BACKGROUND:
if (isMainThread) { //如果在子线程中发送,则随机开辟一条子线程接收
backgroundPoster.enqueue(subscription, event);
} else { //在子线程中发送,在相同的线程中接收
invokeSubscriber(subscription, event);
}
break;
case ASYNC: //无论是UI还是子线程,都会开辟一个新的线程接收
asyncPoster.enqueue(subscription, event);
break;
default:
throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
}
}
//invokeSubscriber参数: subscription订阅类信息: 订阅类,方法,是否激活信息
void invokeSubscriber(Subscription subscription, Object event) {
try {
//反射调用方法: method.invoke(class, 参数信息)即可,在哪个线程调用就运行在哪个线程
subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
} catch (InvocationTargetException e) {
handleSubscriberException(subscription, event, e.getCause()); //如果没有找到就发送错误事件,重写即可监听到
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unexpected exception", e);
}
}
复制代码
主线程的mainThreadPoster.equeue()
//在EventBus中创建的
mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10);
final class HandlerPoster extends Handler {
private final PendingPostQueue queue; //事件队列
private final int maxMillisInsideHandleMessage;
private final EventBus eventBus;
private boolean handlerActive;
HandlerPoster(EventBus eventBus, Looper looper, int maxMillisInsideHandleMessage) {
super(looper);
this.eventBus = eventBus;
this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage;
queue = new PendingPostQueue(); //每个线程都有他自己的时间队列
}
void enqueue(Subscription subscription, Object event) {
//获取很有意思,所有线程共用同一个对象池,当存在可用返回,否则新建,回收的时候添加到池中,了解过Message消息对象的很熟悉啦
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");
}
}
}
}
@Override
public void handleMessage(Message msg) {
boolean rescheduled = false;
try {
long started = SystemClock.uptimeMillis();
while (true) {
PendingPost pendingPost = queue.poll();
if (pendingPost == null) {
synchronized (this) {
// Check again, this time in synchronized
pendingPost = queue.poll();
if (pendingPost == null) {
handlerActive = false;
return;
}
}
}
eventBus.invokeSubscriber(pendingPost); //UI线程取出来调用invokeSubscriber方法
long timeInMethod = SystemClock.uptimeMillis() - started;
if (timeInMethod >= maxMillisInsideHandleMessage) {
if (!sendMessage(obtainMessage())) {
throw new EventBusException("Could not send handler message");
}
rescheduled = true;
return;
}
}
} finally {
handlerActive = rescheduled;
}
}
}
//Event中的方法
void invokeSubscriber(PendingPost pendingPost) {
Object event = pendingPost.event;
Subscription subscription = pendingPost.subscription;
PendingPost.releasePendingPost(pendingPost); //回收对象到对象池中以便下一个运用
if (subscription.active) { //如果当前激活状态,则反射调用方法即可
invokeSubscriber(subscription, event);
}
}
//类的静态常量,所有线程共用同一个
private final static List pendingPostPool = new ArrayList();
复制代码
直到现在我们还没有用到线程池的概念吧,这个时候你是不是都忘记了EventBus构造函数中创建了一个线程池newCacheExecutor() 可能导致OOM,看一下backgroundPoster.enqueue(subscription, event);
//EventBus构造函数中创建
backgroundPoster = new BackgroundPoster(this);
final class BackgroundPoster implements Runnable { //代码量不多,整个拿过来
private final PendingPostQueue queue; //时间队列
private final EventBus eventBus;
private volatile boolean executorRunning;
BackgroundPoster(EventBus eventBus) {
this.eventBus = eventBus;
queue = new PendingPostQueue();
}
public void enqueue(Subscription subscription, Object event) {
//通过事件封装对象池获取对象
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
synchronized (this) {
queue.enqueue(pendingPost); //添加一个到队列中,下面就可以执行了
if (!executorRunning) { //线程池是否正在运行
executorRunning = true;
//调用EventBus中创建的线程池执行该类的run方法
eventBus.getExecutorService().execute(this);
}
}
}
@Override
public void run() {
try {
try {
while (true) {
PendingPost pendingPost = queue.poll(1000);
if (pendingPost == null) { //双重检索队列中还没有新加则退出,开始回收线程
synchronized (this) {
// Check again, this time in synchronized
pendingPost = queue.poll();
if (pendingPost == null) {
executorRunning = false;
return;
}
}
}
//线程池的一个线中程执行方法,同上方的Ui线程一致的,反射调用执行方法即可,无非就是运行线程不同,这个是子线程,上面的是UI线程
eventBus.invokeSubscriber(pendingPost);
}
} catch (InterruptedException e) {
Log.w("Event", Thread.currentThread().getName() + " was interruppted", e);
}
} finally {
executorRunning = false;
}
}
}
// asyncPoster.enqueue(subscription, event);同样是在EventBus构造方法中创建的
class AsyncPoster implements Runnable { //类比较简单
private final PendingPostQueue queue;
private final EventBus eventBus;
AsyncPoster(EventBus eventBus) {
this.eventBus = eventBus;
queue = new PendingPostQueue();
}
public void enqueue(Subscription subscription, Object event) {
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
queue.enqueue(pendingPost);
eventBus.getExecutorService().execute(this);
}
@Override
public void run() {
PendingPost pendingPost = queue.poll(); //通过子线程直接运行即可
if(pendingPost == null) {
throw new IllegalStateException("No pending post available");
}
eventBus.invokeSubscriber(pendingPost);
}
}
复制代码
通过上面分析,equeue添加是一个个添加,一个个执行,通过executorRunning变量控制着,如果run执行中耗时过长,添加的特别迅速,线程池将会创建大量线程,最终可能OOM
Posting: 表示post线程在哪儿发送,接受的就是该线程
Main: 如果在主线程中发送,则直接执行接受,否则利用Handler回调到主线程中执行
BackGround子线程: 如果发布事件在主线程,则调用线程池中的一个子线程去执行,否则直接在发送线程中执行
(ASYNC)异步线程: 无论发布事件是UI还是子线程都利用一个异步线程来执行!
注意:以上所有的方法分析都是在Post()方法内部调用逻辑,所以线程切换需要注意是否是线程池创建还是原来的post方法所在的线程之内
配置索引
EventBus3.0以后为了提高效率避免在运行期通过反射来做以上大量的工作,使用了索引配置生成类,可以在编译器生成注册文件,从而提升效率
使用:两种方式:使用android-apt三方插件,或者annotationProcessor
//1.使用android-apt
buildscript {
dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
apply plugin: 'com.neenbedankt.android-apt'
dependencies {
compile 'org.greenrobot:eventbus:3.0.0'
apt 'org.greenrobot:eventbus-annotation-processor:3.0.1'
}
apt {
arguments {
eventBusIndex "com.monster.android.wild.MyEventBusIndex"
}
}
//2\. 使用annotationProcessor
android {
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments = [eventBusIndex:'com.monster.android.wild.MyEventBusIndex']
}
}
}
}
dependencies {
compile 'org.greenrobot:eventbus:3.0.0'
annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.0.1'
}
复制代码
com.monster.android.wild.MyEventBusIndex就是我们想要生成的Subscriber Index类
public class MyEventBusIndex implements SubscriberInfoIndex {
private static final Map, SubscriberInfo> SUBSCRIBER_INDEX; //缓存mao:key当前订阅类 , 值是封装的SimpleSubscriberInfo类(订阅类,应该检查父类,该订阅类中对应的订阅方法的数组)
static {
SUBSCRIBER_INDEX = new HashMap, SubscriberInfo>();
// 从以上createInfoIndexFile()的实现来看,除了类名MyEventBusIndex,只有这一段代码是非hardcode。编译器自动生成的代码
// 以订阅者类为单位,将该订阅者类中的所有订阅函数以及相关参数,封装到SimpleSubscriberInfo类对象中,
// 以供EventBus在注册过程中使用。注意SimpleSubscriberInfo类对象是在编译时期就生成的,
// 因而在运行时期可以直接使用,省去了运行时期通过反射做相似操作的时间和资源消耗,从而提高效率,这里就是全文的精髓所在。
putIndex(new SimpleSubscriberInfo(com.monster.android.wild.myeventbusdemo.MainActivity.class, true,
new SubscriberMethodInfo[] {
new SubscriberMethodInfo("onEvent",
com.monster.android.wild.myeventbusdemo.MainActivity.EventBusEvent.class, ThreadMode.MAIN),
}));
}
private static void putIndex(SubscriberInfo info) {
SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
}
// 将会在EventBus在注册过程中使用,等会大家会看到
@Override
public SubscriberInfo getSubscriberInfo(Class subscriberClass) {
SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
if (info != null) {
return info;
} else {
return null;
}
}
}
复制代码
上面register中有两个分支,通过ignoreGeneratedIndex是否直接使用反射调用区分,我们当时走的是第一个findUsingReflection,下面看添加索引后的第二个方法findUsingInfo
private List findUsingInfo(Class subscriberClass) {
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass); //findstate数组中准备并初始化FindState对象
while (findState.clazz != null) {
//重点,重点,重点: 通过索引获取编译器生成的订阅者信息
findState.subscriberInfo = getSubscriberInfo(findState);
if (findState.subscriberInfo != null) { //注意这里的subscriberInfo实际上是系统自动生成的类SimpleSubscriberInfo对象 //如果找到了就遍历订阅类中的订阅方法数组加入进去
SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods(); //调用SimpleSubscriberInfo.getSubscriberMethods方法获取订阅方法集合封装成SubscriberMethod[]
for (SubscriberMethod subscriberMethod : array) { //轮询,下面就跟反射是一致的操作了
if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
findState.subscriberMethods.add(subscriberMethod);
}
}
} else { //否则还是老实的回到反射注册上面去吧
findUsingReflectionInSingleClass(findState);
}
findState.moveToSuperclass();
}
return getMethodsAndRelease(findState); //回收findState对象,以便下次使用
}
//getSubscriberInfo
private SubscriberInfo getSubscriberInfo(FindState findState) {
if (findState.subscriberInfo != null && findState.subscriberInfo.getSuperSubscriberInfo() != null) { //新建的findState为null
SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo();
if (findState.clazz == superclassInfo.getSubscriberClass()) {
return superclassInfo;
}
}
//subscriberInfoIndexes是在合适创建的
if (subscriberInfoIndexes != null) { //subscriberInfoIndexes可能添加多个文件信息,这里我们只是一个
for (SubscriberInfoIndex index : subscriberInfoIndexes) {
//拿到唯一的一个index类即index为: MyEventBusIndex类对象
SubscriberInfo info = index.getSubscriberInfo(findState.clazz); //调用它的getSubscriberInfo(订阅类)即可获得已经put进缓存SUBSCRIBER_INDEX中的方法封装类SimpleSubscriberInfo
if (info != null) {
return info;
}
}
}
return null;
}
//通过EventBus构造函数中创建通过build传递进来的,如果我们使用索引并且重写build方法传递进来数据了
subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
builder.strictMethodVerification, builder.ignoreGeneratedIndex);
//将索引值穿进去
EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();
复制代码
至此:无论是直接反射注册还是通过添加索引在编译器生成文件运行期直接读取注册都分析完成了,不知道你明白了没有呢?最后的话也整理了一份《Android相关源码精编解析》,方便大家笔记学习,有需要的朋友可以点赞+评论支持下,然后评论留言或私信我获取!
你可能感兴趣的:(万人收藏!关于Android EventBus源码解析,看这一篇就够了!)
如何学黑客并从事网络安全
网络安全-杰克
web安全 安全
关于如何从菜鸟成为高技术黑客的问题,我想你要首先搞清楚,你是做那种黑客,你要是做国内记者意义中的黑客(大多是网络罪犯的代名词)还是真正意义的黑客(致力于完善和维护系统网络的IT人才),若是前者黑客的话,我想你多用一些工具,多搞一些操作,其实很快就可以了,比如:国内的不少所谓的黑客都是这样,连网络的OSI七层结构都搞不清楚,还自称黑客的那种,若是后者那么你就要从基础做起,不断学习,并且,我个人认为是
dp 1.4协议_DP接口与HDMI接口那个更好?
TechSavvy
dp 1.4协议
弱电行业网前发布了关于VGA、DVI、HDMI的区别在哪里?有朋友就问到,关于DP和HDMI哪个更好?本期我们一起来看下。一、dp接口DisplayPort缩写DP,与目前主流的HDMI接口均属于数字高清接口,都支持一根信号线同时传输视频和音频信号,DP接口从第一代就达到了10.8Gbps带宽,支持2560x160012bit输出。目前市面最多的DP1.2已经高达21.6Gbit/s超越了HDMI
dp 1.4协议_DP接口与HDMI接口各有什么优势?哪个更好?
喵星人向前冲
dp 1.4协议
一、DP接口DisplayPort缩写DP,与目前主流的HDMI接口均属于数字高清接口,都支持一根信号线同时传输视频和音频信号,DP接口从第一代就达到了10.8Gbps带宽,支持2560x160012bit输出。目前市面最多的DP1.2已经高达21.6Gbit/s超越了HDMI2.0,支持1080P240、2K165、4K75、5K30。DP1.3支持2K240、4K120、8K30。最新的DP1
dp 1.4协议_DP接口与HDMI接口的区别?
刘震撼
dp 1.4协议
有朋友问到关于DP和HDMI哪个更好?这个在我们实际项目中也经常会遇到,之前我们也曾提到过,本期我们再来详细总结下。一、dp接口DisplayPort缩写DP,与目前主流的HDMI接口均属于数字高清接口,都支持一根信号线同时传输视频和音频信号,DP接口从第一代就达到了10.8Gbps带宽,支持2560x160012bit输出。目前市面最多的DP1.2已经高达21.6Gbit/s超越了HDMI2.0
关于控制台或者日志输出堆栈信息的输出和e.getMessage()输出不了空值针的异常报错的问题
麻花2013
java技术 java
可以看到异常里面,如果是空值针,e.getMessage()不能输出任何信息,所以不建议使用,可以输出e对象,但是这样还是没有堆栈信息,如果要输出堆栈信息,需要输入将异常信息输入到字符串流,最后输出finalStringWritersw=newStringWriter();finalPrintWriterpw=newPrintWriter(sw,true);e.printStackTrace(pw
关于shell文本处理
yuwu94
实践代码 shell 文本处理 mac
最近用做了一些处理文本的工作,发现shell真的比较方便,总结了一些小代码。统计子目录下多个文件夹的行数forxin*.txt;docat$x|wc-l;done如果仅需要对单独文本catfilename|wc-l(会少一行,如果文件最后没有换行符)即可删除目录下所有文件的最后一行换行符,生成新的文件forxin*.txt;docat$x|perl-pe'chompifeof'>>../new/$
冗余-安全设计的基石
功能安全
功能安全 轨道交通 冗余 安全设计 铁路信号
冗余构成原理就是在系统中采用2套中央处理器(CPU)单元,其中1套为工作主机,1套为热备,一旦工作主机发生故障,热备的CPU将自动投入工作,此时热备的CPU变为工作主机,原工作主机故障处理完成后就变为热备CPU。除主机冗余外,输入输出模板也可以采用冗余构成或非冗余构成。按照主机冗余方式和输入输出冗余方式,系统可以有各种不同类型。1.单一故障准则在一个系统的运行过程中,单一子系统出现故障或失效的几率
[点点搬家]初试mod_perl+apache 之二
promenade
perl apache
[四年前的博客了,学习apache的纯真年代]学习practicalmod_perl中关于apache的配置,章节连接如下http://www.modperlbook.org/html/4-1-1-Configuration-Files.html1,".htaccess"文件,可以看到httpd.conf中有这样的模块AllowOverrideNoneOptionsNoneOrderallow,d
腾讯云数据库 TencentDB for PostgreSQL常见问题的解答
上云使者
腾讯云知识分享 腾讯云 数据库 postgresql mysql sql
本页面提供关于云数据库PostgreSQL的一些常见问题的解答,如果您在使用过程中遇到问题,您也可以在文章中评论提问来寻求帮助。更多参阅腾讯云官方文档。腾讯云数据库TencentDBforPostgreSQL强大的计算性能、空间地理信息处理能力,面向企业复杂SQL处理的OLTP场景云数据库PostgreSQL简介腾讯云数据库PostgreSQL(TencentDBforPostgreSQL,云AP
Spring 事务(详解)
Bejpse
spring java 后端
4.Spring事务4.1什么是事务?将组操作封装成个执单元,要么全部成功要么全部失败。为什么要事务?如转账分为两个操作:第步操作:A账户-100元,第步操作:B账户+100元。如果没有事务,第步执成功了,第步执失败了,那么A账户平故的100元就“间蒸发”了。如果使事务就可以解决这个问题,让这组操作要么起成功,要么起失败。4.1.1事务特性事务有4特性(ACID):原性(Atomicity,或称不
关于循环要注意的小问题
01292520
C++学习记录 c++
错误示范1:#includeusingnamespacestd;intmain(){intm,n,sum=0;while(cin>>m>>n&&n>0&&m>0){for(inti=m;in)swap(m,n);coutusingnamespacestd;intmain(){intm,n;while(cin>>m>>n&&n>0&&m>0){if(m>n)swap(m,n);//在进入循环前,m是
如何实现对用户密码的加密
燃星cro
java 数据库 后端 安全
摘要算法:同样的明文,经过同样的摘要算法,得到的结果是一样的验证方法:验证经过摘要算法处理后的结果,如果密文一样那么就认为明文是一样的//数据库存储的一定是密文,用户输入的是明文;把用户输入的明文经过MD5处理后和数据库的内容进行对比,结果一样就认为密码正确网站解密MD5的原理是将一些较常见的密码经过MD5处理后的结果存储下来之后再与我们输入的需要解密的MD5数据进行比对达成的盐值salt明文+s
基于RK3588的AI摄像头应用解决方案
浙江启扬智能科技有限公司
linux ARM 嵌入式开发 嵌入式硬件
随着人工智能(AI)技术的快速发展,越来越多的视频监控系统开始直接在摄像头上部署AI分析,视频监控从早期的图像记录发展到如今具备AI运算能力和算法,可进行目标识别、行为分析以及事件反馈,实现从被动记录到主动预警的转变。目前有三种算力部署方式:AI分析部署在云端、AI分析部署在边缘、AI分析部署在摄像头,也就是我们常说的云,边,端。但越来越多的摄像头本身就集成了AI分析能力,这一趋势的出现存在多方面
Android Sending non-protected broadcast,sendBroadcastAsUser方式发送广播
码点
android framwork android
有同事遇到发送广播接收不到的问题,分析log发现是system进程发送non-protected广播的问题。Ams在发送广播时,对于systemApp会要求广播必须是声明在frameworks\base\core\res\AndroidManifest.xml里面的protected-broadcast。这样可以避免三方垃圾应用也发送这些广播来捣蛋。03-3009:35:31.8293827445
数据分析学习目录
且行且安~
数据分析进阶之路 # 数据分析目录 数据分析
在未来5个月里,将会陪伴大家一起来学习关于数据分析的相关内容,包括从数据思维,数据工具(Excel,Mysql,Hive,Python),数据方法论,数据展示(Tableau,BI),数据挖掘、数据实战项目一整套的内容,同步会将可能用到的以及有用的知识点整理出来。内容会慢慢更新。如下为数据分析的整个目录一、数据分析思维与方法论1.1、从0-1搭建指标体系、用户标签体系1.1.1、指标体系搭建-专项
每日一题之数字诗意
Ace'
c++ 算法 开发语言
题描述在诗人的眼中,数字是生活的韵律,也是诗意的表达。小蓝,当代顶级诗人与数学家,被赋予了"数学诗人"的美誉。他擅长将冰冷的数字与抽象的诗意相融合,并用优雅的文字将数学之美展现于纸上。某日,小蓝静坐书桌前,目光所及,展现着nn个数字,它们依次为a1,a2,…,an,熠熠生辉。小蓝悟到,如果一个数能够以若干个(至少两个)连续的正整数相加表示,那么它就蕴含诗意。例如,数字6就蕴含诗意,因为它可以表示为
Android广播机制简析
漆黑迷夜
Android应用开发
1.广播机制简介Android中的广播主要是用来在不同的组件间传递消息用的。app能够接收来自Android系统或者其他app所发送的广播。也能像其发送系统提供的或者自定义的广播。例如,Android系统启动时就会发送一个开机广播,如果想要在开机是处理一些逻辑,可以监听该广播。应用场景如下:同一个app内部的同一个组件内的消息通信(单个或多个线程);同一个app内部的不同组件之间的消息通信(单个或
【数据库初阶】MySQL中表的约束(上)
bsefef
面试 学习路线 阿里巴巴 数据库 mysql android
??博主首页:??专栏首页:数据库初阶??其它专栏:C++初阶|C++进阶|初阶数据结构亲爱的小伙伴们,大家好!在这篇文章中,我们将深入浅出地为大家讲解MySQL中表的约束帮助您轻松入门,快速掌握核心概念。如果文章对您有所启发或帮助,请别忘了点赞??、收藏??、留言??支持!您的每一份鼓励,都是我持续创作的源动力。让我们携手前行,共同进步!文章目录@[toc]`0.什么是表的约束``1.NULL&
Android 14.0 mt6771新增分区功能实现一
安卓兼职framework应用工程师
android 14.0 Rom定制化系列讲解 android config 新增分区 mt6771
1.前言在14.0的系统ROM定制化开发中,在对某些特殊模块中关于数据的存储方面等需要新增分区来保存,所以就需要在系统分区新增相关的分区,来实现功能,接下来就来实现这个功能,来新增分区功能2.mt6771新增分区功能实现一的核心类build/make/core/Makefilebuild/make/core/board_config.mkbuild/make/core/config.mk3.mt6
Android 13.0 Launcher修改density禁止布局改变功能实现
安卓兼职framework应用工程师
android 13.0 Rom定制化系列讲解 android Launcher3 density 禁止布局改变 configuration
1.前言在13.0的系统rom定制化开发中,在关于Launcher3的定制化功能中,在有些功能需要要求改变系统原有的density屏幕密度,这样就会造成Launcher3的布局变化,所以就不符合要求,接下来就来看下如何禁止改变density造成Launcher3布局功能改变的实现2.Launcher修改density禁止布局改变功能实现的核心类packages/apps/Launcher3/src
比尔盖茨自述:中学时期就开始偷偷996写代码
量子位microsoft
57年前的西雅图冬夜,一个瘦削少年翻出卧室窗户,奔向名为“C的立方”的计算机实验室。比尔·盖茨不会想到,这段在寒夜中追逐代码的岁月,正悄然叩响数字时代的大门。当湖滨中学的编程少年们以无偿劳动换取珍贵的上机时间时,一场改变人类文明进程的变革已在晶体管与二进制中孕育——在这个数字时代的黎明,一群年轻的先驱者站在了浪潮之巅,他们以智慧和勇气,开辟了一个全新的领域:个人计算机软件。几乎与此同时,远在英特尔
SSL证书概述、类型、价格、作用及应用等10大常见问题解答
在当今互联网+时代,随着数字化进程的加快,网络威胁日益增加,中间人攻击、网络钓鱼等已成为常见的网络攻击手段,由此,为了维护网站安全,采取必要的安全防护措施很有必要。其中SSL证书作为遵循SSL协议,实现HTTPS加密的数字证书,可验证网站服务器身份,确保传输数据的安全性和完整性,有效避免中间人攻击,防止钓鱼网站假冒,是保障网站安全的有利工具。在本文中,我们将介绍关于SSL证书的10大常见问题解答,
Docker部署CRMEB多商户版再优化
HOOLOO
docker 容器 运维 CRMEB 多商户
不说费话,上一篇已经讲过了,直接上代码。Dockerfile:#使用官方的Ubuntu24.04镜像作为基础镜像FROMubuntu:24.04#设置环境变量以避免交互式配置工具ENVDEBIAN_FRONTEND=noninteractive#设置时区RUNln-sf/usr/share/zoneinfo/Asia/Shanghai/etc/localtime&&\echo"Asia/Shang
成为LLM大师的必读书籍:这几本大模型书籍,详细到让你一篇文章就收藏足够
AGI大模型老王
产品经理 大模型教程 学习 大模型 人工智能 LLM 大模型书籍
以下是几本关于大模型和人工智能领域的经典书籍,它们各自具有独特的特点和适用人群:《深度学习》(DeepLearning)作者:伊恩·古德费洛(IanGoodfellow)、约书亚·本吉奥(YoshuaBengio)、亚伦·库维尔(AaronCourville)简介:《深度学习》是深度学习领域的经典之作,全面介绍了深度学习的基础知识、主要模型及其应用。书中详细讲解了神经网络、卷积神经网络、循环神经网
软件工程/计算机科学与技术专业毕业设计选题
计算机毕设选手
计算机毕设选题推荐案例 课程设计 软件工程 spring boot 开发语言 java 后端 spring
博主介绍:✌全网粉丝30W+,CSDN全栈领域优质创作者,博客之星、掘金/华为云/阿里云等平台优质作者,计算机毕设实战导师。目前专注于大学生项目实战开发,讲解,毕业答疑辅导✌主要服务内容:选题定题、开题报告、任务书、程序开发、文档编写和辅导、文档降重、程序讲解、答辩辅导等,欢迎咨询~文末获取源码+数据库+文档感兴趣的可以先收藏起来,还有大家在毕设选题,项目以及文档编写等相关问题都可以给我沟通,希望
Android10.0关于发送广播Sending non-protected broadcast android.price.public.close
安卓兼职framework应用工程师
android 10.0 Rom定制化高级进阶 android 广播 broadcast system 异常广播
1.前言在10.0的相关rom定制化开发中,在进行某些功能开发过程中,发送广播在开发功能的过程中,也是非常常用的,然而在系统中为了安全,在发送第三方广播中,会出现ams中的异常,接下来分析下相关的功能,看下解决怎么不发生异常的功能2.关于发送广播Sendingnon-protectedbroadcastandroid.price.public.close.panelfromsystem异常处理的核
2025年计算机毕业设计springboot 校园生活服务平台
zhihao501
课程设计 spring boot 后端
本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容选题背景关于校园生活服务平台的研究,现有研究主要以校园信息化管理、服务系统集成以及学生需求满足为主。然而,专门针对校园生活服务平台综合功能设计与实现的研究较少。当前,随着信息技术的快速发展,校园生活服务平台的构建与优化成为提升校园生活质量、增强学生学习体验的重要途径。本课题目前存在的争
关于网络数通工程师 OSPF 协议的常见面试问题
他不爱吃香菜
网络面试解答 网络协议 网络 服务器 php 面试 运维 网络协议
基础理论部分OSPF是什么?其核心设计目标及主要特性有哪些?OSPF(开放式最短路径优先)是基于链路状态的内部网关协议(IGP),使用Dijkstra的SPF算法计算最短路径树,核心目标包括快速收敛、分层网络设计(区域划分)和避免路由环路12。主要特性:支持VLSM/CIDR,适用于复杂IP规划12。通过组播(224.0.0.5/224.0.0.6)传递协议报文,减少广播流量13。
嵌入式linux bootloader,嵌入式系统启动之bootloader 源码解析
三月十六
嵌入式linux bootloader
要探讨bootloader,我们首先从全局来看看,嵌入式系统启动流程是怎么样的。大体上一个嵌入式Linux系统从软件角度分析可以分为四个部分:引导加载程序(bootloader),Linux内核,文件系统,应用程序。当系统首次引导时,或系统被重置时,bootloader首先被执行(位于Flash/ROM中的已知位置处)的代码。它主要用来初始化处理器及外设,然后调用Linux内核。Linux内核在完
AI学习预备知识-数据操作(3)广播机制
羞涩的小吉他
人工智能 学习
AI学习预备知识-数据操作(3)广播机制提示:本系列持续更新中文章目录AI学习预备知识-数据操作(3)广播机制前言广播机制总结前言随着开始人工智能的学习越来越多,那么再学习过程中,我们应该有一定的基础知识储备,本系列为基础知识储备介绍,在以往系列中我们提到了相同形状的张量按元素操作,那不同形状的张量操作就涉及到本文主要讲解AI学习储备知识–广播机制。广播机制提示:默认使用python,数据操作使用
jQuery 键盘事件keydown ,keypress ,keyup介绍
107x
js jquery keydown keypress keyup
本文章总结了下些关于jQuery 键盘事件keydown ,keypress ,keyup介绍,有需要了解的朋友可参考。
一、首先需要知道的是: 1、keydown() keydown事件会在键盘按下时触发. 2、keyup() 代码如下 复制代码
$('input').keyup(funciton(){
AngularJS中的Promise
bijian1013
JavaScript AngularJS Promise
一.Promise
Promise是一个接口,它用来处理的对象具有这样的特点:在未来某一时刻(主要是异步调用)会从服务端返回或者被填充属性。其核心是,promise是一个带有then()函数的对象。
为了展示它的优点,下面来看一个例子,其中需要获取用户当前的配置文件:
var cu
c++ 用数组实现栈类
CrazyMizzz
数据结构 C++
#include<iostream>
#include<cassert>
using namespace std;
template<class T, int SIZE = 50>
class Stack{
private:
T list[SIZE];//数组存放栈的元素
int top;//栈顶位置
public:
Stack(
java和c语言的雷同
麦田的设计者
java 递归 scaner
软件启动时的初始化代码,加载用户信息2015年5月27号
从头学java二
1、语言的三种基本结构:顺序、选择、循环。废话不多说,需要指出一下几点:
a、return语句的功能除了作为函数返回值以外,还起到结束本函数的功能,return后的语句
不会再继续执行。
b、for循环相比于whi
LINUX环境并发服务器的三种实现模型
被触发
linux
服务器设计技术有很多,按使用的协议来分有TCP服务器和UDP服务器。按处理方式来分有循环服务器和并发服务器。
1 循环服务器与并发服务器模型
在网络程序里面,一般来说都是许多客户对应一个服务器,为了处理客户的请求,对服务端的程序就提出了特殊的要求。
目前最常用的服务器模型有:
·循环服务器:服务器在同一时刻只能响应一个客户端的请求
·并发服务器:服
Oracle数据库查询指令
肆无忌惮_
oracle数据库
20140920
单表查询
-- 查询************************************************************************************************************
-- 使用scott用户登录
-- 查看emp表
desc emp
ext右下角浮动窗口
知了ing
JavaScript ext
第一种
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/
浅谈REDIS数据库的键值设计
矮蛋蛋
redis
http://www.cnblogs.com/aidandan/
原文地址:http://www.hoterran.info/redis_kv_design
丰富的数据结构使得redis的设计非常的有趣。不像关系型数据库那样,DEV和DBA需要深度沟通,review每行sql语句,也不像memcached那样,不需要DBA的参与。redis的DBA需要熟悉数据结构,并能了解使用场景。
maven编译可执行jar包
alleni123
maven
http://stackoverflow.com/questions/574594/how-can-i-create-an-executable-jar-with-dependencies-using-maven
<build>
<plugins>
<plugin>
<artifactId>maven-asse
人力资源在现代企业中的作用
百合不是茶
HR 企业管理
//人力资源在在企业中的作用人力资源为什么会存在,人力资源究竟是干什么的 人力资源管理是对管理模式一次大的创新,人力资源兴起的原因有以下点: 工业时代的国际化竞争,现代市场的风险管控等等。所以人力资源 在现代经济竞争中的优势明显的存在,人力资源在集团类公司中存在着 明显的优势(鸿海集团),有一次笔者亲自去体验过红海集团的招聘,只 知道人力资源是管理企业招聘的 当时我被招聘上了,当时给我们培训 的人
Linux自启动设置详解
bijian1013
linux
linux有自己一套完整的启动体系,抓住了linux启动的脉络,linux的启动过程将不再神秘。
阅读之前建议先看一下附图。
本文中假设inittab中设置的init tree为:
/etc/rc.d/rc0.d
/etc/rc.d/rc1.d
/etc/rc.d/rc2.d
/etc/rc.d/rc3.d
/etc/rc.d/rc4.d
/etc/rc.d/rc5.d
/etc
Spring Aop Schema实现
bijian1013
java spring AOP
本例使用的是Spring2.5
1.Aop配置文件spring-aop.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmln
【Gson七】Gson预定义类型适配器
bit1129
gson
Gson提供了丰富的预定义类型适配器,在对象和JSON串之间进行序列化和反序列化时,指定对象和字符串之间的转换方式,
DateTypeAdapter
public final class DateTypeAdapter extends TypeAdapter<Date> {
public static final TypeAdapterFacto
【Spark八十八】Spark Streaming累加器操作(updateStateByKey)
bit1129
update
在实时计算的实际应用中,有时除了需要关心一个时间间隔内的数据,有时还可能会对整个实时计算的所有时间间隔内产生的相关数据进行统计。
比如: 对Nginx的access.log实时监控请求404时,有时除了需要统计某个时间间隔内出现的次数,有时还需要统计一整天出现了多少次404,也就是说404监控横跨多个时间间隔。
Spark Streaming的解决方案是累加器,工作原理是,定义
linux系统下通过shell脚本快速找到哪个进程在写文件
ronin47
一个文件正在被进程写 我想查看这个进程 文件一直在增大 找不到谁在写 使用lsof也没找到
这个问题挺有普遍性的,解决方法应该很多,这里我给大家提个比较直观的方法。
linux下每个文件都会在某个块设备上存放,当然也都有相应的inode, 那么透过vfs.write我们就可以知道谁在不停的写入特定的设备上的inode。
幸运的是systemtap的安装包里带了inodewatch.stp,位
java-两种方法求第一个最长的可重复子串
bylijinnan
java 算法
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class MaxPrefix {
public static void main(String[] args) {
String str="abbdabcdabcx";
Netty源码学习-ServerBootstrap启动及事件处理过程
bylijinnan
java netty
Netty是采用了Reactor模式的多线程版本,建议先看下面这篇文章了解一下Reactor模式:
http://bylijinnan.iteye.com/blog/1992325
Netty的启动及事件处理的流程,基本上是按照上面这篇文章来走的
文章里面提到的操作,每一步都能在Netty里面找到对应的代码
其中Reactor里面的Acceptor就对应Netty的ServerBo
servelt filter listener 的生命周期
cngolon
filter listener servelt 生命周期
1. servlet 当第一次请求一个servlet资源时,servlet容器创建这个servlet实例,并调用他的 init(ServletConfig config)做一些初始化的工作,然后调用它的service方法处理请求。当第二次请求这个servlet资源时,servlet容器就不在创建实例,而是直接调用它的service方法处理请求,也就是说
jmpopups获取input元素值
ctrain
JavaScript
jmpopups 获取弹出层form表单
首先,我有一个div,里面包含了一个表单,默认是隐藏的,使用jmpopups时,会弹出这个隐藏的div,其实jmpopups是将我们的代码生成一份拷贝。
当我直接获取这个form表单中的文本框时,使用方法:$('#form input[name=test1]').val();这样是获取不到的。
我们必须到jmpopups生成的代码中去查找这个值,$(
vi查找替换命令详解
daizj
linux 正则表达式 替换 查找 vim
一、查找
查找命令
/pattern<Enter> :向下查找pattern匹配字符串
?pattern<Enter>:向上查找pattern匹配字符串
使用了查找命令之后,使用如下两个键快速查找:
n:按照同一方向继续查找
N:按照反方向查找
字符串匹配
pattern是需要匹配的字符串,例如:
1: /abc<En
对网站中的js,css文件进行打包
dcj3sjt126com
PHP 打包
一,为什么要用smarty进行打包
apache中也有给js,css这样的静态文件进行打包压缩的模块,但是本文所说的不是以这种方式进行的打包,而是和smarty结合的方式来把网站中的js,css文件进行打包。
为什么要进行打包呢,主要目的是为了合理的管理自己的代码 。现在有好多网站,你查看一下网站的源码的话,你会发现网站的头部有大量的JS文件和CSS文件,网站的尾部也有可能有大量的J
php Yii: 出现undefined offset 或者 undefined index解决方案
dcj3sjt126com
undefined
在开发Yii 时,在程序中定义了如下方式:
if($this->menuoption[2] === 'test'),那么在运行程序时会报:undefined offset:2,这样的错误主要是由于php.ini 里的错误等级太高了,在windows下错误等级
linux 文件格式(1) sed工具
eksliang
linux linux sed工具 sed工具 linux sed详解
转载请出自出处:
http://eksliang.iteye.com/blog/2106082
简介
sed 是一种在线编辑器,它一次处理一行内容。处理时,把当前处理的行存储在临时缓冲区中,称为“模式空间”(pattern space),接着用sed命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕。接着处理下一行,这样不断重复,直到文件末尾
Android应用程序获取系统权限
gqdy365
android
引用
如何使Android应用程序获取系统权限
第一个方法简单点,不过需要在Android系统源码的环境下用make来编译:
1. 在应用程序的AndroidManifest.xml中的manifest节点
HoverTree开发日志之验证码
hvt
.net C# asp.net hovertree webform
HoverTree是一个ASP.NET的开源CMS,目前包含文章系统,图库和留言板功能。代码完全开放,文章内容页生成了静态的HTM页面,留言板提供留言审核功能,文章可以发布HTML源代码,图片上传同时生成高品质缩略图。推出之后得到许多网友的支持,再此表示感谢!留言板不断收到许多有益留言,但同时也有不少广告,因此决定在提交留言页面增加验证码功能。ASP.NET验证码在网上找,如果不是很多,就是特别多
JSON API:用 JSON 构建 API 的标准指南中文版
justjavac
json
译文地址:https://github.com/justjavac/json-api-zh_CN
如果你和你的团队曾经争论过使用什么方式构建合理 JSON 响应格式, 那么 JSON API 就是你的 anti-bikeshedding 武器。
通过遵循共同的约定,可以提高开发效率,利用更普遍的工具,可以是你更加专注于开发重点:你的程序。
基于 JSON API 的客户端还能够充分利用缓存,
数据结构随记_2
lx.asymmetric
数据结构 笔记
第三章 栈与队列
一.简答题
1. 在一个循环队列中,队首指针指向队首元素的 前一个 位置。
2.在具有n个单元的循环队列中,队满时共有 n-1 个元素。
3. 向栈中压入元素的操作是先 移动栈顶指针&n
Linux下的监控工具dstat
网络接口
linux
1) 工具说明dstat是一个用来替换 vmstat,iostat netstat,nfsstat和ifstat这些命令的工具, 是一个全能系统信息统计工具. 与sysstat相比, dstat拥有一个彩色的界面, 在手动观察性能状况时, 数据比较显眼容易观察; 而且dstat支持即时刷新, 譬如输入dstat 3, 即每三秒收集一次, 但最新的数据都会每秒刷新显示. 和sysstat相同的是,
C 语言初级入门--二维数组和指针
1140566087
二维数组 c/c++ 指针
/*
二维数组的定义和二维数组元素的引用
二维数组的定义:
当数组中的每个元素带有两个下标时,称这样的数组为二维数组;
(逻辑上把数组看成一个具有行和列的表格或一个矩阵);
语法:
类型名 数组名[常量表达式1][常量表达式2]
二维数组的引用:
引用二维数组元素时必须带有两个下标,引用形式如下:
例如:
int a[3][4]; 引用:
10点睛Spring4.1-Application Event
wiselyman
application
10.1 Application Event
Spring使用Application Event给bean之间的消息通讯提供了手段
应按照如下部分实现bean之间的消息通讯
继承ApplicationEvent类实现自己的事件
实现继承ApplicationListener接口实现监听事件
使用ApplicationContext发布消息