当前位置:网站首页>Source code analysis of activityrouter
Source code analysis of activityrouter
2022-07-28 09:39:00 【Yu Qirong】
ActivityRouter :https://github.com/mzule/ActivityRouter
Header
In today's Android Component based development , A good routing framework is indispensable . For example, Alibaba's ARouter 、 US mission WMRouter etc. . The routing framework can reduce Activity The coupling between , Thus, there is no need to care about the goal Activity Implementation class of , Use the protocol to complete the jump .
ActivityRouter Usage method
stay AndroidManifest.xml To configure
<activity
android:name="com.github.mzule.activityrouter.router.RouterActivity"
android:theme="@android:style/Theme.NoDisplay">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="mzule" /><!-- Change to your own scheme-->
</intent-filter>
</activity>Where configuration is required Activity Add comment on
@Router("main")
public class MainActivity extends Activity {
...
}Want to jump to MainActivity , Just call the following code
Routers.open(context, "mzule://main")If you want to use @Router To call methods
@Router("logout")
public static void logout(Context context, Bundle bundle) {
Toast.makeText(context, "logout", Toast.LENGTH_SHORT).show();
}The source code parsing
ActivityRouter The structure of the project is as follows
20181216160032.png
- activityrouter: The specific implementation code of route jump
- annotaition: Route annotation
- app: route demo
- app_module: route demo module
- compiler: Annotation Processing
- stub: shell module
annotation
First look at it. Router Annotations
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.CLASS)
public @interface Router {
String[] value();
String[] stringParams() default "";
String[] intParams() default "";
String[] longParams() default "";
String[] booleanParams() default "";
String[] shortParams() default "";
String[] floatParams() default "";
String[] doubleParams() default "";
String[] byteParams() default "";
String[] charParams() default "";
String[] transfer() default "";
}@Router Defines the Activity The name of the route and some parameters , It can be noted here that @Retention yes CLASS , So it must be used later during compilation Processor Parsing @Router Generate routing table .
in addition , notice @Target yes ElementType.TYPE and ElementType.METHOD , Actually @Router Except jump Activity outside , Another function is to execute methods , Just add @Router that will do .
The source code of routing table generation will be discussed later , Let's first look at the agreement ,Routers How to realize jump Activity Of .
activityrouter
public class Routers {
...
public static boolean open(Context context, String url) {
return open(context, Uri.parse(url));
}
public static boolean open(Context context, String url, RouterCallback callback) {
return open(context, Uri.parse(url), callback);
}
public static boolean open(Context context, Uri uri) {
return open(context, uri, getGlobalCallback(context));
}
public static boolean open(Context context, Uri uri, RouterCallback callback) {
return open(context, uri, -1, callback);
}
public static boolean openForResult(Activity activity, String url, int requestCode) {
return openForResult(activity, Uri.parse(url), requestCode);
}
public static boolean openForResult(Activity activity, String url, int requestCode, RouterCallback callback) {
return openForResult(activity, Uri.parse(url), requestCode, callback);
}
public static boolean openForResult(Activity activity, Uri uri, int requestCode) {
return openForResult(activity, uri, requestCode, getGlobalCallback(activity));
}
public static boolean openForResult(Activity activity, Uri uri, int requestCode, RouterCallback callback) {
return open(activity, uri, requestCode, callback);
}
...
} You can see different open openForResult Method overloading , Finally, it calls open(Context context, Uri uri, int requestCode, RouterCallback callback) . Then follow up :
private static boolean open(Context context, Uri uri, int requestCode, RouterCallback callback) {
boolean success = false;
// If there is callback Callback before jump
if (callback != null) {
if (callback.beforeOpen(context, uri)) {
return false;
}
}
// Perform route jump
try {
success = doOpen(context, uri, requestCode);
} catch (Throwable e) {
e.printStackTrace();
if (callback != null) {
// Wrong callback
callback.error(context, uri, e);
}
}
// Success or failure callback
if (callback != null) {
if (success) {
callback.afterOpen(context, uri);
} else {
callback.notFound(context, uri);
}
}
return success;
}open Many of the methods are in different states callback The callback , The logic of the real jump is doOpen In the method .
private static boolean doOpen(Context context, Uri uri, int requestCode) {
// If not initialized , call Router.init Initialize the routing table
initIfNeed();
// analysis uri Get the corresponding path
Path path = Path.create(uri);
// according to path To find the corresponding matching mapping , And then jump
for (Mapping mapping : mappings) {
if (mapping.match(path)) {
// If activity It's empty. , It means that the method is executed
if (mapping.getActivity() == null) {
mapping.getMethod().invoke(context, mapping.parseExtras(uri));
return true;
}
// Otherwise, it's using intent To jump activity
Intent intent = new Intent(context, mapping.getActivity());
intent.putExtras(mapping.parseExtras(uri));
intent.putExtra(KEY_RAW_URL, uri.toString());
if (!(context instanceof Activity)) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
if (requestCode >= 0) {
if (context instanceof Activity) {
((Activity) context).startActivityForResult(intent, requestCode);
} else {
throw new RuntimeException("can not startActivityForResult context " + context);
}
} else {
context.startActivity(intent);
}
return true;
}
}
return false;
} Let's analyze... Step by step doOpen The specific steps in . First from Path path = Path.create(uri); Start looking at .
public static Path create(Uri uri) {
Path path = new Path(uri.getScheme().concat("://"));
String urlPath = uri.getPath();
if (urlPath == null) {
urlPath = "";
}
if (urlPath.endsWith("/")) {
urlPath = urlPath.substring(0, urlPath.length() - 1);
}
parse(path, uri.getHost() + urlPath);
return path;
}
private static void parse(Path scheme, String s) {
String[] components = s.split("/");
Path curPath = scheme;
for (String component : components) {
Path temp = new Path(component);
curPath.next = temp;
curPath = temp;
}
}After reading the above code, some students may feel very confused , Simply explain . The main thing that the above code does is to pass in uri analysis , It generates a Path object . The Path Objects mainly contain uri Medium scheme 、host 、path These three parts , Use the characteristics of single linked list to connect these three parts . This Path It is used to match the routing table later .
Maybe some students are right uri Of scheme 、 host Wait and don't understand , Simply popularize it here .
For example, now there is a uri
mzule://main/home/login?username=tomThis uri It can be broken down into
scheme :mzule , Namely “://” The preceding string host :main ,“://” String after path :home and login All belong to path, Namely “/” And “/” String between query : Parameters , It can be understood as a key value pair , Use... Between multiple & Connect . obtain username This parameter , The corresponding value is tom
Generate good Path after , Just traverse the routing table to match .
The so-called routing table is actually a List
private static List<Mapping> mappings = new ArrayList<>();Calling RouterInit.init The routing data will be added to List in . Accurately speaking , RouterInit.init Called in Router.map Method to implement the added .
static void map(String format, Class<? extends Activity> activity, MethodInvoker method, ExtraTypes extraTypes) {
mappings.add(new Mapping(format, activity, method, extraTypes));
}that , Let's see Mapping Structure
public class Mapping {
private final String format;
private final Class<? extends Activity> activity;
private final MethodInvoker method;
private final ExtraTypes extraTypes;
private Path formatPath;
...
}- format It's what we introduced uri
- activity It is the corresponding route activity
- method Indicates whether it is an execution method
- extraTypes Is the parameter type carried
- formatPath Namely uri Corresponding Path
Concrete Mapping Initialization is in Processor Completed in the generated code , Let's talk later .
Looking back doOpen Method , stay mapping.match(path) Method is used to judge the path Is there a route matching in the route table
public boolean match(Path fullLink) {
if (formatPath.isHttp()) {
return Path.match(formatPath, fullLink);
} else {
// fullLink without host
boolean match = Path.match(formatPath.next(), fullLink.next());
if (!match && fullLink.next() != null) {
// fullLink with host
match = Path.match(formatPath.next(), fullLink.next().next());
}
return match;
}
}Mapping Of match The way is to put your own formatPath and fullLink Compare , The final call is Path.match Method , The essence is to Path Compare each item in the linked list , To judge two Path Whether it is equal or not . I won't show the specific source code here , Interested students can go back and see by themselves .
Then there is judgment activity , If it's empty , It is considered as the implementation method , Otherwise, we will construct Intent To make a jump , recycling requestCode To judge is startActivity still startActivityForResult . The execution method mainly calls MethodInvoker.invoke Method
public interface MethodInvoker {
void invoke(Context context, Bundle bundle);
}Pay more attention to mapping.parseExtras(uri) This code . The main thing to do here is to construct Bundle Pass in uri Parameters of .
public Bundle parseExtras(Uri uri) {
Bundle bundle = new Bundle();
// path segments // ignore scheme
Path p = formatPath.next();
Path y = Path.create(uri).next();
while (p != null) {
// Whether it is path Middle pass parameter
if (p.isArgument()) {
put(bundle, p.argument(), y.value());
}
p = p.next();
y = y.next();
}
// analysis uri Parameters in , Put in bundle in
Set<String> names = UriCompact.getQueryParameterNames(uri);
for (String name : names) {
String value = uri.getQueryParameter(name);
put(bundle, name, value);
}
return bundle;
}
// The main thing this method does is to judge the parameter type according to the parameter name
private void put(Bundle bundle, String name, String value) {
int type = extraTypes.getType(name);
name = extraTypes.transfer(name);
if (type == ExtraTypes.STRING) {
type = extraTypes.getType(name);
}
switch (type) {
case ExtraTypes.INT:
bundle.putInt(name, Integer.parseInt(value));
break;
case ExtraTypes.LONG:
bundle.putLong(name, Long.parseLong(value));
break;
case ExtraTypes.BOOL:
bundle.putBoolean(name, Boolean.parseBoolean(value));
break;
case ExtraTypes.SHORT:
bundle.putShort(name, Short.parseShort(value));
break;
case ExtraTypes.FLOAT:
bundle.putFloat(name, Float.parseFloat(value));
break;
case ExtraTypes.DOUBLE:
bundle.putDouble(name, Double.parseDouble(value));
break;
case ExtraTypes.BYTE:
bundle.putByte(name, Byte.parseByte(value));
break;
case ExtraTypes.CHAR:
bundle.putChar(name, value.charAt(0));
break;
default:
bundle.putString(name, value);
break;
}
}The code is simple , Basically, they are annotated , I believe everyone can understand , Don't talk .
Come here , Whole ActivityRouter That's the end of the process .
The rest , Namely Processor Parsing annotations generates code .
compiler
First tell the comments supported by the processor
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> ret = new HashSet<>();
ret.add(Modules.class.getCanonicalName());
ret.add(Module.class.getCanonicalName());
ret.add(Router.class.getCanonicalName());
return ret;
}The rest mainly depends on RouterProcessor Of process Method .
The code of the method is as follows :
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
debug("process apt with " + annotations.toString());
if (annotations.isEmpty()) {
return false;
}
boolean hasModule = false;
boolean hasModules = false;
// module
String moduleName = "RouterMapping";
Set<? extends Element> moduleList = roundEnv.getElementsAnnotatedWith(Module.class);
if (moduleList != null && moduleList.size() > 0) {
Module annotation = moduleList.iterator().next().getAnnotation(Module.class);
moduleName = moduleName + "_" + annotation.value();
hasModule = true;
}
// modules
String[] moduleNames = null;
Set<? extends Element> modulesList = roundEnv.getElementsAnnotatedWith(Modules.class);
if (modulesList != null && modulesList.size() > 0) {
Element modules = modulesList.iterator().next();
moduleNames = modules.getAnnotation(Modules.class).value();
hasModules = true;
}
// RouterInit
if (hasModules) {
debug("generate modules RouterInit");
generateModulesRouterInit(moduleNames);
} else if (!hasModule) {
debug("generate default RouterInit");
generateDefaultRouterInit();
}
// RouterMapping
return handleRouter(moduleName, roundEnv);
}process The logic in the method can be divided into three parts :
- To determine if there is @module and @modules , That is, whether it is component-based development
- Generate RouterInit
- Generate RouterMapping
Let's analyze it slowly , Let's look at the first part
// module
String moduleName = "RouterMapping";
Set<? extends Element> moduleList = roundEnv.getElementsAnnotatedWith(Module.class);
if (moduleList != null && moduleList.size() > 0) {
Module annotation = moduleList.iterator().next().getAnnotation(Module.class);
// If it is more module Component development , Every module Label required @module , So each of them module Will generate their own RouterMapping , Prevent duplication
// such as @Module("abc") moduleName Namely RouterMapping_abc
moduleName = moduleName + "_" + annotation.value();
hasModule = true;
}
// @Modules The function of is to put the above generated RouterMapping Put it all together , Unified to RouterInit Inside , So just call RouterInit.init Method completes the route initialization of each module
String[] moduleNames = null;
Set<? extends Element> modulesList = roundEnv.getElementsAnnotatedWith(Modules.class);
if (modulesList != null && modulesList.size() > 0) {
Element modules = modulesList.iterator().next();
// such as @Modules("abc","def") , moduleNames Namely [“abc”, "def"]
moduleNames = modules.getAnnotation(Modules.class).value();
hasModules = true;
}Next is generation RouterInit class
if (hasModules) {
debug("generate modules RouterInit");
generateModulesRouterInit(moduleNames);
} else if (!hasModule) {
debug("generate default RouterInit");
generateDefaultRouterInit();
}If it is more module Component development , Will eventually call generateModulesRouterInit , Otherwise, what is called is the default generateDefaultRouterInit .
Here we will see generateModulesRouterInit The code of .
private void generateModulesRouterInit(String[] moduleNames) {
MethodSpec.Builder initMethod = MethodSpec.methodBuilder("init")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC);
for (String module : moduleNames) {
initMethod.addStatement("RouterMapping_" + module + ".map()");
}
TypeSpec routerInit = TypeSpec.classBuilder("RouterInit")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(initMethod.build())
.build();
try {
JavaFile.builder("com.github.mzule.activityrouter.router", routerInit)
.build()
.writeTo(filer);
} catch (Exception e) {
e.printStackTrace();
}
}You can see , Take advantage of javapoet To generate java Code , The code is simple , There is no need to talk more , Let's take a look at the final generation RouterInit Class code
package com.github.mzule.activityrouter.router;
public final class RouterInit {
public static final void init() {
RouterMapping_app.map();
RouterMapping_sdk.map();
}
}RouterInit After generation , The final job is to generate the corresponding RouterMapping_app and RouterMapping_sdk These two classes .
The generated entry is handleRouter(moduleName, roundEnv) Method .
private boolean handleRouter(String genClassName, RoundEnvironment roundEnv) {
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Router.class);
// Define methods public static final void map()
MethodSpec.Builder mapMethod = MethodSpec.methodBuilder("map")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC)
.addStatement("java.util.Map<String,String> transfer = null")
.addStatement("com.github.mzule.activityrouter.router.ExtraTypes extraTypes")
.addCode("\n");
// Traverse @Router Embellished element
for (Element element : elements) {
Router router = element.getAnnotation(Router.class);
// Judge @Router Is there a transfer
String[] transfer = router.transfer();
if (transfer.length > 0 && !"".equals(transfer[0])) {
mapMethod.addStatement("transfer = new java.util.HashMap<String, String>()");
for (String s : transfer) {
String[] components = s.split("=>");
if (components.length != 2) {
error("transfer `" + s + "` not match a=>b format");
break;
}
mapMethod.addStatement("transfer.put($S, $S)", components[0], components[1]);
}
} else {
mapMethod.addStatement("transfer = null");
}
// Resolve the routing parameter type
mapMethod.addStatement("extraTypes = new com.github.mzule.activityrouter.router.ExtraTypes()");
mapMethod.addStatement("extraTypes.setTransfer(transfer)");
addStatement(mapMethod, int.class, router.intParams());
addStatement(mapMethod, long.class, router.longParams());
addStatement(mapMethod, boolean.class, router.booleanParams());
addStatement(mapMethod, short.class, router.shortParams());
addStatement(mapMethod, float.class, router.floatParams());
addStatement(mapMethod, double.class, router.doubleParams());
addStatement(mapMethod, byte.class, router.byteParams());
addStatement(mapMethod, char.class, router.charParams());
// Traverse @Router Generate resolution codes for all routes
for (String format : router.value()) {
ClassName className;
Name methodName = null;
if (element.getKind() == ElementKind.CLASS) {
className = ClassName.get((TypeElement) element);
} else if (element.getKind() == ElementKind.METHOD) {
className = ClassName.get((TypeElement) element.getEnclosingElement());
methodName = element.getSimpleName();
} else {
throw new IllegalArgumentException("unknow type");
}
if (format.startsWith("/")) {
error("Router#value can not start with '/'. at [" + className + "]@Router(\"" + format + "\")");
return false;
}
if (format.endsWith("/")) {
error("Router#value can not end with '/'. at [" + className + "]@Router(\"" + format + "\")");
return false;
}
// If @Router It's a modifier class Is the route jump
if (element.getKind() == ElementKind.CLASS) {
mapMethod.addStatement("com.github.mzule.activityrouter.router.Routers.map($S, $T.class, null, extraTypes)", format, className);
} else {
// Otherwise, it is the route calling method , The third parameter is passed in MethodInvoker object
mapMethod.addStatement("com.github.mzule.activityrouter.router.Routers.map($S, null, " +
"new MethodInvoker() {\n" +
" public void invoke(android.content.Context context, android.os.Bundle bundle) {\n" +
" $T.$N(context, bundle);\n" +
" }\n" +
"}, " +
"extraTypes)", format, className, methodName);
}
}
mapMethod.addCode("\n");
}
TypeSpec routerMapping = TypeSpec.classBuilder(genClassName)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(mapMethod.build())
.build();
// Generate RouterMapping_xxx class
try {
JavaFile.builder("com.github.mzule.activityrouter.router", routerMapping)
.build()
.writeTo(filer);
} catch (Throwable e) {
e.printStackTrace();
}
return true;
}
// Generate extraTypes Parameter type setting code
// such as
// extraTypes.setLongExtra("id,updateTime".split(","));
// extraTypes.setBooleanExtra("web".split(","));
private void addStatement(MethodSpec.Builder mapMethod, Class typeClz, String[] args) {
String extras = join(args);
if (extras.length() > 0) {
String typeName = typeClz.getSimpleName();
String s = typeName.substring(0, 1).toUpperCase() + typeName.replaceFirst("\\w", "");
mapMethod.addStatement("extraTypes.set" + s + "Extra($S.split(\",\"))", extras);
}
}Take a look at the last generated RouterMapping_xxx Code for :
public final class RouterMapping_app {
public static final void map() {
java.util.Map<String,String> transfer = null;
com.github.mzule.activityrouter.router.ExtraTypes extraTypes;
transfer = null;
extraTypes = new com.github.mzule.activityrouter.router.ExtraTypes();
extraTypes.setTransfer(transfer);
com.github.mzule.activityrouter.router.Routers.map("user/:userId", UserActivity.class, null, extraTypes);
com.github.mzule.activityrouter.router.Routers.map("user/:nickname/city/:city/gender/:gender/age/:age", UserActivity.class, null, extraTypes);
transfer = new java.util.HashMap<String, String>();
transfer.put("web", "fromWeb");
extraTypes = new com.github.mzule.activityrouter.router.ExtraTypes();
extraTypes.setTransfer(transfer);
extraTypes.setLongExtra("id,updateTime".split(","));
extraTypes.setBooleanExtra("web".split(","));
com.github.mzule.activityrouter.router.Routers.map("http://mzule.com/main", MainActivity.class, null, extraTypes);
com.github.mzule.activityrouter.router.Routers.map("main", MainActivity.class, null, extraTypes);
com.github.mzule.activityrouter.router.Routers.map("home", MainActivity.class, null, extraTypes);
transfer = null;
extraTypes = new com.github.mzule.activityrouter.router.ExtraTypes();
extraTypes.setTransfer(transfer);
com.github.mzule.activityrouter.router.Routers.map("with_host", HostActivity.class, null, extraTypes);
transfer = null;
extraTypes = new com.github.mzule.activityrouter.router.ExtraTypes();
extraTypes.setTransfer(transfer);
com.github.mzule.activityrouter.router.Routers.map("home/:homeName", HomeActivity.class, null, extraTypes);
transfer = null;
extraTypes = new com.github.mzule.activityrouter.router.ExtraTypes();
extraTypes.setTransfer(transfer);
com.github.mzule.activityrouter.router.Routers.map("logout", null, new MethodInvoker() {
public void invoke(android.content.Context context, android.os.Bundle bundle) {
NonUIActions.logout(context, bundle);
}
}, extraTypes);
transfer = null;
extraTypes = new com.github.mzule.activityrouter.router.ExtraTypes();
extraTypes.setTransfer(transfer);
com.github.mzule.activityrouter.router.Routers.map("upload", null, new MethodInvoker() {
public void invoke(android.content.Context context, android.os.Bundle bundle) {
NonUIActions.uploadLog(context, bundle);
}
}, extraTypes);
transfer = null;
extraTypes = new com.github.mzule.activityrouter.router.ExtraTypes();
extraTypes.setTransfer(transfer);
com.github.mzule.activityrouter.router.Routers.map("user/collection", UserCollectionActivity.class, null, extraTypes);
}
}thus ,ActivityRouter All the processes have been finished !!!
RouterActivity
Yes , And a little bit more ,ActivityRouter Support external arousal Activity .
stay AndroidManifest.xml In a statement RouterActivity , Fill in corresponding scheme and host .
<activity
android:name="com.github.mzule.activityrouter.router.RouterActivity"
android:theme="@android:style/Theme.NoDisplay">
...
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" android:host="mzule.com" />
</intent-filter>
</activity>In fact, the first thing to arouse is RouterActivity , And then in RouterActivity According to the uri Then jump to the corresponding Activity , This can be done from RouterActivity The code of .
public class RouterActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
RouterCallback callback = getRouterCallback();
Uri uri = getIntent().getData();
if (uri != null) {
Routers.open(this, uri, callback);
}
finish();
}
private RouterCallback getRouterCallback() {
if (getApplication() instanceof RouterCallbackProvider) {
return ((RouterCallbackProvider) getApplication()).provideRouterCallback();
}
return null;
}
}This is really the end of the story
That's it
It's over
La
Footer
In fact, the current routing framework is basically this routine , Knowing the profound meaning of it can make better use of it .
Interested students can go and have a look again ARouter Source code like that , I believe the harvest will be greater !
bye
边栏推荐
- 7 C control statements: branches and jumps
- 【一花一世界-郑一教授-繁简之道】可解释神经网络
- How does gbase 8A use preprocessing to quickly insert data?
- 21 day learning challenge - "AUTOSAR from introduction to mastery - practical part"
- go语言切片Slice和数组Array对比panic runtime error index out of range问题解决
- Window source code analysis (III): window update mechanism
- Retrofit源码解析
- Window source code analysis (I): things with decorview
- Window源码解析(三):Window的更新机制
- Common tool functions are constantly updated
猜你喜欢

