当前位置: 首页 > news >正文

【Android Framework系列】第9章 AMS之Hook实现登录页跳转

1 前言

前面章节我们学习了【Android Framework系列】第5章 AMS启动流程和【Android Framework系列】第6章 AMS原理之Launcher启动流程,大概了解了AMS的原理及启动流程,这一章节我们通过反射和动态代理对不同Android版本下的AMS进行Hook实现登录页面的跳转

这里我们只简单介绍一下HookAMS思路和重点代码,需要详细了解的请到文末处项目地址下载查看。

1.1 实现打开统一登录页面

我们在Android的APP中,一般会有登录状态。如果非登录状态下,除了闪屏页和登录页外,其他页面打开需要先登录。往往登录状态也会有有效期的说法,如果在有效期到了跳转,我们则需要跳转到登录页面,而不是继续打开页面,这种情况下我们通过HookAMS可以实现。

1.2 实现打开动态插件下发页面

另外通过HookAMS还可以实现动态下发插件的功能,比如动态下发的ActivityAndroidManifest.xml里是没有注册,要想打开则需要通过HookAMS的方式,使用代理页面在AndroidManifest.xml注册,在跳转时动态切换到下发下来的插件内Activity

2 实现

2.1 实现思路

通过动态代理的方式,将AMSstartActivity方法拦截下来,把要跳转的意图替换成我们要打开的Activity。由于不同的Android版本AMS源码有所差别,所以这里区分SDK<=23SDK<=28SDK>28这三种情况做HookAMS适配。下面我们来看看项目结构

2.2 项目结构

在这里插入图片描述
上图我们可以看到项目结构如下:

// Config					常量配置类
// HookAMSApplication		Application进行HookAMS初始化
// HookAMSUtils				HookAMS工具类,主要的Hook逻辑	
// ListActivity				数据页面,登录后才可打开
// LoginActivity			登录页
// MainActivity				首页,这里打开数据页面
// ProxyActivity			代理页,用于欺瞒AMS,跳转时动态替换为真正Activity

首先我们来看是怎么HookAMSApplication

2.3 HookAMSApplication

package com.yvan.hookams;import android.app.Application;
import android.os.Handler;
import android.os.Looper;/*** @author yvan* @date 2023/7/28* @description*/
public class HookAMSApplication extends Application {private final Handler handler = new Handler(Looper.getMainLooper());@Overridepublic void onCreate() {super.onCreate();handler.post(this::hookAMS);}public void hookAMS() {try {HookAMSUtils hookUtils = new HookAMSUtils(this, ProxyActivity.class);hookUtils.hookAms();} catch (Exception e) {e.printStackTrace();}}
}

在App启动时,ApplicationonCreate()方法内通过Handlerpost方法进行对HookAMSUtils类的hookAms()方法调用。为什么要使用Handler呢?初始化ApplicationonCreate()初始化还没完成,直接调hookAms()方法会崩溃,这里加了post,将任务加入到主线程的队列里,这样就不会出现崩溃异常。

我们继续看HookAMSUtils类:

2.4 HookAMSUtils

