android组建之间通信_Android组件化(三)组件之间的通信

介绍

在组件化开发的时候,组件之间是相互独立的没有依赖关系,我们不能在使用显示调用来跳转页面了,因为我们组件化的目的之一就是解决模块间的强依赖问题,假如现在要从A业务组件跳转到业务B组件,并且要携带参数跳转,这时候怎么办呢?

上学的时候在书上看到了一句很有意义的话:任何软件工程遇到的问题都可以通过增加一个中间层来解决!

我们从这句话出发去思考:组件之间是平行结构的,它们之间相互没有交集,要实现通信只有添加一个中间层将它们连接到一起,这就是“路由”的概念,由第一篇文章开始的组件化模型下的业务关系图可知路由就是起到一个转发的作用。路由就像一个桥梁一样让平行的河流(组件)之间可以通信和传递数据,这样看起来好像他们之间又是强耦合的关系,维护起来还是代价还有点大,那么还有没有好点的办法呢?那就是路由层使用接口和其他module之间建立弱耦合的关系。

在组件化开发中实现跨组件跳转的库比较有名气的是阿里的ARouter,ARouter的功能很丰富,这一节我们根据ARouter的源码理解自己实现一个简单版的ARouter,ARouter的功能太多了如过滤器,拦截器等,这里只是手写实现一部分核心功能-路由跳转。

准备

实现

定义注解

在router_annotation的module中定义注解Route.java

/**

* Target用于指定被此元注解标注的注解可以标出的程序元素

*

*/

@Target(ElementType.TYPE)

/**

* RetentionPolicy.SOURCE 源码阶段 注解信息只会保留在源码中,编译器在编译源码的时候会将其直接丢弃

* RetentionPolicy.CLASS 编译阶段 javapoet使用 注解信息保留在class文件中,VM不会持有其信息

* RetentionPolicy.RUNTIME 运行阶段,注解信息保留在class文件中,而且VM也会持有此注解信息, 反射获得注解信息

*/

@Retention(RetentionPolicy.CLASS)

public @interface Route {

/**

* 路由的路径,标识一个路由节点

*/

String path();

/**

* 将路由节点进行分组,可以实现按组动态加载

*/

String group() default "";

}

Route注解里有path和group,这是仿照ARouter对路由进行分组。因为当项目变得越来越庞大的时候,为了便于管理和减小首次加载路由表过于耗时的问题,我们对所有的路由进行分组。

定义注解处理器生成路由映射文件

定义RouteProcessor实现AbstractProcessor类, 这里使用google的 AutoService注册处理器,使用JavaPoet实现源文件的编写。在router_compiler的build.gradle引入这两个库

apply plugin: 'java-library'

dependencies {

implementation fileTree(dir: 'libs', include: ['*.jar'])

annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'

compileOnly 'com.google.auto.service:auto-service:1.0-rc4'

implementation 'com.squareup:javapoet:1.11.1'

implementation project(':router_annotation')

}

// java控制台输出中文乱码

tasks.withType(JavaCompile) {

options.encoding = "UTF-8"

}

sourceCompatibility = "7"

targetCompatibility = "7"

Processor 一般会重写父类的4个方法:

init:初始化工作,我们可以得到一些有用的工具,例如 Filer,ElementUtils,TypeUtils.

process:最重要的方法,所有的注解处理都是在此完成

getSupportedAnnotationTypes:返回我们所要处理的注解的一个集合

getSupportedSourceVersion:要支持的java版本

@AutoService(Processor.class)

//处理器接受到的参数 代替 {@link AbstractProcessor#getSupportedOptions()} 函数

@SupportedOptions(Constants.ARGUMENTS_NAME)

/**

* 指定使用的Java版本 替代 {@link AbstractProcessor#getSupportedSourceVersion()} 函数

* 声明我们注解支持的JDK的版本

*/

@SupportedSourceVersion(SourceVersion.RELEASE_7)

/**

* 注册给哪些注解的 替代 {@link AbstractProcessor#getSupportedAnnotationTypes()} 函数

* 声明我们要处理哪一些注解 该方法返回字符串的集合表示该处理器用于处理哪些注解

*/

