DroidPlugin 插件化源码前奏:系统 Activity 启动流程深度解析与占坑 Hook 的切入点
移动开发插件系统【免费下载链接】DroidPluginA plugin framework on android,Run any third-party apk without installation, modification or repackage项目地址https://gitcode.com/gh_mirrors/dro/DroidPlugin点击查看免费下载本文基于仓库文档 DOC/hejunlin/插件占坑四大组件动态注册前奏一 系统Activity的启动流程.md 展开围绕「系统 Activity 的启动流程」这一插件化必修课讲清 Launcher 进程、AMSActivityManagerService进程与应用进程三者如何通过 Binder 协作完成一次 Activity 启动并对照 DroidPlugin 源码IActivityManagerHookHandle、PluginCallback、ActivityStub等说明为何startActivity()与handleLaunchActivity()是插件框架瞒天过海欺骗 AMS 的两大关键切入点。适用读者想深入理解 Android 插件化占坑、动态注册原理的开发者正在阅读 DroidPlugin 源码、但被 Hook 机制与预注册占坑绕晕的学习者。为什么必须先搞懂系统 Activity 的启动流程DroidPlugin 的核心能力是免安装、免修改、免重新打包运行任意第三方 APK。要实现这一点插件中的 Activity、Service、BroadcastReceiver、ContentProvider 四大组件都必须绕过系统的安装校验逻辑以宿主进程的身份动态注册、动态启动。而这一切的前提是必须先搞清楚系统四大组件的启动流程。原因很直接不了解 Activity 启动流程就不知道插件该hook 哪些东西不知道 AMS 校验组件身份的时机就不知道占坑占在哪个环节不知道 Binder 通信发生在哪两个进程之间就不知道中间人代理应该插在哪条链路上。本节系列第一篇就是先把Activity 的启动流程这一条主链路彻底讲透为后续 二Hook 机制、四Activity 预注册占坑、三系统 BroadCast 的注册发送流程、二系统 Service 的启动流程 等篇章打下地基。Activity 启动流程总览三个进程、一次 Binder 接力假设我们的应用第一个 Activity 是MyActivity它由桌面 Launcher 组件启动。需要立刻建立的认知是MyActivity组件运行在应用进程App Process中Launcher组件运行在桌面进程Launcher Process中ActivityManagerServiceAMS运行在系统进程system_process中。三个组件分别运行在三个不同的进程中因此MyActivity的启动过程**完全依赖 Binder 进程间通信IPC**来串联任何一次调用都是跨进程的请求-应答。对应到文档给出的时序图原图位于外部图床此处以文字还原时序关系Launcher → AMSLauncher 组件向 ActivityManagerService 发送一个启动 MyActivity 组件的进程间通信请求AMS → LauncherAMS 首先将要启动的 MyActivity 组件的信息保存下来然后向 Launcher 组件发送一个进入中止pause状态的进程间通信请求Launcher → AMSLauncher 进入中止状态后向 AMS 发送一个已中止的进程间通信请求以便 AMS 可以继续执行 MyActivity 组件的启动操作AMS → 新进程AMS 发现用来运行 MyActivity 组件的应用程序进程不存在于是启动一个新的应用程序进程新进程 → AMS新的应用程序进程启动完成后向 AMS 发送一个启动完成的进程间通信请求以便 AMS 可以继续执行启动 MyActivity 组件的操作AMS → 新进程AMS 将第 2 步保存下来的 MyActivity 组件信息发送给第 4 步创建的应用程序进程由该进程将MyActivity真正启动起来。从插件化的视角看这 6 个步骤中藏着两个最值得做手脚的消息交换点第 1 步应用进程内发起startActivity()→ Binder 调用 AMS此时 Intent 携带的是插件 Activity 的真实组件名AMS 会因为该组件未在宿主 Manifest 注册而拒绝启动 —— 所以这里需要 HookstartActivity()把真实 Intent 替换成占坑 Stub Activity 的 Intent第 6 步AMS 回调应用进程通过ActivityThread.H派发LAUNCH_ACTIVITY消息此时 AMS 以为启动的是 Stub但应用进程内必须把 Stub 还原成真实插件 Activity 来创建 —— 所以这里需要 HookhandleLaunchActivity()实际是 HookH.mCallback。这两点的源码级实现正是 IActivityManagerHookHandle.java 与 PluginCallback.java 所做的事后文会逐一对照。ActivityThread.H应用进程内的消息中枢H 是什么ActivityThread是应用进程的主线程管理类内部有一个名为H的内部类。H继承自Handler重写了handleMessage(Message msg)方法负责将 AMS 发来的跨进程消息转换成对ActivityClientRecord、ServiceArgsData、ReceiverData等对象的具体处理调用。ActivityThread持有一个H类型的成员变量mH。当ActivityThread通过 Binder 收到 AMS 的scheduleLaunchActivity()调用时会向mH发送一条LAUNCH_ACTIVITY类型的消息该消息被mH处理进而调用handleLaunchActivity()。H 类中的完整消息常量表以下是原文档完整给出的H内部类消息常量定义Android 4.x 时代源码它涵盖了 Activity、Service、BroadcastReceiver、ContentProvider、Application 等几乎所有组件生命周期消息。建议通读一遍后续分析PluginCallback时会发现它是逐条照搬这份常量表的private class H extends Handler { public static final int LAUNCH_ACTIVITY 100; public static final int PAUSE_ACTIVITY 101; public static final int PAUSE_ACTIVITY_FINISHING 102; public static final int STOP_ACTIVITY_SHOW 103; public static final int STOP_ACTIVITY_HIDE 104; public static final int SHOW_WINDOW 105; public static final int HIDE_WINDOW 106; public static final int RESUME_ACTIVITY 107; public static final int SEND_RESULT 108; public static final int DESTROY_ACTIVITY 109; public static final int BIND_APPLICATION 110; public static final int EXIT_APPLICATION 111; public static final int NEW_INTENT 112; public static final int RECEIVER 113; public static final int CREATE_SERVICE 114; public static final int SERVICE_ARGS 115; public static final int STOP_SERVICE 116; public static final int REQUEST_THUMBNAIL 117; public static final int CONFIGURATION_CHANGED 118; public static final int CLEAN_UP_CONTEXT 119; public static final int GC_WHEN_IDLE 120; public static final int BIND_SERVICE 121; public static final int UNBIND_SERVICE 122; public static final int DUMP_SERVICE 123; public static final int LOW_MEMORY 124; public static final int ACTIVITY_CONFIGURATION_CHANGED 125; public static final int RELAUNCH_ACTIVITY 126; public static final int PROFILER_CONTROL 127; public static final int CREATE_BACKUP_AGENT 128; public static final int DESTROY_BACKUP_AGENT 129; public static final int SUICIDE 130; public static final int REMOVE_PROVIDER 131; public static final int ENABLE_JIT 132; public static final int DISPATCH_PACKAGE_BROADCAST 133; public static final int SCHEDULE_CRASH 134; public static final int DUMP_HEAP 135; public static final int DUMP_ACTIVITY 136; public static final int SLEEPING 137; public static final int SET_CORE_SETTINGS 138; public static final int UPDATE_PACKAGE_COMPATIBILITY_INFO 139; public static final int TRIM_MEMORY 140; public static final int DUMP_PROVIDER 141; public static final int UNSTABLE_PROVIDER_DIED 142; public static final int REQUEST_ASSIST_CONTEXT_EXTRAS 143; public static final int TRANSLUCENT_CONVERSION_COMPLETE 144; public static final int INSTALL_PROVIDER 145; }几个与插件化直接相关的消息号需要重点记忆消息值处理函数对应组件LAUNCH_ACTIVITY100handleLaunchActivity(r, null)Activity 启动RECEIVER113handleReceiver((ReceiverData) msg.obj)广播接收CREATE_SERVICE114handleCreateService((CreateServiceData) msg.obj)Service 创建BIND_SERVICE121handleBindService((BindServiceData) msg.obj)Service 绑定SERVICE_ARGS115handleServiceArgs((ServiceArgsData) msg.obj)Service startINSTALL_PROVIDER145handleInstallProvider((ProviderInfo) msg.obj)ContentProvider 安装LAUNCH_ACTIVITY 消息的分发细节H.handleMessage()中对LAUNCH_ACTIVITY分支的处理原文档完整给出case LAUNCH_ACTIVITY: { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, activityStart); ActivityClientRecord r (ActivityClientRecord)msg.obj; r.packageInfo getPackageInfoNoCheck( r.activityInfo.applicationInfo, r.compatInfo); handleLaunchActivity(r, null); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } break;三个关键动作msg.obj强转为ActivityClientRecordActivityClientRecord是H处理 Activity 生命周期时的核心数据载体内部持有intent、activityInfo、packageInfo等字段 —— 这正是后文PluginCallback用反射读写msg.obj中intent、activityInfo字段的位置getPackageInfoNoCheck(applicationInfo, compatInfo)不经过系统校验NoCheck地拿到一个LoadedApkLoadApk对象并保存在r.packageInfo中 —— 注意NoCheck这个名字它本身就暗示了系统在这里对 APK 的已安装状态是不做校验的这正是插件 APK 能被宿主进程加载的缝隙之一调用handleLaunchActivity(r, null)handleLaunchActivity(ActivityClientRecord r, Intent customIntent)将启动r所描述的 Activity 组件即前文的MyActivity内部会依次完成performLaunchActivity→Activity.onCreate等流程。从源码结构看H.handleMessage是所有 AMS → 应用进程下行消息的唯一入口。因此谁能在handleMessage之前截住消息、并能修改msg.obj里记录的组件信息谁就能狸猫换太子——这就是 DroidPlugin 攻击H.mCallback的底层逻辑。与 DroidPlugin 的源码印证两个 Hook 切入点原文档明确指出和插件相关主要在第三张图即 AMS 回调应用进程、H派发消息这一段。下面结合本仓库源码把两个切入点的实现逐一对照。切入点一HookstartActivity()把真实 Intent 换成 Stub IntentstartActivity()是应用进程发起启动请求的入口也是插件框架第一个要欺骗的前门。在 DroidPlugin 中IActivityManagerHookHandle的init()方法将startActivity映射到内部类startActivity继承自ReplaceCallingPackageHookedMethodHandler源码见 IActivityManagerHookHandle.java。其核心流程protected boolean doReplaceIntentForStartActivityAPIHigh(Object[] args) throws RemoteException { int intentOfArgIndex findFirstIntentIndexInArgs(args); if (args ! null args.length 1 intentOfArgIndex 0) { Intent intent (Intent) args[intentOfArgIndex]; // 1. 该插件 Activity 是否允许被启动补丁判定 if (!PluginPatchManager.getInstance().canStartPluginActivity(intent)) { PluginPatchManager.getInstance().startPluginActivity(intent); return false; } ActivityInfo activityInfo resolveActivity(intent); // 2. 只有目标是插件包内的 Activity 才替换 if (activityInfo ! null isPackagePlugin(activityInfo.packageName)) { ComponentName component selectProxyActivity(intent); if (component ! null) { Intent newIntent new Intent(); // 3. 设置插件 ClassLoader保证跨进程后 Intent 中的插件类可被反序列化 ClassLoader pluginClassLoader PluginProcessManager.getPluginClassLoader(component.getPackageName()); setIntentClassLoader(newIntent, pluginClassLoader); newIntent.setComponent(component); // 换成 Stub 组件 newIntent.putExtra(Env.EXTRA_TARGET_INTENT, intent); // 真实 Intent 藏进 extra newIntent.setFlags(intent.getFlags()); ... args[intentOfArgIndex] newIntent; // 替换方法参数 args[1] mHostContext.getPackageName(); // 伪装 callingPackage } } } return true; }要点拆解selectProxyActivity(intent)从预先注册好的 Stub 占坑 Activity 中挑选一个替身源码实现见 IActivityManagerHookHandle.java内部调用PluginManager.selectStubActivityInfo(intent)Env.EXTRA_TARGET_INTENT真实插件 Intent 被作为 extra 塞进新 Intent跟随 Binder 跨进程送给 AMSAMS 看到并校验的是已经注册过的 Stub 组件从而通过合法性检查setIntentClassLoader插件类的 ClassLoader 被设置到 Intent 的 extras Bundle 上保证后续getParcelableExtra还原时不会因 ClassNotFound 失败按系统版本分支beforeInvoke中根据Build.VERSION.SDK_INT JELLY_BEAN_MR2即 API 18分别调用doReplaceIntentForStartActivityAPILow与doReplaceIntentForStartActivityAPIHigh以适配不同系统版本IActivityManager.startActivity()的参数签名低版本第二个参数是resolvedType高版本第二个参数是callingPackage。切入点二HookhandleLaunchActivity()把 Stub 还原成真实插件 ActivityhandleLaunchActivity()属于ActivityThread.H是后门。DroidPlugin 采用了**中间人攻击MITM**式的做法不是直接攻击H本身而是攻击H的成员变量mCallbackHandler.Callback将mCallback替换为一个PluginCallback对象由PluginCallback负责消息分发于是可以在handleMessage()中任意拦截想要截获的消息类型达到欺骗 AMS 的目的原本的mCallback在初始化时本身为null因此替换后并不损失原有分发能力——PluginCallback在拦截处理完毕后会回落到mCallback为 null 时返回 false最终由H.handleMessage()调回真正的ActivityThread.handleLaunchActivity()。PluginCallback的完整实现见 PluginCallback.java它几乎就是仿照ActivityThread.H写的常量LAUNCH_ACTIVITY 100等与上文 H 类完全一致handleMessage()中对LAUNCH_ACTIVITY消息的拦截逻辑如下if (msg.what LAUNCH_ACTIVITY) { return handleLaunchActivity(msg); } ... if (mCallback ! null) { return mCallback.handleMessage(msg); } else { return false; // 最终回落到真正的 H.handleMessage }而handleLaunchActivity(msg)的还原动作PluginCallback.javaObject obj msg.obj; Intent stubIntent (Intent) FieldUtils.readField(obj, intent); stubIntent.setExtrasClassLoader(mHostContext.getClassLoader()); Intent targetIntent stubIntent.getParcelableExtra(Env.EXTRA_TARGET_INTENT); if (targetIntent ! null !isShortcutProxyActivity(stubIntent)) { IPackageManagerHook.fixContextPackageManager(mHostContext); ComponentName targetComponentName targetIntent.resolveActivity(mHostContext.getPackageManager()); ActivityInfo targetActivityInfo PluginManager.getInstance().getActivityInfo(targetComponentName, 0); ... FieldUtils.writeDeclaredField(msg.obj, intent, targetIntent); // 还原 Intent FieldUtils.writeDeclaredField(msg.obj, activityInfo, targetActivityInfo); // 还原 ActivityInfo }这里完成了偷梁换柱的最后一环AMS 眼里启动的是ActivityStub$P00$Standard00而应用进程实际创建的是插件 APK 里的真实 Activity且ActivityClientRecord.activityInfo也被替换为插件的ActivityInfo从而让Activity后续生命周期onCreate等都以插件 Activity 的身份执行。拦截链路的统一入口HookedMethodHandler无论是前门startActivity还是后门handleLaunchActivity最终都要落到统一的拦截执行模型上。DroidPlugin 的 Hook 机制由三层组成详见 二Hook 机制Hook.java抽象基类提供setEnable()/isEnable()开关声明onInstall()/onUnInstall()生命周期ProxyHook.java继承Hook并实现InvocationHandlerJDK 动态代理setOldObj()保存原始 Binder 对象invoke()中先查mHookHandles拿到对应方法的HookedMethodHandler再调用doHookInner(mOldObj, method, args)BaseHookHandle.java内部维护MapString, HookedMethodHandler以方法名为 key 映射处理对象由子类如IActivityManagerHookHandle在init()中填充。HookedMethodHandler.doHookInner()源码见 HookedMethodHandler.java定义了前置拦截 原始调用 后置回调三段式boolean suc beforeInvoke(receiver, method, args); // beforeInvoke 返回 true 则不再执行原始方法 Object invokeResult null; if (!suc) { invokeResult method.invoke(receiver, args); } afterInvoke(receiver, method, args, invokeResult); if (mUseFakedResult) { return mFakedResult; // 可伪造返回值例如 Activity.RESULT_CANCELED } else { return invokeResult; }前面doReplaceIntentForStartActivityAPIHigh的调用时机正是beforeInvoke内IActivityManagerHookHandle.java替换 Intent 后若bRet false还会通过setFakedResult(Activity.RESULT_CANCELED)伪造返回值、直接吞掉这次启动。占坑的备胎们ActivityStub 与 Manifest 预注册理解了前门换 Intent、后门换组件信息还需回答一个基础问题Stub 从哪里来答案写在两处源码里1. ActivityStub.java26 个每进程占坑 Activity 的类骨架ActivityStub.java 定义了一个抽象类ActivityStub extends Activity其内部按进程 × 启动模式组织了大量空实现子类public abstract class ActivityStub extends Activity { private static class SingleInstanceStub extends ActivityStub {} private static class SingleTaskStub extends ActivityStub {} private static class SingleTopStub extends ActivityStub {} private static class StandardStub extends ActivityStub {} // p1第 0 个进程组 public static class P00 { public static class SingleInstance00 extends SingleInstanceStub {} public static class SingleTask00 extends SingleTaskStub {} public static class SingleTop00 extends SingleTopStub {} public static class SingleInstance01 extends SingleInstanceStub {} ... public static class Standard00 extends StandardStub {} } // p2 ~ p9 结构相同P01 ~ P08 ... public static class Dialog { ... } // Dialog 专用占坑组 }从源码结构可以统计出每层P00~P08共9 个进程组组内包含 4 种启动模式standard、singleTop、singleTask、singleInstance的占坑类Dialog组额外提供 Dialog 型 Activity 的占坑 —— 因为 Dialog 建立在 Activity 之上若承载它的 Activity 被 finish/destroy会抛出android.view.WindowManager$BadTokenException: Unable to add window ... token ... is not valid; is your activity running?所以需要独立占坑保护所有 Stub 都直接继承Activity天然拥有 Activity 的全部身份特征Intent、launchMode、theme 等这正是文档中天然的冒充的含义。2. AndroidManifest.xml预注册的占坑声明占坑必须在安装期就完成即宿主 APK 的 Manifest 中预注册大量 Stub Activity。仓库主工程 Manifest 位于 project/Libraries/DroidPlugin/src/main/AndroidManifest.xml典型的 Stub 注册片段如下activity android:name.stub.ActivityStub$P00$Standard00 android:allowTaskReparentingtrue android:excludeFromRecentstrue android:exportedfalse android:hardwareAcceleratedtrue android:labelstring/stub_name_activity android:launchModestandard ... /关键属性含义与原文档第四篇的讲解一致见 四Activity 预注册占坑属性含义与作用android:nameStub Activity 的全限定名如.stub.ActivityStub$P00$Standard00必须是 Manifest 已声明的组件AMS 才会放行android:launchMode占坑 Activity 的启动模式与插件 Activity 的目标 launchMode 一一对应standard / singleTop / singleTask / singleInstance保证插件 Activity 的启动行为不被破坏android:allowTaskReparentingtrue允许 Task 重归属一个原本属于 task1 的 Activity若 task2 启动起来可能不再属于 task1 而投奔 task2用于保证插件 Activity 的 Task 行为与真实一致android:excludeFromRecentstrue占坑 Activity 不显示在最近任务列表中避免用户看到大量空壳Activityandroid:exportedfalse不对外部应用暴露占坑组件只供宿主内部使用android:hardwareAcceleratedtrue开启硬件加速用 GPU 在 View 画布上执行绘制代价是占用更多内存DroidPlugin 为占坑 Activity 默认开启以贴近真实插件 Activity 的渲染环境android:labelstring/stub_name_activityStub 的显示标签避免暴露占坑痕迹3. 坑位回收RunningActivities 的滚动淘汰占坑数量有限若所有 Stub 都被占用却不释放后续插件 Activity 将无坑可用。因此 DroidPlugin 维护了运行时 Activity 台账 —— RunningActivities.javaonActivtyCreate()/onActivtyDestory()按 launchMode 分桶记录正在运行的 Stub ↔ 目标 Activity映射beforeStartActivity()每次startActivity被拦截时调用遍历台账对非standard模式的占坑记录执行doFinshIt(...)doFinshIt()当某模式占坑 Activity 数量≥PluginManager.STUB_NO_ACTIVITY_MAX_NUM - 1时按index排序后把最早进入的那一个finish()掉源码中STUB_NO_ACTIVITY_MAX_NUM取值为 4即单个模式最多并行占用 3 个坑——坑就那么多占着不干活就让它滚蛋。private static void doFinshIt(MapInteger, RunningActivityRecord runningActivityList) { if (runningActivityList ! null runningActivityList.size() PluginManager.STUB_NO_ACTIVITY_MAX_NUM - 1) { ListRunningActivityRecord activitys new ArrayList(runningActivityList.size()); activitys.addAll(runningActivityList.values()); Collections.sort(activitys, sRunningActivityRecordComparator); // index 升序最老的排最前 RunningActivityRecord record activitys.get(0); if (record.activity ! null !record.activity.isFinishing()) { record.activity.finish(); // 淘汰最老的占坑 Activity } } }串联全链路一次插件 Activity 启动的瞒天过海全程将上述所有片段串起来插件 Activity 从startActivity到onCreate的完整链路如下可对照文档第四篇的Activity 预注册占坑整体流程图见 四Activity 预注册占坑应用进程调用startActivity(intent)Intent 指向插件 APK 内的真实 Activity该调用被 DroidPlugin 通过IActivityManagerHook代理 AMS Binder拦截startActivityhandler 的beforeInvoke执行RunningActivities.beforeStartActivity()回收占满的坑位用selectProxyActivity()选出一个 Stub如ActivityStub$P00$Standard00构造新 IntentsetComponent(Stub)putExtra(EXTRA_TARGET_INTENT, 真实Intent)并替换args[]中的原始 Intent被替换后的 Intent 通过 Binder 到达 AMSAMS 校验通过Stub 已注册将ActivityClientRecord信息保存、通知 Launcher 暂停、创建/复用应用进程AMS 通过scheduleLaunchActivity把LAUNCH_ACTIVITY消息发回应用进程的ActivityThread.mH消息先落入被替换过的mCallbackPluginCallback若插件进程尚未绑定插件管理服务PluginCallback会延迟 5ms 重发消息以保证时序源码见 PluginCallback.java拦截LAUNCH_ACTIVITY从msg.obj.intent的EXTRA_TARGET_INTENT中取出真实插件 Intent并把msg.obj的intent、activityInfo反射写回为插件组件信息返回 false 回落到真正的H.handleMessage()→handleLaunchActivity()→performLaunchActivity()此时创建的是插件 APK 的真实 Activity——启动成功且 AMS 全程被蒙在鼓里。总结本篇文章作为系列的第一篇完成了三件事讲透系统 Activity 启动流程Launcher 进程、AMS 进程、应用进程三者通过 Binder 完成请求启动 → 保存信息 → 暂停旧界面 → 创建新进程 → 发送组件信息 → 启动 Activity的六步接力定位两个 Hook 切入点应用进程侧的startActivity()前门换 Intent与ActivityThread.H的LAUNCH_ACTIVITY消息后门换组件信息并说明H.mCallback为什么是中间人攻击的理想靶点对照 DroidPlugin 仓库源码验证了占坑与还原的完整实现ActivityStub的 9 进程 × 4 启动模式占坑骨架、Manifest 预注册的关键属性、IActivityManagerHookHandle.startActivity的 Intent 替换逻辑、PluginCallback.handleLaunchActivity的组件还原逻辑以及RunningActivities的坑位回收机制。在此基础上可以继续深入插件开发之360 DroidPlugin源码分析二Hook机制Hook 基类、ProxyHook动态代理、BaseHookHandle方法名映射与HookedMethodHandler三段式拦截的完整剖析插件占坑四大组件动态注册前奏二 系统Service的启动流程 与 三系统BroadCast的注册发送流程Service 与广播的启动/注册链路同样是占坑的前提插件开发之360 DroidPlugin源码分析四Activity预注册占坑Activity 占坑从 Manifest 到欺骗 AMS 的完整流程与整体流程图关键源码IActivityManagerHookHandle.java、PluginCallback.java、ActivityStub.java、RunningActivities.java、AndroidManifest.xml。理解了系统怎么启动 Activity下一篇再来看 DroidPlugin 如何在Service、BroadcastReceiver、ContentProvider上复制这套占坑 换身份的打法就会顺理成章。赞分享移动开发插件系统【免费下载链接】DroidPluginA plugin framework on android,Run any third-party apk without installation, modification or repackage项目地址https://gitcode.com/gh_mirrors/dro/DroidPlugin点击查看免费下载相关推荐一文读懂RIOT OS启动流程从硬件初始化到应用运行的完整指南一文读懂RIOT OS启动流程从硬件初始化到应用运行的完整指南 RIOT是面向物联网的友好操作系统以其轻量级和模块化设计著称。本文将深入解析RIOT OS的物联网嵌入式操作系统实时系统ArchiveBox 插件 Hook 系统源码级解析事件驱动、Hook 发现与 JSONL 输出协议ArchiveBox 插件 Hook 系统源码级解析事件驱动、Hook 发现与 JSONL 输出协议 本指南以 archivebox/plugins/hook后端数据工程DroidPlugin Service 插件化深度解析从系统工作原理到代理分发实现DroidPlugin Service 插件化深度解析从系统工作原理到代理分发实现 导读Service 是 Android 四大组件中最适合手动控制生命周移动开发插件系统上一篇Kubernetes SIG Network 贡献指南从代码提交到 KEP 增强提案的完整路径下一篇es-toolkit memoize 深入解析函数记忆化缓存的使用与源码实现创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

