项目地址 :https://github.com/greenrobot/EventBus

这个项目个人感觉就是为了解决回调事件过多的,比方说A函数在做完以后 要调用b类的c函数,那我们通常的做法就是 定义一个接口 然后再A函数所属的类里面注册这个接口。

然后a函数做完以后 直接调用这个接口即可。但是这种方法写多了以后确实很麻烦,于是EventBus就是用来解决这种场景的。

和以往一样,我们只解析他的源码,如果你要学习他的用法请自行谷歌。

我们就从register函数开始说起。

  private synchronized void register(Object subscriber, boolean sticky, int priority) {
//这个list就是方法的集合
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass());
//这个循环的目的就是为了用 方法对象 来构造 Subscription对象
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod, sticky, priority);
}
}

首先来看一下SubscriberMethod这个类,

 /*
* Copyright (C) 2012 Markus Junginger, greenrobot (http://greenrobot.de)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.greenrobot.event; import java.lang.reflect.Method; import android.util.Log; /**
* 这个类就是描述方法用的
*/
final class SubscriberMethod {
final Method method;
final ThreadMode threadMode;
final Class<?> eventType;
/** Used for efficient comparison */
/**
* 这个methodString 实际上就是用来描述SubscriberMethod对象的,尤其是在重写的equals方法里 起到关键的作用
* 就类似于这种结构
* com.example.administrator.eventbustest.ItemDetailFragment#onEventMainThread(com.example.administrator.eventbustest.Item
*其实也很好理解就是 包名+类名#方法名(参数的类型
* 注意这里参数的类型也是全路径名 包名+类名
*/
String methodString; SubscriberMethod(Method method, ThreadMode threadMode, Class<?> eventType) {
this.method = method;
this.threadMode = threadMode;
this.eventType = eventType;
} @Override
public boolean equals(Object other) {
if (other instanceof SubscriberMethod) {
checkMethodString();
SubscriberMethod otherSubscriberMethod = (SubscriberMethod) other;
otherSubscriberMethod.checkMethodString();
// Don't use method.equals because of http://code.google.com/p/android/issues/detail?id=7811#c6
return methodString.equals(otherSubscriberMethod.methodString);
} else {
return false;
}
} private synchronized void checkMethodString() {
if (methodString == null) {
// Method.toString has more overhead, just take relevant parts of the method
StringBuilder builder = new StringBuilder(64);
builder.append(method.getDeclaringClass().getName());
builder.append('#').append(method.getName());
builder.append('(').append(eventType.getName());
methodString = builder.toString();
}
} @Override
public int hashCode() {
return method.hashCode();
}
}

然后我们来看看这个SubscriberMethod对象组成的集合 是怎么构造出来的。

 List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
