当前位置: 首页 > article >正文

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

背景:

近来有学员朋友在马哥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

    @Nullable
    public 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//代码1
	android::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.kler.cn/a/509728.html

相关文章:

  • LLM - 大模型 ScallingLaws 的 C=6ND 公式推导 教程(1)
  • 登录校验Cookie、Session、JWT
  • 微透镜阵列精准全检,白光干涉3D自动量测方案提效70%
  • 单元测试与unittest框架
  • 01.17周五F34-Day58打卡
  • MySQL-索引
  • 数据结构(初阶)(一)----算法复杂度
  • 《leetcode-runner》【图解】【源码】如何手搓一个debug调试器——架构
  • Web自动化:Cypress 测试框架概述
  • 自己造轮子-基于Ceres的GNSS-INS松耦合组合导航算法
  • 为AI聊天工具添加一个知识系统 之51 从形态学简约到纯粹的思维再映射到AI操作系统 之2
  • liunx进程函数汇总(包含管道、共享内存、消息队列、信号)
  • 浅谈计算机网络03 | 现代网络组成
  • 【tailscale 和 ssh】当服务器建立好节点,但通过客户端无法通过 ssh 连接
  • PostgreSQL-WAL日志介绍(一)
  • 蓝桥杯训练—矩形面积交
  • 服务器怎样防范网络蠕虫病毒?
  • Ubuntu 手动安装 Open WebUI 完整指南
  • 《CPython Internals》阅读笔记:p221-p231
  • 【优化算法】狭义相对论搜索算法:一种基于狭义相对论物理学的元启发式方法
  • 《内网穿透:开启网络世界的任意门》
  • 于灵动的变量变幻间:函数与计算逻辑的浪漫交织(下)
  • 20250118拿掉荣品pro-rk3566开发板上Android13下在uboot和kernel启动阶段的Rockchip这个LOGO标识
  • 深入浅出JSON:数据交换的轻量级解决方案
  • OpenVela——专为AIoT领域打造的开源操作系统
  • SpringBoot3 升级介绍