安卓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实战技术干货,请关注下面“千里马学框架”