String key = subscriberClass.getName();
List<SubscriberMethod> subscriberMethods;
synchronized (methodCache) {
subscriberMethods = methodCache.get(key);
} if (subscriberMethods != null) {
return subscriberMethods;
}
subscriberMethods = new ArrayList<SubscriberMethod>();
Class<?> clazz = subscriberClass;
HashSet<String> eventTypesFound = new HashSet<String>();
StringBuilder methodKeyBuilder = new StringBuilder();
while (clazz != null) {
String name = clazz.getName();
//这个地方判断如果是这些类,那么就直接跳出这个while循环
//注意name的值 也是包名+类名,所以这里就是过滤掉基础的sdk的那些方法
//如果你有引用其他公共lib库的话 你也可以过滤他们的包,
if (name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("android.")) {
// Skip system classes, this just degrades performance
break;
} // Starting with EventBus 2.2 we enforced methods to be public (might change with annotations again)
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
String methodName = method.getName();
Log.e("burning", "methodName == " + methodName);
//只有那些以onEvent开头的方法才是我们需要的
if (methodName.startsWith(ON_EVENT_METHOD_NAME)) {
int modifiers = method.getModifiers();
//注意这个地方判断方法属性的技巧 与 操作
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
Class<?>[] parameterTypes = method.getParameterTypes();
//如果参数只有一个
if (parameterTypes.length == 1) {
String modifierString = methodName.substring(ON_EVENT_METHOD_NAME.length());
//取ThreadMode
ThreadMode threadMode;
if (modifierString.length() == 0) {
threadMode = ThreadMode.PostThread;
} else if (modifierString.equals("MainThread")) {
threadMode = ThreadMode.MainThread;
} else if (modifierString.equals("BackgroundThread")) {
threadMode = ThreadMode.BackgroundThread;
} else if (modifierString.equals("Async")) {
threadMode = ThreadMode.Async;
} else {
if (skipMethodVerificationForClasses.containsKey(clazz)) {
continue;
} else {
throw new EventBusException("Illegal onEvent method, check for typos: " + method);
}
}
Class<?> eventType = parameterTypes[0];
methodKeyBuilder.setLength(0);
methodKeyBuilder.append(methodName);
methodKeyBuilder.append('>').append(eventType.getName());
//onEventMainThread>java.lang.String
//methodKey就是上面的形式,可以看出来是方法名>参数 的格式
String methodKey = methodKeyBuilder.toString();
//这个地方先去这个hashset里面add这个key,当然了,如果你这个hashset里面已经有这个key
//那必然是add不成功的,只有add成功返回true以后括号内的代码才会得到执行
if (eventTypesFound.add(methodKey)) {
// Only add if not already found in a sub class
//这个地方就是构造SubscriberMethod对象放到list里 准备返回
subscriberMethods.add(new SubscriberMethod(method, threadMode, eventType));
}
}
} else if (!skipMethodVerificationForClasses.containsKey(clazz)) {
Log.d(EventBus.TAG, "Skipping method (not public, static or abstract): " + clazz + "."
+ methodName);
}
}
}
//这里注意还在大的while循环内,所以你传进去的类自己查完一遍方法以后 还会去找他的父类继续查询方法
//一直遍历到父类为 那些java android开头的基类为止!
clazz = clazz.getSuperclass(); }
if (subscriberMethods.isEmpty()) {
throw new EventBusException("Subscriber " + subscriberClass + " has no public methods called "
+ ON_EVENT_METHOD_NAME);
} else {
synchronized (methodCache) {
methodCache.put(key, subscriberMethods);
}
return subscriberMethods;
}
}

然后我们来看看register函数里面 5-6行 那个循环遍历做了什么

首先我们看看这个循环调用的方法:

 /**
* @param subscriber 方法所述的类的 包名+类名
* @param subscriberMethod
* @param sticky
* @param priority
*/
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod, boolean sticky, int priority) {
//这个eventtype就是方法的参数的类名
Class<?> eventType = subscriberMethod.eventType; CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
Subscription newSubscription = new Subscription(subscriber, subscriberMethod, priority);
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<Subscription>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
}
} // Starting with EventBus 2.2 we enforced methods to be public (might change with annotations again)
// subscriberMethod.method.setAccessible(true);
//这个就是优先级高的位置在前面
int size = subscriptions.size();
for (int i = 0; i <= size; i++) {
if (i == size || newSubscription.priority > subscriptions.get(i).priority) {
subscriptions.add(i, newSubscription);
break;
}
} List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<Class<?>>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType); if (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<Class>).
Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
for (Map.Entry<Class<?>, 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);
}
}
}

10-13行 我们可以看出来 这个函数 主要是为了构造subscription这个list对象。

  /**
* 这个map 存储方法的地方 key就是eventType,value就是copyOnWriteArrayList value就是方法的一切
*/
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;

我们可以看看这个类是什么

 /**
* 这个类里面包含有SubscriberMethod类对象,
* subscriber
*/
final class Subscription {
//这个实际上就是描述方法所属的类的
final Object subscriber;
//描述方法的类
final SubscriberMethod subscriberMethod;
//优先级
final int priority;
/**
* 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, int priority) {
this.subscriber = subscriber;
this.subscriberMethod = subscriberMethod;
this.priority = priority;
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();
}
}

所以总结起来,SubscriberMethod 就是对方法的描述,而我们的SubscriberMethod 实际上就是Subscription的子集,Subscription除了有描述方法的对象以外,还有这个方法所属的类,

而我们的register方法总体来说 就是先通过findSubscriberMethods方法 取得我们注册类(就是你register调用的时候传的this)所需要的的那些方法(注意不是每个方法都需要 只选择自己需要的)

然后把这些方法 做一个list,最后再通过便利这个list : 用每一个SubscriberMethod 对象和这个方法所需的类(包名+类名) 来构造出一个Subscription对象,然后把这个对象

存储在SubscriptionsByEventType里,注意这个map的key 实际上就是eventType,而value则代表方法的list,换句话说。

这个SubscriptionsByEventType 是一个键值对,它的key 实际上就是我们的类名,value则是这个类里面我们需要存储的方法的list!

这就是register的大致流程,我们再来看看post 流程即可。

  /**
* Posts the given event to the event bus.
*/
public void post(Object event) {
PostingThreadState postingState = currentPostingThreadState.get();
//这个地方可以看出来是每次有人调用post方法的时候 都会从postingState取出这个队列,然后把这个事件放到这个队列里
List<Object> eventQueue = postingState.eventQueue;
eventQueue.add(event); //这个判断isPosting 主要是为了保证同一时间只能有一个线程在处理括号体里的内容
//currentPostingThreadState 是用threadlocal来构造的 所以保证了同步性
if (!postingState.isPosting) {
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;
}
}
}