@SupportedAnnotationTypes(Constants.ANN_TYPE_ROUTE)

public class RouteProcessor extends AbstractProcessor {

....

/**

* key是组名,value是注解标记的element的元素数据集合

* 使用Route的注解按照组名分表存储

*/

private Map> groupMap = new HashMap<>();

/**

* key:组名 value:实现IRouteGroup接口的className

*/

private Map rootMap = new TreeMap<>();

...

}

init方法中根据ProcessingEnvironment得到一些工具和参数

@Override

public synchronized void init(ProcessingEnvironment processingEnvironment) {

super.init(processingEnvironment);

Messager ms = processingEnvironment.getMessager();

Log.init(ms);

elementUtils = processingEnvironment.getElementUtils();

filerUtils = processingEnvironment.getFiler();

typeUtils = processingEnvironment.getTypeUtils();

Map options = processingEnvironment.getOptions();

if (!Utils.isEmpty(options)) {

moduleName = options.get(Constants.ARGUMENTS_NAME);

}

if (Utils.isBlank(moduleName)) {

throw new RuntimeException("Not set Processor Parmaters.");

}

}

扫描所有Route修饰的注解文件,根据组名进行分组

/**

* 处理注解

*

* @param set 使用了支持注解的节点集合

* @param roundEnvironment 上下文

* @return true 表示后续处理器不会再处理

*/

@Override

public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnvironment) {

if (!Utils.isEmpty(set)) {

Set extends Element> elementsAnnotateds = roundEnvironment.getElementsAnnotatedWith(Route.class);

if (!Utils.isEmpty(elementsAnnotateds)) {

try {

parseRoute(elementsAnnotateds);

} catch (Exception e) {

Log.i("创建实现类异常---》"+e);

}

}

}

return true;

}

private void parseRoute(Set extends Element> routeElements) throws Exception {

// 通过elementUtils获得节点

TypeElement activityTypeElement = elementUtils.getTypeElement(Constants.ACTIVITY);

//mirror 节点自描述

TypeMirror activityTypeMirror = activityTypeElement.asType();

TypeElement IServiceTypeElement = elementUtils.getTypeElement(Constants.ISERVICE);

TypeMirror IServiceTypeMirror = IServiceTypeElement.asType();

Log.i("activityTypeMirror=" + activityTypeMirror + ", IServiceTypeMirror=" + IServiceTypeMirror);

RouteMeta routeMeta;

for (Element routeElement : routeElements) {

Log.i("routeElement="+routeElement);

Route route = routeElement.getAnnotation(Route.class);

TypeMirror typeMirror = routeElement.asType();

if (typeUtils.isSubtype(typeMirror, activityTypeMirror)) {//activity节点

routeMeta = new RouteMeta(RouteMeta.Type.ACTIVITY, route, routeElement);

}/* else if (typeUtils.isSubtype(typeMirror, IServiceTypeMirror)) {//IService节点

routeMeta = new RouteMeta(RouteMeta.Type.ISERVICE, route, routeElement);

} **/else {

throw new RuntimeException("[Just Support Activity/IService Route] :" + routeElement);

}

//分组记录信息, groupMap

categories(routeMeta);

}

//生成类 需要实现的接口

generatedGroup();

generatedRoot();

}

每次检索到一个注解元素routeElement都要去groupMap 分类。

private void categories(RouteMeta routeMeta) throws ClassNotFoundException {

if (routeVerify(routeMeta)) {//1

Log.i("group name =" + routeMeta.getGroup() + ", path=" + routeMeta.getPath());

List routeMetas = groupMap.get(routeMeta.getGroup());

if (Utils.isEmpty(routeMetas)) {

routeMetas = new ArrayList<>();

groupMap.put(routeMeta.getGroup(), routeMetas);

}

//2

/* Class> destination = routeMeta.getDestination();

if (destination == null) {

String className = routeMeta.getElement().asType().toString();

Log.i("name="+className);

destination = Class.forName(className);

routeMeta.setDestination(destination);

}*/

routeMetas.add(routeMeta);

} else Log.e("group info error:" + routeMeta.getPath());

}

