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),仅供参考