转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/40920453,本文出自:【张鸿洋的博客】

上一篇带大家初步了解了EventBus的使用方式,详见:Android EventBus实战 没听过你就out了,本篇博客将解析EventBus的源码,相信能够让大家深入理解该框架的实现,也能解决很多在使用中的疑问:为什么可以这么做?为什么这么做不好呢?

1、概述

一般使用EventBus的组件类,类似下面这种方式:

  1. public class SampleComponent extends Fragment
  2. {
  3. @Override
  4. public void onCreate(Bundle savedInstanceState)
  5. {
  6. super.onCreate(savedInstanceState);
  7. EventBus.getDefault().register(this);
  8. }
  9. public void onEventMainThread(param)
  10. {
  11. }
  12. public void onEventPostThread(param)
  13. {
  14. }
  15. public void onEventBackgroundThread(param)
  16. {
  17. }
  18. public void onEventAsync(param)
  19. {
  20. }
  21. @Override
  22. public void onDestroy()
  23. {
  24. super.onDestroy();
  25. EventBus.getDefault().unregister(this);
  26. }
  27. }

大多情况下,都会在onCreate中进行register,在onDestory中进行unregister ;

看完代码大家或许会有一些疑问:

1、代码中还有一些以onEvent开头的方法,这些方法是干嘛的呢?

在回答这个问题之前,我有一个问题,你咋不问register(this)是干嘛的呢?其实register(this)就是去当前类,遍历所有的方法,找到onEvent开头的然后进行存储。现在知道onEvent开头的方法是干嘛的了吧。

2、那onEvent后面的那些MainThread应该是什么标志吧?

嗯,是的,onEvent后面可以写四种,也就是上面出现的四个方法,决定了当前的方法最终在什么线程运行,怎么运行,可以参考上一篇博客或者细细往下看。

既然register了,那么肯定得说怎么调用是吧。

  1. EventBus.getDefault().post(param);

调用很简单,一句话,你也可以叫发布,只要把这个param发布出去,EventBus会在它内部存储的方法中,进行扫描,找到参数匹配的,就使用反射进行调用。

现在有没有觉得,撇开专业术语:其实EventBus就是在内部存储了一堆onEvent开头的方法,然后post的时候,根据post传入的参数,去找到匹配的方法,反射调用之。

那么,我告诉你,它内部使用了Map进行存储,键就是参数的Class类型。知道是这个类型,那么你觉得根据post传入的参数进行查找还是个事么?

下面我们就去看看EventBus的register和post真面目。

2、register

EventBus.getDefault().register(this);

首先:

EventBus.getDefault()其实就是个单例,和我们传统的getInstance一个意思:

  1. /** Convenience singleton for apps using a process-wide EventBus instance. */
  2. public static EventBus getDefault() {
  3. if (defaultInstance == null) {
  4. synchronized (EventBus.class) {
  5. if (defaultInstance == null) {
  6. defaultInstance = new EventBus();
  7. }
  8. }
  9. }
  10. return defaultInstance;
  11. }

使用了双重判断的方式,防止并发的问题,还能极大的提高效率。

然后register应该是一个普通的方法,我们去看看:

register公布给我们使用的有4个:

  1. public void register(Object subscriber) {
  2. register(subscriber, DEFAULT_METHOD_NAME, false, 0);
  3. }
  4. public void register(Object subscriber, int priority) {
  5. register(subscriber, DEFAULT_METHOD_NAME, false, priority);
  6. }
  7. public void registerSticky(Object subscriber) {
  8. register(subscriber, DEFAULT_METHOD_NAME, true, 0);
  9. }
  10. public void registerSticky(Object subscriber, int priority) {
  11. register(subscriber, DEFAULT_METHOD_NAME, true, priority);
  12. }

本质上就调用了同一个:

  1. private synchronized void register(Object subscriber, String methodName, boolean sticky, int priority) {
  2. List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass(),
  3. methodName);
  4. for (SubscriberMethod subscriberMethod : subscriberMethods) {
  5. subscribe(subscriber, subscriberMethod, sticky, priority);
  6. }
  7. }

四个参数

subscriber 是我们扫描类的对象,也就是我们代码中常见的this;

methodName 这个是写死的:“onEvent”,用于确定扫描什么开头的方法,可见我们的类中都是以这个开头。

sticky 这个参数,解释源码的时候解释,暂时不用管

priority 优先级,优先级越高,在调用的时候会越先调用。

下面开始看代码:

  1. List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass(),
  2. methodName);

调用内部类SubscriberMethodFinder的findSubscriberMethods方法,传入了subscriber 的class,以及methodName,返回一个List<SubscriberMethod>。

那么不用说,肯定是去遍历该类内部所有方法,然后根据methodName去匹配,匹配成功的封装成SubscriberMethod,最后返回一个List。下面看代码:

  1. List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass, String eventMethodName) {
  2. String key = subscriberClass.getName() + '.' + eventMethodName;
  3. List<SubscriberMethod> subscriberMethods;
  4. synchronized (methodCache) {
  5. subscriberMethods = methodCache.get(key);
  6. }
  7. if (subscriberMethods != null) {
  8. return subscriberMethods;
  9. }
  10. subscriberMethods = new ArrayList<SubscriberMethod>();
  11. Class<?> clazz = subscriberClass;
  12. HashSet<String> eventTypesFound = new HashSet<String>();
  13. StringBuilder methodKeyBuilder = new StringBuilder();
  14. while (clazz != null) {
  15. String name = clazz.getName();
  16. if (name.startsWith("<a href="http://lib.csdn.net
    /base/17" class="replace_word" title="Java EE知识
    库" target="_blank" style="color:#df3434; font-weight:bold;">java</a>.") || name.startsWith("javax.") || name.startsWith("android.")) {
  17. // Skip system classes, this just degrades performance
  18. break;
  19. }
  20. // Starting with EventBus 2.2 we enforced methods to be public (might change with annotations again)
  21. Method[] methods = clazz.getMethods();
  22. for (Method method : methods) {
  23. String methodName = method.getName();
  24. if (methodName.startsWith(eventMethodName)) {
  25. int modifiers = method.getModifiers();
  26. if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
  27. Class<?>[] parameterTypes = method.getParameterTypes();
  28. if (parameterTypes.length == 1) {
  29. String modifierString = methodName.substring(eventMethodName.length());
  30. ThreadMode threadMode;
  31. if (modifierString.length() == 0) {
  32. threadMode = ThreadMode.PostThread;
  33. } else if (modifierString.equals("MainThread")) {
  34. threadMode = ThreadMode.MainThread;
  35. } else if (modifierString.equals("BackgroundThread")) {
  36. threadMode = ThreadMode.BackgroundThread;
  37. } else if (modifierString.equals("Async")) {
  38. threadMode = ThreadMode.Async;
  39. } else {
  40. if (skipMethodVerificationForClasses.containsKey(clazz)) {
  41. continue;
  42. } else {
  43. throw new EventBusException("Illegal onEvent method, check for typos: " + method);
  44. }
  45. }
  46. Class<?> eventType = parameterTypes[0];
  47. methodKeyBuilder.setLength(0);
  48. methodKeyBuilder.append(methodName);
  49. methodKeyBuilder.append('>').append(eventType.getName());
  50. String methodKey = methodKeyBuilder.toString();
  51. if (eventTypesFound.add(methodKey)) {
  52. // Only add if not already found in a sub class
  53. subscriberMethods.add(new SubscriberMethod(method, threadMode, eventType));
  54. }
  55. }
  56. } else if (!skipMethodVerificationForClasses.containsKey(clazz)) {
  57. Log.d(EventBus.TAG, "Skipping method (not public, static or abstract): " + clazz + "."
  58. + methodName);
  59. }
  60. }
  61. }
  62. clazz = clazz.getSuperclass();
  63. }
  64. if (subscriberMethods.isEmpty()) {
  65. throw new EventBusException("Subscriber " + subscriberClass + " has no public methods called "
  66. + eventMethodName);
  67. } else {
  68. synchronized (methodCache) {
  69. methodCache.put(key, subscriberMethods);
  70. }
  71. return subscriberMethods;
  72. }
  73. }

呵,代码还真长;不过我们直接看核心部分:

22行:看到没,clazz.getMethods();去得到所有的方法:

23-62行:就开始遍历每一个方法了,去匹配封装了。

25-29行:分别判断了是否以onEvent开头,是否是public且非static和abstract方法,是否是一个参数。如果都复合,才进入封装的部分。

32-45行:也比较简单,根据方法的后缀,来确定threadMode,threadMode是个枚举类型:就四种情况。

最后在54行:将method, threadMode, eventType传入构造了:new SubscriberMethod(method, threadMode, eventType)。添加到List,最终放回。

注意下63行:clazz = clazz.getSuperclass();可以看到,会扫描所有的父类,不仅仅是当前类。

继续回到register:

  1. for (SubscriberMethod subscriberMethod : subscriberMethods) {
  2. subscribe(subscriber, subscriberMethod, sticky, priority);
  3. }

for循环扫描到的方法,然后去调用suscribe方法。

  1. // Must be called in synchronized block
  2. private void subscribe(Object subscriber, SubscriberMethod subscriberMethod, boolean sticky, int priority) {
  3. subscribed = true;
  4. Class<?> eventType = subscriberMethod.eventType;
  5. CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
  6. Subscription newSubscription = new Subscription(subscriber, subscriberMethod, priority);
  7. if (subscriptions == null) {
  8. subscriptions = new CopyOnWriteArrayList<Subscription>();
  9. subscriptionsByEventType.put(eventType, subscriptions);
  10. } else {
  11. for (Subscription subscription : subscriptions) {
  12. if (subscription.equals(newSubscription)) {
  13. throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
  14. + eventType);
  15. }
  16. }
  17. }
  18. // Starting with EventBus 2.2 we enforced methods to be public (might change with annotations again)
  19. // subscriberMethod.method.setAccessible(true);
  20. int size = subscriptions.size();
  21. for (int i = 0; i <= size; i++) {
  22. if (i == size || newSubscription.priority > subscriptions.get(i).priority) {
  23. subscriptions.add(i, newSubscription);
  24. break;
  25. }
  26. }
  27. List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
  28. if (subscribedEvents == null) {
  29. subscribedEvents = new ArrayList<Class<?>>();
  30. typesBySubscriber.put(subscriber, subscribedEvents);
  31. }
  32. subscribedEvents.add(eventType);
  33. if (sticky) {
  34. Object stickyEvent;
  35. synchronized (stickyEvents) {
  36. stickyEvent = stickyEvents.get(eventType);
  37. }
  38. if (stickyEvent != null) {
  39. // If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state)
  40. // --> Strange corner case, which we don't take care of here.
  41. postToSubscription(newSubscription, stickyEvent, Looper.getMainLooper() == Looper.myLooper());
  42. }
  43. }
  44. }

我们的subscriberMethod中保存了method, threadMode, eventType,上面已经说了;

4-17行:根据subscriberMethod.eventType,去subscriptionsByEventType去查找一个CopyOnWriteArrayList<Subscription> ,如果没有则创建。

顺便把我们的传入的参数封装成了一个:Subscription(subscriber, subscriberMethod, priority);

这里的subscriptionsByEventType是个Map,key:eventType ; value:CopyOnWriteArrayList<Subscription> ; 这个Map其实就是EventBus存储方法的地方,一定要记住!

22-28行:实际上,就是添加newSubscription;并且是按照优先级添加的。可以看到,优先级越高,会插到在当前List的前面。

30-35行:根据subscriber存储它所有的eventType ; 依然是map;key:subscriber ,value:List<eventType> ;知道就行,非核心代码,主要用于isRegister的判断。

37-47行:判断sticky;如果为true,从stickyEvents中根据eventType去查找有没有stickyEvent,如果有则立即发布去执行。stickyEvent其实就是我们post时的参数。

postToSubscription这个方法,我们在post的时候会介绍。

到此,我们register就介绍完了。

你只要记得一件事:扫描了所有的方法,把匹配的方法最终保存在subscriptionsByEventType(Map,key:eventType ; value:CopyOnWriteArrayList<Subscription> )中;

eventType是我们方法参数的Class,Subscription中则保存着subscriber, subscriberMethod(method, threadMode, eventType), priority;包含了执行改方法所需的一切。

3、post

register完毕,知道了EventBus如何存储我们的方法了,下面看看post它又是如何调用我们的方法的。

再看源码之前,我们猜测下:register时,把方法存在subscriptionsByEventType;那么post肯定会去subscriptionsByEventType去取方法,然后调用。

下面看源码:

  1. /** Posts the given event to the event bus. */
  2. public void post(Object event) {
  3. PostingThreadState postingState = currentPostingThreadState.get();
  4. List<Object> eventQueue = postingState.eventQueue;
  5. eventQueue.add(event);
  6. if (postingState.isPosting) {
  7. return;
  8. } else {
  9. postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
  10. postingState.isPosting = true;
  11. if (postingState.canceled) {
  12. throw new EventBusException("Internal error. Abort state was not reset");
  13. }
  14. try {
  15. while (!eventQueue.isEmpty()) {
  16. postSingleEvent(eventQueue.remove(0), postingState);
  17. }
  18. } finally {
  19. postingState.isPosting = false;
  20. postingState.isMainThread = false;
  21. }
  22. }
  23. }

currentPostingThreadState是一个ThreadLocal类型的,里面存储了PostingThreadState;PostingThreadState包含了一个eventQueue和一些标志位。

  1. private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
  2. @Override
  3. protected PostingThreadState initialValue() {
  4. return new PostingThreadState();
  5. }
  6. }

把我们传入的event,保存到了当前线程中的一个变量PostingThreadState的eventQueue中。

10行:判断当前是否是UI线程。

16-18行:遍历队列中的所有的event,调用postSingleEvent(eventQueue.remove(0), postingState)方法。

这里大家会不会有疑问,每次post都会去调用整个队列么,那么不会造成方法多次调用么?

可以看到第7-8行,有个判断,就是防止该问题的,isPosting=true了,就不会往下走了。

下面看postSingleEvent

  1. private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
  2. Class<? extends Object> eventClass = event.getClass();
  3. List<Class<?>> eventTypes = findEventTypes(eventClass);
  4. boolean subscriptionFound = false;
  5. int countTypes = eventTypes.size();
  6. for (int h = 0; h < countTypes; h++) {
  7. Class<?> clazz = eventTypes.get(h);
  8. CopyOnWriteArrayList<Subscription> subscriptions;
  9. synchronized (this) {
  10. subscriptions = subscriptionsByEventType.get(clazz);
  11. }
  12. if (subscriptions != null && !subscriptions.isEmpty()) {
  13. for (Subscription subscription : subscriptions) {
  14. postingState.event = event;
  15. postingState.subscription = subscription;
  16. boolean aborted = false;
  17. try {
  18. postToSubscription(subscription, event, postingState.isMainThread);
  19. aborted = postingState.canceled;
  20. } finally {
  21. postingState.event = null;
  22. postingState.subscription = null;
  23. postingState.canceled = false;
  24. }
  25. if (aborted) {
  26. break;
  27. }
  28. }
  29. subscriptionFound = true;
  30. }
  31. }
  32. if (!subscriptionFound) {
  33. Log.d(TAG, "No subscribers registered for event " + eventClass);
  34. if (eventClass != NoSubscriberEvent.class && eventClass != SubscriberExceptionEvent.class) {
  35. post(new NoSubscriberEvent(this, event));
  36. }
  37. }
  38. }

将我们的event,即post传入的实参;以及postingState传入到postSingleEvent中。

2-3
行:根据event的Class,去得到一个List<Class<?>>;其实就是得到event当前对象的Class,以及
父类和接口的Class类型;主要用于匹配,比如你传入Dog extends Dog,他会把Animal也装到该List中。