routeMeta合法后开始根据groupName进行分组归类。

上边的注释2处,我的本意是通过反射给routeMeta.setDestination设置值,在后边生成文件的时候直接通过routeMeta.getsetDestination使用。但是这样做的时候会报错 ClassNotFoundException。一时半会也不知道原因,晚上在看书的时候突然想起来,这是在编译期完成的,反射是在运行期使用,所以会报ClassNotFoundException。

注释1处的是验证routeMeta是否包含group,包含group条件是,注解的时候声明group,或者path的路径必须是// 大于等于两个“/”

private boolean routeVerify(RouteMeta meta) {

String path = meta.getPath();

String group = meta.getGroup();

//路由地址必须是/ 开头

if (Utils.isBlank(path) || !path.startsWith("/")) {

return false;

}

String[] split = path.split("/");

if (split.length < 3) {

return false;

}

//如果没有分组就以第一个/后的节点为分组

if (Utils.isBlank(group)) {

String defaultGroup = split[1];

if (Utils.isBlank(defaultGroup)) {

return false;

}

meta.setGroup(defaultGroup);

}

return true;

}

对RouteMeta进行分组整理后,在parseRoute方法中根据组名生成IRouteGroup的实现类,实现类中接受map参数,将扫描的路由文件存储到map中。IRouteGroup接口的定义如下:

public interface IRouteGroup {

/**

* 扫描注解 搜集路由信息

* @param atlas key:路由路径 path,value:Route注解修饰的路由信息数据

*/

void loadInto(Map atlas);

}

该文件是在route_core的module中声明的。

下边是通过Apt实现该接口的方法。

/**

* 生成IRouteGroup的实现类

* @throws Exception

*/

private void generatedGroup() throws Exception {

//参数类型 Map

ParameterizedTypeName atlas = ParameterizedTypeName.get(

ClassName.get(Map.class),

ClassName.get(String.class),

ClassName.get(RouteMeta.class)

);

//创建参数 Map atlas

ParameterSpec groupParamSpec = ParameterSpec.builder(atlas, "atlas").build();

//遍历分组,每一个分组创建一个类

for (Map.Entry> entry : groupMap.entrySet()) {

//声明方法

// @Override

// public void loadTo(Map atlas)

MethodSpec.Builder loadIntoMethodOfGroupBuilder = MethodSpec.methodBuilder(Constants.METHOD_LOAD_INTO)

.addModifiers(Modifier.PUBLIC)

.addAnnotation(Override.class)

.returns(void.class)

.addParameter(groupParamSpec);

//方法体 实现接口的抽象方法,将分组后的路由信息插入atlas中

//key 是path,value---RouteMeta

String groupName = entry.getKey();

List routeMetas = entry.getValue();

for (RouteMeta routeMeta : routeMetas) {

//atlas.put(path, RouteMeta.build(class, path, group)

// RouteMeta build(Type type, Class> destination, String path, String group)

loadIntoMethodOfGroupBuilder.addStatement("atlas.put($S, $T.build($T.$L,$T.class, $S, $S))",

routeMeta.getPath(),

ClassName.get(RouteMeta.class),

ClassName.get(RouteMeta.Type.class),

routeMeta.getType(),

ClassName.get((TypeElement) routeMeta.getElement()),

routeMeta.getPath().toLowerCase(),

groupName.toLowerCase()

);

}

TypeElement iRouteGroupTypeElement = elementUtils.getTypeElement(Constants.ROUTE_GROUP);

//创建Java文件

String groupClassName = Constants.NAME_OF_GROUP + groupName;

Log.i("groupClassName="+groupClassName);

JavaFile.builder(Constants.PACKAGE_OF_GENERATE_FILE,

TypeSpec.classBuilder(groupClassName)

.addModifiers(Modifier.PUBLIC)

.addSuperinterface(ClassName.get(iRouteGroupTypeElement))

.addMethod(loadIntoMethodOfGroupBuilder.build()).build()

).build().writeTo(filerUtils);

Log.i("Generated RouteGroup: " + Constants.PACKAGE_OF_GENERATE_FILE + "." +

groupClassName);

rootMap.put(groupName, groupClassName);

}

}

