安卓java端service如何在native进程进行访问-跨进程通讯高端知识

news/2025/1/19 15:35:39/

背景:

近来有学员朋友在马哥vip群里提出关于跨进程相关的问题,具体问题如下:
想要在纯native进程中获取当前android系统有多个display,然后获取每个display的Id。但是这个获取display的数目和id的接口其实是在systemserver进程的DisplayManagerService里面。
在这里插入图片描述
目前很多系统服务,比如wms,ams,display都是java形式作为服务端运行在systemserver,正常客户端访问肯定也是用java相关接口进行调用。但是今天需求就是需要使用native进程来跨进程访问到systemserver的DisplayManagerService。
在这里插入图片描述
简单总结需求:
需要在纯native只有c++代码的进程中跨进程访问systemserver的DisplayManagerService相关的接口getDisplayIds。
这里其实我们在跨进程通迅专题课程中有讲解过类似native和java端的相互调用情况,当时的案例是服务端是native的,客户端是java的情况
,相信大家只要实战过马哥跨进程通讯课程肯定就对这个题目有自己的思路和方案。

思路设计和参考

因为跨进程通讯本质上都是需要通过native端,虽然服务端或者客户端是java端,那都是先经过了native端再jni调用到java的,所以完全是可以实现客户端和服务端不要求两个都属于native或者java,完全可以native和java互通。
但是这个需求是要调用到DisplayManagerService的java接口,native因为不可以调用java,也就无法直接使用aidl生成的java接口。那么具体该怎么写这个native端调用服务端的接口呢?这里就需要我们拿aidl文件生成的java文件来参考,看看这个java文件是如何实现的跨进程通讯。

上一篇文章
aosp系统源码aidl文件如何查看对应生成的java文件-安卓系统开发实战小技巧分享
已经分享了如何查看系统中aidl文件对应的java文件,这里我们展示一下我们要访问的DisplayManagerService的getDisplayIds相关方法详情:

 private static class Proxy implements android.hardware.display.IDisplayManager{
//省略部分@Override public int[] getDisplayIds(boolean includeDisabled) throws android.os.RemoteException{android.os.Parcel _data = android.os.Parcel.obtain();android.os.Parcel _reply = android.os.Parcel.obtain();int[] _result;try {_data.writeInterfaceToken(DESCRIPTOR);_data.writeBoolean(includeDisabled);boolean _status = mRemote.transact(Stub.TRANSACTION_getDisplayIds, _data, _reply, 0);_reply.readException();_result = _reply.createIntArray();}finally {_reply.recycle();_data.recycle();}return _result;}

可以从上面这个java的Proxy端即客户端看出,调用一个跨进程接口getDisplayIds,本质上套路如下:
1、parcel对象会包装好相关的传递数据
2、需要知道跨进程调用的方法的code,这里的话是Stub.TRANSACTION_getDisplayIds,这里的

static final int TRANSACTION_getDisplayIds = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);

其实就是一个整数,这里是一般根据aidl编写方法的上到下顺序生成

3、调用transact方法把上面准备的parcel,code,_reply带上,这样数据到了远端就可以根据code找到对应的实现方法,同时通过parcel获取相关参数,也可以写回相关数据到_reply

4、读取跨进程返回的_reply相关数据

分析到这里我们就有了一个很清晰思路,那就要针对native端使用c++代码写出和上面客户端跨进程访问服务端代码。

实战开发:

代码参考就是上面Proxy的的getDisplayIds方法,然后变成c++形式而已,整体的流程属性,和parcel包装都可以完全参考java的,这里其实核心代码就是以下几行:

_data.writeInterfaceToken(DESCRIPTOR);
_data.writeBoolean(includeDisabled);
boolean _status = mRemote.transact(Stub.TRANSACTION_getDisplayIds, _data, _reply, 0);
_reply.readException();
_result = _reply.createIntArray();

首先就是writeInterfaceToken方法:
frameworks/base/core/java/android/os/Parcel.java

public final void writeInterfaceToken(@NonNull String interfaceName) {nativeWriteInterfaceToken(mNativePtr, interfaceName);}

frameworks/base/core/jni/android_os_Parcel.cpp

static void android_os_Parcel_writeInterfaceToken(JNIEnv* env, jclass clazz, jlong nativePtr,jstring name)
{Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);if (parcel != nullptr) {InterfaceDescriptorString descriptor(env, name);parcel->writeInterfaceToken(reinterpret_cast<const char16_t*>(descriptor.str()),descriptor.size());}
}

最后调用到Parcel.cpp
在这里插入图片描述
可以看出本质就是writeString16
writeBoolean
frameworks/base/core/java/android/os/Parcel.java

 public final void writeBoolean(boolean val) {writeInt(val ? 1 : 0);}

就是直接writeInt

readException