先看看第5行的postingState是什么

  /**
* 静态类,里面除了有一个队列以外,还有几个标志位,以及一个Subscription
*/
final static class PostingThreadState {
final List<Object> eventQueue = new ArrayList<Object>();
boolean isPosting;
boolean isMainThread;
Subscription subscription;
Object event;
boolean canceled;
}

这个地方就能看出来,我们每次调用post 都是往eventQueue里面添加一个事件,而12行开始则是从队列里面

取事件来处理,注意12行开始 一次性只能允许一个线程使用~同步的

然后继续看是怎么处理的。

  /**
* @param event 方法的参数的类名
* @param postingState
* @throws Error
*/
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error { Class<?> eventClass = event.getClass();
boolean subscriptionFound = false; if (eventInheritance) {
List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
int countTypes = eventTypes.size();
for (int h = 0; h < countTypes; h++) {
Class<?> clazz = eventTypes.get(h);
//这个地方就是取出Subscription对象的的所有信息!发消息也是在这个函数里发送的
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));
}
}
}

继续跟进去

 private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
CopyOnWriteArrayList<Subscription> subscriptions;
synchronized (this) {
subscriptions = subscriptionsByEventType.get(eventClass);
}
if (subscriptions != null && !subscriptions.isEmpty()) {
for (Subscription subscription : subscriptions) {
postingState.event = event;
postingState.subscription = subscription;
boolean aborted = false;
try {
//这个地方就是真正发消息的地方了
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;
}

可以看出来 2-6行 就是从我们register流程里存储的键值对里 把我们存放的方法给取出来。,

取出来以后 就可以反射调用他们的方法了

   /**
* 这个类就是反射执行方法 并且是最终执行回调方法的地方
*
* @param subscription
* @param event
* @param isMainThread
*/
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);
}
}

13-18行 这个case 如果是主线程,那么就直接反射方法,如果不是的话 则要放到主线程handler里执行。

  private final HandlerPoster mainThreadPoster;
  //这个就是主线程handler初始化
mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10);
 /*
* Copyright (C) 2012 Markus Junginger, greenrobot (http://greenrobot.de)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.greenrobot.event; import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock; 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) {
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);
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;
}
}
}

51-77行 就是我们实际最终调用的地方。

同样的 我们在看看20-26行的这个background这个case

 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.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;
}
}
}
eventBus.invokeSubscriber(pendingPost);
}
} catch (InterruptedException e) {
Log.w("Event", Thread.currentThread().getName() + " was interruppted", e);
}
} finally {
executorRunning = false;
}
}

一看就知道 他是runnable对象 必然是在后台 在子线程内执行,同时他也是一次性只能做一次操作,完成一个事件,

最后我们来看看Async这个case:

 /*
* Copyright (C) 2012 Markus Junginger, greenrobot (http://greenrobot.de)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.greenrobot.event; /**
* Posts events in background.
*
* @author Markus 并发执行任务,在线程池内执行
*/
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);
} }

这个地方和background相同的就是也是在非主线程,在子线程内执行,但是这个地方是在线程池内执行,可以并发执行多个任务,

而我们的background 则一次性只能执行一个任务,这是2者之间的区别。