在编写的时候这里需要格外小心,出错了很难排查,因为这是编译期不能进行debug,所以只能借助Log在关键的地方加上日志。另外在编写的时候,可以自己先在module2中创建一个实现该接口的类Router$$Group$$test,然后对比着进行编码,最后记得把Router$$Group$$test注释掉,

public class Router$$Group$$test implements IRouteGroup {

private Map> groupMap;

@Override

public void loadInto(Map atlas) {

for (String groupName : groupMap.keySet()) {

List routeMetas = groupMap.get(groupName);

for (RouteMeta routeMeta : routeMetas) {

//RouteMeta build(Type type, Class> destination, String path, String group)

Class> destination = routeMeta.getDestination();

if (destination == null) {

destination = ClassName.get((Type) routeMeta.getElement());

}//

atlas.put(routeMeta.getPath(),

RouteMeta.build(routeMeta.getType(),

routeMeta.getDestination(),

routeMeta.getPath(),

routeMeta.getGroup()));

}

}

}

}

回到parseRoute中下一步是generatedRoot()

private void generatedRoot() throws Exception {

TypeElement iRouteRootTypeElement = elementUtils.getTypeElement(Constants.ROUTE_ROOT);

TypeElement iRouteGroupTypeElement = elementUtils.getTypeElement(Constants.ROUTE_GROUP);

//Map> routes

ParameterizedTypeName routes = ParameterizedTypeName.get(

ClassName.get(Map.class),

ClassName.get(String.class),

ParameterizedTypeName.get(

ClassName.get(Class.class),

WildcardTypeName.subtypeOf(ClassName.get(iRouteGroupTypeElement))

)

);

//参数 Map routes

ParameterSpec rootParameterSpec = ParameterSpec.builder(routes, "routes").build();

//函数 public void loadInto(Map> routes> routes)

MethodSpec.Builder loadIntoMethodBuilder = MethodSpec.methodBuilder(Constants.METHOD_LOAD_INTO)

.addAnnotation(Override.class)

.addModifiers(Modifier.PUBLIC)

.returns(TypeName.VOID)

.addParameter(rootParameterSpec);

//函数体

for (String key : rootMap.keySet()) {

loadIntoMethodBuilder.addStatement("routes.put($S, $T.class)",

key,

ClassName.get(Constants.PACKAGE_OF_GENERATE_FILE, rootMap.get(key)));

}

String rootClassName = Constants.NAME_OF_ROOT + moduleName;

JavaFile.builder(Constants.PACKAGE_OF_GENERATE_FILE,

TypeSpec.classBuilder(rootClassName)

.addSuperinterface(ClassName.get(iRouteRootTypeElement))

.addModifiers(Modifier.PUBLIC)

.addMethod(loadIntoMethodBuilder.build()).build())

.build().writeTo(filerUtils);

Log.i("Generated RouteRoot: " + Constants.PACKAGE_OF_GENERATE_FILE + "." + rootClassName);

}

上边的moduleName是在init方法中初始化的

Map options = processingEnvironment.getOptions();

if (!Utils.isEmpty(options)) {

//Constants.ARGUMENTS_NAME = "moduleName"

moduleName = options.get(Constants.ARGUMENTS_NAME);

}

processingEnvironment能够接收的参数需要在getSupportedOptions方法中定义,也可以用注解的方式定义,这里在定义RouteProcessor的时候使用@SupportedOptions(Constants.ARGUMENTS_NAME)

我们需要在依赖route_core库的 组件的build.gradle的添加这个参数:

javaCompileOptions{

annotationProcessorOptions {

arguments = [moduleName : project.getName()]

}

}

generatedRoot方法根据rootMap来创建一个IRouteRoot接口的实现类,这个接口主要是记录每个组名对应Group类。同样在编码前也是先写一个实现类Router$$Root$$impl,然后对比着进行编码。