frameworks/base/core/java/android/os/Parcel.java

   public final void readException() {int code = readExceptionCode();if (code != 0) {String msg = readString();readException(code, msg);}}public final int readExceptionCode() {int code = readInt();//省略异常情况return code;}

也就是正常情况readException也就是readInt()获取一个返回int值而已

createIntArray

还有差异的就是java端的_reply.createIntArray()代码c++没有,这里就需要进去看看它的具体实现:
frameworks/base/core/java/android/os/Parcel.java

    @Nullablepublic final int[] createIntArray() {int N = readInt();//首先读取数组大小ensureWithinMemoryLimit(SIZE_INT, N);if (N >= 0 && N <= (dataAvail() >> 2)) {int[] val = new int[N];for (int i=0; i<N; i++) {val[i] = readInt();//然后读取int放入数组}return val;} else {return null;}}

有了上面的这些转化后,写出native的如下代码:

#define LOG_TAG "Main"#include <android-base/logging.h>
#include <android-base/properties.h> 
#include <binder/IBinder.h>
#include <binder/Parcel.h>
#include <binder/IServiceManager.h>#include <utils/Log.h>
#include <utils/threads.h>
#include "MyThread.h"using namespace android;
void testNativeCallJavaServer() {android::sp<android::IServiceManager> sm = android::defaultServiceManager();android::sp<android::IBinder> binder = sm->checkService(android::String16("display"));if (binder == nullptr) {ALOGV("Failed to get display service.");std::cerr << "Failed to get display service." << std::endl;return;}android::Parcel _aidl_data;_aidl_data.markForBinder(binder);_aidl_data.writeInterfaceToken(android::String16("android.hardware.display.IDisplayManager"));_aidl_data.writeInt32(1);android::Parcel _aidl_reply;int TRANSACTION_getDisplayIds = 2//代码1android::status_t _aidl_ret_status = binder->transact(TRANSACTION_getDisplayIds, _aidl_data, &_aidl_reply);if (_aidl_ret_status != android::NO_ERROR) {std::cerr << "Transact failed with status: " << _aidl_ret_status << std::endl;return;}int32_t resultCode;if (_aidl_reply.readInt32(&resultCode) != android::NO_ERROR || resultCode != 0) {std::cerr << "Error in response or non-zero resultCode." << std::endl;return;}size_t count = _aidl_reply.readInt32();//读取std::cerr << "Number of displays: " << count << std::endl;for (size_t i = 0; i < count; ++i) {int32_t displayId;if (_aidl_reply.readInt32(&displayId) == android::NO_ERROR) {std::cerr << "Display ID: " << displayId << std::endl;} else {std::cerr << "Failed to read displayId at index " << i << std::endl;}}
}

代码1处的int TRANSACTION_getDisplayIds = 2,注意这里的2就是从aidl对应的java文件中获得的,所有这个就是为啥要一定要找一个aidl文件生成的参考代码。

验证成果:

NX563J:/ # android_thread                                                                                                                                                                                         
Number of displays: 2
Display ID: 0
Display ID: 2

运行后输出当前display有2个,id分别0和2.

更多framework实战技术干货,请关注下面“千里马学框架”


http://www.ppmy.cn/news/1564445.html

相关文章

K8S中Pod调度之污点和容忍

污点和容忍 在 Kubernetes 中&#xff0c;污点&#xff08;Taints&#xff09;和容忍&#xff08;Tolerations&#xff09;是调度系统中的两个重要概念&#xff0c;它们允许管理员对节点&#xff08;Node&#xff09;进行标记&#xff0c;以此来影响 Pod 的调度行为。 前面的调…

leetcode刷题记录(四十八)——128. 最长连续序列

&#xff08;一&#xff09;问题描述 128. 最长连续序列 - 力扣&#xff08;LeetCode&#xff09;128. 最长连续序列 - 给定一个未排序的整数数组 nums &#xff0c;找出数字连续的最长序列&#xff08;不要求序列元素在原数组中连续&#xff09;的长度。请你设计并实现时间复…

WEB攻防-通用漏洞_XSS跨站_绕过修复_http_only_CSP_标签符号

目录 1、关卡361 - 反射型xss 2、关卡317 - 过滤标签 3、关卡318 319 - 过滤标签 4、关卡320--326 - 过滤空格和尖括号 5、关卡327 - 存储型跨站 6、关卡328 7、关卡329 - 失效凭据需1步完成所需操作 8、关卡330 - 存储型-借助修改密码URL重置管理员密码&#xff08;GE…

探索 Transformer²:大语言模型自适应的新突破

目录 一、来源&#xff1a; 论文链接&#xff1a;https://arxiv.org/pdf/2501.06252 代码链接&#xff1a;SakanaAI/self-adaptive-llms 论文发布时间&#xff1a;2025年1月14日 二、论文概述&#xff1a; 图1 Transformer 概述 图2 训练及推理方法概述 图3 基于提示的…

详解深度学习中的Dropout

Dropout是一种在神经网络训练中常用的正则化技术&#xff0c;其操作是在每次训练迭代中随机“丢弃”一部分神经元&#xff08;即将其输出置为零&#xff09;。以下是对这一操作的详细解释&#xff1a; 一、基本思想 Dropout的基本思想是减少神经元之间的复杂共适应关系&#…

Web安全|渗透测试|网络安全

基础入门(P1-P5) p1概念名词 1.1域名 什么是域名&#xff1f; 域名&#xff1a;是由一串用点分隔的名字组成的Internet上某一台计算机或计算机组的名称&#xff0c;用于在数据传输时对计算机的定位标识&#xff08;有时也指地理位置&#xff09;。 什么是二级域名多级域名…

用ChatGPT进行酒店评论情感分析

现在,许多开发人员已经使用并测试过这款聊天机器人来尝试开发他们的代码和AI想法。当然,这款聊天机器人的使用严格取决于你的背景。例如,如果你是一名Web开发人员,你会要求ChatGPT使用HTML构建一个网站。如果您是一名测试人员,您可以请求ChatGPT帮助您查找特定系统中的错误…

【AIGC-ChatGPT进阶提示词指令】心灵修复师:一个基于情感共鸣的智慧对话系统设计

引言 在当今快节奏的生活中&#xff0c;心理健康问题日益凸显。如何借助人工智能技术&#xff0c;构建一个既富有温度又专业可靠的心理支持系统&#xff0c;成为了一个值得深入探讨的课题。本文将详细介绍一个名为"心灵修复师"的对话系统设计&#xff0c;这个系统通…