ARTICLE · 1246724
Mac 系统双击 APK 就能打开安卓应用,不用模拟器

APK 是安卓的安装包。Mac 双击它,只会得到一句「无法打开」。
想在 Mac 上用只有安卓版的软件,眼下基本只有一条路:装模拟器。几 G 的镜像,几分钟的启动,外带一堆管不好的后台进程。
有个项目换了个方向——不模拟安卓,直接把 APK 变成一个 Mac App。
装完是什么样子
你的 Mac 上会多一个图标。点它是安卓应用,但拖到 Dock、Spotlight 搜得到、Finder 里能直接删——因为它就是一个真的 .app。
打开方式也直接:双击任意 .apk(还有 .apkm、.xapk、.apks),它解包、转换、生成一个属于这个应用的 bundle,然后就能像普通程序那样启动。第一次慢一点,之后没区别。

窗口是 Mac 窗口:红绿灯旁边多一个返回键,能拖大小、能跟着系统切深浅色、能全屏。安卓 TV 应用可以用键盘操作。
装它需要什么
系统要求是硬的:
安装四步:拖进 Applications → 点 Install 装运行时 → 设置里把 .apk 关联成默认打开 → 双击 APK。
为什么卡在 26.4,下面 x18 那节会讲清楚。这不是随手写的建议值。
不用模拟器,那用什么
模拟器的做法,是在 Mac 里养一台安卓手机:系统镜像、内核、驱动整套跑在虚拟机里,应用只跟那台假手机打交道。
它反过来——让应用本身直接成为一个 Mac 进程。拆开看,一个 APK 就三样东西:Java/Kotlin 编出来的代码(打包成 dex)、可选的 C/C++ 原生库(.so)、以及图片音频等资源。这三样它分别处理。

一、dex 翻成 JVM 字节码
安卓的 dex 是给寄存器机设计的字节码,跑在 ART 上。它没有重写一个 ART,而是把这些指令翻译成标准 Java 字节码,交给内置的 OpenJDK 跑。
转换器单独开源过(dex2jvm,Apache-2.0,纯 Java,要 JDK 21)。难点不在「翻」,在翻出来的东西 JVM 肯不肯收:
• 输出 class 版本 52,带完整 StackMapTable,帧类型按真实最小上界算 • JVM 单方法上限 65,535 字节,dex 没有这个限制。直线代码提到辅助方法里,控制流复杂的拆成方法链,拆不动的整方法变成抛异常的桩 • R8 混淆产物里有 JVM 直接拒收的形态(构造函数被重定向到父类、对间接接口的 invoke-super),要逐个改写成合法等价物• 注解、泛型签名、行号表全保留,否则 Retrofit、Gson、Moshi、Kotlin reflection 当场歇菜
有人拿同一批应用横向比过:它只被校验器拒了 1 个类,enjarify 是 9,961,dex2jar 是 11,068。三者输出的 class 版本不同、校验严格度不同,这个对比偏它自己,看个量级就行。
二、原生库:64 位照跑,32 位重编译
这一条是「原生」两个字的全部底气。
arm64 的 .so,Apple Silicon 本来就是 arm64,指令照原样执行,一个字节不动。
arm32 的 .so 麻烦些——Apple Silicon 已经不能执行 32 位 ARM 代码。但它的做法不是解释执行,而是一次性重编译成 arm64 的 Mach-O dylib,之后跟普通动态库一样被 dyld 加载。
实现方式挺有意思:不生成 LLVM IR、不 fork clang、不起链接器,自己吐 arm64 机器码、自己写 Mach-O 文件头。寄存器映射把 guest 的 r0–r14 钉到宿主寄存器上,而 x18 永不分配,代码里用 static_assert 钉死。
三、系统库:自己写一套 Bionic
原生库跑起来要调 libc、libm、pthread——在安卓那边是 Bionic,跟 macOS 的 libSystem 不是一回事。它提供一层 shim 做映射,没有 Linux 内核,也不翻译 Linux 系统调用。
有个细节能说明这层的密度:ARM 的软浮点 ABI 把 double 放在 r0:r1 两个通用寄存器里,macOS arm64 的硬浮点用 d0。shim 得在调用点把这对寄存器拆开,装到浮点寄存器再调过去。
还有一个不那么显然的:C++ 静态初始化守卫 __cxa_guard_acquire,直译出来的版本会去读宿主进程的低地址布局(错的),得换成在 guest 自己的 .bss 上做位测试。
那行被 macOS 清零的寄存器
系统门槛卡在 macOS 26.4,原因落在 x18 这一个寄存器上。
ARM64 的 ABI 里 x18 是「平台保留寄存器」,给谁用由平台自己定。安卓把它给了 Bionic 当线程 TLS 基址,而 XNU 每次从内核返回用户态都会把它清零——除非进程拿着 com.apple.security.custom-x18-abi-toggle。这个 entitlement 对应的 API 是 macOS 26.4 才有的,所以 26.4 不是建议值,是地板。签名里它确实在。
更值钱的是他怎么绕过去的:Apple 那个 API 一开,当前线程就不许再回调任何 macOS 库代码,而 Bionic shim 几乎每步都要调 libSystem。于是他改走 TPIDR_EL0 的第 48 位,请内核在上下文切换时保住 x18。源码注释里留了一句 Do not "modernise" this onto the API.
我在这台 M3 Pro 上跑了一个
找了个 19 MB 的开源安卓应用(Open Sudoku),用它自带的命令行运行时直接跑,没往 Applications 里装。
几个只有跑起来才看得到的细节:

能跑什么
项目方手测了一份清单,71 个应用:58 个能用,10 个部分可用,3 个还不行。
能玩的里面有几个不太像兼容层该有的东西——Minecraft 联机进了服务器;Epic Games Store 能登录、装上 Rocket League Sideswipe 打在线对战;Asphalt Legends 走 Vulkan 能玩;Telegram、Signal、VLC、Subway Surfers 都在列。
部分可用的典型是登录卡住:Duolingo 建不了号,Thunderbird 的 Google 登录通了自己登录不通,Reddit 用 Google 登录不行。这条线的问题在 Google 账号体系——它自己实现了一部分 Play Services,对外自报为「Mac.apk 设备」,不是真的 GMS。
沙箱、alpha,和授权
它没有沙箱。 这是最该说清楚的一条。entitlements 里没有 app-sandbox,反倒装了 JIT、可写可执行内存、关掉库校验——因为要跑翻译出来的代码。结果是:安卓应用在你的 Mac 上以你的用户权限跑原生代码,能读能写你的文件,跟任何一个从网上下载来直接运行的程序一样。APK 的来源,得按这个标准挑。
它自己标着 alpha。 今天能跑不等于明天不崩,兼容表是手测的、会变。
授权是双轨的:AGPL-3.0 加商业授权。 自己用免费;公司想随产品分发得买商业授权。转换器 dex2jvm 是 Apache-2.0,可以单独拿去用。
最后
这项目真正有意思的地方,不是「Mac 能跑安卓了」——模拟器早就能,只是沉。
是它选了另一条路:不去模拟一台手机,而是把安卓应用改造成 Mac 程序。
Wine 在这条路上干了三十年,Proton 让 Linux 跑 Windows 游戏跑成了主流。Mac 跑安卓的第一个像样样本,代价是七十八万行代码,和一堆像 x18 这样只有踩进去才知道的坑。















































