public class Router$$Root$$impl implements IRouteRoot {

private Map> map;

@Override

public void loadInto(Map> routes) {

for (String groupName : map.keySet()) {

routes.put(groupName, map.get(groupName));

}

}

}

撰写完成后记得把这个类注释掉。注解和注解处理器我们都写完了,我们写一个demo看看效果,这里举一个setting的demo,目录结构如下:

setting_module.png

编译后生成的文件如下

setting_build.png

public class Router$$Group$$business implements IRouteGroup {

@Override

public void loadInto(Map atlas) {

atlas.put("/business/news", RouteMeta.build(RouteMeta.Type.ACTIVITY,BusinessNewsActivity.class, "/business/news", "business"));

atlas.put("/business/other", RouteMeta.build(RouteMeta.Type.ACTIVITY,BusinessOtherActivity.class, "/business/other", "business"));

}

}

public class Router$$Group$$personal implements IRouteGroup {

@Override

public void loadInto(Map atlas) {

atlas.put("/personal/wallet", RouteMeta.build(RouteMeta.Type.ACTIVITY,MyWalletActivity.class, "/personal/wallet", "personal"));

atlas.put("/personal/userInfo", RouteMeta.build(RouteMeta.Type.ACTIVITY,UserInfoActivity.class, "/personal/userinfo", "personal"));

}

}

public class Router$$Root$$setting implements IRouteRoot {

@Override

public void loadInto(Map> routes) {

routes.put("business", Router$$Group$$business.class);

routes.put("personal", Router$$Group$$personal.class);

}

}

这里看到生成的类分别实现了IRouteRoot和IRouteGroup接口,并且实现了loadInto()方法,而loadInto方法通过传入一个特定类型的map就能把分组信息放入map里,只要分组信息存入到特定的map里后,我们就可以随意的从map里取路由地址对应的Activity.class做跳转使用。

路由框架的初始化

我们要实现一个路由框架,就要考虑在合适的时机拿到这些映射文件中的信息,以供上层业务做跳转使用。那么在什么时机去拿到这些映射文件中的信息呢?首先我们需要在上层业务做路由跳转之前把这些路由映射关系拿到手,但我们不能事先预知上层业务会在什么时候做跳转,那么拿到这些路由关系最好的时机就是应用程序初始化的时候。另外如何去拿这些路由信息呢?在上面已经介绍过IRouteRoot接口的所有实现文件里保存着各个module的分组文件(分组文件就是实现了IRouteGroup接口的类文件),那么只要拿到所有实现IRouteGroup接口的类的集合,就可以根据path实现页面跳转了。

public class MyApplication extends Application {

@Override

public void onCreate() {

super.onCreate();

Router.init(this);

}

}

route_core module中的Router.java

public static void init(Application application){

context = application;

try {

loadInfo();

} catch (Exception e) {

Log.e(TAG, "初始化失败!", e);

e.printStackTrace();

}

}

private static void loadInfo() throws PackageManager.NameNotFoundException, InterruptedException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

//1.扫描apk中的所有dex文件找出使用注解生成的类

Set routeMap = ClassUtils.getFileNameByPackageName(context, Router.ROUTE_ROOT_PACKAGE);

if (routeMap == null) {

return;

}

for (String className : routeMap) {

if (className.startsWith(ROUTE_ROOT_PACKAGE+"."+SDK_NAME+SEPARATOR+SUFFIX_ROOT)) {

// root中注册的分组信息,将分组信息加入仓库中

((IRouteRoot)(Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);

}

}

}

我们首先通过ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE)得到apt生成的所有实现IRouteRoot接口的类文件集合,通过上面的讲解我们知道,拿到这些类文件便可以得到所有的路由地址和Activity映射关系。

路由跳转实现

经过前面的介绍,我们已经能够在app启动的时候获得所有的路由信息,接下来就可以实现跳转了。

在app module的Mainctiviy中如下使用:

public void personalInfoJump(View view) {

Router.getInstance().build("/personal/userInfo").navigation();

}

public void businessNewsJump(View view) {

Router.getInstance().build("/business/news").navigation();

}

