Android 加壳应用运行流程 与 生命周期类处理方案

版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/

DexClassLoader

DexClassLoader 可以加载任意路径下的 dex,或者 jar、apk、zip 文件(包含classes.dex)。常用于插件化、热修复以及 dex 加壳。

源码如下:

public class DexClassLoader extends BaseDexClassLoader {public DexClassLoader(String dexPath, String optimizedDirectory,String librarySearchPath, ClassLoader parent) {super(dexPath, null, librarySearchPath, parent);}
}

http://aospxref.com/android-10.0.0_r47/xref/libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java

参数说明

参数类型说明
dexPathString需要加载的 dex、apk、jar、zip 文件的路径,多个路径用 : 分隔。支持任意目录下的文件。
optimizedDirectoryString用于存放优化后的 dex 文件(即 .odex),在 API level 26 已弃用(Android 8.0 Oreo)。
librarySearchPathString指定本地库(native library,.so 文件)搜索路径,多个路径用 : 分隔。
parentClassLoader父类加载器,用于实现类加载的委托机制。

动态加载

动态加载 = 运行时按需加载代码或资源

动态加载 是实现 dex加壳、插件化、热更新、热修复 的基础。比如阿里的 AndFix 、腾讯 tinker、美团 Robust 等热修复框架的基础。

使用 DexClassLoader 加载一个外部 .dex 或 .apk 文件,然后反射调用里面的类和方法。

步骤如下:

  1. 准备好外部的 dex / apk / jar;

  2. 将它放在你 app 可以访问的路径(如 /data/data/包名/files/);

  3. 用 DexClassLoader 加载它;

  4. 使用反射调用其中的类和方法。

1. 插件工程示例

插件工程主要包含这两个类:

word/media/image1.png

PluginActivity 源码:使用 Jetpack Compose 创建一个白色背景、居中显示文本的界面,文本内容为 “PluginActivity from plugin” 加上 ClassLoader 信息

package com.cyrus.example.pluginimport android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.spclass PluginActivity : ComponentActivity() {private val TAG = "PluginActivity"override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)Log.d(TAG, "onCreate")val classLoaderInfo = this.javaClass.classLoader.toString()setContent {PluginActivityContent(classLoaderInfo)}}@Composablefun PluginActivityContent(classLoaderInfo: String) {Box(modifier = Modifier.fillMaxSize().background(Color.White).padding(horizontal = 16.dp),contentAlignment = Alignment.Center) {Text(text = "PluginActivity from plugin\n\n$classLoaderInfo",fontSize = 18.sp,color = Color.Black)}}override fun onStart() {super.onStart()Log.d(TAG, "onStart")}override fun onResume() {super.onResume()Log.d(TAG, "onResume")}override fun onPause() {super.onPause()Log.d(TAG, "onPause")}override fun onStop() {super.onStop()Log.d(TAG, "onStop")}override fun onRestart() {super.onRestart()Log.d(TAG, "onRestart")}override fun onDestroy() {super.onDestroy()Log.d(TAG, "onDestroy")}}

PluginClass 源码:

package com.cyrus.example.pluginclass PluginClass {fun getString(): String {return "String from plugin."}}

编译 apk

word/media/image2.png

把 apk 推送到设备 sdcard

adb push plugin-debug.apk /sdcard/Android/data/com.cyrus.example/files

2. 动态加载示例

创建 DexClassLoader 实例,加载指定路径下的 APK/DEX 文件

val apkPath = "/sdcard/Android/data/com.cyrus.example/files/plugin-debug.apk"// 创建 DexClassLoader 加载 sdcard 上的 apk
val classLoader = DexClassLoader(apkPath,null,this@ClassLoaderActivity.packageResourcePath,context.classLoader // parent 设为当前 context 的类加载器
)

调用示例:

// classLoader 加载 com.cyrus.example.plugin.PluginClass 类并通过反射调用 getString 方法
val pluginClass = classLoader.loadClass("com.cyrus.example.plugin.PluginClass")
val constructor = pluginClass.getDeclaredConstructor()
constructor.isAccessible = true
val instance = constructor.newInstance()
val method = pluginClass.getDeclaredMethod("getString")
method.isAccessible = true
val result = method.invoke(instance) as? Stringoutput = "动态加载:${pluginPath}\n\ncall ${method}\n\nreuslt=${result}"