SpringBoot电影票预订系统源码拆包:从跑通到答辩避坑全指南

SpringBoot电影票预订系统源码拆包:从跑通到答辩避坑全指南

简介:这是一套面向Java方向毕业设计场景的电影票预订系统完整源码,采用SpringBoot与MyBatis构建后端,MySQL存储数据,适合正在准备毕设或需要实战项目练手的计算机专业学生与初级开发者。系统分为前台与后台两大模块:前…

2026/10/9 13:07:53 阅读更多 →
Cobalt 视频下载工具:3 步自建,贴链接秒存无水印素材

Cobalt 视频下载工具:3 步自建,贴链接秒存无水印素材

Cobalt 视频下载工具:3 步自建,贴链接秒存无水印素材 【免费下载链接】cobalt best way to save what you love 项目地址: https://gitcode.com/GitHub_Trending/cob/cobalt 想存个视频,总得先找个下载器网站,忍两屏弹窗广…

2026/10/9 13:02:40 阅读更多 →
Chrome DevTools MCP MCP 服务说明文档

Chrome DevTools MCP MCP 服务说明文档

1. 服务概述一句话简介:让您的编码代理(如Gemini、Claude、Cursor或Copilot)控制和检查实时Chrome浏览器,通过Chrome DevTools提供可靠的自动化、深度调试和性能分析。服务名称:Chrome DevTools MCP版本号:…