IDC script file running

2022 safety officer-c certificate special operation certificate examination question bank and answers

Talk to the father of MySQL: code completion at one time is a good programmer

go语言切片Slice和数组Array对比panic runtime error index out of range问题解决

咸鱼ESP32实例—MQTT 点亮LED

脉冲风采|Committer 专访——腾讯工程师张大伟喊你吃“螃蟹”啦

Oracle creates users with query permission only

【一花一世界-郑一教授-繁简之道】可解释神经网络

Conditions and procedures of stock index futures account opening

QT basic hand training applet - simple calculator design (with source code, analysis)
随机推荐
Alibaba cloud server setup and pagoda panel connection
Personal blog applet
[solution] error in [eslint] eslint is not a constructor
LeetCode - 哈希表专题
Window source code analysis (II): the adding mechanism of window
QT basic hand training applet - simple calculator design (with source code, analysis)
478-82(56、128、718、129)
《数据库系统内 幕》分布式系统
【一花一世界-郑一教授-繁简之道】可解释神经网络
JDBC connection database
使用 OpenSSL 创建ssl证书
final关键字和枚举类型
[autosar-rte] - 3-runnable and its task mapping mapping
Salted fish esp32 instance - mqtt lit LED
Oracle-11gr2 default system job
Changes in the relationship between data and application in IT industry
负数的十六进制表示
力扣376-摆动序列——贪心
21 day learning challenge - "AUTOSAR from introduction to mastery - practical part"
2022 high voltage electrician examination simulated 100 questions and simulated examination