7.1 服务暴露前的准备-ServiceBean的装配
dubbo的服务暴露以第一章 第一个dubbo项目中的dubbo-demo-provider来讲述。
列出dubbo-demo-provider的xml配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <!-- 提供方应用信息,用于计算依赖关系 -->
<dubbo:application name="demo-provider"/> <!-- 使用zookeeper注册中心,并使用curator客户端 -->
<dubbo:registry protocol="zookeeper" address="10.211.55.5:2181" client="curator"/> <!-- 用dubbo协议在20880端口暴露服务 -->
<dubbo:protocol name="dubbo" port="20880"/> <!-- 和本地bean一样实现服务 -->
<bean id="demoService" class="com.alibaba.dubbo.demo.provider.DemoServiceImpl"/>
<!--<bean id="demo2Service" class="com.alibaba.dubbo.demo.provider.Demo2ServiceImpl"/>--> <!-- 声明需要暴露的服务接口 -->
<dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService"/>
<!--<dubbo:service interface="com.alibaba.dubbo.demo.Demo2Service" ref="demo2Service"/>-->
</beans>
服务暴露是由com.alibaba.dubbo.config.spring.ServiceBean这个类来实现的,这个类是spring通过解析<dubbo:service>节点创建的单例Bean,每一个<dubbo:service>都会创建一个ServiceBean。先看一下ServiceBean的继承类图:
看一下ServiceBean的源码:
package com.alibaba.dubbo.config.spring; import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ModuleConfig;
import com.alibaba.dubbo.config.MonitorConfig;
import com.alibaba.dubbo.config.ProtocolConfig;
import com.alibaba.dubbo.config.ProviderConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.config.ServiceConfig;
import com.alibaba.dubbo.config.annotation.Service;
import com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory; import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.support.AbstractApplicationContext; import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map; /**
* ServiceFactoryBean
*/
public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean, ApplicationContextAware, ApplicationListener, BeanNameAware {
private static final long serialVersionUID = 213195494150089726L; private static transient ApplicationContext SPRING_CONTEXT; private transient ApplicationContext applicationContext; private transient String beanName; private transient boolean supportedApplicationListener; public ServiceBean() {
super();
} public ServiceBean(Service service) {
super(service);
} public static ApplicationContext getSpringContext() {
return SPRING_CONTEXT;
} /**
* ApplicationContextAware接口的方法
* Set the ApplicationContext that this object runs in.
* Invoked after population of normal bean properties but before an init callback such
* as {@link org.springframework.beans.factory.InitializingBean#afterPropertiesSet()}
* or a custom init-method.
*
* 流程:
* 1 将applicationContext设置到SpringExtensionFactory中,用于后续从SpringExtensionFactory中获取Bean
* 2 获取方法addApplicationListener(ApplicationListener<?> listener),之后将当前类(因为当前类监听了ContextRefreshedEvent事件)加入spring的监听器列表
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
SpringExtensionFactory.addApplicationContext(applicationContext);
if (applicationContext != null) {
SPRING_CONTEXT = applicationContext;
try {
/** */
Method method = applicationContext.getClass().getMethod("addApplicationListener", new Class<?>[]{ApplicationListener.class}); // 兼容Spring2.0.1
method.invoke(applicationContext, new Object[]{this});
supportedApplicationListener = true;
} catch (Throwable t) {
if (applicationContext instanceof AbstractApplicationContext) {
try {
Method method = AbstractApplicationContext.class.getDeclaredMethod("addListener", new Class<?>[]{ApplicationListener.class}); // 兼容Spring2.0.1
if (!method.isAccessible()) {
method.setAccessible(true);
}
method.invoke(applicationContext, new Object[]{this});
supportedApplicationListener = true;
} catch (Throwable t2) {
}
}
}
}
} /**
* BeanNameAware接口的方法
* Set the name of the bean in the bean factory that created this bean.
* Invoked after population of normal bean properties but before an
* init callback such as {@link InitializingBean#afterPropertiesSet()}
* or a custom init-method.
*/
@Override
public void setBeanName(String name) {
this.beanName = name;
} /**
* ApplicationListener接口的方法
* delay没有设置或者是-1 && 服务没有暴露 && 服务没有反注册,则进行服务暴露
*/
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (ContextRefreshedEvent.class.getName().equals(event.getClass().getName())) {
if (isDelay() && !isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
export();
}
}
} private boolean isDelay() {
Integer delay = getDelay();
ProviderConfig provider = getProvider();
if (delay == null && provider != null) {
delay = provider.getDelay();
}
return supportedApplicationListener && (delay == null || delay.intValue() == -1);
} /**
* InitializingBean接口的方法:
* This method allows the bean instance to perform initialization only
* possible when all bean properties have been set
*
* 流程:
* 1 检查ServiceBean的ProviderConfig provider,如果为空,从applicationContext获取ProviderConfig类型的bean(这里查找的过程其实就是看有没有配置<dubbo:provider>),如果获取到了,进行设置
* 2 后续会参照1分别进行
* -- ApplicationConfig application
* -- ModuleConfig module
* -- List<RegistryConfig> registries
* -- MonitorConfig monitor
* -- List<ProtocolConfig> protocols
* -- String path:服务名称
* 3 判断延迟的事件是否大于0,如果是,执行export(),进行服务暴露,如果不是,结束(这种情况下服务暴露,会发生在发布上下文刷新事件的时候)
*/
@Override
@SuppressWarnings({"unchecked", "deprecation"})
public void afterPropertiesSet() throws Exception {
if (getProvider() == null) {
Map<String, ProviderConfig> providerConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProviderConfig.class, false, false);
if (providerConfigMap != null && providerConfigMap.size() > 0) {
Map<String, ProtocolConfig> protocolConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProtocolConfig.class, false, false);
if ((protocolConfigMap == null || protocolConfigMap.size() == 0)
&& providerConfigMap.size() > 1) { // 兼容旧版本
List<ProviderConfig> providerConfigs = new ArrayList<ProviderConfig>();
for (ProviderConfig config : providerConfigMap.values()) {
if (config.isDefault() != null && config.isDefault().booleanValue()) {
providerConfigs.add(config);
}
}
if (providerConfigs.size() > 0) {
setProviders(providerConfigs);
}
} else {
ProviderConfig providerConfig = null;
for (ProviderConfig config : providerConfigMap.values()) {
if (config.isDefault() == null || config.isDefault().booleanValue()) {
if (providerConfig != null) {
throw new IllegalStateException("Duplicate provider configs: " + providerConfig + " and " + config);
}
providerConfig = config;
}
}
if (providerConfig != null) {
setProvider(providerConfig);
}
}
}
}
if (getApplication() == null
&& (getProvider() == null || getProvider().getApplication() == null)) {
Map<String, ApplicationConfig> applicationConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ApplicationConfig.class, false, false);
if (applicationConfigMap != null && applicationConfigMap.size() > 0) {
ApplicationConfig applicationConfig = null;
for (ApplicationConfig config : applicationConfigMap.values()) {
if (config.isDefault() == null || config.isDefault().booleanValue()) {
if (applicationConfig != null) {
throw new IllegalStateException("Duplicate application configs: " + applicationConfig + " and " + config);
}
applicationConfig = config;
}
}
if (applicationConfig != null) {
setApplication(applicationConfig);
}
}
}
if (getModule() == null
&& (getProvider() == null || getProvider().getModule() == null)) {
Map<String, ModuleConfig> moduleConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ModuleConfig.class, false, false);
if (moduleConfigMap != null && moduleConfigMap.size() > 0) {
ModuleConfig moduleConfig = null;
for (ModuleConfig config : moduleConfigMap.values()) {
if (config.isDefault() == null || config.isDefault().booleanValue()) {
if (moduleConfig != null) {
throw new IllegalStateException("Duplicate module configs: " + moduleConfig + " and " + config);
}
moduleConfig = config;
}
}
if (moduleConfig != null) {
setModule(moduleConfig);
}
}
}
if ((getRegistries() == null || getRegistries().size() == 0)
&& (getProvider() == null || getProvider().getRegistries() == null || getProvider().getRegistries().size() == 0)
&& (getApplication() == null || getApplication().getRegistries() == null || getApplication().getRegistries().size() == 0)) {
Map<String, RegistryConfig> registryConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, RegistryConfig.class, false, false);
if (registryConfigMap != null && registryConfigMap.size() > 0) {
List<RegistryConfig> registryConfigs = new ArrayList<RegistryConfig>();
for (RegistryConfig config : registryConfigMap.values()) {
if (config.isDefault() == null || config.isDefault().booleanValue()) {
registryConfigs.add(config);
}
}
if (registryConfigs != null && registryConfigs.size() > 0) {
super.setRegistries(registryConfigs);
}
}
}
if (getMonitor() == null
&& (getProvider() == null || getProvider().getMonitor() == null)
&& (getApplication() == null || getApplication().getMonitor() == null)) {
Map<String, MonitorConfig> monitorConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, MonitorConfig.class, false, false);
if (monitorConfigMap != null && monitorConfigMap.size() > 0) {
MonitorConfig monitorConfig = null;
for (MonitorConfig config : monitorConfigMap.values()) {
if (config.isDefault() == null || config.isDefault().booleanValue()) {
if (monitorConfig != null) {
throw new IllegalStateException("Duplicate monitor configs: " + monitorConfig + " and " + config);
}
monitorConfig = config;
}
}
if (monitorConfig != null) {
setMonitor(monitorConfig);
}
}
}
if ((getProtocols() == null || getProtocols().size() == 0)
&& (getProvider() == null || getProvider().getProtocols() == null || getProvider().getProtocols().size() == 0)) {
Map<String, ProtocolConfig> protocolConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProtocolConfig.class, false, false);
if (protocolConfigMap != null && protocolConfigMap.size() > 0) {
List<ProtocolConfig> protocolConfigs = new ArrayList<ProtocolConfig>();
for (ProtocolConfig config : protocolConfigMap.values()) {
if (config.isDefault() == null || config.isDefault().booleanValue()) {
protocolConfigs.add(config);
}
}
if (protocolConfigs != null && protocolConfigs.size() > 0) {
super.setProtocols(protocolConfigs);
}
}
}
if (getPath() == null || getPath().length() == 0) {
if (beanName != null && beanName.length() > 0
&& getInterface() != null && getInterface().length() > 0
&& beanName.startsWith(getInterface())) {
setPath(beanName);
}
}
if (!isDelay()) {//设置的延迟时间大于0
export();
}
} /**
* DisposableBean接口的方法
*/
@Override
public void destroy() throws Exception {
unexport();
}
}
这里最重要的两个方法:afterPropertiesSet()和onApplicationEvent(ApplicationEvent event)。
一 设置属性与服务暴露
当所有的Bean的属性被设置好之后,执行afterPropertiesSet()。该方法的流程:
1 设置属性
检查ServiceBean的某个属性(这里的属性包含如下6个)是否为空,如果为空,从applicationContext获取相应类型的bean,如果获取到了,则进行相应的设置。
- ProviderConfig provider:其实就是看有没有配置<dubbo:provider>
- ApplicationConfig application:其实就是看有没有配置<dubbo:application>
- ModuleConfig module:其实就是看有没有配置<dubbo:module>
- List<RegistryConfig> registries:其实就是看有没有配置<dubbo:registry>
- MonitorConfig monitor:其实就是看有没有配置<dubbo:monitor>
- List<ProtocolConfig> protocols:其实就是看有没有配置<dubbo:protocol>
- String path:服务名称
2 是否暴露服务
之后判断延迟的时间是否大于0,如果是,执行export(),进行服务暴露,如果不是,结束(这种情况下服务暴露会发生在容器发布上下文刷新事件的时候)。在这里,我们并没有指定delay,所以delay==null,服务暴露会发生在容器发布上下文刷新事件的时候。
当afterPropertiesSet()结束之后,来看一下此时的ServiceBean实例,实例的私有属性如下:(没有值的暂时不说)
id = com.alibaba.dubbo.demo.DemoService
applicationContext = ClassPathXmlApplicationContext实例
beanName = com.alibaba.dubbo.demo.DemoService
interfaceName = com.alibaba.dubbo.demo.DemoService
supportedApplicationListener = true
ref = DemoServiceImpl实例
path = com.alibaba.dubbo.demo.DemoService application:
-- id = demo-provider
-- name = demo-provider registries = [
RegistryConfig:
-- id = com.alibaba.dubbo.config.RegistryConfig
-- protocol = zookeeper
-- address = 10.211.55.5:2181
-- client = curator
] protocols = [
ProtocolConfig:
-- id = dubbo
-- name = dubbo
-- port = 20880
]
实际上在创建ServiceBean实例的时候,也会初始化其父类ServiceConfig的静态属性:
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
其中protocol的实例是:Protocol$Adaptive实例,Protocol$Adaptive类的代码在5.2 dubbo-compiler源码解析已经列出。
下边来看一下第二句代码的源码。首先,看一下ProxyFactory的定义:
package com.alibaba.dubbo.rpc; import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.extension.Adaptive;
import com.alibaba.dubbo.common.extension.SPI; /**
* ProxyFactory. (API/SPI, Singleton, ThreadSafe)
*/
@SPI("javassist")
public interface ProxyFactory {
/**
* create proxy.
*/
@Adaptive({Constants.PROXY_KEY})
<T> T getProxy(Invoker<T> invoker) throws RpcException; /**
* create invoker.
*/
@Adaptive({Constants.PROXY_KEY})
<T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException;
}
ExtensionLoader.getExtensionLoader(ProxyFactory.class)的实现结果还是:
ExtensionLoader<com.alibaba.dubbo.rpc.ProxyFactory> loader,最终的loader包含如下属性:
- Class<?> type = interface com.alibaba.dubbo.rpc.ProxyFactory
- ExtensionFactory objectFactory = AdaptiveExtensionFactory(适配类)
- factories = [SpringExtensionFactory实例, SpiExtensionFactory实例]
之后,执行getAdaptiveExtension()。
来看一下:META-INF/dubbo/internal/com.alibaba.dubbo.rpc.ProxyFactory的内容:
stub=com.alibaba.dubbo.rpc.proxy.wrapper.StubProxyFactoryWrapper
jdk=com.alibaba.dubbo.rpc.proxy.jdk.JdkProxyFactory
javassist=com.alibaba.dubbo.rpc.proxy.javassist.JavassistProxyFactory
从ProxyFactory的@SPI("javassist"),默认选用的实现是com.alibaba.dubbo.rpc.proxy.javassist.JavassistProxyFactory。com.alibaba.dubbo.rpc.proxy.wrapper.StubProxyFactoryWrapper是一个wrapper类,但是wrapper类只在getExtension("xxx")中会实现aop,而在getAdaptiveExtension()不会进行aop包裹。
这里的三个实现类没有一个类上带有@Adaptive注解,所以会动态创建类。动态生成的类ProxyFactory$Adaptive代码如下:
package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader; public class ProxyFactory$Adaptive implements com.alibaba.dubbo.rpc.ProxyFactory {
public com.alibaba.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, com.alibaba.dubbo.common.URL arg2) throws com.alibaba.dubbo.rpc.RpcException {
if (arg2 == null)
throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg2;
String extName = url.getParameter("proxy", "javassist");
if(extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName);
return extension.getInvoker(arg0, arg1, arg2);
} public java.lang.Object getProxy(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
if (arg0 == null)
throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null)
throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");com.alibaba.dubbo.common.URL url = arg0.getUrl();
String extName = url.getParameter("proxy", "javassist");
if(extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName);
return extension.getProxy(arg0);
}
}
所以ServiceConfig中的静态属性proxyFactory为ProxyFactory$Adaptive实例。
至此,一个ServiceBean实例完成了。
二 在上下文刷新时进行初始化
/**
* ApplicationListener接口的方法
* delay没有设置或者是-1 && 服务没有暴露 && 服务没有反注册,则进行服务暴露
*/
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (ContextRefreshedEvent.class.getName().equals(event.getClass().getName())) {
if (isDelay() && !isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
export();
}
}
}
一切准备好之后,就在这里开始进行服务暴露!export()!!!
7.1 服务暴露前的准备-ServiceBean的装配的更多相关文章
- Dubbo原理和源码解析之服务暴露
github新增仓库 "dubbo-read"(点此查看),集合所有<Dubbo原理和源码解析>系列文章,后续将继续补充该系列,同时将针对Dubbo所做的功能扩展也进行 ...
- Dubbo 服务暴露注册流程
Dubbo的应用会在启动时完成服务注册或订阅(不论是生产者,还是消费者)如下图所示. 图中小方块Protocol, Cluster, Proxy, Service, Container, Regist ...
- dubbo源码分析12——服务暴露3_doExportUrls()方法分析
本文紧接上文,doExportUrls()方法位于ServiceConfig类中,代码入口如下: private void doExportUrls() { List<URL> regis ...
- dubbo源码分析8——服务暴露概述
从上文中可知,com.alibaba.dubbo.config.spring.ServiceBean类是负责解析<dubbo:service/>的配置的,下面是它的类图 从类图上可知它继承 ...
- dubbo系列四、dubbo服务暴露过程源码解析
一.代码准备 1.示例代码 参考dubbo系列二.dubbo+zookeeper+dubboadmin分布式服务框架搭建(windows平台) 2.简单了解下spring自定义标签 https://w ...
- 【Dubbo源码阅读系列】服务暴露之本地暴露
在上一篇文章中我们介绍 Dubbo 自定义标签解析相关内容,其中我们自定义的 XML 标签 <dubbo:service /> 会被解析为 ServiceBean 对象(传送门:Dubbo ...
- 2、Dubbo源码解析--服务发布原理(Netty服务暴露)
一.服务发布 - 原理: 首先看Dubbo日志,截取重要部分: 1)暴露本地服务 Export dubbo service com.alibaba.dubbo.demo.DemoService to ...
- Dubbo服务暴露分析
Dubbo的服务暴露是一个重要的特性,了解其机制很重要.之前有很多人写了有关的源代码分析,在本文中不再重新分析.官方文档中的一篇写的就很好,本文主要是有关内容进行补充与总结. 传送门:服务导出 为什么 ...
- Dubbo之服务暴露
前言 本文 Dubbo 使用版本2.7.5 Dubbo 通过使用dubbo:service配置或@service在解析完配置后进行服务暴露,供服务消费者消费. Dubbo 的服务暴露有两种: 远程暴露 ...
随机推荐
- 求有向图的强连通分量个数 之 Kosaraju算法
代码: #include<cstdio> #include<cstring> #include<iostream> using namespace std; ][] ...
- 1038 一元三次方程求解 2001年NOIP全国联赛提高组
题目描述 Description 有形如:ax3+bx2+cx+d=0 这样的一个一元三次方程.给出该方程中各项的系数(a,b,c,d 均为实数),并约定该方程存在三个不同实根(根的范围在-100 ...
- java自动给版本升级,遇9变0且前面一个版本加1
/** * 自动升级版本号,版本号+1 * @param version * @return */ private String autoUpgradeVersion(String version){ ...
- HQL的内连接查询
/** * HQL的内连接查询 * String hql="from Customer c inner join fetch c.linkmans"; */ @Test publi ...
- 实现DIV层内的文字垂直居中(转)
有时候,为了网页设计的美观,需要把div+css设计的页面里的某些div层里的文字垂直居中,包括多行文字以及单行文字:方法有不少,但真正能实现而代码又简洁的介绍不多,flymorn就为大家介绍几种适用 ...
- THE TOOLS TO MANAGE YOUR DATA ACROSS CLOUDS
http://blog.grexit.com/manage-data-across-clouds/ That the average small business uses a cloud servi ...
- SQL Server DATEDIFF() 函数(SQL计算时间差)
select * from task_list where 1=1 and datediff(dd,carateTime,getdate()) =0 定义和用法 DATED ...
- SourceTree 的初次使用的两个小问题
菜鸟才开始使用SourceTree,出现了两个小问题,特此整理一下,希望对各位新手有帮助.刚开始以为装了SourceTree就不用装git了,其实不然,不装git就会出现下面第一个问题: 1.新手使用 ...
- 多用StringBuilder,少用字符串拼接
在C#中,在处理字符串拼接的时候,使用StringBuilder的效率会比硬拼接字符串高很多.到底有多高,如下: static void Main(string[] args) { string st ...
- Android 性能优化的一些方法
1.采用硬件加速,在androidmanifest.xml中application添加 android:hardwareAccelerated="true".不过这个需要在and ...