package com.yvan.hookams;import static android.os.Build.VERSION.SDK_INT;import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.Message;
import android.util.Log;import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;/*** @author yvan* @date 2023/7/28* @description Hook AMS工具类*/
public class HookAMSUtils {private static final String TAG = HookAMSUtils.class.getSimpleName();private Context context;private Class<?> proxyActivity;/*** proxyActivity 传入一个有注册在AndroidManifest的就行** @param context* @param proxyActivity*/public HookAMSUtils(Context context, Class<?> proxyActivity) {this.context = context;this.proxyActivity = proxyActivity;}public void hookAms() throws Exception {if (SDK_INT <= 23) {hookAmsFor6();} else if (SDK_INT <= 28) {hookAmsFor9();} else {hookAmsFor10();}hookSystemHandler();}public void hookAmsFor10() throws Exception {Class<?> iActivityManagerClazz = Class.forName("android.app.IActivityTaskManager");Class<?> clazz = Class.forName("android.app.ActivityTaskManager");Field singletonField = clazz.getDeclaredField("IActivityTaskManagerSingleton");singletonField.setAccessible(true);Object singleton = singletonField.get(null);Class<?> singletonClass = Class.forName("android.util.Singleton");Field mInstanceField = singletonClass.getDeclaredField("mInstance");mInstanceField.setAccessible(true);final Object mInstance = mInstanceField.get(singleton);Object proxyInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{iActivityManagerClazz}, new AmsInvocationHandler(mInstance));mInstanceField.setAccessible(true);mInstanceField.set(singleton, proxyInstance);}public void hookAmsFor9() throws Exception {// 1.反射获取类>ActivityTaskManager,这个就是AMS实例Class ActivityManagerClz = Class.forName("android.app.ActivityManager");// 2.获取IActivityManagerSingleton,并设置访问权限Field iActivityManagerSingletonFiled = ActivityManagerClz.getDeclaredField("IActivityManagerSingleton");iActivityManagerSingletonFiled.setAccessible(true);// 因为是静态变量,所以获取的到的是默认值final Object iActivityManagerSingletonObj = iActivityManagerSingletonFiled.get(null);// 3.现在创建我们的AMS实例// 由于IActivityManager是一个接口,那么其实我们可以使用Proxy类来进行代理对象的创建// 结果被摆了一道,IActivityManager这玩意居然还是个AIDL,动态生成的类,编译器还不认识这个类,怎么办?反射咯// 反射创建一个Singleton的classClass SingletonClz = Class.forName("android.util.Singleton");Field mInstanceField = SingletonClz.getDeclaredField("mInstance");mInstanceField.setAccessible(true);// 4.获取AMS ProxyObject iActivityManagerObj = mInstanceField.get(iActivityManagerSingletonObj);// 5.获取需要实现的接口IActivityManager实现类Class iActivityManagerClz = Class.forName("android.app.IActivityManager");// 6.动态生成接口对象// 构建代理类需要两个东西用于创建伪装的Intent// 拿到AMS实例,然后用代理的AMS换掉真正的AMS,代理的AMS则是用 假的Intent骗过了 activity manifest检测.Object proxyIActivityManager = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),new Class<?>[]{iActivityManagerClz}, new AmsInvocationHandler(iActivityManagerObj));mInstanceField.setAccessible(true);// 7.替换掉系统的变量mInstanceField.set(iActivityManagerSingletonObj, proxyIActivityManager);}public void hookAmsFor6() throws Exception {//1.反射获取类>ActivityManagerNativeClass ActivityManagerClz = Class.forName("android.app.ActivityManagerNative");//2.获取变量>gDefaultField IActivityManagerSingletonFiled = ActivityManagerClz.getDeclaredField("gDefault");//2.1 设置访问权限IActivityManagerSingletonFiled.setAccessible(true);//3. 获取变量的实例值Object IActivityManagerSingletonObj = IActivityManagerSingletonFiled.get(null);//4.获取mInstanceClass SingletonClz = Class.forName("android.util.Singleton");Field mInstanceField = SingletonClz.getDeclaredField("mInstance");mInstanceField.setAccessible(true);//5.获取AMS ProxyObject AMSProxy = mInstanceField.get(IActivityManagerSingletonObj);//6.由于不能去手动实现IActivityManager实现类,//  所以只能通过动态代理去动态生成实现类//6.1 获取需要实现的接口Class IActivityManagerClz = Class.forName("android.app.IActivityManager");//6.2 动态生成接口对象Object proxyIActivityManager = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),new Class[]{IActivityManagerClz}, new AmsInvocationHandler(AMSProxy));mInstanceField.setAccessible(true);//7.替换掉系统的变量mInstanceField.set(IActivityManagerSingletonObj, proxyIActivityManager);}private class AmsInvocationHandler implements InvocationHandler {private Object iActivityManagerObject;public AmsInvocationHandler(Object iActivityManagerObject) {this.iActivityManagerObject = iActivityManagerObject;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if ("startActivity".contains(method.getName())) {Intent intent = null;int index = 0;for (int i = 0; i < args.length; i++) {Object arg = args[i];if (arg instanceof Intent) {intent = (Intent) args[i]; // 原意图,过不了安检index = i;break;}}Intent proxyIntent = new Intent();ComponentName componentName = new ComponentName(context, proxyActivity);proxyIntent.setComponent(componentName);proxyIntent.putExtra("realIntent", intent);//替换原有的intent为我们自己生成的,为了骗过PMS//为跳到我们的传入的proxyActivityargs[index] = proxyIntent;}return method.invoke(iActivityManagerObject, args);}}//上面的主要是替换成我们自己的intent,骗过系统//下面的主要是将我们上面替换的intent中,取出我们真正的意图(也就是正在要启动的Activity)////下面是为了拿到mH对象,但是mH是一个非static 的值,那我们就只能拿到他的持有对象,也就是ActivityThread//正好发现在ActivityThread类中有一个static变量sCurrentActivityThread值可以拿到ActivityThread类,那我们就从他入手public void hookSystemHandler() throws Exception {//1.反射ActivityThreadClass ActivityThreadClz = Class.forName("android.app.ActivityThread");//2. 获取sCurrentActivityThread 是一个static变量Field field = ActivityThreadClz.getDeclaredField("sCurrentActivityThread");field.setAccessible(true);//3.获取ActivityThread对象Object ActivityThreadObj = field.get(null);//4.通过ActivityThreadObj获取到mH变量Field mHField = ActivityThreadClz.getDeclaredField("mH");mHField.setAccessible(true);//5.获取到mH的对象Handler mHObj = (Handler) mHField.get(ActivityThreadObj);//ok,当前的mH拿到了//到这里,获取到mH的对象了,那我们怎么去监听他的方法调用呢?//能不能通过动态代理?不能,因为它不是个接口//由于在Handler的源码中,我们知道如果mCallback如果不等于空,就会调用mCallback的handleMessage方法。//6.获取mH的mCallbackField mCallbackField = Handler.class.getDeclaredField("mCallback");mCallbackField.setAccessible(true);//7.创建我们自己的Callback,自己处理handleMessageHandler.Callback proxyMHCallback = getMHCallback();//8.给系统的mH(Handler)的mCallback设值(proxyMHCallback)mCallbackField.set(mHObj, proxyMHCallback);}private Handler.Callback getMHCallback() {if (SDK_INT <= 23) {return new ProxyHandlerCallbackFor6();} else if (SDK_INT <= 28) {return new ProxyHandlerCallbackFor();} else {return new ProxyHandlerCallbackFor();}}private class ProxyHandlerCallbackFor6 implements Handler.Callback {private int LAUNCH_ACTIVITY = 100;@Overridepublic boolean handleMessage(Message msg) {if (msg.what == LAUNCH_ACTIVITY) {try {Class ActivityClientRecord = Class.forName("android.app.ActivityThread$ActivityClientRecord");//判断传过来的值(msg.obj)是不是ClientTransaction对象if (!ActivityClientRecord.isInstance(msg.obj)) return false;//获取ActivityClientRecord的intent变量Field intentField = ActivityClientRecord.getDeclaredField("intent");intentField.setAccessible(true);if (intentField == null) return false;Intent mIntent = (Intent) intentField.get(msg.obj);if (mIntent == null) return false;//获取我们之前传入的realIntent,也就是我们真正要打开的ActivityIntent realIntent = mIntent.getParcelableExtra("realIntent");if (realIntent == null) {return false;}realStartActivity(mIntent, realIntent);} catch (Exception e) {e.printStackTrace();}}return false;}}private class ProxyHandlerCallbackFor implements Handler.Callback {private int EXECUTE_TRANSACTION = 159;@Overridepublic boolean handleMessage(Message msg) {if (msg.what == EXECUTE_TRANSACTION) {try {Class ClientTransactionClz = Class.forName("android.app.servertransaction.ClientTransaction");//判断传过来的值(msg.obj)是不是ClientTransaction对象if (!ClientTransactionClz.isInstance(msg.obj)) return false;Class LaunchActivityItemClz = Class.forName("android.app.servertransaction.LaunchActivityItem");//获取ClientTransaction的mActivityCallbacks变量Field mActivityCallbacksField = ClientTransactionClz.getDeclaredField("mActivityCallbacks");//ClientTransaction的成员//设值成mActivityCallbacksField.setAccessible(true);//获取到ASM传递过来的值(ClientTransaction对象)里的mActivityCallbacks变量Object mActivityCallbacksObj = mActivityCallbacksField.get(msg.obj);List list = (List) mActivityCallbacksObj;if (list.size() == 0) return false;Object LaunchActivityItemObj = list.get(0);if (!LaunchActivityItemClz.isInstance(LaunchActivityItemObj)) return false;//获取mIntent变量Field mIntentField = LaunchActivityItemClz.getDeclaredField("mIntent");mIntentField.setAccessible(true);//获取mIntent对象Intent mIntent = (Intent) mIntentField.get(LaunchActivityItemObj);//获取我们之前传入的realIntent,也就是我们真正要打开的ActivityIntent realIntent = mIntent.getParcelableExtra("realIntent");if (realIntent == null) {return false;}realStartActivity(mIntent, realIntent);} catch (Exception e) {e.printStackTrace();}}return false;}}private void realStartActivity(Intent mIntent, Intent realIntent) {//登录判断SharedPreferences share = context.getSharedPreferences(Config.SP_NAME,Context.MODE_PRIVATE);if (share.getBoolean(Config.SP_KEY_LOGIN, false)) {mIntent.setComponent(realIntent.getComponent());} else {Log.i(TAG, "handleLauchActivity: " + realIntent.getComponent().getClassName());ComponentName componentName = new ComponentName(context, LoginActivity.class);mIntent.putExtra("extraIntent", realIntent.getComponent().getClassName());mIntent.setComponent(componentName);}}}

从上面代码我们能看到:

