关于handler:Consumer最后一个发送请求HandlerTransportClientHandler分析

发送申请Consumer 的Handler解决链最初一个是TransportClientHandler,这个Handler次要是: 1.发动连贯2.申请之前执行HttpClientFilter的beforeSendRequest3.塞入ServiceComb的微服务调用上下文,设置响应回调解决,发送申请 ServiceComb发送申请应用vertx,而vertx网络发送接管构建在netty之上,因此可能异步高并发。 java很大一个特点是向建设大楼的脚手架,一个套在一个下面,举例,serviceComb网络发送与接管是:最根底是jdk,下面是netty,而后是vertx,而后是serviceComb。 1.发动连贯 transport实际上是VertxRestTransport,最终到RestClientInvocation的invoke,这个invoke重点剖析, public void invoke(Invocation invocation, AsyncResponse asyncResp) throws Exception {... Future<HttpClientRequest> requestFuture = createRequest(ipPort, path);...} Future<HttpClientRequest> createRequest(IpPort ipPort, String path) {... return httpClientWithContext.getHttpClient().request(requestOptions);... } public Future<HttpClientRequest> request(RequestOptions options) { ContextInternal ctx = this.vertx.getOrCreateContext(); PromiseInternal<HttpClientRequest> promise = ctx.promise(); this.doRequest(options, promise); return promise.future(); }private void doRequest(HttpMethod method, SocketAddress peerAddress, SocketAddress server, String host, int port, Boolean useSSL, String requestURI, MultiMap headers, long timeout, Boolean followRedirects, ProxyOptions proxyOptions, PromiseInternal<HttpClientRequest> requestPromise) {... this.httpCM.getConnection(eventLoopContext, key, timeout, (ar1) -> {... requestPromise.tryComplete(req);...}...}httpCM 建设连贯胜利后,把requestPromise设置Complete,到此连贯建设实现。 ...

January 1, 2022 · 1 min · jiezi

关于handler:拦截器

在业务处理器解决申请之前会调用prehandle() 【在这个办法中会进行一些前置初始化操作或者是对以后申请的一个预处理,也能够在这个办法中进行一些判断来决定申请是否要持续进行上来】如果该办法的返回值为true,则程序持续向下执行处理器中的办法,否则将不再向下执行——业务处理器解决完申请——**postHandler()—— 进行视图解析渲染——执行afterCompletion()办法——向客户端返回响应 配置拦截器:import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration //等同于web.xml配置文件public class MvcConfigurer implements WebMvcConfigurer{@Autowiredprivate UserInterceptor userInterceptor;//开启匹配后缀型配置@Overridepublic void configurePathMatch(PathMatchConfigurer configurer) {//开启匹配后缀型配置 .htmlconfigurer.setUseSuffixPatternMatch(true);}/**** 拦挡相似购物车 和订单的所有申请 交给 咱们自定义的拦截器进行解决*/@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(userInterceptor). addPathPatterns("/cart/**","/order/**");}/**** 工夫拦截器配置*/ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new TimeAccessInterceptor()) .addPathPatterns("/user/doLogin");//设置要拦挡的url地址 }}自定义工夫拦截器public class TimeAccessInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("preHandler()"); //获取java中的日历对象 LocalDateTime localDateTime=LocalDateTime.now(); //获取以后工夫对应的小时 int hour=localDateTime.getHour(); System.out.println("hour:"+hour); if(hour<=9.5||hour>=15.5) throw new ServiceException("请在9:30-15:30之间拜访"); return true; }}电商我的项目自定义拦截器 登录/** * 需要; 拦挡/cart 结尾的所有的申请进行拦挡,并且校验用户登录 * 拦截器抉择 preHandler * 如何判断用户是否登录: 1.查看cookie信息 2.查看Redis中是否有记录 * true:放行 false 申请应该拦挡 配合重定向 */@Componentpublic class UserInterceptor implements HandlerInterceptor { @Autowired private JedisCluster jedisCluster;//redis集群 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //1.判断用户是否登录 查看cookie是否有值 String ticket= CookieUtil.getCookieValue(request,"JT_TICKET"); //校验ticket if(!StringUtils.isEmpty(ticket)){ //判断redis中是否有值 if(jedisCluster.exists(ticket)){ return true; } } response.sendRedirect("/user/login.html");//拦挡申请 重定向到登录页面 return false; }}

October 22, 2020 · 1 min · jiezi

Android-复盘帮你彻底了解消息机制

1. 什么是消息机制说到消息机制,作为一名 Android 开发者一定先想到的是 Handler。Handler 就是 Android 消息机制的上层接口,我们可用通过 Handler 轻松的在不同的线程中切换任务,但 Handler 的实现还有两个很重要的概念 MessageQueue 和 Looper。 MessageQueue 的翻译是消息队列,它的内部采用了单链表的结构存储 Handler 对象发送的消息。 Looper 的作用是不断地查询 MessageQueue 中是否有消息,如果 Looper 发现 MessageQueue 中存入了新的消息,它就会去处理这条消息,如果没有新消息,Looper 就会以无限循环的方式去查询 MessageQueue 中是否有新消息。 2. 为什么要有 Handler2.1)官方文档中 Handler 的主要作用(1)安排将来某个时间点执行的 Message 和 Runnables;(2)在不同于当前的线程上执行的操作; 2.2)Handler 被用来做的最多的一件事就是更新主线程的 UI。在 Android 开发中,默认子线程是不可以更新 UI 的,这一点可以从 View 的最高层级 ViewRootImpl 类中找到答案 void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException("Only the original thread that created a view hierarchy can touch its views."); }}ViewRootImpl 类中的 checkThread 方法会在更新 UI 前被执行,如果当前线程不是主线程,就会抛出 Only the original thread that created a view hierarchy can touch its views. 的异常 ...

April 26, 2019 · 12 min · jiezi

集成源码深度剖析:Fescar x Spring Cloud