2026/10/11 8:01:48 阅读更多 →

最新新闻

分页查询接口实战:从MySQL优化到数据权限设计

分页查询接口实战:从MySQL优化到数据权限设计

1. 需求拆解与接口设计思路先说下背景。我手上的项目是一个面向个人用户的成长管理类应用,里面有一个核心模块叫“学习计划”,用户可以创建自己的学习计划,比如“三个月完成英语词汇量突破”、“每天刷一道算法题”之类的,然后系统…

2026/10/19 5:34:24 阅读更多 →
蓝牙AoA室内定位原理与实战:从相位差到亚米级精度

蓝牙AoA室内定位原理与实战:从相位差到亚米级精度

从蓝牙5.1规范正式引入测向功能开始,蓝牙AoA(到达角)定位就一直被圈内人当成“下一代室内定位技术”来看待。但说实话,在真正上手跑过完整系统之前,我对它更多是观望态度——毕竟从纸面参数到现场稳定可是隔着无数坑。…

2026/10/19 5:34:24 阅读更多 →
2026年10月12日AI行业日报:模型选型与工程落地关键信号

2026年10月12日AI行业日报:模型选型与工程落地关键信号

每天早上打开信息流,AI行业的标题几乎能把人淹没。今天是2026年10月12日,我照例把过去24小时里真正值得看的东西挑出来,做一份能直接指导工作的行业简报。这份简报不是新闻列表,也不是热搜搬运,而是我从技术选型、工程…