Android EventBus源码解析的更多相关文章

  1. Android EventBus源码解析 带你深入理解EventBus

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/40920453,本文出自:[张鸿洋的博客] 上一篇带大家初步了解了EventBus ...

  2. 【Android】EventBus 源码解析

    EventBus 源码解析 本文为 Android 开源项目实现原理解析 中 EventBus 部分项目地址:EventBus,分析的版本:ccc2771,Demo 地址:EventBus Demo分 ...

  3. EventBus源码解析 源码阅读记录

    EventBus源码阅读记录 repo地址: greenrobot/EventBus EventBus的构造 双重加锁的单例. static volatile EventBus defaultInst ...

  4. Android -- AsyncTask源码解析

    1,前段时间换工作的时候,关于AsyncTask源码这个点基本上大一点的公司都会问,所以今天就和大家一起来总结总结.本来早就想写这篇文章的,当时写<Android -- 从源码解析Handle+ ...

  5. 还怕问源码?Github上神级Android三方源码解析手册,已有7.6 KStar

    或许对于许多Android开发者来说,所谓的Android工程师的工作"不过就是用XML实现设计师的美术图,用JSON解析服务器的数据,再把数据显示到界面上"就好了,源码什么的,看 ...

  6. Android AsyncTask 源码解析

    1. 官方介绍 public abstract class AsyncTask extends Object  java.lang.Object    ↳ android.os.AsyncTask&l ...

  7. Android -- 从源码解析Handle+Looper+MessageQueue机制

    1,今天和大家一起从底层看看Handle的工作机制是什么样的,那么在引入之前我们先来了解Handle是用来干什么的 handler通俗一点讲就是用来在各个线程之间发送数据的处理对象.在任何线程中,只要 ...

  8. Android LayoutInflater源码解析:你真的能正确使用吗?

    版权声明:本文出自汪磊的博客,未经作者允许禁止转载. 好久没写博客了,最近忙着换工作,没时间写,工作刚定下来.稍后有时间会写一下换工作经历.接下来进入本篇主题,本来没想写LayoutInflater的 ...

  9. Android HandlerThread源码解析

    在上一章Handler源码解析文章中,我们知道App的主线程通过Handler机制完成了一个线程的消息循环.那么我们自己也可以新建一个线程,在线程里面创建一个Looper,完成消息循环,可以做一些定时 ...

随机推荐

  1. 基础DOM和CSS操作(一)

    DOM简介 DOM是一种文档对象模型,方便开发者对HTML结构元素内容进行展示和修改.在JavaScript中,DOM不但内容庞大繁杂,而且我们开发的过程中需要考虑更多的兼容性.扩展性.在jQuery ...

  2. Dice chrone execise

    def score(dices_input): count = {}.fromkeys(range(1, 7), 0) points = 0 for dice_side in dices_input: ...

  3. RTMP/RTP/RTSP/RTCP的区别

    RTCP RTMP/RTP/RTSP/RTCP的区别 http://blog.csdn.net/frankiewang008/article/details/7665547 流媒体协议介绍(rtp/r ...

  4. java内存模型优化建议

    八.Java编程建议 根据GC的工作原理,我们可以通过一些技巧和方式,让GC运行更加有效率,更加符合应用程序的要求.一些关于程序设计的几点建议: 1)最基本的建议就是尽早释放无用对象的引用.大多数程序 ...

  5. React组件生命周期-初始化阶段的函数执行顺序

    <!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="UTF-8& ...

  6. Java科普之基础知识回顾

    本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! Java语言由C衍生,丢弃C中的指针,使用"发出指令-接收返回值-验证-发出指令-接收返回 ...

  7. ubuntu下搭建cocos2dx编程环境-中

        上篇文章里讲了在ubuntu下部署cocos2d-x开发环境,这篇文章主要示范在ubuntu下部署cocos2d-x android开发环境.分开写就是因为我看很多文章里都将这两件事情混杂着写 ...

  8. ThreadLocal,ThreadLocalMap,Thread 的相互关系

    1.ThreadLocal. 真正关键的类是它的内部类ThreadLocalMap,ThreadLocal 基本上相当于一个代理,或者算是Facade模式的应用,还没想清楚这种设计的妙处.(经过分析, ...

  9. sql 随笔 2015-08-07

    xls 导入数据库 --删除现有数据 DELETE FROM dbo.PhoneList --插入数据 insert into dbo.PhoneList --读取xls数据 ) , as [Enab ...

  10. Android 如何去除桌面上下边框暗度逐渐变暗的效果

    前言          欢迎大家我分享和推荐好用的代码段~~ 声明          欢迎转载,但请保留文章原始出处:          CSDN:http://www.csdn.net        ...