  1. hookAms()方法分别是SDK<=23、SDK<=28、SDK>28三种情况进行HookAMS,其实都是大同小异。实际上是获取到IActivityManager对象,通过动态代理Proxy.newProxyInstance()Hook到其所有方法,通过AmsInvocationHandler进行方法调用的回调。
  2. hookSystemHandler()方法在hookAms()方法调用后立刻执行,通过反射获取android.app.ActivityThread类对象的sCurrentActivityThread属性和Handler实例mH,将Handler的回调handleMessage()方法进行拦截。根据SDK<=23、SDK<=28、SDK>28三种情况不同来区别处理。
  3. 1中的AmsInvocationHandler负责hookAms()内Hook到的方法调用的处理,在Hook到的Callback中判断为startActivity()方法则拦截下来,将我们真正要跳转的Activity意图存在Extra内,由于在原来的Intent中隐藏了真正的Activity意图,所以只需要将真正的意图拿出来替换将其Intent的意图替换为要打开的Activity
  4. 2中handleMessage()其实就是将startActivity()方法进行拦截,判断如果是未登录状态,则将真正要跳转的ActivityExtra内拿出来进行跳转,已登录则不替换Intent意图跳转。
    在这里插入图片描述

3 总结

文章只做核心HookAMS代码思路的分析,这里是项目地址,小伙伴可以自行下载查看,别忘了点Star喔,谢谢!!

相关文章:

【Android Framework系列】第9章 AMS之Hook实现登录页跳转

1 前言 前面章节我们学习了【Android Framework系列】第5章 AMS启动流程和【Android Framework系列】第6章 AMS原理之Launcher启动流程&#xff0c;大概了解了AMS的原理及启动流程&#xff0c;这一章节我们通过反射和动态代理对不同Android版本下的AMS进行Hook&#xff0c;实现…...

哪些行业需要连接云专线?

在诸多行业之中&#xff0c;有一些行业对数据安全性要求高、业务需要实时性、业务需求复杂&#xff0c;往往需要建立起私密、高速、安全的传输通道&#xff0c;云专线是他们经常采用的方案。具体来讲&#xff0c;都有哪些行业需要连接云专线呢&#xff1f;请见下方。 1、金融行…...

【Mysql】group语句删除重复数据只保留一条

【Mysql】group语句删除重复数据只保留一条 【一】案例分析 假如在数据初始化的时候&#xff0c;insert脚本执行了两次&#xff0c;导致表里的数据都是重复的&#xff08;没有设置唯一键&#xff09;。这个时候再加上mybatis-plus的selectOne方法&#xff0c;就会出现报错。因…...

Git详解和命令大全

目录 一、Git 的基本概念二、Git 的安装和使用三、Git 的版本分支管理四、Git 的命令大全1. 常用命令2. 命令大全 五、版本分支管理的最佳实践六、Git 实践七、高级特性八、Git 的未来发展 Git 是一款开源的分布式版本控制系统&#xff0c;可以有效地处理从小到非常大的项目版本…...

北漂Java程序员入职五个月的收获总结

&#x1f468;‍&#x1f4bb;博主主页&#xff1a;小尘要自信 &#x1f468;‍&#x1f4bb;本文专栏&#xff1a;Java程序员的成长 &#x1f468;‍&#x1f4bb;上一篇文章&#xff1a;告别过去&#xff0c;拥抱未来&#xff1a;一个Java开发者的成长之路 &#x1f468;‍&a…...

Android系统的进程管理(创建->优先级->回收)

一、进程的创建 1、概述 Android系统以Linux内核为基础&#xff0c;所以对于进程的管理自然离不开Linux本身提供的机制。例如&#xff1a; 通过fork来创建进行通过信号量来管理进程通过proc文件系统来查询和调整进程状态 等 对于Android来说&#xff0c;进程管理的主要内容…...

C#界面美化小技巧

1.窗体设置为无边框 FormBorderStyle的属性设置为none 2.窗体无边框&#xff0c;可以拖拽 private Point mPoint new Point(); private void Download_MouseDown(object sender, MouseEventArgs e) { mPoint.X e.X; mPoint.Y e.Y; …...

‘vite‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件。

1.切换到工程目录下 2.执行npm install(最关键的一步了&#xff01;&#xff01;) 3. 最后直接运行&#xff1a;npm run dev 4.浏览器直接打开就行了&#xff01;...

Linux下查阅帮助文档必学命令 man

Linux操作系统的使用中,我们经常会遇到很多问题,这个时候查询文档的能力至关重要,黄老师来推荐大家使用man,这时我们必须掌握的查阅能力: 最常用的命令: man 名称 man 数字(1~9) 名称 这里的数字分别代表:...

uniapp scroll-view显示滚动条

在style中添加样式&#xff1a; ::v-deep ::-webkit-scrollbar {/* 滚动条整体样式 */display: block;width: 10rpx !important;height: 10rpx !important;-webkit-appearance: auto !important;background: transparent;overflow: auto !important;}::v-deep ::-webkit-scroll…...

15、PHP神奇的数组索引替代

1、有数字索引指定的数组元素时&#xff0c;以数字索引的为准。 <?php $aarray(a,b,1>c,5>"d","e"); print_r($a); ?> 输出结果&#xff1a;b的位置直接被c替代了&#xff0c;e 的值为最大的整数索引1。 PHP不这么搞&#xff0c;怎么可能成…...

同为科技(TOWE)带热插拔功能机柜PDU插座的应用

所谓热插拔&#xff08;hot-plugging或Hot Swap&#xff09;&#xff0c;即带电插拔&#xff0c;指的是在不关闭系统电源的情况下&#xff0c;将模块、板卡插入或拔出系统而不影响系统的正常工作&#xff0c;从而提高了系统的可靠性、快速维修性、冗余性和对灾难的及时恢复能力…...

GR5526 128BIT UUID改16BIT UUID

以下两个宏定义是我添加的。其中USING_128BIT_UUID的条件编译部分是SDK原生部分&#xff0c;USING_16BIT_UUID条件编译部分则是由我修改&#xff0c;通过这样的修改&#xff0c;128BIT UUID就变更为16BIT UUID了。如果你的广播、扫描响应有涉及UUID&#xff0c;不要忘记更改它。…...

【Android】使用 CameraX 实现基础拍照功能

目录 目录 1. 基础开发环境 2. 添加相关依赖 3. APP 布局 4. 主流程逻辑 5. 调试或安装 APK 1. 基础开发环境 JDK&#xff1a;JDK17 Android Studio&#xff1a;Android Studio Giraffe | 2022.3.1 Android SDK&#xff1a;Android API 34 Gradle: gradle-7.2-bin.zip Ca…...

刷题笔记 day2

力扣 1089 复写零 思路&#xff1a;双指针 第一步&#xff1a;利用指针 cur 去记录最后一位要复写的数 &#xff0c; 利用指针 dest 指向最后一位数所要复写的位置&#xff1b; 实现过程&#xff1a;最开始 cur 指向0&#xff0c;dest 指向 -1 &#xff0c; 当arr[cur] ! …...

回归预测 | MATLAB实现SO-CNN-LSTM蛇群算法优化卷积长短期记忆神经网络多输入单输出回归预测

回归预测 | MATLAB实现SO-CNN-LSTM蛇群算法优化卷积长短期记忆神经网络多输入单输出回归预测 目录 回归预测 | MATLAB实现SO-CNN-LSTM蛇群算法优化卷积长短期记忆神经网络多输入单输出回归预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 MATLAB实现SO-CNN-LS…...

使用UltraISO制作麒麟v10系统盘

大家好&#xff0c;我是早九晚十二&#xff0c;目前是做运维相关的工作。写博客是为了积累&#xff0c;希望大家一起进步&#xff01; 我的主页&#xff1a;早九晚十二 文章目录 1 背景2 准备工作2.1 镜像准备2.2 制作工具2.3 启动U盘 3 制作步骤3.1 找到ISO文件&#xff0c;右…...

【RabbitMQ】之消息的可靠性方案

目录 一、数据丢失场景二、数据可靠性方案 1、生产者丢失消息解决方案2、MQ 队列丢失消息解决方案3、消费者丢失消息解决方案 一、数据丢失场景 MQ 消息数据完整的链路为&#xff1a;从 Producer 发送消息到 RabbitMQ 服务器中&#xff0c;再由 Broker 服务的 Exchange 根据…...

性能测试/负载测试/压力测试之间的区别

做测试一年多来&#xff0c;虽然平时的工作都能很好的完成&#xff0c;但最近突然发现自己在关于测试的整体知识体系上面的了解很是欠缺&#xff0c;所以&#xff0c;在工作之余也做了一些测试方面的知识的补充。不足之处&#xff0c;还请大家多多交流&#xff0c;互相学习。 …...

Mybatis ,Mybatis-plus列表多字段排序,包含sql以及warpper

根据 mybatis 根据多字段排序已经wrapper 根据多字段排序 首先根据咱们返回前端的数据列来规划好排序字段 如下&#xff1a; 这里的字段为返回VO的字段,要转换成数据库字段然后加入到排序中 示例&#xff0c;穿了 surname,cerRank 多字段,然后是倒序 false 首先创建好映射&am…...

Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器

第一章 引言&#xff1a;语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域&#xff0c;文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量&#xff0c;支撑着搜索引擎、推荐系统、…...

C++中string流知识详解和示例

一、概览与类体系 C 提供三种基于内存字符串的流&#xff0c;定义在 <sstream> 中&#xff1a; std::istringstream&#xff1a;输入流&#xff0c;从已有字符串中读取并解析。std::ostringstream&#xff1a;输出流&#xff0c;向内部缓冲区写入内容&#xff0c;最终取…...

视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)

前言&#xff1a; 最近在做行为检测相关的模型&#xff0c;用的是时空图卷积网络&#xff08;STGCN&#xff09;&#xff0c;但原有kinetic-400数据集数据质量较低&#xff0c;需要进行细粒度的标注&#xff0c;同时粗略搜了下已有开源工具基本都集中于图像分割这块&#xff0c…...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)

Aspose.PDF 限制绕过方案&#xff1a;Java 字节码技术实战分享&#xff08;仅供学习&#xff09; 一、Aspose.PDF 简介二、说明&#xff08;⚠️仅供学习与研究使用&#xff09;三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...

LLMs 系列实操科普(1)

写在前面&#xff1a; 本期内容我们继续 Andrej Karpathy 的《How I use LLMs》讲座内容&#xff0c;原视频时长 ~130 分钟&#xff0c;以实操演示主流的一些 LLMs 的使用&#xff0c;由于涉及到实操&#xff0c;实际上并不适合以文字整理&#xff0c;但还是决定尽量整理一份笔…...

RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)

RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发&#xff0c;后来由Pivotal Software Inc.&#xff08;现为VMware子公司&#xff09;接管。RabbitMQ 是一个开源的消息代理和队列服务器&#xff0c;用 Erlang 语言编写。广泛应用于各种分布…...

基于Springboot+Vue的办公管理系统

角色&#xff1a; 管理员、员工 技术&#xff1a; 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能&#xff1a; 该办公管理系统是一个综合性的企业内部管理平台&#xff0c;旨在提升企业运营效率和员工管理水…...

tomcat指定使用的jdk版本

说明 有时候需要对tomcat配置指定的jdk版本号&#xff0c;此时&#xff0c;我们可以通过以下方式进行配置 设置方式 找到tomcat的bin目录中的setclasspath.bat。如果是linux系统则是setclasspath.sh set JAVA_HOMEC:\Program Files\Java\jdk8 set JRE_HOMEC:\Program Files…...

聚六亚甲基单胍盐酸盐市场深度解析:现状、挑战与机遇

根据 QYResearch 发布的市场报告显示&#xff0c;全球市场规模预计在 2031 年达到 9848 万美元&#xff0c;2025 - 2031 年期间年复合增长率&#xff08;CAGR&#xff09;为 3.7%。在竞争格局上&#xff0c;市场集中度较高&#xff0c;2024 年全球前十强厂商占据约 74.0% 的市场…...

数据结构:泰勒展开式:霍纳法则(Horner‘s Rule)

目录 &#x1f50d; 若用递归计算每一项&#xff0c;会发生什么&#xff1f; Horners Rule&#xff08;霍纳法则&#xff09; 第一步&#xff1a;我们从最原始的泰勒公式出发 第二步&#xff1a;从形式上重新观察展开式 &#x1f31f; 第三步&#xff1a;引出霍纳法则&…...