build的时候根据path路径得到一个postCard对象,然后调用Postcard的navigation()方法完成跳转。Postcard的内容如下:

public class Postcard extends RouteMeta {

private Bundle extras;

private int flag = -1;

private Bundle optionCompat;

private int enterAnim, exitAnim;

...

public Object navigation() {

return navigation(null, null);

}

public Object navigation(Context context) {

return navigation(context, null);

}

public Object navigation(Context context, NavigationCallback callback) {

return Router.getInstance().navigation(context, this, -1, callback);

}

...

}

下面看一下Router 的navigation核心功能:

public Object navigation(Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback){

prepareCard(postcard);

if (callback != null) {

callback.onFound(postcard);

}

switch (postcard.getType()) {

case ACTIVITY:

final Context currentContext = context==null?Router.context:context;

final Intent intent = new Intent(currentContext, postcard.getDestination());

intent.putExtras(postcard.getExtras());

if (postcard.getFlag()!=-1) {

intent.setFlags(postcard.getFlag());

}else if (!(currentContext instanceof Activity)){

intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

}

new Handler(Looper.getMainLooper()).post(new Runnable() {

@Override

public void run() {

if (requestCode>0) {

ActivityCompat.startActivityForResult((Activity) currentContext, intent,

requestCode, postcard.getOptionCompat());

}else {

ActivityCompat.startActivity(currentContext, intent, postcard.getOptionCompat());

}

if ((postcard.getEnterAnim()!=0||postcard.getExitAnim()!=0) && currentContext instanceof Activity) {

((Activity)currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());

}

if (callback != null) {

callback.onArrival(postcard);

}

}

});

break;

}

return null;

}

prepareCard的实现如下:

private void prepareCard(Postcard card){

RouteMeta routeMeta = Warehouse.routes.get(card.getPath());

if (routeMeta == null) {

Class extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(card.getGroup());

if (groupMeta == null) {

throw new NoRouteFoundException("没找到对应路由: " + card.getGroup() + " " +

card.getPath());

}

IRouteGroup iRouteGroup;

try {

iRouteGroup = groupMeta.getConstructor().newInstance();

} catch (Exception e) {

throw new RuntimeException("路由分组映射表记录失败.", e);

}

iRouteGroup.loadInto(Warehouse.routes);

Warehouse.groupsIndex.remove(card.getGroup());

prepareCard(card);

}else {

card.setDestination(routeMeta.getDestination());

card.setType(routeMeta.getType());

}

}

这段代码Warehouse.routes.get(card.getPath())通过path拿到对应的RouteMeta,这个RouteMeta里面保存了activityClass等信息。继续往下看,如果判断拿到的RouteMeta是空,说明这个路由地址还没有加载到map里面(初始化时为了节省性能,只会加载所有的分组信息,而每个分组下的路由映射关系,会使用懒加载,在首次用到的时候去加载),只有在第一次用到当前路由地址的时候,会去Warehouse.routes里面拿routeMeta,如果拿到的是空,会根据当前路由地址的group拿到对应的分组,通过反射创建实例,然后调用实例的loadInto方法,把它里面保存的映射信息添加到Warehouse.routes里面,并且再次调用prepareCard(card),这时再通过Warehouse.routes.get(card.getPath())就可以顺利拿到RouteMeta了。进入else{}里面,调用了card.setDestination(routeMeta.getDestination()),这个setDestination就是将RouteMeta里面保存的activityClass放入Postcard里面。

prepareCard()方法调用完成后,我们的postcard里面就保存了activityClass,然后switch (postcard.getType()){}会判断postcard的type为ACTIVITY,然后通过ActivityCompat.startActivity启动Activity。到这里,路由跳转的实现已经讲解完毕了。

结束语

关于组件之间传递数据,可以参考ARoute添加Extra 注解,ExtraProcessor处理器以及IExtra接口。

通过手写ARoute我们会学到注解,注解处理器,JavaPoet和组件化思路,编写框架的思路等等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/537005.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

继牛津大学后,加大伯克利分校等多家美国高校终止与华为合作