6-31行:遍历所有的Class,到subscriptionsByEventType去查找subscriptions;哈哈,熟不熟悉,还记得我们register里面把方法存哪了不?

是不是就是这个Map;

12-30行:遍历每个subscription,依次去调用postToSubscription(subscription, event, postingState.isMainThread);
这个方法就是去反射执行方法了,大家还记得在register,if(sticky)时,也会去执行这个方法。

下面看它如何反射执行:

  1. private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
  2. switch (subscription.subscriberMethod.threadMode) {
  3. case PostThread:
  4. invokeSubscriber(subscription, event);
  5. break;
  6. case MainThread:
  7. if (isMainThread) {
  8. invokeSubscriber(subscription, event);
  9. } else {
  10. mainThreadPoster.enqueue(subscription, event);
  11. }
  12. break;
  13. case BackgroundThread:
  14. if (isMainThread) {
  15. backgroundPoster.enqueue(subscription, event);
  16. } else {
  17. invokeSubscriber(subscription, event);
  18. }
  19. break;
  20. case Async:
  21. asyncPoster.enqueue(subscription, event);
  22. break;
  23. default:
  24. throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
  25. }
  26. }

前面已经说过subscription包含了所有执行需要的东西,大致有:subscriber, subscriberMethod(method, threadMode, eventType), priority;

