ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Android 进程间通信实战:Intent、Messenger、AIDL 配 TaoToken 统一 Key 通道

Android 进程间通信实战:Intent、Messenger、AIDL 配 TaoToken 统一 Key 通道 1. 为什么 Android 进程间通信总在真机联调时翻车Android 进程间通信IPCInter-Process Communication底层跑的是 Binder 机制应用层能摸到的入口其实就三类Intent 传参、Messenger 轻量跨进程、AIDL 复杂接口。Intent 适合 Activity/Service/Broadcast 之间丢一个 Bundle 就走Messenger 把 Binder 包了一层 Handler-Message天然串行、不用自己写 .aidlAIDL 则是给需要并发、需要回调、需要传 Parcelable 对象的场景准备的。问题在于很多人本地写完 Demo 能跑一上真机、一拆多进程就出问题android:process配了但 Service 没起来、Messenger 的replyTo忘了赋值导致服务端回不了消息、AIDL 的 Parcelable 类没在 aidl 目录声明、oneway用错方向把主线程卡死。更麻烦的是跨进程调用往往还要连一个远端能力比如模型推理、统一鉴权Key 散落在客户端和服务端两边联调时根本不知道是哪一段断的。这篇就按 Intent → Messenger → AIDL 三条链路把可复制的 AndroidManifest、AIDL 骨架、Messenger 回调配置全部给出来并且用 TaoToken 的统一 Key/API 通道做一次端到端验证让你能确认「跨进程调用 远端请求」整条链路是通的。适合已经会写 Service、但一碰多进程就头大的 Android 开发者。2. 先把 TaoToken 的 Key 通道准备好跨进程验证最怕的就是「本地 IPC 通了但远端 401」。所以先把统一 Key 通道搭好后面三条链路都复用它。TaoToken 在这里扮演的角色是你不需要在客户端和服务端各维护一套模型/接口凭证而是通过一个统一的 API 通道去调用。官网入口在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 基址是 https://taotoken.net/api 这个不加 UTM。操作顺序是这样的先登录控制台 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 在 API Keys 页面 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 创建一个 Key。这个 Key 就是后面客户端跨进程调用时携带的凭证。注意Key 不要硬编码进 AIDL 接口或 Messenger 的 Bundle 里明文传递。正确做法是客户端持有 Key服务端只负责转发请求或者由服务端统一持有、客户端通过 IPC 请求服务端代发。本文为了演示链路采用「客户端持有 Key通过 IPC 把请求参数传给服务端服务端代发」的方式。如果你只是想先验证模型通道是否可用可以直接用模型对话页面 https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 发一条消息确认 Key 有效。长期做编码类 Agent 的可以看 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。接入细节都在文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里。3. 可复制配置Manifest、Messenger 回调、AIDL 骨架3.1 AndroidManifest 多进程配置先让 Service 跑在独立进程这是所有 IPC 验证的前提。manifest xmlns:androidhttp://schemas.android.com/apk/res/android packagecom.example.ipcdemo application android:allowBackuptrue android:labelIpcDemo !-- 主进程 Activity -- activity android:name.ClientActivity intent-filter action android:nameandroid.intent.action.MAIN / category android:nameandroid.intent.category.LAUNCHER / /intent-filter /activity !-- Messenger 服务端独立进程 -- service android:name.messenger.ServerService android:process:remote_messenger android:exportedfalse / !-- AIDL 服务端独立进程 -- service android:name.aidl.AidlServerService android:process:remote_aidl android:exportedfalse intent-filter action android:namecom.example.ipcdemo.aidl.ServerService / /intent-filter /service /application /manifestandroid:process:remote_messenger里的冒号表示这是应用私有进程不会和其他应用冲突。exportedfalse保证只有本应用能绑定避免被外部应用调用。3.2 Messenger 回调配置Messenger 的核心是「双向信使」客户端拿服务端的 Messenger 发请求服务端拿客户端的replyTo回消息。服务端代码public class ServerService extends Service { public static final int MSG_BIND 0; public static final int MSG_SAY_HELLO 1; private Messenger sMessenger; private Messenger cMessenger; Override public void onCreate() { super.onCreate(); sMessenger new Messenger(new ServerHandler(getMainLooper())); } Override public IBinder onBind(Intent intent) { return sMessenger.getBinder(); } class ServerHandler extends Handler { ServerHandler(Looper looper) { super(looper); } Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_BIND: cMessenger msg.replyTo; reply(MSG_BIND, server: connect succeed); break; case MSG_SAY_HELLO: reply(MSG_SAY_HELLO, server: hello from remote process); break; default: super.handleMessage(msg); } } } private void reply(int what, String content) { if (cMessenger null) return; Bundle data new Bundle(); data.putString(content, content); Message msg Message.obtain(null, what); msg.setData(data); try { cMessenger.send(msg); } catch (RemoteException e) { e.printStackTrace(); } } }客户端绑定并设置replyToprivate final ServiceConnection conn new ServiceConnection() { Override public void onServiceConnected(ComponentName name, IBinder service) { sMessenger new Messenger(service); cMessenger new Messenger(new ClientHandler(getMainLooper())); Message msg Message.obtain(null, ServerService.MSG_BIND); msg.replyTo cMessenger; // 关键把客户端信使交给服务端 try { sMessenger.send(msg); } catch (RemoteException e) { e.printStackTrace(); } } Override public void onServiceDisconnected(ComponentName name) { sMessenger null; } };msg.replyTo cMessenger这一行是 Messenger 双向通信的命门漏了它服务端就永远回不了消息。3.3 AIDL 接口骨架AIDL 适合需要并发和回调的场景。先定义 Parcelable 和接口。QCEmail.aidlpackage com.example.ipcdemo.aidl; parcelable QCEmail;ICallback.aidl用oneway让服务端通知客户端时不阻塞package com.example.ipcdemo.aidl; oneway interface ICallback { void onEmailReceived(String subject); }IServer.aidlpackage com.example.ipcdemo.aidl; import com.example.ipcdemo.aidl.QCEmail; import com.example.ipcdemo.aidl.ICallback; interface IServer { QCEmail getEmail(); boolean registerCallback(ICallback callback); boolean unregisterCallback(); }对应的QCEmail.java必须实现 Parcelable并且放在和 aidl 相同的包路径下public class QCEmail implements Parcelable { public String subject; public String body; public QCEmail(String subject, String body) { this.subject subject; this.body body; } protected QCEmail(Parcel in) { subject in.readString(); body in.readString(); } public static final CreatorQCEmail CREATOR new CreatorQCEmail() { Override public QCEmail createFromParcel(Parcel in) { return new QCEmail(in); } Override public QCEmail[] newArray(int size) { return new QCEmail[size]; } }; Override public int describeContents() { return 0; } Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(subject); dest.writeString(body); } }服务端实现 Stubpublic class AidlServerService extends Service { private QCEmail mEmail new QCEmail(welcome, hello from aidl); private ICallback mCallback; private final IServer.Stub mBinder new IServer.Stub() { Override public QCEmail getEmail() { return mEmail; } Override public boolean registerCallback(ICallback callback) { mCallback callback; return mCallback ! null; } Override public boolean unregisterCallback() { mCallback null; return true; } }; Override public IBinder onBind(Intent intent) { return mBinder; } }客户端通过IServer.Stub.asInterface(service)拿到接口就能像调本地方法一样跨进程调用。4. 验证请求跑通一次跨进程 远端调用三条链路里AIDL 最能体现「复杂接口 回调」所以用它做端到端验证。思路是客户端通过 AIDL 请求服务端服务端拿到请求后用 TaoToken 的统一 Key 通道发一次远端请求再把结果通过回调返回客户端。服务端在getEmail()里发起远端请求这里用 OkHttp 示意Override public QCEmail getEmail() { try { Request request new Request.Builder() .url(https://taotoken.net/api/v1/chat/completions) .addHeader(Authorization, Bearer BuildConfig.TAOTOKEN_KEY) .addHeader(Content-Type, application/json) .post(RequestBody.create( {\model\:\gpt-4o-mini\,\messages\:[{\role\:\user\,\content\:\ping\}]}, MediaType.parse(application/json))) .build(); Response response client.newCall(request).execute(); String body response.body() ! null ? response.body().string() : ; return new QCEmail(remote-ok, body); } catch (IOException e) { return new QCEmail(remote-fail, e.getMessage()); } }客户端调用并打印结果private final ServiceConnection aidlConn new ServiceConnection() { Override public void onServiceConnected(ComponentName name, IBinder service) { IServer server IServer.Stub.asInterface(service); try { server.registerCallback(new ICallback.Stub() { Override public void onEmailReceived(String subject) { Log.d(IPC, callback: subject); } }); QCEmail email server.getEmail(); Log.d(IPC, subject email.subject , body email.body); } catch (RemoteException e) { e.printStackTrace(); } } Override public void onServiceDisconnected(ComponentName name) {} };绑定服务Intent intent new Intent(this, AidlServerService.class); bindService(intent, aidlConn, Context.BIND_AUTO_CREATE);跑起来后Logcat 里应该能看到subjectremote-ok说明 AIDL 跨进程调用成功并且服务端用统一 Key 通道拿到了远端响应。如果只想快速确认模型通道本身是否通可以先用模型对话页面 https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 发一条消息对照。5. 本篇常见错排查Service 没起来onServiceConnected不回调。先看 Manifest 里android:process是否写对再看bindService的 Intent 是否匹配。AIDL 服务端如果用了intent-filter客户端用显式 Intent 更稳。Messenger 服务端收不到消息。九成是msg.replyTo没赋值或者客户端cMessenger在onServiceConnected之前就用了。把cMessenger的初始化放在绑定之前。AIDL 编译报couldnt find import for class。Parcelable 类的 .aidl 声明文件和 .java 实现必须在同一包路径下且 .java 要能被编译到。检查src/main/aidl和src/main/java的包名是否一致。oneway用反了导致主线程卡顿。oneway只加在不需要等待返回值的接口上比如服务端通知客户端的回调。如果加在需要返回值的getEmail()上编译直接报错。远端请求 401。检查 Key 是否从 API Keys 页面 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 正确复制Header 里Bearer后面有没有多余空格。接入细节对照文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。跨进程传大对象 OOM。Binder 事务缓冲区约 1MBParcelable 对象别塞大图或长文本超过就改用文件或 ContentProvider 中转。6. 选型建议与后续接入Intent 适合一次性传参和启动组件别拿它做高频通信Messenger 适合请求量不大、不需要并发的场景代码量最少AIDL 适合需要并发、回调、传自定义对象的复杂接口。三条链路都可以复用同一套 TaoToken Key 通道区别只在于请求从哪个进程发出。如果你后面要把这套链路接到编码类 Agent 或长期运行的跨进程服务上可以看 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。ClaudeCode 相关的接入在 https://taotoken.net/claudecode-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecode-anthropicutm_campaignrewrite 。控制台 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 里可以随时轮换 Key避免把长期凭证写死在客户端。实测下来最容易踩的坑不是 AIDL 语法而是replyTo和android:process这两个配置项。把这两处盯死剩下的就是按接口签名填实现。
返回列表