效果如下,可以看到正常调用了 apk 中 PluginClass 的 getString 方法并拿到了返回值

word/media/image3.png

组件类 ClassNotFoundException

在 AndroidManifest.xml 中声明 PluginActivity

<activityandroid:name="com.cyrus.example.plugin.PluginActivity"android:exported="true">
</activity>

通过自定义 ClassLoader 加载 PluginActivity 类并启动

// 通过 classLoader 加载 PluginActivity 类并启动
val pluginActivityClass = classLoader.loadClass("com.cyrus.example.plugin.PluginActivity")
val intent = Intent(this@ClassLoaderActivity, pluginActivityClass)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
this@ClassLoaderActivity.startActivity(intent)

报错如下:

java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{com.cyrus.example/com.cyrus.example.plugin.PluginActivity}: java.lang.ClassNotFoundException: Didn't find class "com.cyrus.example.plugin.PluginActivity" on path: DexPathList[[zip file "/data/app/com.cyrus.example-MZoMs5LmgjwUZ_FiJ-u0fQ==/base.apk"],nativeLibraryDirectories=[/data/app/com.cyrus.example-MZoMs5LmgjwUZ_FiJ-u0fQ==/lib/arm64, /data/app/com.cyrus.example-MZoMs5LmgjwUZ_FiJ-u0fQ==/base.apk!/lib/arm64-v8a, /system/lib64, /system/product/lib64]]
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3194)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3409)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:491)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:940)
Caused by: java.lang.ClassNotFoundException: Didn't find class "com.cyrus.example.plugin.PluginActivity" on path: DexPathList[[zip file "/data/app/com.cyrus.example-MZoMs5LmgjwUZ_FiJ-u0fQ==/base.apk"],nativeLibraryDirectories=[/data/app/com.cyrus.example-MZoMs5LmgjwUZ_FiJ-u0fQ==/lib/arm64, /data/app/com.cyrus.example-MZoMs5LmgjwUZ_FiJ-u0fQ==/base.apk!/lib/arm64-v8a, /system/lib64, /system/product/lib64]]
at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:196)
at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
at android.app.AppComponentFactory.instantiateActivity(AppComponentFactory.java:95)
at androidx.core.app.CoreComponentFactory.instantiateActivity(CoreComponentFactory.java:44)
at android.app.Instrumentation.newActivity(Instrumentation.java:1250)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3182)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3409) 
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83) 
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) 
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) 
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016) 
at android.os.Handler.dispatchMessage(Handler.java:107) 
at android.os.Looper.loop(Looper.java:214) 
at android.app.ActivityThread.main(ActivityThread.java:7356) 
at java.lang.reflect.Method.invoke(Native Method) 
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:491) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:940) 

断点调试看看,PluginActivity 类是正常加载了的

word/media/image4.png

原因是:

  • 动态加载的 dex 不具有生命周期特征,APP 中的 Activity 、Service 等组件无法正常工作,只能完成一般函数的调用;

  • 需要对 ClassLoader 进行修正,APP 才能够正常运行。

生命周期类处理

DexClassLoader 加载的类是没有组件生命周期的,也就是说即使 DexClassLoader 通过对 APK 动态加载完成了对组件类的加载,当系统启动该组件时,依然会出现加载类失败的异常。

为什么组件类被动态加载入虚拟机,但系统却出现加载类失败呢?

因为 系统在启动组件(如 Activity、Service)时,是通过 AMS → ActivityThread → Instrumentation 最终调用 Class.forName(组件类名) 来加载组件类的,而这个过程默认使用的是系统的 ClassLoader(通常是 PathClassLoader),而不是我们自定义的 DexClassLoader。

从 ClassLoader 来看,两种解决方案:

  1. 替换系统组件类加载器为我们的 DexClassLoader,同时设置 DexClassLoader 的 parent 为系统组件类加载器

  2. 打破原有的双亲关系,在系统组件类加载器和 BootClassLoader 的中间插入我们自己的 DexClassLoader 即可

