系统源码版本:android5.1
ndk版本:android-ndk-r17
Android Studio版本: 3.2
硬件:核心板为64bit
需求:屏幕供应商提供升级程序cpp文件源码,操作/dev/i2c-1,调用ioctl读写数据。需要编写App,调用cpp源码相关接口,App目标平台为Android P,cpp源码与Android平台无关。由于cpp源码平台无关,因此可以采用android 5.1源码的头文件来支持jni so的编译。这样App运行到Android P上运行就不会出问题。
注意:如果cpp源码中引用了某些头文件中定义的宏,而Android P的定义与Android 5.1如果宏定义值不一致,这将会出现问题,因为宏定义在编译时就会进行替换。
方案一:NDK直接编译:
失败方案,cpp源码引用的头文件较少时可以使用。详细的编写过程,参见方案二。
1. Android.mk:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libScreenUpdateJni
LOCAL_SRC_FILES := screen_update.cpp \
core.cpp \
update.cpp \
i2c.cpp
LOCAL_LDLIBS := -lm -llog
LOCAL_CFLAGS += -I/home/xxx/sourcecode/frameworks/native/include \
-I/home/xxx/sourcecode/system/core/include
#include下添加其他的.h文件
#LOCAL_C_INCLUDES += 与LOCAL_CFLAGS += -I效果类似
LOCAL_C_INCLUDES += \
$(LOCAL_PATH) \
$(LOCAL_PATH)/include
#LOCAL_SHARED_LIBRARIES :=
include $(BUILD_SHARED_LIBRARY)
2.Application.mk:
APP_MODULES := libScreenUpdateJni
APP_ABI := armeabi-v7a
APP_PLATFORM := android-21
3.build.gradle:
defaultConfig {
ndk {
abiFilters "armeabi-v7a"
}
}
// ndk-build模式
externalNativeBuild {
ndkBuild {
// Provides a relative path to your ndkBuild script.
path file("src/main/jni/Android.mk")
}
}
这种便捷方式编译失败,原因为system/core/include下的头文件与ndk中的头文件有冲突,导致某些struct变量定义冲突。解决这些冲突,需要从源码中提取出所有cpp源码include的头文件,由于源码引用的头文件非常多,因此这个工作比较繁琐,放弃此方案。
直接采用方案二。
方案二:
Jni so由源码base中编译出,导入Android Studio中load调用。
1.编写java文件:
ScreenUpdateImpl.java:
package com.neusoft.screenupdate;
public class ScreenUpdateImpl {
public native int SynapticsRmi4Init();
public native int SynapticsRmi4FwuUpdater(String PathFwImage,int ImageFwId);
static {
System.loadLibrary("ScreenUpdateJni");
}
}
2.生成jni头文件:
在终端中生成jni头文件:
cd screenupdate/src/main/java/com/neusoft/screenupdate
javac ScreenUpdateImpl.java //这将生成对应的ScreenUpdateImpl.class
cd screenupdate/src/main
javah -d jni -classpath ./java com.neusoft.screenupdate.ScreenUpdateImp
//这将生成com_neusoft_screenupdate_ScreenUpdateImpl.h
3.编写cpp文件
将com_neusoft_screenupdate_ScreenUpdateImpl.h文件内容拷贝至screenupdate.cpp:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
#include
#include "synaptics_tp_i2c.h"
#include "synaptics_rmi4_fw_update.h"
/* Header for class com_neusoft_screenupdate_ScreenUpdateImpl */
#ifndef _Included_com_neusoft_screenupdate_ScreenUpdateImpl
#define _Included_com_neusoft_screenupdate_ScreenUpdateImpl
#ifdef __cplusplus
#define TAG "screenupdatejni" // 这个是自定义的LOG的标识
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定义LOGD类型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定义LOGI类型
extern "C" {
#endif
char* jstringToChar(JNIEnv* env, jstring jstr) {
char* rtn = NULL;
jclass clsstring = env->FindClass("java/lang/String");
jstring strencode = env->NewStringUTF("utf-8");
jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray barr = (jbyteArray) env->CallObjectMethod(jstr, mid, strencode);
jsize alen = env->GetArrayLength(barr);
jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
if (alen > 0) {
rtn = (char*) malloc(alen + 1);
memcpy(rtn, ba, alen);
rtn[alen] = 0;
}
env->ReleaseByteArrayElements(barr, ba, 0);
return rtn;
}
/*
* Class: com_neusoft_screenupdate_ScreenUpdateImpl
* Method: SynapticsRmi4Init
* Signature: ()I
*/
JNIEXPORT jint JNICALL Java_com_neusoft_screenupdate_ScreenUpdateImpl_SynapticsRmi4Init
(JNIEnv * env, jobject object){
return 0;
}
/*
* Class: com_neusoft_screenupdate_ScreenUpdateImpl
* Method: SynapticsRmi4FwuUpdater
* Signature: (Ljava/lang/String;I)I
*/
JNIEXPORT jint JNICALL Java_com_neusoft_screenupdate_ScreenUpdateImpl_SynapticsRmi4FwuUpdater
(JNIEnv * env, jobject object, jstring path, jint id){
LOGD("SynapticsRmi4FwuUpdater jni called");
return 0;
}
#ifdef __cplusplus
}
#endif
4.Android.mk:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libScreenUpdateJni
LOCAL_SRC_FILES := screen_update.cpp \
core.cpp \
update.cpp \
i2c.cpp
LOCAL_LDLIBS := -llog
#下面注释掉,因为源码base的编译环境中已经有这些include path。
#LOCAL_CFLAGS += -I/home/zjs/ecarxframework/frameworks/native/include \
-I/home/zjs/ecarxframework/system/core/include
LOCAL_C_INCLUDES += \
$(LOCAL_PATH) \
$(LOCAL_PATH)/include
#LOCAL_SHARED_LIBRARIES :=
include $(BUILD_SHARED_LIBRARY)
5.Android Studio配置
第4步中会生成/system/lib/libScreenUpdateJni.so
/system/lib64/libScreenUpdateJni.so
在App src的同级目录建立libs文件夹,建立arm64-v8a及armeabi文件夹,分别拷贝64bit及32bit的对应so至对应的文件夹。
Build.gradle:
//默认为jniLibs文件夹,其他路径需要指定。
sourceSets.main{
jniLibs.srcDirs = ['libs']
}
MainActivity.java:
package com.neusoft.screenupdate;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.Button;
public class MainActivity extends Activity {
private static final String TAG = "screenupdate";
private ScreenUpdateImpl mScreenUpdate;
private String mImagePath = "screen_update.img";
private static final int SCREEN_UPDATE = 1;
HandlerThread handlerThread = new HandlerThread("screenupdate");
Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mScreenUpdate = new ScreenUpdateImpl();
//mScreenUpdate.SynapticsRmi4Init();
Button updateBtn = (Button) findViewById(R.id.screenupdate);
handlerThread.start();
mHandler = new Handler(handlerThread.getLooper()){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case SCREEN_UPDATE:
int ret = mScreenUpdate.SynapticsRmi4FwuUpdater(mImagePath,0);
Log.d(TAG, "SynapticsRmi4FwuUpdater ret:" + ret);
break;
default:
break;
}
}
};
updateBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mHandler.sendEmptyMessage(SCREEN_UPDATE);
}
});
}
}
升级过程耗时,需要handlerthread支持。
App添加shareduserid,platform签名,保证system权限。
至此已经完成调用。
6.调试问题
权限:
/dev/i2c-1设备文件owner为root,权限600,system权限的app也无法操作。
open时的errno为-13,权限拒绝。
正常需要写有root权限的service端,客户端跨进程调用ioctl才可以。
chmod 777 /dev/i2c-1暂且解决
ioctl失败:
第一次生成app时测试,在权限设置777后,open能够成功,但是ioctl失败,原因为那次app只是使用了32bit的so,而核心板及板载系统均为64bit。正常的情况下,对Ioctl的调用,会走unlock_ioctl,但在32位系统64位的内核上面会走compat_ioctl接口,很可能是这个原因导致ioctl失败。
通过showmap pid查看App进程调用的so,发现确实是32bit so的问题。将arm64的so添加重新生成App,此时showmap可以看到调用的so的dex都是arm64的。此时调用成功。