android jni調用ioctl實現

系統源碼版本: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 <jni.h>
#include <android/log.h>
#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的。此時調用成功。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章