2026/10/19 5:34:24 阅读更多 →
AI每日简报:模型成本下探、Agent落地、端侧算力升级与安全治理

AI每日简报:模型成本下探、Agent落地、端侧算力升级与安全治理

每天整理AI行业信息已经成了我的固定动作。这份2026年10月12日的每日简报,信息量比往常更大:模型调用成本又降了一档,Agent开始真正接进业务流程,端侧芯片的算力参数也在今天集中更新。如果你关心的是“AI行业今天到底发生了什么、…

2026/10/19 5:34:24 阅读更多 →
数码管小数点精准识别:OpenCV手工特征+SVM实战方案

数码管小数点精准识别:OpenCV手工特征+SVM实战方案

简介:本资源是一套完整的基于OpenCV的数码管数字识别系统毕设方案,专为计算机、自动化、电子信息等专业学生设计,解决工业仪表图像中带小数点的七段数码管数字自动识别难题,适用于毕业设计、课程设计及AI视觉入门实践。压缩包共18…

2026/10/19 5:34:24 阅读更多 →
B站评论数据抓取全攻略:从接口解析到分页存储与合规实践

B站评论数据抓取全攻略:从接口解析到分页存储与合规实践

1. 先想清楚:评论抓取到底能干什么做评论抓取这件事,最开始往往是出于很朴素的需求。我自己第一次动手,是因为要给一个视频做选题分析,想知道观众对某个话题的真实反应,而不是只看弹幕里刷的几句话。评论区比弹幕更有信…

2026/10/19 5:33:23 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介:本资源为电源技术方向的学术研究文档,聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略,面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址: https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集,共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →