支付宝移动支付SDK2.0应用实战小结

 更新时间:2016年9月20日 19:58  点击:1606
现在我来分享一篇本人在工作实践中使用支付宝移动支付SDK2.0应用的文章,希望同大家共同进步。

临时接受支付宝支付任务,最初研究旧版本,后来发现新版本更简单明了优化,使用最新版的,看见旧版的写出来的人多,新版的少,咱这最精炼的通过实践滴,与大家共同进步。

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);

 }
ThreadPoolExecutor是一个 ExecutorService,它使用可能的几个池线程之一执行每个提交的任务,通常使用 Executors 工厂方法配置。

一、获取线程池的三种方法:

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开发的ListView中嵌套GridView显示不完的解决方法,如果你也遇到了这个问题,以下就是解决办法。

项目需要,在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);
    }
}
现在的Android应用功能越来越多了,为了保证应用的各部分保持高效的通信,这时候就需要用到Android组件间通信利器EventBus。

概述及基本概念

**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

现在的智能手机都是触屏的,现在我们来学习一下Android开发的Touch(触屏)事件,看了本文章后你可以在30分钟之内快速明白Touch事件分发机制。

Touch事件分发中只有两个主角:ViewGroup和View。Activity的Touch事件事实上是调用它内部的ViewGroup的Touch事件,可以直接当成ViewGroup处理。

View在ViewGroup内,ViewGroup也可以在其他ViewGroup内,这时候把内部的ViewGroup当成View来分析。

ViewGroup的相关事件有三个:onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent。View的相关事件只有两个:dispatchTouchEvent、onTouchEvent。

先分析 ViewGroup的处理流程:首先得有个结构模型概念:ViewGroup和View组成了一棵树形结构,最顶层为Activity的 ViewGroup,下面有若干的ViewGroup节点,每个节点之下又有若干的ViewGroup节点或者View节点,依次类推。如图:

快速掌握Android开发中Touch事件分发机制

当一个 Touch事件(触摸事件为例)到达根节点,即Acitivty的ViewGroup时,它会依次下发,下发的过程是调用子 View(ViewGroup)的dispatchTouchEvent方法实现的。简单来说,就是ViewGroup遍历它包含着的子View,调用每个View的dispatchTouchEvent方法,而当子View为ViewGroup时,又会通过调用ViwGroup的 dispatchTouchEvent方法继续调用其内部的View的dispatchTouchEvent方法。上述例子中的消息下发顺序是这样的:①-②-⑤-⑥-⑦-③-④。dispatchTouchEvent方法只负责事件的分发,它拥有boolean类型的返回值,当返回为true时,顺序下发会中断。在上述例子中如果⑤的dispatchTouchEvent返回结果为true,那么⑥-⑦-③-④将都接收不到本次Touch事件。来个简单版的代码加深理解:

 

 代码如下 复制代码
/**
     * ViewGroup
     * @param ev
     * @return
     */
    public boolean dispatchTouchEvent(MotionEvent ev){
        ....//其他处理,在此不管
        View[] views=getChildView();
        for(int i=0;i<views.length;i++){
           //判断下Touch到屏幕上的点在该子View上面
            if(...){
            if(views[i].dispatchTouchEvent(ev))
              return true;
             }
        }
        ...//其他处理,在此不管
    }
    /**
     * View
     * @param ev
     * @return
     */
    public boolean dispatchTouchEvent(MotionEvent ev){
        ....//其他处理,在此不管
        return false;
    }

 

在此可以看出,ViewGroup的dispatchTouchEvent是真正在执行“分发”工作,而View的dispatchTouchEvent方法,并不执行分发工作,或者说它分发的对象就是自己,决定是否把touch事件交给自己处理,而处理的方法,便是onTouchEvent事件,事实上子View 的dispatchTouchEvent方法真正执行的代码是这样的

 

 代码如下 复制代码
/**
     * View
     * @param ev
     * @return
     */
    public boolean dispatchTouchEvent(MotionEvent ev){
        ....//其他处理,在此不管
        return onTouchEvent(event);
    }

 

一般情况下,我们不该在普通View内重写dispatchTouchEvent方法,因为它并不执行分发逻辑。当Touch事件到达View时,我们该做的就是是否在onTouchEvent事件中处理它。

那么,ViewGroup的onTouchEvent事件是什么时候处理的呢?当ViewGroup所有的子View都返回false 时,onTouchEvent事件便会执行。由于ViewGroup是继承于View的,它其实也是通过调用View的 dispatchTouchEvent方法来执行onTouchEvent事件。

在目前的情况看来,似乎只要我们把所有的onTouchEvent都返回false,就能保证所有的子控件都响应本次Touch事件了。但必须要说明的是,这里的 Touch事件,只限于Acition_Down事件,即触摸按下事件,而Aciton_UP和Action_MOVE却不会执行。事实上,一次完整的 Touch事件,应该是由一个Down、一个Up和若干个Move组成的。Down方式通过dispatchTouchEvent分发,分发的目的是为了找到真正需要处理完整Touch请求的View。当某个View或者ViewGroup的onTouchEvent事件返回true时,便表示它是真正要处理这次请求的View,之后的Aciton_UP和Action_MOVE将由它处理。当所有子View的onTouchEvent都返回false 时,这次的Touch请求就由根ViewGroup,即Activity自己处理了。

看看改进后的ViewGroup的dispatchTouchEvent方法

 

 代码如下 复制代码

View mTarget=null;//保存捕获Touch事件处理的View
    public boolean dispatchTouchEvent(MotionEvent ev) {

        //....其他处理,在此不管
       
        if(ev.getAction()==KeyEvent.ACTION_DOWN){
            //每次Down事件,都置为Null

            if(!onInterceptTouchEvent()){
            mTarget=null;
            View[] views=getChildView();
            for(int i=0;i<views.length;i++){
                if(views[i].dispatchTouchEvent(ev))
                    mTarget=views[i];
                    return true;
            }
          }
        }
        //当子View没有捕获down事件时,ViewGroup自身处理。这里处理的Touch事件包含Down、Up和Move
        if(mTarget==null){
            return super.dispatchTouchEvent(ev);
        }
        //...其他处理,在此不管
        if(onInterceptTouchEvent()){

         //...其他处理,在此不管   
         }
//这一步在Action_Down中是不会执行到的,只有Move和UP才会执行到。
        return mTarget.dispatchTouchEvent(ev);

    }

 

ViewGroup还有个onInterceptTouchEvent,看名字便知道这是个拦截事件。这个拦截事件需要分两种情况来说明:

1.假如我们在某个ViewGroup的onInterceptTouchEvent中,将Action为Down的Touch事件返回true,那便表示将该 ViewGroup的所有下发操作拦截掉,这种情况下,mTarget会一直为null,因为mTarget是在Down事件中赋值的。由于mTarge 为null,该ViewGroup的onTouchEvent事件被执行。这种情况下可以把这个ViewGroup直接当成View来对待。

2.假如我们在某个ViewGroup的onInterceptTouchEvent中,将Acion为Down的Touch事件都返回false,其他的都返回 True,这种情况下,Down事件能正常分发,若子View都返回false,那mTarget还是为空,无影响。若某个子View返回了 true,mTarget被赋值了,在Action_Move和Aciton_UP分发到该ViewGroup时,便会给mTarget分发一个 Action_Delete的MotionEvent,同时清空mTarget的值,使得接下去的Action_Move(如果上一个操作不是UP)将由 ViewGroup的onTouchEvent处理。

情况一用到的比较多,情况二个人还未找到使用场景。

从头到尾总结一下:

1.Touch 事件分发中只有两个主角:ViewGroup和View。ViewGroup包含onInterceptTouchEvent、 dispatchTouchEvent、onTouchEvent三个相关事件。View包含dispatchTouchEvent、 onTouchEvent两个相关事件。其中ViewGroup又继承于View。

2.ViewGroup和View组成了一个树状结构,根节点为Activity内部包含的一个ViwGroup。

3.触摸事件由Action_Down、Action_Move、Aciton_UP组成,其中一次完整的触摸事件中,Down和Up都只有一个,Move有若干个,可以为0个。

4.当Acitivty接收到Touch事件时,将遍历子View进行Down事件的分发。ViewGroup的遍历可以看成是递归的。分发的目的是为了找到真正要处理本次完整触摸事件的View,这个View会在onTouchuEvent结果返回true。

5.当某个子 View返回true时,会中止Down事件的分发,同时在ViewGroup中记录该子View。接下去的Move和Up事件将由该子View直接进行处理。由于子View是保存在ViewGroup中的,多层ViewGroup的节点结构时,上级ViewGroup保存的会是真实处理事件的View所在的ViewGroup对象:如ViewGroup0-ViewGroup1-TextView的结构中,TextView返回了true,它将被保存在 ViewGroup1中,而ViewGroup1也会返回true,被保存在ViewGroup0中。当Move和UP事件来时,会先从 ViewGroup0传递至ViewGroup1,再由ViewGroup1传递至TextView。

6.当 ViewGroup中所有子View都不捕获Down事件时,将触发ViewGroup自身的onTouch事件。触发的方式是调用 super.dispatchTouchEvent函数,即父类View的dispatchTouchEvent方法。在所有子View都不处理的情况下,触发Acitivity的onTouchEvent方法。

7.onInterceptTouchEvent有两个作用:1.拦截Down事件的分发。2.中止Up和Move事件向目标View传递,使得目标View所在的ViewGroup捕获Up和Move事件。

另外,上文所列出的代码并非真正的源码,只是概括了源码在事件分发处理中的核心处理流程,真正源码各位可以自己去看,包含了更丰富的内容。

[!--infotagslink--]

相关文章

  • pycharm 实现光标快速移动到括号外或行尾的操作

    这篇文章主要介绍了pycharm 实现光标快速移动到括号外或行尾的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-02-06
  • 基于C#实现微信支付宝扫码支付功能

    为公司系统业务需要,这几天了解了一下微信和支付宝扫码支付的接口,并用c#实现了微信和支付宝扫码支付的功能。需要的朋友跟随小编一起看看吧...2020-06-25
  • JavaScript仿支付宝密码输入框

    那么今天我就用JavaScript代码来实现这个效果吧,那么首先介绍一下整个的思路,首先我们先将确定输入密码的位数,我的需求是5位,那么就用一个div标签包住5个input标签...2016-01-02
  • javascript 广告移动特效的实现代码

    下面小编就为大家带来一篇javascript 广告移动特效的实现代码。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2016-07-01
  • JS跨浏览器解析XML应用过程详解

    这篇文章主要介绍了JS跨浏览器解析XML应用过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-10-16
  • vivo X9如何查出后台偷跑流量应用?vivo X9查出后台偷跑流量应用的方法

    vivo X9如何查看后台流量偷跑的情况?小编教你轻松查到!还不了解的小伙伴快来看看吧! 1)打开手机自带的【i管家】应用,打开后点击【流量监控】选项。(如下图) 2)接着选...2016-12-31
  • PHP云存储Redis的应用场景与Redis实现排行榜功能

    Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。本文我们来讲解Redis的应用场景实例。 C...2016-11-25
  • 手机未实名认证被停机了怎么办?中国移动/联通/电信手机号实名认证方法

    ,“手机实名制”就是每一个手机号码对应一个身份证,对应唯一真实的主人。如果不认证可能会陆续遭到停机,这该怎么办呢?手机怎么认证呢?下面就详情来看看移动联通电信手机号实名认证方式吧...2016-11-01
  • Ecshop 支付宝手机网页支付免费版

    Ecshop手机网页版本支持在网上找了很多需要花钱购买了,在这里小编整理了一个Ecshop 支付宝手机网页支付免费版供大家参考。 Ecshop 支付宝手机网页支付,针对ecshop...2016-11-25
  • js canvas仿支付宝芝麻信用分仪表盘

    这篇文章主要为大家详细介绍了js canvas仿支付宝芝麻信用分仪表盘,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2016-11-22
  • PHP explode()函数的几个应用和implode()函数有什么区别

    explode()函数介绍explode() 函数可以把字符串分割为数组。语法:explode(separator,string,limit)。 参数 描述 separator 必需。规定在哪里分割字符串。 string...2015-11-08
  • PHP Libevent扩展安装配置及简单应用

    Libevent 是一个用C语言编写的、轻量级的开源高性能网络库,主要有以下几个亮点:事件驱动( event-driven),高性能;轻量级,专注于网络,下文我们就一起来看PHP Libevent扩展安装...2016-11-25
  • php中echo <<< 的应用

    <? $a="变量的值将被带入"; echo <<< help <pre> php中echo <<< 的应用 虽然echo "...";可以断行,但若其中如出现",则仍需做转义 处理。需写做: echo " ...2016-11-25
  • 谁最靠谱?中国移动智能手机售后服务排名

    调查,目前手机维修最常见的问题手机屏幕损坏,而屏幕损坏多半是用户不小心摔坏或者碰坏,这种状况通常都不在免费维修内。目前中国移动智能手机售后服务排名已经出炉了,下面我们来看看吧...2016-09-12
  • PHP-GTK 介绍及其应用

    1. PHP-GTK介绍 1.1 PHP-GTK PHP-GTK是PHP的延伸模组,它可以让程式设计师写出在客户端执行的、且独立的GUI的程式。这个模组不允许在浏览器上显视GTK+的程式,它一开始就...2016-11-25
  • 支付宝小程序实现省市区三级联动

    这篇文章主要为大家详细介绍了支付宝小程序实现省市区三级联动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-06-21
  • VS+opencv实现鼠标移动图片

    这篇文章主要为大家详细介绍了VS+opencv实现鼠标移动图片,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-09-22
  • Oracle dbf文件移动的方法

    这篇文章主要介绍了Oracle dbf文件移动的方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2020-07-11
  • ajax简单与ajax+php实例应用(1/4)

    [ajax介绍]   ajax是使用客户端脚本与web服务器交换数据的web应用开发方法。web页面不用打断交互流程进行重新加裁,就可以动态地更新。使用ajax,用户可以创建接近本地...2016-11-25
  • css中Float属性深入剖析与应用

    这篇文章介绍了css中的float属性的应用和理解,让设置的标签产生浮动效果。希望能帮助到有需要的同学。 一、Float的特性 1. 应用于文字围绕图片 2. 创建一个块级框...2017-01-22