文&#xff0f;AI财经社 唐煜编&#xff0f;嵇国华据 Nature News 报道&#xff0c;在美国相关部门的压力之下&#xff0c;加州大学伯克利分校&#xff08;UC Berkeley&#xff09;近日宣布不再与华为签署新的研究合作&#xff1b;德州大学奥斯丁分校也正在审查自身与华为的关系…

为什么varchar字段长度最好是2的n次方-1

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 计算机是二进制计算的&#xff0c;1 bytes 8 bit ,一个字节最多可以代表的数据长度是2的8次方 11111111 在计算机中也就是-128到127。 而var…

运筹学状态转移方程例子_强化学习第4期:H-J-B方程

在上一篇文章中&#xff0c;我们介绍了一种最简单的MDP——s与a都是有限的MDP的求解方法。其中&#xff0c;我们用到了动态规划的思想&#xff0c;并且推出了“策略迭代”、“值迭代”这样的方法。今天&#xff0c;我们要来讲更加一般的最优控制问题——t、a与s都是连续的问题。…

Python之celery的简介与使用

celery的简介 celery是一个基于分布式消息传输的异步任务队列&#xff0c;它专注于实时处理&#xff0c;同时也支持任务调度。它的执行单元为任务&#xff08;task&#xff09;&#xff0c;利用多线程&#xff0c;如Eventlet&#xff0c;gevent等&#xff0c;它们能被并发地执行…

不使用比较运算符如何比较两个数的大小

分享一波:程序员赚外快-必看的巅峰干货 前言 今天在水群的过程中看到有位群员谈论到这个话题&#xff0c;是他找工作过程中某家公司的面试题&#xff08;到底是哪家公司才会出这种没营养的题目刁难别人&#xff09;&#xff0c;有点兴趣&#xff0c;就开始写了。 开搞 想了一…

java占位符填充_Java使用freemark生成word

1、制作模板先用office word做一个模板word文档&#xff0c;${usrName}、${nowDate}占位符 可以使用 office 或者 wps 先创建一个模板表格 &#xff08;替换$部分可以在 模板格式改变之后 在替换xml 格式改了后有些原本的字符会分开&#xff09;2、用office word将模板word另存…

Java中如何使用非阻塞异步编程——CompletableFuture

分享一波:程序员赚外快-必看的巅峰干货 对于Node开发者来说&#xff0c;非阻塞异步编程是他们引以为傲的地方。而在JDK8中&#xff0c;也引入了非阻塞异步编程的概念。所谓非阻塞异步编程&#xff0c;就是一种不需要等待返回结果的多线程的回调方法的封装。使用非阻塞异步编程…

城市运行一网统管_【宣传活动】持续开展城市运行“一网统管”建设宣传活动...

为进一步推进本镇城市运行“一网统管”建设工作&#xff0c;提高城市治理能力和治理水平&#xff0c;提升社会各界的知晓度和参与度&#xff0c;激发职能部门人员、党员、群众参与“一网统管”工作的热情。9月10日&#xff0c;镇网格中心于福泉居委会议室开展“推进城市运行‘一…

Java如何只使用位运算实现加减乘除

分享一波:程序员赚外快-必看的巅峰干货 前言 接前面一篇博客&#xff0c;这又是某个公司的奇葩面试题&#xff08;都说了到底是哪家公司才会出这种没营养的面试题&#xff09;。不过吐槽归吐槽&#xff0c;这个题目还是有点学问的&#xff0c;比前面那个 不使用比较运算符如何…

Netweaver里某个software component和C4C的版本

有同事问如何通过代码的方式获得Netweaver里某个Software component的版本信息&#xff0c;以及Cloud for Customer&#xff08;C4C&#xff09;的版本信息。 Netweaver 点了Detail按钮后&#xff1a; 这些版本信息存在表CVERS里&#xff1a; C4C C4C的版本号在Help->About …

pmc订单表格_复工了,读一则“如何提升订单准交率和生产效率”的真实故事

