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.pswp.cn/news/537005.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/537005.shtml
英文地址,請注明出處:http://en.pswp.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;橘紅色乒乓球從筒道中送出&#…