或者可以对 PathClassLoader 中的 Elements 进行合并(常用于热修复框架(如 Tinker、Robust))。

相关文章:Android 下的 ClassLoader 与 双亲委派机制

方案 1:替换 ClassLoader 为 自定义ClassLoader

变化前结构(系统默认)

[BootClassLoader]↑
[PathClassLoader]    ← 原来的 ClassLoader↑Activity、Application 加载组件类

变化后结构(方案1)

[BootClassLoader]↑
[PathClassLoader]    ← 原来的 ClassLoader↑
[DexClassLoader]     ← 反射替换 LoadedApk.mClassLoader↑Activity、Application 加载组件类

通过 反射替换掉 系统的 ClassLoader 为自定义的 ClassLoader,同时设置 parent 为 系统的 ClassLoader

示例代码:

private fun replaceClassLoader(context: Context): ClassLoader? {try {// 1. 创建自定义 ClassLoader 实例,加载 sdcard 上的 apkval classLoader = DexClassLoader(apkPath,null,this@ClassLoaderActivity.packageResourcePath,context.classLoader // 设置 parent 为 系统的 ClassLoader)// 2. 拿到 ActivityThreadval activityThreadClass = Class.forName("android.app.ActivityThread")val currentActivityThread = activityThreadClass.getMethod("currentActivityThread").invoke(null)// 3. 拿到 mPackages 字段: Map<String, WeakReference<LoadedApk>>val mPackagesField = activityThreadClass.getDeclaredField("mPackages")mPackagesField.isAccessible = trueval mPackages = mPackagesField.get(currentActivityThread) as Map<*, *>// 4. 拿到当前包名对应的 LoadedApk 实例val loadedApkRef = mPackages[context.packageName] as? WeakReference<*>?: throw IllegalStateException("LoadedApk not found for package: ${context.packageName}")val loadedApk = loadedApkRef.get()?: throw IllegalStateException("LoadedApk is null")// 5. 替换 LoadedApk.mClassLoaderval loadedApkClass = loadedApk.javaClassval mClassLoaderField = loadedApkClass.getDeclaredField("mClassLoader")mClassLoaderField.isAccessible = truemClassLoaderField.set(loadedApk, classLoader)// ✅ 替换成功Log.d(TAG, "✅ ClassLoader has been replaced successfully!")return classLoader} catch (e: Exception) {e.printStackTrace()Log.d(TAG, "❌ Failed to replace ClassLoader: ${e.message}")}return null
}
  • Android 每个 App 都有一个 LoadedApk 对象负责管理 dex 加载;

  • mClassLoader 是决定类加载的实际执行者;

  • 修改它就等于修改了整个 App 的“类查找逻辑”。

调用示例:

// 方案 1:替换 ClassLoader 为 自定义ClassLoader
val classLoader = replaceClassLoader(context)if (classLoader == null) {Log.d(TAG, "❌ Failed to replace ClassLoader")return@Button
}// classLoader 加载 com.cyrus.example.plugin.PluginClass 类并通过反射调用 getString 方法
val pluginClass = classLoader.loadClass("com.cyrus.example.plugin.PluginClass")
val constructor = pluginClass.getDeclaredConstructor()
constructor.isAccessible = true
val instance = constructor.newInstance()
val method = pluginClass.getDeclaredMethod("getString")
method.isAccessible = true
val result = method.invoke(instance) as? String// 通过 classLoader 加载 PluginActivity 类并启动
val pluginActivityClass = classLoader.loadClass("com.cyrus.example.plugin.PluginActivity")
val intent = Intent(this@ClassLoaderActivity, pluginActivityClass)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
this@ClassLoaderActivity.startActivity(intent)output = "动态加载:${apkPath}\n\ncall ${method}\n\nreuslt=${result}"

成功启动 PluginActivity,而且可以看到 ClassLoader 是自定义的 DexClassLoader

word/media/image5.png

日志输出如下,生命周期相关方法也是正常执行

word/media/image6.png

方案 2:插入中间 ClassLoader / 打破双亲委派

变化前结构

[BootClassLoader]↑
[PathClassLoader]   ← 原始 ClassLoader

变化后结构(方案2)

[BootClassLoader]↑
[DexClassLoader]     ← 反射插入自定义的 DexClassLoader↑
[PathClassLoader]    ← 原始 ClassLoader

在 BootClassLoader 和 PathClassLoader 中间 通过反射插入 自定义的 DexClassLoader

通过反射修改 PathClassLoader 的 parent 为 自定义ClassLoader,并设置 自定义ClassLoader 的 parent 为 BootClassLoader

目标结构如下:

  • PathClassLoader.parent = 自定义ClassLoader

  • 自定义ClassLoader.parent = BootClassLoader

通过反射获取并修改 PathClassLoader 的 parent 字段

private fun injectClassLoader(context: Context): DexClassLoader? {try {// 拿到当前 PathClassLoaderval appClassLoader = context.classLoaderval pathClassLoaderClass = ClassLoader::class.java// 反射访问 parent 字段val parentField = pathClassLoaderClass.getDeclaredField("parent")parentField.isAccessible = trueval bootClassLoader = ClassLoader.getSystemClassLoader().parent// 自定义ClassLoader.parent = BootClassLoaderval classLoader = DexClassLoader(apkPath,null,this@ClassLoaderActivity.packageResourcePath,bootClassLoader // 设置 parent 为 BootClassLoader)// PathClassLoader.parent = 自定义ClassLoaderparentField.set(appClassLoader, classLoader)Log.d(TAG, "✅ 成功将 ${classLoader} 注入到 PathClassLoader.parent")return classLoader} catch (e: Exception) {e.printStackTrace()Log.d(TAG, "❌ 注入失败:${e.message}")}return null
}

调用示例:

// 方案 2:插入中间 ClassLoader / 打破双亲委派
val classLoader = injectClassLoader(context)if (classLoader == null) {Log.d(TAG, "❌ Failed to replace ClassLoader")return@Button
}// classLoader 加载 com.cyrus.example.plugin.PluginClass 类并通过反射调用 getString 方法
val pluginClass = classLoader.loadClass("com.cyrus.example.plugin.PluginClass")
val constructor = pluginClass.getDeclaredConstructor()
constructor.isAccessible = true
val instance = constructor.newInstance()
val method = pluginClass.getDeclaredMethod("getString")
method.isAccessible = true
val result = method.invoke(instance) as? String// 通过 classLoader 加载 PluginActivity 类并启动
val pluginActivityClass = classLoader.loadClass("com.cyrus.example.plugin.PluginActivity")
val intent = Intent(this@ClassLoaderActivity, pluginActivityClass)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
this@ClassLoaderActivity.startActivity(intent)output = "动态加载:${apkPath}\n\ncall ${method}\n\nreuslt=${result}"

成功启动 PluginActivity,而且可以看到 ClassLoader 是自定义的 DexClassLoader

word/media/image7.png

日志输出如下,生命周期相关方法也是正常执行

word/media/image8.png

这样你就“无缝拦截”了类加载流程,同时也保留系统 PathClassLoader 的全部能力,比完全替换更可靠 💯

加壳应用的运行流程

参考文章:

  • 详解 Android APP 启动流程

  • FART:ART环境下基于主动调用的自动化脱壳方案

壳执行的时机必须比 app 原来的逻辑早。

在 APP启动流程中我们最终可以得出结论,app 最先获得执行权限的是 app 中声明的 Application 类中的 attachBaseContext 和 onCreate 函数。因此,壳要想完成应用中加固代码的解密以及应用执行权的交付就都是在这两个函数上做文章。

加壳应用运行流程:

→ AMS 发起启动
→ Zygote fork → ActivityThread.main
→ ActivityThread.handleBindApplication()
→ 壳Application.attachBaseContext(base)↳ 解密 dex↳ 自定义 ClassLoader 加载解密 dex↳ 反射设置 LoadedApk.mClassLoader 为自定义 ClassLoader
→ 壳Application.onCreate()↳ 反射生成真实的 Application 对象↳ 反射替换 ActivityThread.mInitialApplication 为真实 Application 对象↳ 已进入原始 app 世界

完整源码

开源地址:https://github.com/CYRUS-STUDIO/AndroidExample

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

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

相关文章

c++进阶——类与继承

文章目录 继承继承的基本概念继承的基本定义继承方式继承的一些注意事项 继承类模板 基类和派生类之间的转换继承中的作用域派生类的默认成员函数默认构造函数拷贝构造赋值重载析构函数默认成员函数总结 不能被继承的类继承和友元继承与静态成员多继承及其菱形继承问题继承模型…

GAEA情感坐标背后的技术原理

基于GAEA的去中心化物理基础设施网络&#xff08;DePIN&#xff09;&#xff0c;用户有机会在GAEA平台上获得宝贵的数据共享积分。为了提升这些洞察的丰富性&#xff0c;用户必须花费一定数量的积分&#xff0c;将过去的网络数据与当前的情感数据绑定&#xff0c;从而产生一种新…

图形编辑器基于Paper.js教程27:对图像描摹的功能实现,以及参数调整

本篇文章来讲一下 图像描摹的功能的实现。 我们知道要雕刻图片可以通过分析图片的像素来生成相应的gcode进行雕刻&#xff0c;但如果你想要将图片转换为线稿进行雕刻&#xff0c;这个时候就要从图片中提取出 线稿。 例如下面的图片&#xff1a; 你想要获取到这个图片的线稿&…

人工智能与机器学习,谁是谁的子集 —— 再谈智能的边界与演进路径

人工智能&#xff08;Artificial Intelligence, AI&#xff09;作为当代最具影响力的前沿技术之一&#xff0c;常被大众简化为 “深度学习” 或 “大模型” 等标签。然而&#xff0c;这种简化认知往往掩盖了AI技术内部结构的复杂性与多样性。事实上&#xff0c;AI并非单一方法的…

Oracle_开启归档日志和重做日志

在Oracle中&#xff0c;类似于MySQL的binlog的机制是归档日志&#xff08;Archive Log&#xff09;和重做日志&#xff08;Redo Log&#xff09; 查询归档日志状态 SELECT log_mode FROM v$database; – 输出示例&#xff1a; – LOG_MODE – ARCHIVELOG (表示已开启) – NO…

IDEA编写flinkSQL(快速体验版本,--无需配置环境)

相关资料 文档内容链接地址datagen生成器https://nightlies.apache.org/flink/flink-docs-release-1.16/docs/connectors/table/datagen/print 生成器https://nightlies.apache.org/flink/flink-docs-release-1.16/docs/connectors/table/print/ 准备工作 优点就是下载个ide…

基于AI技术的高速公路交通引流系统设计与应用研究

基于AI技术的高速公路交通引流系统设计与应用研究 1. 研究背景与意义 1.1 交通系统演化脉络 1.1.1 发展阶段划分 机械化时代&#xff08;1950-1990&#xff09;&#xff1a;固定式信号控制信息化时代&#xff08;1991-2010&#xff09;&#xff1a;SCATS/SCOOT系统智能化时代…

NEGATIVE LABEL GUIDED OOD DETECTION WITH PRETRAINED VISION-LANGUAGE MODELS

1. 介绍: 这篇论文也是基于CLIP通过后处理的方法实现的OOD的检测,但是设计点在于,之前的方法是使用的ID的类别,这篇工作是通过添加一些在语义上非常不同于ID的类别的外分布类来做的OOD检测。 CLIP做OOD检测的这个系列里面我看的以及记录的第一篇就是MCM的方法,这也是确实是…

Linux 网络基础三 (数据链路层协议:以太网协议、ARP 协议)

一、以太网 两个不同局域网的主机传递数据并不是直接传递的&#xff0c;而是通过路由器 “一跳一跳” 的传递过去。 跨网络传输的本质&#xff1a;由无数个局域网&#xff08;子网&#xff09;转发的结果。 所以&#xff0c;要理解数据跨网络转发原理就要先理解一个局域网中数…

Azure Data Factory ETL设计与调度最佳实践

一、引言 在Azure Data Factory (ADF) 中&#xff0c;调度和设计ETL&#xff08;抽取、转换、加载&#xff09;过程需要综合考量多方面因素&#xff0c;以确保数据处理高效、可扩展、可靠且易于维护。以下将详细介绍相关关键考虑因素、最佳实践&#xff0c;并辅以具体示例说明…

非序列实现MEMS聚焦功能

zemax非序列模式下有MEMS,但是没有对应的代码。无法修改成自己需要的功能 以下是实现MEMS聚焦功能: #include <windows.h> #include <cmath> #include <stdio.h> #include <string.h> #include <algorithm> #undef max #undef min#define D…

android studio sdk unavailable和Android 安装时报错:SDK emulator directory is missing

md 网上说的都是更换proxy代理什么的&#xff0c;换网的&#xff0c;还有一些二其他乱七八糟的&#xff0c;根本没用&#xff0c;感觉很多就是解决不了问题&#xff0c;还贼多贼一致&#xff0c;同质化&#xff0c;感觉很坑人&#xff0c;让人觉得他们和我的一样的&#xff0c;…

三维重建模块VR,3DCursor,MPR与VR的坐标转换

MPR里的reslicecursor 的坐标与 vtkimage 坐标一致。 但三维窗格里的vtkvolume 的坐标是相对坐标&#xff0c;坐标值依然是MM单位。 用中心点的偏移量比较容易实现&#xff0c;交互中Reslicercursor中心点 距离 vtkimagedata 的中心点 的偏移量&#xff0c;用于vtkvolume即可…

Python Cookbook-6.9 快速复制对象

任务 为了使用 copy.copy&#xff0c;需要实现特殊方法__copy__。而且你的类的__init__比较耗时所以你希望能够绕过它并获得一个“空的”未初始化的类实例。 解决方案 下面的解决方案可同时适用于新风格和经典类: def empty_copy(obj):class Empty(obj.__class__):def __in…

kubernets集群的安装-node节点安装-(简单可用)-超详细

一、kubernetes 1、简介 kubernetes&#xff0c;简称K8s&#xff08;库伯内特&#xff09;&#xff0c;是用8代替名字中间的8个字符“ubernete”而成的缩写 云计算的三种主要服务模式——基础设施即服务&#xff08;IaaS&#xff09;、平台即服务&#xff08;PaaS&#xff0…

【Linux学习笔记】进程的fork创建 exit终止 wait等待

【Linux学习笔记】进程的fork创建 exit终止 wait等待 &#x1f525;个人主页&#xff1a;大白的编程日记 &#x1f525;专栏&#xff1a;Linux学习笔记 文章目录 【Linux学习笔记】进程的fork创建 exit终止 wait等待前言1.进程创建1.1 fork函数初识1.2fork函数返回值1.3写时拷…

鸿蒙应用开发证书考试的一点想法

一、介绍&#xff1a; 直接上图 二、体验后的想法&#xff1a; 1.知识点在指南API参考最佳实践里面找 2.没有明确说明考试不能查第1点的文档&#xff0c;但是考试只有1个小时&#xff0c;合理分配时间 3.切屏三次后自动提交要注意&#xff0c;每月3次机会下月又有3次机会&a…

含锡废水处理的经济效益

主要体现在成本节约和资源回收两方面&#xff0c;具体收益因处理工艺、废水浓度及规模差异而不同。以下结合不同技术路线进行量化分析&#xff1a; 一、直接经济效益 资源回收收益 金属锡回收&#xff1a; 若废水中锡浓度为100 mg/L&#xff0c;日处理量100吨&#xff0c;则每…

Base64编码原理:二进制数据与文本的转换技术

&#x1f504; Base64编码原理&#xff1a;二进制数据与文本的转换技术 开发者的数据编码困境 作为开发者&#xff0c;你是否曾遇到这些与Base64相关的挑战&#xff1a; &#x1f4ca; 需要在JSON中传输二进制数据&#xff0c;但不确定如何正确编码&#x1f5bc;️ 想要在HT…

day49—双指针+贪心—验证回文串(LeetCode-680)

题目描述 给你一个字符串 s&#xff0c;最多 可以从中删除一个字符。 请你判断 s 是否能成为回文字符串&#xff1a;如果能&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 示例 1&#xff1a; 输入&#xff1a;s "aba" 输出&#xff1a;true…