故事发生在中国南方小镇上一个做办公家具的公司……家具公司创建于1995年&#xff0c;是一家集研发、生产、销售、服务为一体的现代办公家具、酒店家具制造企业。主要产品有实木班台系列、会议台系列、职员桌系列、屏风系列、沙发系列、办公座椅、酒店家具系列。在省外还有两个…

GET和POST请求到底有什么区别?

分享一波:程序员赚外快-必看的巅峰干货 看到这个标题&#xff0c;想必大部分人都已经想关掉这篇博客了。先别急&#xff0c;你真的知道这两个的区别吗&#xff1f; 做过WEB开发的朋友可能很熟悉&#xff0c;看到这个问题能立马脱口而出二者的区别。 GET在浏览器回退时是无害的…

有赞电商云应用框架设计

背景 有赞是 SaaS 公司&#xff0c;向商家提供了全方位的软件服务&#xff0c;支撑商家进行采购、店铺、商品、营销、订单、物流等等管理服务。 在这个软件服务里&#xff0c;能够满足大部分的商家&#xff0c;为商家保驾护航。 但是很多大商家往往会有自己的特殊需求&#xff…

vivado 如何创建工程模式_基于Vivado的FPGA高性能开发研修班2019年8月30日上海举行...

一、课程介绍&#xff1a;从7系列FPGA开始&#xff0c;Xilinx提出了Vivado Design Suite设计软件&#xff0c;提供全新构建的SoC 增强型、以 IP 和系统为中心的下一代开发环境&#xff0c;以解决系统级集成和实现的生产力瓶颈。同时&#xff0c;Xilinx专门针对Vivado推出了Ultr…

程序员的自我修养——远离“外包思维”

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 在我们做开发的日子里&#xff0c;不免会进行跳槽&#xff0c;跳来跳去公司无非就分成两大类——互联网公司、外包公司。当然我们本次讨论的并…

英特尔为 Kubernetes 推出分布式深度学习平台:Nauta

2019独角兽企业重金招聘Python工程师标准>>> 随着人工智能的发展&#xff0c;深度学习的价值不断增长&#xff0c;但实现它可能是一个复杂耗时的过程。英特尔(Intel)正寻求通过其在 Kubernetes 进行分布式深度学习的新开源平台来改变这一状况&#xff0c;该深度学习…

pytorch梯度下降函数_Pytorch中常用的四种优化器SGD、Momentum、RMSProp、Adam

来源&#xff1a;AINLPer微信公众号编辑: ShuYini校稿: ShuYini时间: 2019-8-16 引言很多人在使用pytorch的时候都会遇到优化器选择的问题&#xff0c;今天就给大家介绍对比一下pytorch中常用的四种优化器。SGD、Momentum、RMSProp、Adam。随机梯度下降法&#xff08;SGD&#…

2019/02/11-分布式数据库概述

分布式数据库类型&#xff08;1&#xff09;同构同质型&#xff1a;各场地都是同一种类型的数据库&#xff0c;如都是关系型数据库&#xff0c;且都是同一型号的数据库管理系统&#xff08;2&#xff09;同构异质型&#xff1a;各场地是同一种类型的数据库&#xff0c;但是数据…

python计算无穷级数求和常用公式_傅里叶变换(二) 从傅里叶级数到傅里叶变换...

在上一部分当中&#xff0c;得到了利用三角函数表示周期函数的方法&#xff0c;但是对于非周期函数就...凉了。所以有什么办法吗&#xff1f;没办法&#xff08;划掉&#xff09;。这时候我们就需要拿出来我们的黑科技——傅里叶变换。一、傅里叶级数的推广当然这东西肯定不是凭…

中鸣投篮机器人怎么组装_1000余人参加洛阳市青少年机器人竞赛

机器人智能识别地面上的黑色线条&#xff0c;并沿着线条来到指定位置&#xff0c;放下“快递包裹”&#xff1b;无人机在空中飞舞&#xff0c;时而钻过圆环&#xff0c;时而来个空翻&#xff0c;犹如跳芭蕾般在空中划过一道优美曲线&#xff1b;橘红色乒乓球从筒道中送出&#…