Fescar 简介常见的分布式事务方式有基于 2PC 的 XA (e.g. atomikos),从业务层入手的 TCC( e.g. byteTCC)、事务消息 ( e.g. RocketMQ Half Message) 等等。XA 是需要本地数据库支持的分布式事务的协议,资源锁在数据库层面导致性能较差,而支付宝作为布道师引入的 TCC 模式需要大量的业务代码保证,开发维护成本较高。分布式事务是业界比较关注的领域,这也是短短时间 Fescar 能收获6k Star的原因之一。Fescar 名字取自 Fast & Easy Commit And Rollback ,简单来说Fescar通过对本地 RDBMS 分支事务的协调来驱动完成全局事务,是工作在应用层的中间件。主要优点是相对于XA模式是性能较好不长时间占用连接资源,相对于 TCC 方式开发成本和业务侵入性较低。类似于 XA,Fescar 将角色分为 TC、RM、TM,事务整体过程模型如下:1. TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID。2. XID 在微服务调用链路的上下文中传播。3. RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖。4. TM 向 TC 发起针对 XID 的全局提交或回滚决议。5. TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。其中在目前的实现版本中 TC 是独立部署的进程,维护全局事务的操作记录和全局锁记录,负责协调并驱动全局事务的提交或回滚。TM RM 则与应用程序工作在同一应用进程。RM对 JDBC 数据源采用代理的方式对底层数据库做管理,利用语法解析,在执行事务保留快照,并生成 undo log。大概的流程和模型划分就介绍到这里,下面开始对 Fescar 事务传播机制的分析。Fescar 事务传播机制Fescar 事务传播包括应用内事务嵌套调用和跨服务调用的事务传播。Fescar 事务是怎么在微服务调用链中传播的呢?Fescar 提供了事务 API 允许用户手动绑定事务的 XID 并加入到全局事务中,所以我们根据不同的服务框架机制,将 XID 在链路中传递即可实现事务的传播。RPC 请求过程分为调用方与被调用方两部分,我们将 XID 在请求与响应时做相应的处理即可。大致过程为:调用方即请求方将当前事务上下文中的 XID 取出,通过RPC协议传递给被调用方;被调用方从请求中的将 XID 取出,并绑定到自己的事务上下文中,纳入全局事务。微服务框架一般都有相应的 Filter 和 Interceptor 机制,我们来分析下 Spring Cloud 与Fescar 的整合过程。Fescar 与 Spring Cloud Alibaba 集成部分源码解析本部分源码全部来自于 spring-cloud-alibaba-fescar. 源码解析部分主要包括AutoConfiguration、微服务被调用方和微服务调用方三大部分。对于微服务调用方方式具体分为 RestTemplate 和 Feign,对于 Feign 请求方式又进一步细分为结合 Hystrix 和 Sentinel 的使用模式。Fescar AutoConfiguration对于 AutoConfiguration 部分的解析此处只介绍与 Fescar 启动相关的部分,其他部分的解析将穿插于【微服务被调用方】和【微服务调用方】章节进行介绍。Fescar 的启动需要配置 GlobalTransactionScanner,GlobalTransactionScanner 负责初始化 Fescar 的 RM client、TM client 和 自动代理标注 GlobalTransactional 注解的类。GlobalTransactionScanner bean 的启动通过 GlobalTransactionAutoConfiguration 加载并注入FescarProperties。FescarProperties 包含了 Fescar的重要属性 txServiceGroup ,此属性的可通过 application.properties 文件中的 key: spring.cloud.alibaba.fescar.txServiceGroup 读取,默认值为 ${spring.application.name}-fescar-service-group 。txServiceGroup 表示Fescar 的逻辑事务分组名,此分组名通过配置中心(目前支持文件、Apollo)获取逻辑事务分组名对应的 TC 集群名称,进一步通过集群名称构造出 TC 集群的服务名,通过注册中心(目前支持Nacos、Redis、ZooKeeper和Eureka)和服务名找到可用的 TC 服务节点,然后 RM client、TM client 与 TC 进行 RPC 交互。微服务被调用方由于调用方的逻辑比较多一点,我们先分析被调用方的逻辑。针对于 Spring Cloud 项目,默认采用的 RPC 传输协议时 HTTP 协议,所以使用了 HandlerInterceptor 机制来对HTTP的请求做拦截。HandlerInterceptor 是 Spring 提供的接口, 它有以下三个方法可以被覆写。 /** * Intercept the execution of a handler. Called after HandlerMapping determined * an appropriate handler object, but before HandlerAdapter invokes the handler. / default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } /* * Intercept the execution of a handler. Called after HandlerAdapter actually * invoked the handler, but before the DispatcherServlet renders the view. * Can expose additional model objects to the view via the given ModelAndView. / default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { } /* * Callback after completion of request processing, that is, after rendering * the view. Will be called on any outcome of handler execution, thus allows * for proper resource cleanup. / default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { }根据注释,我们可以很明确的看到各个方法的作用时间和常用用途。对于 Fescar 集成来讲,它需要重写了 preHandle、afterCompletion 方法。FescarHandlerInterceptor 的作用是将服务链路传递过来的 XID,绑定到服务节点的事务上下文中,并且在请求完成后清理相关资源。FescarHandlerInterceptorConfiguration 中配置了所有的 url 均进行拦截,对所有的请求过来均会执行该拦截器,进行 XID 的转换与事务绑定。/* * @author xiaojing * * Fescar HandlerInterceptor, Convert Fescar information into * @see com.alibaba.fescar.core.context.RootContext from http request’s header in * {@link org.springframework.web.servlet.HandlerInterceptor#preHandle(HttpServletRequest , HttpServletResponse , Object )}, * And clean up Fescar information after servlet method invocation in * {@link org.springframework.web.servlet.HandlerInterceptor#afterCompletion(HttpServletRequest, HttpServletResponse, Object, Exception)} /public class FescarHandlerInterceptor implements HandlerInterceptor { private static final Logger log = LoggerFactory .getLogger(FescarHandlerInterceptor.class); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String xid = RootContext.getXID(); String rpcXid = request.getHeader(RootContext.KEY_XID); if (log.isDebugEnabled()) { log.debug(“xid in RootContext {} xid in RpcContext {}”, xid, rpcXid); } if (xid == null && rpcXid != null) { RootContext.bind(rpcXid); if (log.isDebugEnabled()) { log.debug(“bind {} to RootContext”, rpcXid); } } return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) throws Exception { String rpcXid = request.getHeader(RootContext.KEY_XID); if (StringUtils.isEmpty(rpcXid)) { return; } String unbindXid = RootContext.unbind(); if (log.isDebugEnabled()) { log.debug(“unbind {} from RootContext”, unbindXid); } if (!rpcXid.equalsIgnoreCase(unbindXid)) { log.warn(“xid in change during RPC from {} to {}”, rpcXid, unbindXid); if (unbindXid != null) { RootContext.bind(unbindXid); log.warn(“bind {} back to RootContext”, unbindXid); } } }}preHandle 在请求执行前被调用,xid 为当前事务上下文已经绑定的全局事务的唯一标识,rpcXid 为请求通过 HTTP Header 传递过来需要绑定的全局事务标识。preHandle 方法中判断如果当前事务上下文中没有 XID,且 rpcXid 不为空,那么就将 rpcXid 绑定到当前的事务上下文。afterCompletion 在请求完成后被调用,该方法用来执行资源的相关清理动作。Fescar 通过 RootContext.unbind() 方法对事务上下文涉及到的 XID 进行解绑。下面 if 中的逻辑是为了代码的健壮性考虑,如果遇到 rpcXid和 unbindXid 不相等的情况,再将 unbindXid 重新绑定回去。对于 Spring Cloud 来讲,默认采用的 RPC 方式是 HTTP 的方式,所以对被调用方来讲,它的请求拦截方式不用做任何区分,只需要从 Header 中将 XID 就可以取出绑定到自己的事务上下文中即可。但是对于调用方由于请求组件的多样化,包括熔断隔离机制,所以要区分不同的情况做处理,后面我们来具体分析一下。微服务调用方Fescar 将请求方式分为:RestTemplate、Feign、Feign+Hystrix 和 Feign+Sentinel 。不同的组件通过 Spring Boot 的 Auto Configuration 来完成自动的配置,具体的配置类可以看 spring.factories ,下文也会介绍相关的配置类。RestTemplate先来看下如果调用方如果是是基于 RestTemplate 的请求,Fescar 是怎么传递 XID 的。public class FescarRestTemplateInterceptor implements ClientHttpRequestInterceptor { @Override public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException { HttpRequestWrapper requestWrapper = new HttpRequestWrapper(httpRequest); String xid = RootContext.getXID(); if (!StringUtils.isEmpty(xid)) { requestWrapper.getHeaders().add(RootContext.KEY_XID, xid); } return clientHttpRequestExecution.execute(requestWrapper, bytes); }}FescarRestTemplateInterceptor 实现了 ClientHttpRequestInterceptor 接口的 intercept 方法,对调用的请求做了包装,在发送请求时若存在 Fescar 事务上下文 XID 则取出并放到 HTTP Header 中。FescarRestTemplateInterceptor 通过 FescarRestTemplateAutoConfiguration 实现将 FescarRestTemplateInterceptor 配置到 RestTemplate 中去。@Configurationpublic class FescarRestTemplateAutoConfiguration { @Bean public FescarRestTemplateInterceptor fescarRestTemplateInterceptor() { return new FescarRestTemplateInterceptor(); } @Autowired(required = false) private Collection<RestTemplate> restTemplates; @Autowired private FescarRestTemplateInterceptor fescarRestTemplateInterceptor; @PostConstruct public void init() { if (this.restTemplates != null) { for (RestTemplate restTemplate : restTemplates) { List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>( restTemplate.getInterceptors()); interceptors.add(this.fescarRestTemplateInterceptor); restTemplate.setInterceptors(interceptors); } } }}init 方法遍历所有的 restTemplate ,并将原来 restTemplate 中的拦截器取出,增加 fescarRestTemplateInterceptor 后置入并重排序。Feign接下来看下 Feign 的相关代码,该包下面的类还是比较多的,我们先从其 AutoConfiguration 入手。@Configuration@ConditionalOnClass(Client.class)@AutoConfigureBefore(FeignAutoConfiguration.class)public class FescarFeignClientAutoConfiguration { @Bean @Scope(“prototype”) @ConditionalOnClass(name = “com.netflix.hystrix.HystrixCommand”) @ConditionalOnProperty(name = “feign.hystrix.enabled”, havingValue = “true”) Feign.Builder feignHystrixBuilder(BeanFactory beanFactory) { return FescarHystrixFeignBuilder.builder(beanFactory); } @Bean @Scope(“prototype”) @ConditionalOnClass(name = “com.alibaba.csp.sentinel.SphU”) @ConditionalOnProperty(name = “feign.sentinel.enabled”, havingValue = “true”) Feign.Builder feignSentinelBuilder(BeanFactory beanFactory) { return FescarSentinelFeignBuilder.builder(beanFactory); } @Bean @ConditionalOnMissingBean @Scope(“prototype”) Feign.Builder feignBuilder(BeanFactory beanFactory) { return FescarFeignBuilder.builder(beanFactory); } @Configuration protected static class FeignBeanPostProcessorConfiguration { @Bean FescarBeanPostProcessor fescarBeanPostProcessor( FescarFeignObjectWrapper fescarFeignObjectWrapper) { return new FescarBeanPostProcessor(fescarFeignObjectWrapper); } @Bean FescarContextBeanPostProcessor fescarContextBeanPostProcessor( BeanFactory beanFactory) { return new FescarContextBeanPostProcessor(beanFactory); } @Bean FescarFeignObjectWrapper fescarFeignObjectWrapper(BeanFactory beanFactory) { return new FescarFeignObjectWrapper(beanFactory); } }}FescarFeignClientAutoConfiguration 在存在 Client.class 时生效,且要求作用在 FeignAutoConfiguration 之前。由于FeignClientsConfiguration 是在 FeignAutoConfiguration 生成 FeignContext 生效的,所以根据依赖关系, FescarFeignClientAutoConfiguration 同样早于 FeignClientsConfiguration。FescarFeignClientAutoConfiguration 自定义了 Feign.Builder,针对于 feign.sentinel,feign.hystrix 和 feign 的情况做了适配,目的是自定义 feign 中 Client 的真正实现为 FescarFeignClient。HystrixFeign.builder().retryer(Retryer.NEVER_RETRY) .client(new FescarFeignClient(beanFactory))SentinelFeign.builder().retryer(Retryer.NEVER_RETRY) .client(new FescarFeignClient(beanFactory));Feign.builder().client(new FescarFeignClient(beanFactory));FescarFeignClient 是对原来的 Feign 客户端代理增强,具体代码见下图:public class FescarFeignClient implements Client { private final Client delegate; private final BeanFactory beanFactory; FescarFeignClient(BeanFactory beanFactory) { this.beanFactory = beanFactory; this.delegate = new Client.Default(null, null); } FescarFeignClient(BeanFactory beanFactory, Client delegate) { this.delegate = delegate; this.beanFactory = beanFactory; } @Override public Response execute(Request request, Request.Options options) throws IOException { Request modifiedRequest = getModifyRequest(request); try { return this.delegate.execute(modifiedRequest, options); } finally { } } private Request getModifyRequest(Request request) { String xid = RootContext.getXID(); if (StringUtils.isEmpty(xid)) { return request; } Map<String, Collection<String>> headers = new HashMap<>(); headers.putAll(request.headers()); List<String> fescarXid = new ArrayList<>(); fescarXid.add(xid); headers.put(RootContext.KEY_XID, fescarXid); return Request.create(request.method(), request.url(), headers, request.body(), request.charset()); }上面的过程中我们可以看到,FescarFeignClient 对原来的 Request 做了修改,它首先将 XID 从当前的事务上下文中取出,如果 XID 不为空的情况下,将 XID 放到了 Header 中。FeignBeanPostProcessorConfiguration 定义了3个bean:FescarContextBeanPostProcessor、FescarBeanPostProcessor 和 FescarFeignObjectWrapper。其中 FescarContextBeanPostProcessor FescarBeanPostProcessor 实现了Spring BeanPostProcessor 接口。以下为 FescarContextBeanPostProcessor 实现。 @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof FeignContext && !(bean instanceof FescarFeignContext)) { return new FescarFeignContext(getFescarFeignObjectWrapper(), (FeignContext) bean); } return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; }BeanPostProcessor 中的两个方法可以对 Spring 容器中的 Bean 做前后处理,postProcessBeforeInitialization 处理时机是初始化之前,postProcessAfterInitialization 的处理时机是初始化之后,这2个方法的返回值可以是原先生成的实例 bean,或者使用 wrapper 包装后的实例。FescarContextBeanPostProcessor 将 FeignContext 包装成 FescarFeignContext。FescarBeanPostProcessor 将 FeignClient 根据是否继承了LoadBalancerFeignClient 包装成 FescarLoadBalancerFeignClient 和 FescarFeignClient。FeignAutoConfiguration 中的 FeignContext 并没有加 ConditionalOnXXX 的条件,所以 Fescar 采用预置处理的方式将 FeignContext 包装成 FescarFeignContext。 @Bean public FeignContext feignContext() { FeignContext context = new FeignContext(); context.setConfigurations(this.configurations); return context; }而对于 Feign Client,FeignClientFactoryBean 中会获取 FeignContext 的实例对象。对于开发者采用 @Configuration 注解的自定义配置的 Feign Client 对象,这里会被配置到 builder,导致 FescarFeignBuilder 中增强后的 FescarFeignCliet 失效。FeignClientFactoryBean 中关键代码如下: /* * @param <T> the target type of the Feign client * @return a {@link Feign} client created with the specified data and the context information */ <T> T getTarget() { FeignContext context = applicationContext.getBean(FeignContext.class); Feign.Builder builder = feign(context); if (!StringUtils.hasText(this.url)) { if (!this.name.startsWith(“http”)) { url = “http://” + this.name; } else { url = this.name; } url += cleanPath(); return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type, this.name, url)); } if (StringUtils.hasText(this.url) && !this.url.startsWith(“http”)) { this.url = “http://” + this.url; } String url = this.url + cleanPath(); Client client = getOptional(context, Client.class); if (client != null) { if (client instanceof LoadBalancerFeignClient) { // not load balancing because we have a url, // but ribbon is on the classpath, so unwrap client = ((LoadBalancerFeignClient)client).getDelegate(); } builder.client(client); } Targeter targeter = get(context, Targeter.class); return (T) targeter.target(this, builder, context, new HardCodedTarget<>( this.type, this.name, url)); }上述代码根据是否指定了注解参数中的 URL 来选择直接调用 URL 还是走负载均衡,targeter.target 通过动态代理创建对象。大致过程为:将解析出的feign方法放入map,再通过将其作为参数传入生成InvocationHandler,进而生成动态代理对象。FescarContextBeanPostProcessor 的存在,即使开发者对 FeignClient 自定义操作,依旧可以完成 Fescar 所需的全局事务的增强。对于 FescarFeignObjectWrapper,我们重点关注下Wrapper方法: Object wrap(Object bean) { if (bean instanceof Client && !(bean instanceof FescarFeignClient)) { if (bean instanceof LoadBalancerFeignClient) { LoadBalancerFeignClient client = ((LoadBalancerFeignClient) bean); return new FescarLoadBalancerFeignClient(client.getDelegate(), factory(), clientFactory(), this.beanFactory); } return new FescarFeignClient(this.beanFactory, (Client) bean); } return bean; }wrap 方法中,如果 bean 是 LoadBalancerFeignClient 的实例对象,那么首先通过 client.getDelegate() 方法将 LoadBalancerFeignClient 代理的实际 Client 对象取出后包装成 FescarFeignClient,再生成 LoadBalancerFeignClient 的子类 FescarLoadBalancerFeignClient 对象。如果 bean 是 Client 的实例对象且不是 FescarFeignClient LoadBalancerFeignClient,那么 bean 会直接包装生成 FescarFeignClient。上面的流程设计还是比较巧妙的,首先根据 Spring boot 的 Auto Configuration 控制了配置的先后顺序,同时自定义了 Feign Builder的Bean,保证了 Client 均是经过增强后的 FescarFeignClient 。再通过 BeanPostProcessor 对Spring 容器中的 Bean 做了一遍包装,保证容器内的Bean均是增强后 FescarFeignClient ,避免 FeignClientFactoryBean getTarget 方法的替换动作。Hystrix 隔离下面我们再来看下 Hystrix 部分,为什么要单独把 Hystrix 拆出来看呢,而且 Fescar 代码也单独实现了个策略类。目前事务上下文 RootContext 的默认实现是基于 ThreadLocal 方式的 ThreadLocalContextCore,也就是上下文其实是和线程绑定的。Hystrix 本身有两种隔离状态的模式,基于信号量或者基于线程池进行隔离。Hystrix 官方建议是采取线程池的方式来充分隔离,也是一般情况下在采用的模式:Thread or SemaphoreThe default, and the recommended setting, is to run HystrixCommands using thread isolation (THREAD) and HystrixObservableCommands using semaphore isolation (SEMAPHORE).Commands executed in threads have an extra layer of protection against latencies beyond what network timeouts can offer.Generally the only time you should use semaphore isolation for HystrixCommands is when the call is so high volume (hundreds per second, per instance) that the overhead of separate threads is too high; this typically only applies to non-network calls.service 层的业务代码和请求发出的线程肯定不是同一个,那么 ThreadLocal 的方式就没办法将 XID 传递给 Hystrix 的线程并传递给被调用方的。怎么处理这件事情呢,Hystrix 提供了个机制让开发者去自定义并发策略,只需要继承 HystrixConcurrencyStrategy 重写 wrapCallable 方法即可。public class FescarHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy { private HystrixConcurrencyStrategy delegate; public FescarHystrixConcurrencyStrategy() { this.delegate = HystrixPlugins.getInstance().getConcurrencyStrategy(); HystrixPlugins.reset(); HystrixPlugins.getInstance().registerConcurrencyStrategy(this); } @Override public <K> Callable<K> wrapCallable(Callable<K> c) { if (c instanceof FescarContextCallable) { return c; } Callable<K> wrappedCallable; if (this.delegate != null) { wrappedCallable = this.delegate.wrapCallable(c); } else { wrappedCallable = c; } if (wrappedCallable instanceof FescarContextCallable) { return wrappedCallable; } return new FescarContextCallable<>(wrappedCallable); } private static class FescarContextCallable<K> implements Callable<K> { private final Callable<K> actual; private final String xid; FescarContextCallable(Callable<K> actual) { this.actual = actual; this.xid = RootContext.getXID(); } @Override public K call() throws Exception { try { RootContext.bind(xid); return actual.call(); } finally { RootContext.unbind(); } } }}Fescar 也提供一个 FescarHystrixAutoConfiguration,在存在 HystrixCommand 的时候生成FescarHystrixConcurrencyStrategy。@Configuration@ConditionalOnClass(HystrixCommand.class)public class FescarHystrixAutoConfiguration { @Bean FescarHystrixConcurrencyStrategy fescarHystrixConcurrencyStrategy() { return new FescarHystrixConcurrencyStrategy(); }}参考资料Fescar: https://github.com/alibaba/fescarSpring Cloud Alibaba: https://github.com/spring-cloud-incubator/spring-cloud-alibabaspring-cloud-openfeign: https://github.com/spring-cloud/spring-cloud-openfeign本文作者郭树抗,社区昵称 ywind,曾就职于华为终端云,现搜狐智能媒体中心Java工程师,目前主要负责搜狐号相关开发,对分布式事务、分布式系统和微服务架构有异常浓厚的兴趣。季敏(清铭),社区昵称 slievrly,Fescar 开源项目负责人,阿里巴巴中件间 TXC/GTS 核心研发成员,长期从事于分布式中间件核心研发工作,在分布式事务领域有着较丰富的技术积累。延伸阅读微服务架构下,解决数据一致性问题的实践本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

March 8, 2019 · 7 min · jiezi

ES6 系列之 defineProperty 与 proxy

前言我们或多或少都听过“数据绑定”这个词,“数据绑定”的关键在于监听数据的变化,可是对于这样一个对象:var obj = {value: 1},我们该怎么知道 obj 发生了改变呢?definePropetyES5 提供了 Object.defineProperty 方法,该方法可以在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。语法Object.defineProperty(obj, prop, descriptor)参数obj: 要在其上定义属性的对象。prop: 要定义或修改的属性的名称。descriptor: 将被定义或修改的属性的描述符。举个例子:var obj = {};Object.defineProperty(obj, “num”, { value : 1, writable : true, enumerable : true, configurable : true});// 对象 obj 拥有属性 num,值为 1虽然我们可以直接添加属性和值,但是使用这种方式,我们能进行更多的配置。函数的第三个参数 descriptor 所表示的属性描述符有两种形式:数据描述符和存取描述符。两者均具有以下两种键值:configurable当且仅当该属性的 configurable 为 true 时,该属性描述符才能够被改变,也能够被删除。默认为 false。enumerable当且仅当该属性的 enumerable 为 true 时,该属性才能够出现在对象的枚举属性中。默认为 false。数据描述符同时具有以下可选键值:value该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined。writable当且仅当该属性的 writable 为 true 时,该属性才能被赋值运算符改变。默认为 false。存取描述符同时具有以下可选键值:get一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。该方法返回值被用作属性值。默认为 undefined。set一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。该方法将接受唯一参数,并将该参数的新值分配给该属性。默认为 undefined。值得注意的是:属性描述符必须是数据描述符或者存取描述符两种形式之一,不能同时是两者。这就意味着你可以:Object.defineProperty({}, “num”, { value: 1, writable: true, enumerable: true, configurable: true});也可以:var value = 1;Object.defineProperty({}, “num”, { get : function(){ return value; }, set : function(newValue){ value = newValue; }, enumerable : true, configurable : true});但是不可以:// 报错Object.defineProperty({}, “num”, { value: 1, get: function() { return 1; }});此外,所有的属性描述符都是非必须的,但是 descriptor 这个字段是必须的,如果不进行任何配置,你可以这样:var obj = Object.defineProperty({}, “num”, {});console.log(obj.num); // undefinedSetters 和 Getters之所以讲到 defineProperty,是因为我们要使用存取描述符中的 get 和 set,这两个方法又被称为 getter 和 setter。由 getter 和 setter 定义的属性称做”存取器属性“。当程序查询存取器属性的值时,JavaScript 调用 getter方法。这个方法的返回值就是属性存取表达式的值。当程序设置一个存取器属性的值时,JavaScript 调用 setter 方法,将赋值表达式右侧的值当做参数传入 setter。从某种意义上讲,这个方法负责“设置”属性值。可以忽略 setter 方法的返回值。举个例子:var obj = {}, value = null;Object.defineProperty(obj, “num”, { get: function(){ console.log(‘执行了 get 操作’) return value; }, set: function(newValue) { console.log(‘执行了 set 操作’) value = newValue; }})obj.value = 1 // 执行了 set 操作console.log(obj.value); // 执行了 get 操作 // 1这不就是我们要的监控数据改变的方法吗?我们再来封装一下:function Archiver() { var value = null; // archive n. 档案 var archive = []; Object.defineProperty(this, ’num’, { get: function() { console.log(‘执行了 get 操作’) return value; }, set: function(value) { console.log(‘执行了 set 操作’) value = value; archive.push({ val: value }); } }); this.getArchive = function() { return archive; };}var arc = new Archiver();arc.num; // 执行了 get 操作arc.num = 11; // 执行了 set 操作arc.num = 13; // 执行了 set 操作console.log(arc.getArchive()); // [{ val: 11 }, { val: 13 }]watch API既然可以监控数据的改变,那我可以这样设想,即当数据改变的时候,自动进行渲染工作。举个例子:HTML 中有个 span 标签和 button 标签<span id=“container”>1</span><button id=“button”>点击加 1</button>当点击按钮的时候,span 标签里的值加 1。传统的做法是:document.getElementById(‘button’).addEventListener(“click”, function(){ var container = document.getElementById(“container”); container.innerHTML = Number(container.innerHTML) + 1;});如果使用了 defineProperty:var obj = { value: 1}// 储存 obj.value 的值var value = 1;Object.defineProperty(obj, “value”, { get: function() { return value; }, set: function(newValue) { value = newValue; document.getElementById(‘container’).innerHTML = newValue; }});document.getElementById(‘button’).addEventListener(“click”, function() { obj.value += 1;});代码看似增多了,但是当我们需要改变 span 标签里的值的时候,直接修改 obj.value 的值就可以了。然而,现在的写法,我们还需要单独声明一个变量存储 obj.value 的值,因为如果你在 set 中直接 obj.value = newValue 就会陷入无限的循环中。此外,我们可能需要监控很多属性值的改变,要是一个一个写,也很累呐,所以我们简单写个 watch 函数。使用效果如下:var obj = { value: 1}watch(obj, “num”, function(newvalue){ document.getElementById(‘container’).innerHTML = newvalue;})document.getElementById(‘button’).addEventListener(“click”, function(){ obj.value += 1});我们来写下这个 watch 函数:(function(){ var root = this; function watch(obj, name, func){ var value = obj[name]; Object.defineProperty(obj, name, { get: function() { return value; }, set: function(newValue) { value = newValue; func(value) } }); if (value) obj[name] = value } this.watch = watch;})()现在我们已经可以监控对象属性值的改变,并且可以根据属性值的改变,添加回调函数,棒棒哒~proxy使用 defineProperty 只能重定义属性的读取(get)和设置(set)行为,到了 ES6,提供了 Proxy,可以重定义更多的行为,比如 in、delete、函数调用等更多行为。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。我们来看看它的语法:var proxy = new Proxy(target, handler);proxy 对象的所有用法,都是上面这种形式,不同的只是handler参数的写法。其中,new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。var proxy = new Proxy({}, { get: function(obj, prop) { console.log(‘设置 get 操作’) return obj[prop]; }, set: function(obj, prop, value) { console.log(‘设置 set 操作’) obj[prop] = value; }});proxy.time = 35; // 设置 set 操作console.log(proxy.time); // 设置 get 操作 // 35除了 get 和 set 之外,proxy 可以拦截多达 13 种操作,比如 has(target, propKey),可以拦截 propKey in proxy 的操作,返回一个布尔值。// 使用 has 方法隐藏某些属性,不被 in 运算符发现var handler = { has (target, key) { if (key[0] === ‘_’) { return false; } return key in target; }};var target = { _prop: ‘foo’, prop: ‘foo’ };var proxy = new Proxy(target, handler);console.log(’_prop’ in proxy); // false又比如说 apply 方法拦截函数的调用、call 和 apply 操作。apply 方法可以接受三个参数,分别是目标对象、目标对象的上下文对象(this)和目标对象的参数数组,不过这里我们简单演示一下:var target = function () { return ‘I am the target’; };var handler = { apply: function () { return ‘I am the proxy’; }};var p = new Proxy(target, handler);p();// “I am the proxy"又比如说 ownKeys 方法可以拦截对象自身属性的读取操作。具体来说,拦截以下操作:Object.getOwnPropertyNames()Object.getOwnPropertySymbols()Object.keys()下面的例子是拦截第一个字符为下划线的属性名,不让它被 for of 遍历到。let target = { _bar: ‘foo’, prop: ‘bar’, prop: ‘baz’};let handler = { ownKeys (target) { return Reflect.ownKeys(target).filter(key => key[0] !== ‘’); }};let proxy = new Proxy(target, handler);for (let key of Object.keys(proxy)) { console.log(target[key]);}// “baz"更多的拦截行为可以查看阮一峰老师的 《ECMAScript 6 入门》值得注意的是,proxy 的最大问题在于浏览器支持度不够,而且很多效果无法使用 poilyfill 来弥补。watch API 优化我们使用 proxy 再来写一下 watch 函数。使用效果如下:(function() { var root = this; function watch(target, func) { var proxy = new Proxy(target, { get: function(target, prop) { return target[prop]; }, set: function(target, prop, value) { target[prop] = value; func(prop, value); } }); if(target[name]) proxy[name] = value; return proxy; } this.watch = watch;})()var obj = { value: 1}var newObj = watch(obj, function(key, newvalue) { if (key == ‘value’) document.getElementById(‘container’).innerHTML = newvalue;})document.getElementById(‘button’).addEventListener(“click”, function() { newObj.value += 1});我们也可以发现,使用 defineProperty 和 proxy 的区别,当使用 defineProperty,我们修改原来的 obj 对象就可以触发拦截,而使用 proxy,就必须修改代理对象,即 Proxy 的实例才可以触发拦截。ES6 系列ES6 系列目录地址:https://github.com/mqyqingfeng/BlogES6 系列预计写二十篇左右,旨在加深 ES6 部分知识点的理解,重点讲解块级作用域、标签模板、箭头函数、Symbol、Set、Map 以及 Promise 的模拟实现、模块加载方案、异步处理等内容。如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。本文作者:冴羽阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

November 15, 2018 · 4 min · jiezi

Android 8.1 源码_机制篇 -- 全面解析 Handler 机制(原理篇)

开篇核心源码关键类路径Looper.javaframeworks/base/core/java/android/os/Looper.javaMessage.javaframeworks/base/core/java/android/os/Message.javaMessageQueue.javaframeworks/base/core/java/android/os/MessageQueue.java简述在整个 Android 的源码世界里,有两大利剑,其一是 Binder 机制,另一个便是 Handler 消息机制。消息机制涉及 MessageQueue/Message/Looper/Handler 这4个类。Handler 是 Android 中引入的一种让开发者参与处理线程中消息循环的机制。我们在使用 Handler 的时候与 Message 打交道最多,Message 是 Hanlder 机制向开发人员暴露出来的相关类,可以通过 Message 类完成大部分操作 Handler 的功能。作为一名程序员,我们不仅需要知道怎么用 Handler ,还要知道其内部如何实现的,这就是我写这篇文章的目的。模型消息机制(Handler)主要包含: ✨ Message:消息分为硬件产生的消息(如按钮、触摸)和软件生成的消息; ✨ MessageQueue:消息队列的主要功能向消息池投递消息(MessageQueue.enqueueMessage)和取走消息池的消息(MessageQueue.next); ✨ Handler:消息辅助类,主要功能向消息池发送各种消息事件(Handler.sendMessage)和处理相应消息事件(Handler.handleMessage); ✨ Looper:不断循环执行(Looper.loop),按分发机制将消息分发给目标处理者。实例/* * A typical example of the implementation of a Looper thread, * using the separation of {@link #prepare} and {@link #loop} to create an * initial Handler to communicate with the Looper. /class LooperThread extends Thread { public Handler mHandler; public void run() { Looper.prepare(); mHandler = new Handler() { public void handleMessage(Message msg) { // process incoming messages here } }; Looper.loop(); }}接下来我们就围绕这个实例展开讲解!Looper消息队列 MessageQueue 只是存储 Message 的地方,真正让消息队列循环起来的是 Looper,我们先来重点分析 Looper。Looper 是用来使线程中的消息循环起来的。默认情况下当我们创建一个新的线程的时候,这个线程里面是没有消息队列 MessageQueue 的。为了能够让线程能够绑定一个消息队列,我们需要借助于 Looper :首先我们要调用 Looper 的 prepare() 方法,然后调用 Looper 的 Loop() 方法。需要注意的是 Looper.prepare() 和 Looper.loop() 都是在新线程的 run 方法内调用的,这两个方法都是静态方法。 public static void prepare() {…} private static void prepare(boolean quitAllowed) {…} public static void loop() {…}prepare()我们来看一下 Looper.prepare(),该方法是让 Looper 做好准备,只有 Looper 准备好了之后才能调用 Looper.loop() 方法。 public static void prepare() { prepare(true); // 无参,调用 prepare(boolean quitAllowed) } private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { // 每个线程只允许执行一次该方法,第二次执行时已有 Looper,则会抛出异常! throw new RuntimeException(“Only one Looper may be created per thread”); } // 创建 Looper 对象,并保存到当前线程的本地存储区 sThreadLocal.set(new Looper(quitAllowed)); }上面的代码首先通过 sThreadLocal.get() 拿到线程 sThreadLocal 所绑定的 Looper 对象,由于初始情况下 sThreadLocal 并没有绑定 Looper ,所以第一次调用 prepare 方法时,sThreadLocal.get() 返回 null,不会抛出异常。ThreadLocalThreadLocal:线程本地存储区(Thread Local Storage,简称为 TLS),每个线程都有自己的私有的本地存储区域,不同线程之间彼此不能访问对方的TLS区域。TLS 常用的操作方法:set() public void set(T value) { Thread t = Thread.currentThread(); // 获取当前线程 ThreadLocalMap map = getMap(t); //查找当前线程的本地储存区 if (map != null) map.set(this, value); // 保存数据 value 到当前线程 this else createMap(t, value); }我们看下 getMap() 函数: ThreadLocalMap getMap(Thread t) { return t.threadLocals; }判断 map 是否为空,如果为空则创建 ThreadLocalMap : void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }get() public T get() { Thread t = Thread.currentThread(); // 获取当前线程 ThreadLocalMap map = getMap(t); // 查找当前线程的本地储存区 if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings(“unchecked”) T result = (T)e.value; return result; } } return setInitialValue(); // 创建 ThreadLocalMap }查看 setInitialValue() : 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; }Looper 通过如下代码保存了对当前线程的引用:static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); // sThreadLocal 为 ThreadLocal 类型所以在 Looper 对象中通过 sThreadLocal 就可以找到其绑定的线程。ThreadLocal 中有个 set 方法和 get 方法,可以通过 set 方法向 ThreadLocal 中存入一个对象,然后可以通过 get 方法取出存入的对象。ThreadLocal 在 new 的时候使用了泛型,从上面的代码中我们可以看到此处的泛型类型是 Looper ,也就是我们通过 ThreadLocal 的 set 和 get 方法只能写入和读取 Looper 对象类型。构造函数源码中 Looper 的构造函数是 private 的,也就是在该类的外部不能用 new Looper() 的形式得到一个 Looper 对象。private Looper(boolean quitAllowed) {…}我们看下上面代码中 new Looper() 创建 Looper 对象的工作:private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); // 创建 MessageQueue 对象 mThread = Thread.currentThread(); // 记录当前线程}Looper.prepare()在每个线程只允许执行一次,该方法会创建 Looper 对象,Looper 的构造方法中会创建一个 MessageQueue 对象,再将 Looper 对象保存到当前线程 TLS。prepareMainLooper()另外,与 prepare() 相近功能的,还有一个 prepareMainLooper() 方法,该方法主要在 ActivityThread 类中使用。 public static void prepareMainLooper() { prepare(false); // 设置不允许退出的 Looper synchronized (Looper.class) { // 将当前的 Looper 保存为主 Looper,每个线程只允许执行一次 if (sMainLooper != null) { throw new IllegalStateException(“The main Looper has already been prepared.”); } sMainLooper = myLooper(); } }loop()Looper.loop()的代码如下: /* * Run the message queue in this thread. Be sure to call * {@link #quit()} to end the loop. / public static void loop() { final Looper me = myLooper(); // 获取当前线程绑定的 Looper 对象 if (me == null) { throw new RuntimeException(“No Looper; Looper.prepare() wasn’t called on this thread.”); } final MessageQueue queue = me.mQueue; // 获取 Looper 对象中的消息队列 // Make sure the identity of this thread is that of the local process, // and keep track of what that identity token actually is. Binder.clearCallingIdentity(); // 确保在权限检查时基于本地进程,而不是基于最初调用进程 final long ident = Binder.clearCallingIdentity(); for (;;) { // 进入loop的主循环方法 Message msg = queue.next(); // might block(可能会堵塞) if (msg == null) { // 没有消息,则退出循环 // No message indicates that the message queue is quitting. return; } // This must be in a local variable, in case a UI event sets the logger // 默认为null,可通过 setMessageLogging() 方法来指定输出,用于 debug 功能 final Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + “: " + msg.what); } final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs; final long traceTag = me.mTraceTag; if (traceTag != 0 && Trace.isTagEnabled(traceTag)) { Trace.traceBegin(traceTag, msg.target.getTraceName(msg)); } final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis(); final long end; try { msg.target.dispatchMessage(msg); // 用于分发 Message end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis(); } finally { if (traceTag != 0) { Trace.traceEnd(traceTag); } } if (slowDispatchThresholdMs > 0) { final long time = end - start; if (time > slowDispatchThresholdMs) { Slog.w(TAG, “Dispatch took " + time + “ms on " + Thread.currentThread().getName() + “, h=” + msg.target + " cb=” + msg.callback + " msg=” + msg.what); } } if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } // Make sure that during the course of dispatching the // identity of the thread wasn’t corrupted. final long newIdent = Binder.clearCallingIdentity(); // 确保分发过程中 identity 不会损坏 if (ident != newIdent) { // 打印 identity 改变的 log,在分发消息过程中是不希望身份被改变的 } msg.recycleUnchecked(); // 将 Message 放入消息池 } }我们接下来会重点分析 loop() 里面的几个函数:myLooper()前面我们说过,在执行完了 Looper.prepare() 之后,我们就可以在外部通过调用 Looper.myLooper() 获取当前线程绑定的 Looper 对象。 public static @Nullable Looper myLooper() { return sThreadLocal.get(); // 还是通过 sThreadLocal.get()方法获取当前线程绑定的 Looper 对象 }MessageQueue// Looper 构造函数中创建了 mQueue,即 MessageQueueprivate Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); // 创建 MessageQueue 对象 mThread = Thread.currentThread(); // 记录当前线程} public static void loop() { final Looper me = myLooper(); // 获取当前线程绑定的 Looper 对象 if (me == null) { … … } final MessageQueue queue = me.mQueue; // 获取 Looper 对象中的消息队列变量 me 是通过静态方法 myLooper() 获得的当前线程所绑定的 Looper,me.mQueue 就是当前线程所关联的消息队列。for() for (;;) { // 进入loop的主循环方法我们发现for循环没有设置循环终止的条件,所以这个for循环是个死循环。MessageMessage msg = queue.next(); // might block 我们通过消息队列 MessageQueue 的 next 方法从消息队列中取出一条消息,如果此时消息队列中有 Message,那么 next 方法会立即返回该 Message,如果此时消息队列中没有 Message,那么 next 方法就会阻塞式地等待获取 Message。dispatchMessage()/package/ Handler target;try { msg.target.dispatchMessage(msg); end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();} finally { if (traceTag != 0) { Trace.traceEnd(traceTag); }}msg 的 target 属性是 Handler,该代码的意思是让 Message 所关联的 Handler 通过 dispatchMessage 方法让 Handler 处理该 Message ,关于 Handler 的 dispatchMessage 方法将会在下面详细介绍。recycleUnchecked()msg.recycleUnchecked(); // 分发后的 Message 回收到消息池,以便重复利用小结loop()进入循环模式,不断重复下面的操作,直到没有消息时退出循环:1、读取 MessageQueue 的下一条 Message;2、把 Message 分发给相应的 target;3、再把分发后的 Message 回收到消息池,以便重复利用。quit() public void quit() { mQueue.quit(false); // 消息移除 } public void quitSafely() { mQueue.quit(true); // 安全消息移除 }Looper.quit() 方法的实现最终调用的是 MessageQueue.quit() 方法。MessageQueue.quit() void quit(boolean safe) { if (!mQuitAllowed) { 当 mQuitAllowed 为 false,表示不运行退出,强行调用 quit() 会抛出异常 throw new IllegalStateException(“Main thread not allowed to quit.”); } synchronized (this) { if (mQuitting) { return; } mQuitting = true; if (safe) { removeAllFutureMessagesLocked(); } else { removeAllMessagesLocked(); } // We can assume mPtr != 0 because mQuitting was previously false. nativeWake(mPtr); } }消息退出的方式:当 safe = true 时,只移除尚未触发的所有消息,对于正在触发的消息并不移除当 safe = flase 时,移除所有的消息Handler构造函数无参构造 public Handler() { this(null, false); } public Handler(Callback callback) { this(callback, false); } public Handler(boolean async) { this(null, async); } public Handler(Callback callback, boolean async) { // 匿名类、内部类或本地类都必须申明为static,否则会警告可能出现内存泄露 if (FIND_POTENTIAL_LEAKS) { // 默认为 false final Class<? extends Handler> klass = getClass(); if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) == 0) { Log.w(TAG, “The following Handler class should be static or leaks might occur: " + klass.getCanonicalName()); } } // 必须先执行 Looper.prepare(),才能获取 Looper 对象,否则为null mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( “Can’t create handler inside thread that has not called Looper.prepare()”); } mQueue = mLooper.mQueue; mCallback = callback; // 回调方法 mAsynchronous = async; // 设置消息是否为异步处理方式 }对于 Handler 的无参构造方法,默认采用当前线程 TLS 中的 Looper 对象,并且 callback 回调方法为 null,且消息为同步处理方式。只要执行的 Looper.prepare() 方法,那么便可以获取有效的 Looper 对象。有参构造 public Handler(Looper looper) { this(looper, null, false); } public Handler(Looper looper, Callback callback) { this(looper, callback, false); } public Handler(Looper looper, Callback callback, boolean async) { mLooper = looper; mQueue = looper.mQueue; mCallback = callback; mAsynchronous = async; }Handler 类在构造方法中,可指定 Looper,Callback 回调方法以及消息的处理方式(同步或异步),对于无参的 handler,默认是当前线程的 Looper。dispatchMessage()在 Looper.loop() 中,当发现有消息时,调用消息的目标 handler,执行 dispatchMessage() 方法来分发消息。 /* * Handle system messages here. / public void dispatchMessage(Message msg) { if (msg.callback != null) { // 当 Message 存在回调方法,回调 msg.callback.run() 方法 handleCallback(msg); } else { if (mCallback != null) { // 当 Handler 存在 Callback 成员变量时,回调方法 handleMessage() if (mCallback.handleMessage(msg)) { return; } } // Handler 自身的回调方法 handleMessage() handleMessage(msg); } }我们需要重点分析下这个函数:首先会判断 msg.callback 存不存在,msg.callback 是 Runnable 类型,如果 msg.callback 存在,那么说明该 Message 是通过执行 Handler 的 post() 系列方法将 Message 放入到消息队列中的,这种情况下会执行 handleCallback(msg)。handleCallback源码如下: private static void handleCallback(Message message) { message.callback.run(); }这样我们就清楚地看到我们执行了 msg.callback 的 run 方法,也就是执行了 post() 所传递的 Runnable 对象的 run 方法。mCallback如果我们不是通过 post() 系列方法将 Message 放入到消息队列中的,那么 msg.callback 就是 null ,代码继续往下执行。接着我们会判断 Handler 的成员字段 mCallback 存不存在。mCallback 是 Hanlder.Callback 类型的,我们在上面提到过,在 Handler 的构造函数中我们可以传递 Hanlder.Callback 类型的对象,该对象需要实现 handleMessage 方法,如果我们在构造函数中传递了该 Callback 对象,那么我们就会让 Callback 的 handleMessage 方法来处理 Message。 final Callback mCallback; public interface Callback { /* * @param msg A {@link android.os.Message Message} object * @return True if no further handling is desired / public boolean handleMessage(Message msg); }如果我们在构造函数中没有传入 Callback 类型的对象,那么 mCallback 就为 null ,那么我们会调用 Handler 自身的 hanldeMessage 方法,该方法默认是个空方法,我们需要自己重写实现该方法。 /* * Subclasses must implement this to receive messages. */ public void handleMessage(Message msg) { // 空函数 }综上所述,我们可以看到 Handler 提供了三种途径处理 Message ,而且处理有前后优先级之分:首先尝试让 post() 中传递的 Runnable 执行,其次尝试让 Handler 构造函数中传入的 Callback 的 handleMessage 方法处理,最后才是让 Handler 自身的 handleMessage 方法处理Message。CallbackCallback 是 Handle r中的内部接口,需要实现其内部的 handleMessage 方法,Callback 代码如下: public interface Callback { public boolean handleMessage(Message msg); }Handler.Callback 是用来处理 Message 的一种手段,如果没有传递该参数,那么就应该重写 Handler 的 handleMessage 方法,也就是说为了使得 Handler 能够处理 Message ,有两种办法:向 Hanlder 的构造函数传入一个 Handler.Callback 对象,并实现 Handler.Callback 的 handleMessage 方法无需向 Hanlder 的构造函数传入 Handler.Callback 对象,但是需要重写 Handler 本身的 handleMessage 方法也就是说无论哪种方式,我们都得通过某种方式实现 handleMessage 方法,这点与 Java 中对 Thread 的设计有异曲同工之处。在Java中,如果我们想使用多线程,有两种办法:1. 向 Thread 的构造函数传入一个 Runnable 对象,并实现 Runnable 的 run 方法2. 无需向 Thread 的构造函数传入 Runnable 对象,但是要重写 Thread 本身的 run 方法 所以只要用过多线程 Thread,应该就对 Hanlder 这种需要实现 handleMessage 的两种方式了然于心了。在之前分析 Handler(用法篇)的时候我们讲到过两种重要的方法:sendMessage() 和 post(),我们从源码角度进行进一步分析!sendMessage我们看下 sendMessage() 源码处理流程:public final boolean sendMessage(Message msg) { return sendMessageDelayed(msg, 0);}sendMessageDelayed public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); // 最终调 sendMessageAtTime() }通过以上代码可以看书:sendMessage() 调用了 sendMessageDelayed() ,sendMessageDelayed() 又调用了 sendMessageAtTime()。 Handler 中还有 sendEmptyMessage() 方法: public final boolean sendEmptyMessage(int what) { return sendEmptyMessageDelayed(what, 0); } public final boolean sendEmptyMessageDelayed(int what, long delayMillis) { Message msg = Message.obtain(); msg.what = what; return sendMessageDelayed(msg, delayMillis); } public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); // 最终还是要调 sendMessageAtTime() }由此可见所有的 sendMessage 方法和 sendEmptyMessage 最终都调用了 sendMessageAtTime 方法。post我们看下 post() 源码处理流程: public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); }可以看到内部调用了 getPostMessage 方法,该方法传入一个 Runnable 对象,得到一个 Message 对象。getPostMessagegetPostMessage() 的源码如下: private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }通过上面的代码我们可以看到在 getPostMessage 方法中,我们创建了一个 Message 对象,并将传入的 Runnable 对象赋值给 Message 的 callback 成员字段,然后返回该 Message ,然后在 post 方法中该携带有 Runnable 信息的 Message 传入到 sendMessageDelayed 方法中。由此我们可以看到所有的 post 方法内部都需要借助 sendMessage 方法来实现,所以 post() 与 sendMessage() 并不是对立关系,而是 post() 依赖 sendMessage() ,所以 post() 方法可以通过 sendMessage() 方法向消息队列中传入消息,只不过通过 post() 方法向消息队列中传入的消息都携带有 Runnable 对象(Message.callback)。sendMessageAtTime通过分别分析 sendEmptyMessage()、post() 方法与 sendMessage() 方法之间的关系,我们可以看到在 Handler 中所有可以直接或间接向消息队列发送 Message 的方法最终都调用了 sendMessageAtTime 方法,该方法的源码如下: public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue”); Log.w(“Looper”, e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); }enqueueMessage我们发现 sendMessageAtTime() 方法内部调用了 enqueueMessage() 函数: private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }我们需要重点注意两行代码:msg.target = this; // 将 Message 的 target 绑定为当前的 Handler // 变量 queue 表示的是 Handler 所绑定的消息队列 MessageQueue ,通过调用 queue.enqueueMessage(msg, uptimeMillis) 将 Message 放入到消息队列中。queue.enqueueMessage(msg, uptimeMillis);还记得我们之前在分析 Looper 的时候,最终提到的 dispatchMessage() 吗?我们回忆一下: try { msg.target.dispatchMessage(msg); end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis(); } finally { if (traceTag != 0) { Trace.traceEnd(traceTag); } }MessageQueue每个线程内部都维护了一个消息队列 —— MessageQueue。消息队列 MessageQueue,顾名思义,就是存放消息的队列。那队列中存储的消息是什么呢?假设我们在UI界面上单击了某个按钮,而此时程序又恰好收到了某个广播事件,那我们如何处理这两件事呢?因为一个线程在某一时刻只能处理一件事情,不能同时处理多件事情,所以我们不能同时处理按钮的单击事件和广播事件,我们只能挨个对其进行处理,只要挨个处理就要有处理的先后顺序。为此Android把UI界面上单击按钮的事件封装成了一个 Message ,将其放入到 MessageQueue 里面去,即将单击按钮事件的 Message 入栈到消息队列中,然后再将广播事件的封装成以 Message ,也将其入栈到消息队列中。也就是说一个 Message 对象表示的是线程需要处理的一件事情,消息队列就是一堆需要处理的 Message 的池。线程 Thread 会依次取出消息队列中的消息,依次对其进行处理。MessageQueue 中有两个比较重要的方法,一个是 enqueueMessage 方法,一个是 next 方法。enqueueMessage 方法用于将一个 Messag e放入到消息队列 MessageQueue 中,next 方法是从消息队列 MessageQueue 中阻塞式地取出一个 Message。在 Android 中,消息队列负责管理着顶级程序对象(Activity、BroadcastReceiver等)以及由其创建的所有窗口。创建MessageQueue MessageQueue(boolean quitAllowed) { mQuitAllowed = quitAllowed; // 通过 native 方法初始化消息队列,其中 mPtr 是供 native 代码使用 mPtr = nativeInit(); }next() Message next() { final long ptr = mPtr; if (ptr == 0) { // 当消息循环已经退出,则直接返回 return null; } // 循环迭代的首次为 -1 int pendingIdleHandlerCount = -1; // -1 only during first iteration int nextPollTimeoutMillis = 0; for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } // 阻塞操作,当等待 nextPollTimeoutMillis 时长,或者消息队列被唤醒,都会返回 nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { // Try to retrieve the next message. Return if found. final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) { // 当消息 Handler 为空时,查询 MessageQueue 中的下一条异步消息 msg,则退出循环 do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { if (now < msg.when) { // 当异步消息触发时间大于当前时间,则设置下一次轮询的超时时长 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // 获取一条消息,并返回 mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; // 设置消息的使用状态,即 flags |= FLAG_IN_USE msg.markInUse(); // 成功地获取 MessageQueue 中的下一条即将要执行的消息 return msg; } } else { // 没有消息 nextPollTimeoutMillis = -1; } // 消息正在退出,返回null if (mQuitting) { dispose(); return null; } // 当消息队列为空,或者是消息队列的第一个消息时 if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { pendingIdleHandlerCount = mIdleHandlers.size(); } if (pendingIdleHandlerCount <= 0) { // 没有 idle handlers 需要运行,则循环并等待 mBlocked = true; continue; } if (mPendingIdleHandlers == null) { mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; } mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); } // 只有第一次循环时,会运行 idle handlers,执行完成后,重置 pendingIdleHandlerCount 为 0 for (int i = 0; i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; // 去掉 handler 的引用 boolean keep = false; try { keep = idler.queueIdle(); // idle 时执行的方法 } catch (Throwable t) { Log.wtf(TAG, “IdleHandler threw exception”, t); } if (!keep) { synchronized (this) { mIdleHandlers.remove(idler); } } } // 重置 idle handler 个数为 0,以保证不会再次重复运行 pendingIdleHandlerCount = 0; // 当调用一个空闲 handler 时,一个新 message 能够被分发,因此无需等待可以直接查询 pending message nextPollTimeoutMillis = 0; } }nativePollOnce 是阻塞操作,其中 nextPollTimeoutMillis 代表下一个消息到来前,还需要等待的时长;当 nextPollTimeoutMillis = -1 时,表示消息队列中无消息,会一直等待下去。当处于空闲时,往往会执行 IdleHandler 中的方法。当 nativePollOnce() 返回后,next() 从 mMessages 中提取一个消息。enqueueMessage() boolean enqueueMessage(Message msg, long when) { // 每一个普通 Message 必须有一个 target if (msg.target == null) { throw new IllegalArgumentException(“Message must have a target.”); } if (msg.isInUse()) { throw new IllegalStateException(msg + " This message is already in use.”); } synchronized (this) { // 正在退出时,回收 msg,加入到消息池 if (mQuitting) { IllegalStateException e = new IllegalStateException( msg.target + " sending message to a Handler on a dead thread"); Log.w(TAG, e.getMessage(), e); msg.recycle(); return false; } msg.markInUse(); msg.when = when; Message p = mMessages; boolean needWake; if (p == null || when == 0 || when < p.when) { // p 为 null (代表MessageQueue没有消息) 或者 msg 的触发时间是队列中最早的,则进入该该分支 msg.next = p; mMessages = msg; needWake = mBlocked; // 当阻塞时需要唤醒 } else { // 将消息按时间顺序插入到 MessageQueue。一般地,不需要唤醒事件队列,除非 // 消息队头存在 barrier,并且同时 Message 是队列中最早的异步消息 needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; // invariant: p == prev.next prev.next = msg; } // 消息没有退出,我们认为此时 mPtr != 0 if (needWake) { nativeWake(mPtr); } } return true; }MessageQueue 是按照 Message 触发时间的先后顺序排列的,队头的消息是将要最早触发的消息。当有消息需要加入消息队列时,会从队列头开始遍历,直到找到消息应该插入的合适位置,以保证所有消息的时间顺序。removeMessages() void removeMessages(Handler h, int what, Object object) { if (h == null) { return; } synchronized (this) { Message p = mMessages; // 从消息队列的头部开始,移除所有符合条件的消息 while (p != null && p.target == h && p.what == what && (object == null || p.obj == object)) { Message n = p.next; mMessages = n; p.recycleUnchecked(); p = n; } // 移除剩余的符合要求的消息 while (p != null) { Message n = p.next; if (n != null) { if (n.target == h && n.what == what && (object == null || n.obj == object)) { Message nn = n.next; n.recycleUnchecked(); p.next = nn; continue; } } p = n; } } }这个移除消息的方法,采用了两个 while 循环,第一个循环是从队头开始,移除符合条件的消息,第二个循环是从头部移除完连续的满足条件的消息之后,再从队列后面继续查询是否有满足条件的消息需要被移除。总结最后用一张图,来表示整个消息机制:图解: ✨ Handler通过sendMessage()发送Message到MessageQueue队列; ✨ Looper通过loop(),不断提取出达到触发条件的Message,并将Message交给target来处理; ✨ 经过dispatchMessage()后,交回给Handler的handleMessage()来进行相应地处理。 ✨ 将Message加入MessageQueue时,处往管道写入字符,可以会唤醒loop线程;如果MessageQueue中没有Message,并处于Idle状态,则会执行IdelHandler接口中的方法,往往用于做一些清理性地工作。参考Blog 01. https://blog.csdn.net/iisprin… 02. http://gityuan.com/2015/12/26… ...

October 25, 2018 · 12 min · jiezi

Android 机制篇 -- 全面解析 Handler 机制(用法篇)

开篇引出问题在 Android 开发中,我们经常会遇到这样一种情况:在 UI 界面上进行某项操作后要执行一段很耗时的代码,比如我们在界面上点击了一个 “下载” 按钮,那么我们需要执行网络请求,这是一个耗时操作。为了保证不影响 UI 线程,所以我们会创建一个新的线程去执行我们的耗时的代码。当我们的耗时操作完成时,我们需要更新 UI 界面以告知用户操作完成了。代码实例比如,我们看以下代码:package com.example.marco.handlerdemo;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.TextView;public class MainActivity extends AppCompatActivity implements Button.OnClickListener{ private TextView textView = null; private Button btnDownload = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = findViewById(R.id.textView); btnDownload = findViewById(R.id.btnDownload); btnDownload.setOnClickListener(this); } @Override public void onClick(View v) { DownLoadThread downLoadThread = new DownLoadThread(); downLoadThread.start(); } class DownLoadThread extends Thread { @Override public void run() { try { System.out.println(“开始下载文件”); Thread.sleep(5000); System.out.println(“下载完成”); MainActivity.this.textView.setText(“文件下载完成”); // 执行后,此处崩溃!!! }catch (InterruptedException e){ e.printStackTrace(); } } }}执行结果E/AndroidRuntime: FATAL EXCEPTION: Thread-4 Process: com.example.marco.handlerdemo, PID: 14415 android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7579) at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1200) at android.view.View.requestLayout(View.java:22156) at android.view.View.requestLayout(View.java:22156) at android.view.View.requestLayout(View.java:22156) at android.view.View.requestLayout(View.java:22156) at android.view.View.requestLayout(View.java:22156) at android.view.View.requestLayout(View.java:22156) at android.support.constraint.ConstraintLayout.requestLayout(ConstraintLayout.java:3172) at android.view.View.requestLayout(View.java:22156) at android.widget.TextView.checkForRelayout(TextView.java:8553) at android.widget.TextView.setText(TextView.java:5416) at android.widget.TextView.setText(TextView.java:5272) at android.widget.TextView.setText(TextView.java:5229) at com.example.marco.handlerdemo.MainActivity$DownLoaderThread.run(MainActivity.java:36) // 错误开始错误分析执行之后,我们发现程序崩溃,并且出现了以上的错误:只有创建 View 的原始线程才能更新 View。为什么会出现这个错误,这个错误是什么意思?出现这样错误的原因是 Android 中的 View 不是线程安全的,在 Android 应用启动时,会自动创建一个线程,即程序的主线程,主线程负责 UI 的展示、UI 事件消息的派发处理等等,因此主线程也叫做 UI 线程,textView 是在 UI 线程中创建的,当我们在 DownloadThread 线程中去更新 UI 线程中创建的 textView 时自然会报上面的错误。Android 的 UI 控件是非线程安全的,不同的平台提供了不同的解决方案以实现跨线程更新 UI 控件,Android 为了解决这种问题引入了 Handler机制 。Handler 引入那么 Handler 到底是什么呢?Handler 是 Android 中引入的一种让开发者参与处理线程中消息循环的机制。每个 Hanlder 都关联了一个线程,每个线程内部都维护了一个消息队列 MessageQueue,这样 Handler 实际上也就关联了一个消息队列。可以通过 Handler 将 Message 和 Runnable 对象发送到该 Handler 所关联线程的 MessageQueue(消息队列)中,然后该消息队列一直在循环拿出一个 Message,对其进行处理,处理完之后拿出下一个 Message,继续进行处理,周而复始。当创建一个 Handler 的时候,该 Handler 就绑定了当前创建 Hanlder 的线程。从这时起,该 Hanlder 就可以发送 Message 和 Runnable 对象到该 Handler 对应的消息队列中,当从 MessageQueue 取出某个 Message 时,会让 Handler 对其进行处理。Handler 可以用来在多线程间进行通信,在另一个线程中去更新 UI 线程中的 UI 控件只是 Handler 使用中的一种典型案例,除此之外,Handler 可以做很多其他的事情。每个 Handler 都绑定了一个线程,假设存在两个线程 ThreadA 和 ThreadB,并且 HandlerA 绑定了 ThreadA,在 ThreadB 中的代码执行到某处时,出于某些原因,我们需要让 ThreadA 执行某些代码,此时我们就可以使用 Handler,我们可以在 ThreadB 中向 HandlerA 中加入某些信息以告知 ThreadA 中该做某些处理了。由此可以看出,Handler 是 Thread 的代言人,是多线程之间通信的桥梁,通过 Handler,我们可以在一个线程中控制另一个线程去做某事。Handler 用法Handler 提供了两种方式解决前面遇到的问题(在一个新线程中更新主线程中的 UI 控件),一种是通过 post 方法,一种是调用 sendMessage 方法。post代码实例package com.example.marco.handlerdemo;import android.os.Handler;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.TextView;public class MainActivity extends AppCompatActivity implements Button.OnClickListener{ private TextView textView = null; private Button btnDownload = null; private Handler uiHandler = new Handler(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = findViewById(R.id.textView); btnDownload = findViewById(R.id.btnDownload); btnDownload.setOnClickListener(this); System.out.println(“MainThread id " + Thread.currentThread().getId()); } @Override public void onClick(View v) { DownLoadThread downLoadThread = new DownLoadThread(); downLoadThread.start(); } class DownLoadThread extends Thread { @Override public void run() { try { System.out.println(“DownloadThread id " + Thread.currentThread().getId()); System.out.println(“开始下载文件”); Thread.sleep(5000); System.out.println(“下载完成”); // MainActivity.this.textView.setText(“文件下载完成”); Runnable runnable = new Runnable() { @Override public void run() { System.out.println(“RunnableThread id " + Thread.currentThread().getId()); MainActivity.this.textView.setText(“文件下载完成”); } }; uiHandler.post(runnable); }catch (InterruptedException e){ e.printStackTrace(); } } }}执行结果2018-09-28 15:18:24.474 15864-15864/com.example.marco.handlerdemo I/System.out: MainThread id 22018-09-28 15:18:26.901 15864-15938/com.example.marco.handlerdemo I/System.out: DownloadThread id 24412018-09-28 15:18:26.901 15864-15938/com.example.marco.handlerdemo I/System.out: 开始下载文件2018-09-28 15:18:31.902 15864-15938/com.example.marco.handlerdemo I/System.out: 下载完成2018-09-28 15:18:31.906 15864-15864/com.example.marco.handlerdemo I/System.out: RunnableThread id 2通过输出结果可以看出,Runnable 中的代码所执行的线程 ID 与 DownloadThread 的线程 ID 不同,而与主线程的线程 ID 相同,因此我们也由此看出在执行了 Handler.post(Runnable) 这句代码之后,运行 Runnable 代码的线程与 Handler 所绑定的线程是一致的,而与执行 Handler.post(Runnable) 这句代码的线程(DownloadThread)无关。结果分析我们在 Activity 中创建了一个 Handler 成员变量 uiHandler,Handler 有个特点,在执行 new Handler() 的时候,默认情况下 Handler 会绑定当前代码执行的线程,我们在主线程中实例化了 uiHandler,所以 uiHandle r就自动绑定了主线程,即 UI 线程。当我们在 DownloadThread 中执行完耗时代码后,我们将一个 Runnable 对象通过 post 方法传入到了 Handler 中,Handler 会在合适的时候让主线程执行 Runnable 中的代码,这样 Runnable 就在主线程中执行了,从而正确更新了主线程中的 UI。sendMessage代码实例package com.example.marco.handlerdemo;import android.os.Handler;import android.os.Message;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.TextView;public class MainActivity extends AppCompatActivity implements Button.OnClickListener{ private TextView textView = null; private Button btnDownload = null; private Handler uiHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case 1: System.out.println(“handleMessage thread id " + Thread.currentThread().getId()); System.out.println(“msg.arg1:” + msg.arg1); System.out.println(“msg.arg2:” + msg.arg2); MainActivity.this.textView.setText(“文件下载完成”); break; } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = findViewById(R.id.textView); btnDownload = findViewById(R.id.btnDownload); btnDownload.setOnClickListener(this); System.out.println(“MainThread id " + Thread.currentThread().getId()); } @Override public void onClick(View v) { DownLoadThread downLoadThread = new DownLoadThread(); downLoadThread.start(); } class DownLoadThread extends Thread { @Override public void run() { try { System.out.println(“DownloadThread id " + Thread.currentThread().getId()); System.out.println(“开始下载文件”); Thread.sleep(5000); System.out.println(“下载完成”); // 文件下载完成后更新 UI Message msg = new Message(); msg.what = 1; msg.arg1 = 123; msg.arg2 = 456; //我们也可以通过给 obj 赋值 Object 类型传递向 Message 传入任意数据 //msg.obj = null; // MainActivity.this.textView.setText(“文件下载完成”); /* Runnable runnable = new Runnable() { @Override public void run() { System.out.println(“RunnableThread id " + Thread.currentThread().getId()); MainActivity.this.textView.setText(“文件下载完成”); } }; uiHandler.post(runnable);*/ uiHandler.sendMessage(msg); }catch (InterruptedException e){ e.printStackTrace(); } } }}执行结果2018-09-28 16:16:16.613 19652-19652/? I/System.out: MainThread id 22018-09-28 16:16:19.431 19652-19692/com.example.marco.handlerdemo I/System.out: DownloadThread id 24932018-09-28 16:16:19.431 19652-19692/com.example.marco.handlerdemo I/System.out: 开始下载文件2018-09-28 16:16:24.432 19652-19692/com.example.marco.handlerdemo I/System.out: 下载完成2018-09-28 16:16:24.434 19652-19652/com.example.marco.handlerdemo I/System.out: handleMessage thread id 22018-09-28 16:16:24.434 19652-19652/com.example.marco.handlerdemo I/System.out: msg.arg1:1232018-09-28 16:16:24.435 19652-19652/com.example.marco.handlerdemo I/System.out: msg.arg2:456结果分析通过 Message 与 Handler 进行通信的步骤是:重写 Handler 的 handleMessage 方法,根据 Message 的 what 值进行不同的处理操作;设置 Message 的 what 值:Message.what 是我们自定义的一个 Message 的识别码,以便于在 Handler 的 handleMessage 方法中根据 wha t识别出不同的 Message ,以便我们做出不同的处理操作;设置 Message 的所携带的数据,简单数据可以通过两个 int 类型的 field :arg1 和 arg2 来赋值,并可以在 handleMessage 中读取;如果 Message 需要携带复杂的数据,那么可以设置 Message 的 obj 字段,obj 是 Object 类型,可以赋予任意类型的数据;我们通过 Handler.sendMessage(Message) 方法将 Message 传入 Handler 中让其在 handleMessage 中对其进行处理;需要说明的是,如果在 handleMessage 中不需要判断 Message 类型,那么就无须设置 Message 的 what 值;而且让 Message 携带数据也不是必须的,只有在需要的时候才需要让其携带数据;如果确实需要让 Message 携带数据,应该尽量使用 arg1 或 arg2 或两者,能用 arg1 和 arg2 解决的话就不要用obj,因为用 arg1 和 arg2 更高效。由上我们可以看出,执行 handleMessage 的线程与创建 Handler 的线程是同一线程,在本示例中都是主线程。执行 handleMessage 的线程与执行 uiHandler.sendMessage(msg) 的线程没有关系。参考Blog 01. https://blog.csdn.net/iisprin… ...

September 29, 2018 · 4 min · jiezi