那么这个方法:第一步根据threadMode去判断应该在哪个线程去执行该方法;
case PostThread:

  1. void invokeSubscriber(Subscription subscription, Object event) throws Error {
  2. subscription.subscriberMethod.method.invoke(subscription.subscriber, event);

直接反射调用;也就是说在当前的线程直接调用该方法;

case MainThread:


先去判断当前如果是UI线程,则直接调用;否则: mainThreadPoster.enqueue(subscription,
event);把当前的方法加入到队列,然后直接通过handler去发送一个消息,在handler的handleMessage中,去执行我们的方
法。说白了就是通过Handler去发送消息,然后执行的。

case BackgroundThread:

如果当前非UI线程,则直接调用;如果是UI线程,则将任务加入到后台的一个队列,最终由Eventbus中的一个线程池去调用

executorService = Executors.newCachedThreadPool();。

case Async:将任务加入到后台的一个队列,最终由Eventbus中的一个线程池去调用;线程池与BackgroundThread用的是同一个。

这么说BackgroundThread和Async有什么区别呢?

BackgroundThread中的任务,一个接着一个去调用,中间使用了一个布尔型变量handlerActive进行的控制。

Async则会动态控制并发。

到此,我们完整的源码分析就结束了,总结一下:register会把当前类中匹配的方法,存入一个map,而post会根据实参去map查找进行反射调用。分析这么久,一句话就说完了~~

其实不用发布者,订阅者,事件,总线这几个词或许更好理解,以后大家问了EventBus,可以说,就是在一个单例内部维持着一个map对象存储了一堆的方法;post无非就是根据参数去查找方法,进行反射调用。

4、其余方法

介绍了register和post;大家获取还能想到一个词sticky,在register中,如何sticky为true,会去stickyEvents去查找事件,然后立即去post;

那么这个stickyEvents何时进行保存事件呢?

其实evevntbus中,除了post发布事件,还有一个方法也可以:

  1. public void postSticky(Object event) {
  2. synchronized (stickyEvents) {
  3. stickyEvents.put(event.getClass(), event);
  4. }
  5. // Should be posted after it is putted, in case the subscriber wants to remove immediately
  6. post(event);
  7. }

和post功能类似,但是会把方法存储到stickyEvents中去;

大家再去看看EventBus中所有的public方法,无非都是一些状态判断,获取事件,移除事件的方法;没什么好介绍的,基本见名知意。

好了,到此我们的源码解析就结束了,希望大家不仅能够了解这些优秀框架的内部机理,更能够体会到这些框架的很多细节之处,并发的处理,很多地方,为什么它这么做等等。

我建了一个QQ群,方便大家交流。群号:55032675

----------------------------------------------------------------------------------------------------------

博主部分视频已经上线,如果你不喜欢枯燥的文本,请猛戳(初录,期待您的支持):

1、高仿微信5.2.1主界面及消息提醒

2、高仿QQ5.0侧滑

3、Android智能机器人“小慕”的实现

EventBus (三) 源码解析 带你深入理解EventBus的更多相关文章

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

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

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

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

  3. EventBus3.0源码解析

    本文主要介绍EventBus3.0的源码 EventBus是一个Android事件发布/订阅框架,通过解耦发布者和订阅者简化 Android 事件传递. EventBus使用简单,并将事件发布和订阅充 ...

  4. LinkedList源码解析

    LinkedList是基于链表结构的一种List,在分析LinkedList源码前有必要对链表结构进行说明.1.链表的概念链表是由一系列非连续的节点组成的存储结构,简单分下类的话,链表又分为单向链表和 ...

  5. Spring Security 解析(七) —— Spring Security Oauth2 源码解析

    Spring Security 解析(七) -- Spring Security Oauth2 源码解析   在学习Spring Cloud 时,遇到了授权服务oauth 相关内容时,总是一知半解,因 ...

  6. 给jdk写注释系列之jdk1.6容器(2)-LinkedList源码解析

    LinkedList是基于链表结构的一种List,在分析LinkedList源码前有必要对链表结构进行说明.   1.链表的概念      链表是由一系列非连续的节点组成的存储结构,简单分下类的话,链 ...

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

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

  8. 【Android】EventBus 源码解析

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

  9. Tomcat源码分析三:Tomcat启动加载过程(一)的源码解析

    Tomcat启动加载过程(一)的源码解析 今天,我将分享用源码的方式讲解Tomcat启动的加载过程,关于Tomcat的架构请参阅<Tomcat源码分析二:先看看Tomcat的整体架构>一文 ...

随机推荐

  1. (八)play之yabe项目【身份验证】

    (八)play之yabe项目[身份验证] 博客分类: 框架@play framework   添加身份验证 play提供了一个模块-Secure(安全模块),用来做身份验证 允许Secure模块 修改 ...

  2. XML的约束(schema)

    XML Schema也是一种用于定义和描述XML文档结构与内容的模式语言,其出现是为了克服DTD的局限性 XML Schema符合XML语法结构 DOM.SAX等XML API很容易解析出XML Sc ...

  3. Microsoft Dynamics CRM 2013 安装程序及SDK 下载地址

    Microsoft Dynamics CRM 2013 已经具有相关资料 2013 Setup (Microsoft Dynamics CRM Server 2013) 下载地址: http://ww ...

  4. Setting up your App domain for SharePoint 2013

    from:http://sharepointchick.com/archive/2012/07/29/setting-up-your-app-domain-for-sharepoint-2013.as ...

  5. File类的常用方法

    public static void GetFileInfo()    {                File file=new File("e:","two.txt ...

  6. MQTT for UWP

    老规矩,先简单介绍下MQTT: MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)是IBM开发的一个即时通讯协议,有可能成为物联网的重要组成部分.该协 ...

  7. iOS 架构模式-MVVM

    iOS 架构模式-MVVM MVVM Model-View-ViewModelMVVM 其实是MVC的进化版,他将业务逻辑从VC中解耦到ViewModel,实现VC的瘦身. 做一个简单的登录判断: 创 ...

  8. iOS 核心动画

    核心动画(Core Animation) : •CoreAnimation是一组非常强大的动画处理API,使用它能做出非常炫丽的动画效果,而且往往是事半功倍,使用它需要先添加QuartzCore.fr ...

  9. LeetCode 1 Two Sum(二分法)

    题目来源:https://leetcode.com/problems/two-sum/ Given an array of integers, find two numbers such that t ...

  10. android基础开发之WebView

    WebView 是android平台沟通 http & H5 页面的桥梁. 但是google对这块的表述不是很清晰,而且SDK里面基本看不到源码,只有一个接口而已. 传送:http://dev ...