--- title: '驅動測試 - Android 模擬器系統、NDK' disqus: kyleAlien --- 驅動測試 - Android 模擬器系統、NDK === ## Overview of Content 如有引用參考請詳註出處,感謝 :cat: 在這章,我們要在 Android 模擬器上通過 Native C 程序測試 Linux 驅動 [TOC] ## Android 模擬器內核 在編譯 Android 模擬器驅動時,**必須選擇與 ++當前運行的 Android 模擬器內核版本++ 相同的 ++Android 內核++ 進行編譯** ,否則會無法安裝 Android 驅動 > 也就是說 Android 內核用哪個版本,我們的設備驅動程式就要用對應的版本編譯,才能正確安裝應用 ```mermaid graph LR; Android_驅動 --> |在 Android_kernel_2.3.4 下編譯| ko_檔 ko_檔 --> |ok| Android_裝置_A ko_檔 -.-> |fail| Android_裝置_B subgraph Android_裝置_B Android_kernel_5.5.4 end subgraph Android_裝置_A Android_kernel_2.3.4 end ``` ```shell= # 查看當前模擬器的內核版本 adb shell cat /proc/version ``` > 可以看到當前內核版本是 `Linux version 2.6.29-g46b05b2` > > ![image](https://hackmd.io/_uploads/HJugz7DLT.png) ## 源碼 & 核心 - 設置、編譯 以下分別先來編譯 Android 源碼、Android 核心 * 以下使用 ^1.^ Android 內核 `android-goldfish-2.6.29` > ![image](https://hackmd.io/_uploads/B1meXQw8a.png) * ^2.^ Android 源碼為 `android-4.0.3_r1.1` > ![image](https://hackmd.io/_uploads/Sy5vX7PIa.png) > ### Android 內核 - 設置 .config * **接下來目標是要在 Android 裝置上手動安裝驅動**,但在這之前,需要一些設置 1. **複製預設 `.config` 檔案**:每個核心都有一些預設的 `config` 檔案 ```shell= export ARCH=arm export SUBARCH=arm make goldfish_armv7_defconfig ``` :::info * **可以嘗試手動將檔案複製到根目錄** 我們可以透過複製這些檔案到內核的根目錄之下,並重新改名為 `.config` 檔案(就可以省略一些細節設置) ```shell= cp arch/arm/configs/goldfish_defconfig . mv goldfish_defconfig .config ``` * **可以看到該核心支援的架為有如下圖幾種,像是 `arm`、`x86`… 等等** > ![image](https://hackmd.io/_uploads/HkS-w7wUp.png) 而個別架構資料夾之下的 `configs` 資料夾中,又可以看到預設的 config 檔,其中就有 `goldfish_defconfig` > ![image](https://hackmd.io/_uploads/r1YqPmw86.png) ::: 2. **設置 `.config` 為動態加載 ko 檔**: 用於 Android 模擬器的 **goldfish 內核預設 ++不允許++** 動態加載 Linux 驅動模塊,因此需要,在編譯 Linux 內核之前 **進行 `make menuconfig` 配置** > 代表重新編譯後的 `zImage` 內核可以動態加載、卸載 Linux 驅動模塊 進入 Kernel 根目錄,並運行 `make menuconfig` 命令 ```shell= make menuconfig ``` * 配置 **設定 `Enable loadable module support` 選項**(按下空白) > ![image](https://hackmd.io/_uploads/ByW4dmPUp.png) * 按下 `Enter` 後進入子選項,選擇如下圖的項目(`Forced module loading`、`Module unloading`、`Forced module unloading` 這三項) > ![image](https://hackmd.io/_uploads/SksFdmDIT.png) * 一直按 `Exit` 退出,直到最後選擇 `save configuration` 為 `Yes` > ![image](https://hackmd.io/_uploads/r1dpOQwLp.png) ### Android 內核 - 編譯產出 zImage * 進行 Android 內核編譯 1. **下載預編譯 toolchain** 有正確的 toolchain 才可以編譯 Android 核心源碼,而不同的核心會需要不同的 toolchain… 目前使用的是 `arm-eabi-4.6`(如下) > ![image](https://hackmd.io/_uploads/BJ0jcmDIp.png) ```shell= # 下載 arm-eabi-4.6 git clone https://android.googlesource.com/platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.6 cd arm-eabi-4.6 # 切換分支 git checkout -b tools_r21 origin/tools_r21 ``` > ![image](https://hackmd.io/_uploads/BJ6WnQDLT.png) 2. **設置 toolchain 路徑** 到臨時 PATH 路徑 ```shell= # 目的是將 toolchain(剛剛下載的 arm-eabi-4.6/bin) 加入 PATH # 記得使用 export export PATH=$PATH:$PWD/arm-eabi-4.6/bin ``` > ![image](https://hackmd.io/_uploads/rkDJaQwUa.png) 3. **設置環境變數 `ARCH`、`SUBARCH`、`CROSS_COMPILE`** 目的是為了指定目標設備的體系架構和交叉編譯器,以下指定 `arm` 架構、指定預編譯的前置 `arm-eabi-` ```shell= # 進入源碼目錄 cd android_goldfish/ # 設備的體系架構 export ARCH=arm export SUBARCH=arm # 預編譯的前置名 export CROSS_COMPILE=arm-eabi- ``` 4. **源碼根目錄執行 `make` 編譯內核** ```shell= make clean make -j$(nproc) ``` > 最終會產出 `zImage` > > ![image](https://hackmd.io/_uploads/BJ0m1ED86.png) ### Android 源碼 - 下載、編譯、運行模擬器 * **下載 Android 源碼**: 使用 **Android 源碼 `android-4.0.3_r1.1`**,使用 Android 提供的 repo 工具下載 Android source code 1. repo 初始化、並切換到 `android-4.0.3_r1.1` 分支 ```shell= repo init -u https://android.googlesource.com/platform/manifest \ -b android-4.0.3_r1.1 ``` 2. repo 同步(下載) ```shell= repo sync -j$(nproc) ``` * **編譯 Android 源碼**: 1. **初始化環境**:在 Android 下載好的根資料夾下達以下命令 ```shell= ## 移動到下載的資料夾內 cd ~/android_source source ./build/envsetup.sh make clobber ## 編譯前清除 build 資料夾 ``` 2. **lunch 選擇編譯目標**:選擇要編譯的目標,**格式組成是 `BUILD`、`BUILDTYPE`** * Build 是指編譯完後要運行的平台 | BUILD | 目標設備 | 補充 | | -------- | -------- | -------- | | `full` | 模擬器 | 全編譯,包含所有語言、應用、輸入法... 等等 | | `full_manguro` | manguro | 全編譯,運行於 Galaxy Nexus GSM/HSPA+("manguro") | | `full_panda` | panda | 全編譯,運行於 PandaBoard("panda") | * BUILDTYPE 則分為三種類型 | BUILDTYPE | 功能特點 | | -------- | -------- | | `user` | 發佈到市場上的版本,**預設沒有 root 權限、adb 關閉** | | `userdebug` | **開放 root、adb 權限**,一般用於真機調適 | | `eng` | 擁有最大權限 (**root**),並具有額外調適工具,**一般用於模擬器** | > 有再細分為 `x86`、`x86_64`... 等等 ```shell= # 查看所有可編譯的版本類型 lunch lunch 39 # 選擇 `aosp_x86-eng` 版本 ``` > ![image](https://hackmd.io/_uploads/r1Ao9_u8p.png) 3. `make` 編譯:可以按照自己電腦的 CPU 核心數量來編譯 ```shell= make -j$(nproc) ``` > ![](https://i.imgur.com/jnWmxwD.png) * Android 源碼經過編譯過後,就可以直接使用 `emulator` 命令,並啟動該源碼對應的 Android 模擬器 ```shell= # emulator 命令的位置 `out/host/linux-x86/bin/emulator` which emulator # 查看檔案類型 file $(which emulator) # 運行模擬器 emulator ``` > ![image](https://hackmd.io/_uploads/SyFujOdIa.png) Android 4 模擬器樣式 > ![image](https://hackmd.io/_uploads/Bk5g2duLa.png) ## Android 模擬器驅動 Android 模擬器上不僅可以使用 Linux 命令測試驅動,也可以像 Ubuntu Linux 一樣,可以使用 C/C++ 程式進行測試;但有 **前提**,前提如下 * Android 模擬器、開發版、手機... **具有 root 權限** * 可執行文件需要進行 **交叉編譯**,而這要注意到 Android 核心的版本、編譯目標是 `ARM` 架構 ### Android 驅動程式 * 驅動程式的範例代碼如下 功能是用來紀錄輸入到驅動中的單字數量(這邊不特別說明驅動… 驅動的詳解請看 [**Linux 設備開發 - 概述、編譯、安裝、HelloWrold**](https://hackmd.io/SYKjLpF7Q4qLwytxeFO2FQ)) ```c= #include <linux/module.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/miscdevice.h> #include <asm/uaccess.h> #define TRUE 1 #define FALSE 0 #define DEVICE_NAME "hello_world" static unsigned char mem[10000]; static int word_count = 0; static int is_space_char(char c) { if(c == ' ' || c == 9 || c == 13 || c== 10) { return TRUE; } else { return FALSE; } } static int get_word_count(const char *buf) { if(*buf == '\0') { return 0; } int count = 1; if(is_space_char(*buf) == TRUE) { count--; } int index = 0; int is_space = 0; char curChar = ' '; for(; (curChar = *(buf + index)) != '\0'; index++) { if(is_space == 1 && is_space_char(curChar) == FALSE) { is_space = 0; } else if(is_space == 1 && is_space_char(curChar) == TRUE) { continue; } if(is_space_char(curChar) == TRUE) { count++; is_space = 1; } } if(is_space_char(*(buf + index - 1)) == TRUE) { count--; } return count; } static ssize_t word_count_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { unsigned char tmp[4]; tmp[0] = word_count >> 24; printk("--- tmp[0]: %d\n", (int) tmp[0]); tmp[1] = word_count >> 16; printk("--- tmp[1]: %d\n", (int) tmp[1]); tmp[2] = word_count >> 8; printk("--- tmp[2]: %d\n", (int) tmp[2]); tmp[3] = word_count; printk("--- tmp[3]: %d\n", (int) tmp[3]); copy_to_user(buf, (void*) tmp, 4); printk("--- count: %d\n", (int) count); printk("--- read word count: %d\n", (int) word_count); return count; } static ssize_t word_count_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { copy_from_user(mem, buf, count); mem[count] = '\0'; word_count = get_word_count(mem); printk("--- count: %d\n", (int) count); printk("--- write word count: %d\n", (int) word_count); return count; } static struct file_operations dev_fops = { .owner = THIS_MODULE, .read = word_count_read, .write = word_count_write }; static struct miscdevice misc = { .name = DEVICE_NAME, .minor = MISC_DYNAMIC_MINOR, .fops = &dev_fops }; static int hello_world_init(void) { int ret; ret = misc_register(&misc); printk("--- hello_world_init_success, ret = %d.\n", ret); return ret; } static void hello_world_exit(void) { misc_deregister(&misc); printk("--- hello_world_exit_success.\n"); } module_init(hello_world_init); module_exit(hello_world_exit); MODULE_AUTHOR("Hello author"); MODULE_DESCRIPTION("Hello world device"); MODULE_ALIAS("Hello world module."); MODULE_LICENSE("GPL"); ``` ### 編譯 Android 驅動 - ko 檔 * 要將 ko 驅檔動態安裝到 Android 系統中,需要設置、重新編譯 Android 內核,使其可以動態加載驅動檔(上面小節以說明) * 要編譯 Android 系統可安裝的 ko 檔,使用 make 指令時必須指定 `-C` 選項 > make 指令中的 `-C` 選項用於指定在讀取 makefile 檔案或執行其他操作之 **前先切換到指定的目錄** > > 而這裡我們要先切換到 Android Kernel 的目錄(目前指定的版本是 `goldfish-2.6.29` 版本) ```shell= # 先切換到 Android kernel 目錄, cd android_goldfish/ # 自動取用 Android kernel 目錄來編譯,我們的目標 ko 檔 make -C $PWD M=/home/kyle/Desktop/drivers/4_android/ ``` > 最終編譯結果… 可以得到 ko 驅動檔(之後可以用來動態加載到 Android 系統) > > ![image](https://hackmd.io/_uploads/Sko6LNwLT.png) ### Android 模擬器 - 動態安裝驅動 > 在看這個小節之前請先確保 ^1.^ Android source 已編譯、^2.^ Android Kernel 已編譯(可動態安裝驅動的版本) > > 運行模擬器時,使用自己編譯的核心;切換到 About phone 可以看到核心版本為自己設置的版本 > ![image](https://hackmd.io/_uploads/rkMMFppIT.png) 1. **運行可動態安裝驅動的 Android 內核**: 經過編譯過後的 Android source 可以直接 **運行 `emulator` 指令**… 我們只需要 **使用 `-kernel` 指定編譯過後的 `zImage` 檔** ```shell= emulator -kernel ../android_kernel/goldfish/android_goldfish/arch/arm/boot/zImage ``` 可以透過 adb 工具進入 android 模擬器中查看,是否已經運行指定的核心 ```shell= adb shell cat /proc/version ``` > ![image](https://hackmd.io/_uploads/Bkg5Y4wIT.png) 2. **將 `ko` 檔丟入模擬器**: ```shell= adb push hello_world.ko /data/local/ ``` > ![image](https://hackmd.io/_uploads/SJp95Ew8T.png) 3. **使用 `insmod` 指令安裝驅動**: ```shell= # 動態安裝驅動 ko 檔 adb shell insmod /data/local/hello_world.ko # 進入 adb adb shell # 查看 Android 系統的 /dev 目錄,並找到 hello_world 裝置 ls -laF /dev ``` > ![image](https://hackmd.io/_uploads/rJA6oVw86.png) 4. **對驅動輸入數據,並讀取**: ```shell= # 在 adb 內 echo "1 2 3 Hello world" > /dev/hello_world cat /dev/hello_world ``` > ![image](https://hackmd.io/_uploads/BJMn3VDLa.png) ### 測試 Android 驅動 - 程式 * 接下來是測試 Android 驅動(上面範例的 `hello_world` 驅動)的範例程式 以下檔案是用來讀取,輸入到 `hello_world` 驅動中的單字數量 ```c= #include <stdio.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h> #include <string.h> int main(int argc, char *argv[]) { int testDev; unsigned char buf[4]; testDev = open("/dev/hello_world", O_RDWR); if(testDev == -1) { printf("Cannot open hello_world file, did you insmod?\n"); return -1; } if(argc > 1) { char* str = argv[1]; write(testDev, str, strlen(str)); printf("The string (%s) been written.", str); } read(testDev, buf, 4); int word_count = ((int) buf[0] << 24 | (int) buf[1] << 16 | (int) buf[2] << 8 | (int) buf[3] ); printf("word byte display: %d, %d, %d, %d\n", buf[0], buf[1], buf[2], buf[4]); printf("word count: %d\n", word_count); close(testDev); return 0; } ``` ### 測試 Android 驅動 - 交叉編譯、運行 可執行檔 * **交叉編譯 Linux 程式**:需要的檔案、步驟如下 1. **編寫 `Android.mk` 檔**(編譯規則) :::warning * **`Android.mk` 檔** 以往我們在編譯程式時會撰寫 `Makefile` 檔案,來設定編譯規則、目標;而 Android 中的編譯文件則不使用 `Makefile` > Android 內的編譯規則檔案是 `Android.mk` 檔 ::: ```shell= LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) # 指定要編譯的文件 LOCAL_SRC_FILES := hello_world_test.c # 指定模塊名稱,也是編譯後生成的可執行文件名 LOCAL_MODULE := hello_world_test # 設定在哪個模式下編譯,optional 代表任何模式 LOCAL_MODULE_TAGS := optional # 建立的目標 include $(BUILD_EXECUTABLE) ``` 其中 `include $(BUILD_EXECUTABLE)` 是編譯為可執行文件 | 變量名稱 | 說明 | 補充 | | -------- | -------- | - | | `BUILD_SHARED_LIBRARY` | 編譯成 so 文件 | 可執行文件的建立路徑則是 `<Android source>/out/target/product/generic/system/lib/<驅動名>.so` | | `BUILD_EXTCUTEABLE` | 編譯為可執行模塊 | 可執行文件的建立路徑則是 `<Android source>/out/target/product/generic/system/bin/<驅動名>` | 2. **鏈結到 Android Source 編譯**: 接著為了 **讓 `.c` 程式可以編譯,並運行在 Android 模擬器上**;我們可以有以下兩種方案 * 將 **程式源碼複製** 到 `<Android 源碼目錄>` 下的子目錄 * 也可以在 `<Android 源碼目錄>` 下,建立一個 **符號連結** ```shell= # 將目錄連結到 android source 的 development 資料夾下 ln -s ~/Desktop/drivers/hello_world_test_android_emulator/ $PWD/development/test_hello_world ``` 最終這些檔案會編譯到 > ![image](https://hackmd.io/_uploads/BkLyZHD8T.png) 3. **透過 Android source 編譯測試驅動的可執行檔案** ```shell= mmm development/hello_world_test ``` 在圖中可以看到,最終可執行檔輸出到 `<Android source>/out/target/product/generic/system/bin/` 資料夾中(`hello_world_test` 為最終的可執行檔) > ![image](https://hackmd.io/_uploads/H13ifrD86.png) * **運行可執行檔 `hello_world_test`** ```shell= # 將可執行檔放入模擬器中 adb push out/target/product/generic/system/bin/hello_world_test /data/local adb shell ## -------------------------- 進入模擬器 shell cd /data/local ls -l # 執行驅動測試檔(可執行檔) ./hello_world_test "1 2 3 hello World good night" ``` 從結果可以看出可執行檔會運算輸入到驅動中的單字數量 > ![image](https://hackmd.io/_uploads/HJSb4HPIp.png) ## Android NDK 測試驅動 看這個章節的前提是 1. 啟動模擬器,並指定可以動態安裝驅動(`ko` 檔)的模擬器 ```shell= emulator -kernel ../android_kernel/goldfish/android_goldfish/arch/arm/boot/zImage -partition-size 2000 ``` 2. 測試前先手動使用 `insmod` 指令安裝驅動 ```shell= # 動態安裝驅動 ko 檔 adb shell insmod /data/local/hello_world.ko # 進入 adb adb shell # 查看 Android 系統的 /dev 目錄,並找到 hello_world 裝置 ls -laF /dev ``` > ![image](https://hackmd.io/_uploads/rJA6oVw86.png) ### Android NDK 程式、設置 1. **Android gradle 設置**(以下只列出重點部份) Android 現在預設使用 CMake 作為編譯器,而 CMake 對應的設置檔是 `CMakeList.text`;如果要使用 GNU 編譯,並使用 `Android.mk` 檔作為編譯設置檔,則須設定 `externalNativeBuild`#`ndkBuild` ```kotlin= android { ... externalNativeBuild { // 設置 NDK Build 腳本檔案 ndkBuild { path = file("src/main/cpp/Android.mk") } } // 設置 NDK 版本 ndkVersion = "16.1.4479499" } ``` > ![image](https://hackmd.io/_uploads/By4uR2aLT.png) :::warning * **運行時發生 `Cannot load library: reloc_library...` 問題**? 那可能是 Android ndk 版本不符… 這要一個一個試試看,目前使用 Android NDK `16.1.4479499` 版本 > ![image](https://hackmd.io/_uploads/H1-GyaTLp.png) ::: 2. **Android NDK 檔案** 透過 `open`(開啟)、`read`(讀取)、`write` (對裝置寫入)函數來操作裝置 ```c= #include <string.h> #include <jni.h> #include <stdio.h> #include <stdlib.h> #include <sys/types.h> // 包含了一些基本的系統資料類型的定義,例如 size_t、pid_t、off_t #include <sys/stat.h> // 文件狀態資訊的結構體定義,如 struct stat #include <unistd.h> // 對 POSIX 標準的存取支持,定義了一些通用的系統呼叫和符號常數, 包含了對檔案操作、進程控制、系統呼叫等方面的函數聲明,如read()、write()、close()、fork()等 #include <fcntl.h> // 用於控制文件描述符 const char* const TARGET_DEV = "/dev/hello_world"; char* jstring_to_point_char(JNIEnv* env, jstring str); // 嘗試開啟檔案 jint Java_com_example_hello_1world_MainActivity_devOpen(JNIEnv* env, jobject this) { int dev = open(TARGET_DEV, O_RDWR); close(dev); return (jint) dev; } // 純粹讀取 /dev/hello_world 驅動 jint Java_com_example_hello_1world_MainActivity_devBuf(JNIEnv* env, jobject this, jint index) { int dev = open(TARGET_DEV, O_RDWR); unsigned char buf[4] = {0, 0, 0, 0}; read(dev, buf, 4); int value = ((int) buf[index]); close(dev); return (jint) value; } // 讀取裝置,並取得「單字數量」 jint Java_com_example_hello_1world_MainActivity_readDev(JNIEnv* env, jobject this) { int dev = open(TARGET_DEV, O_RDWR); unsigned char buf[4] = {0, 0, 0, 0}; read(dev, buf, 4); // big endian int wordCount = ((int) buf[0]) << 24 | ((int) buf[1]) << 16 | ((int) buf[2]) << 8 | ((int) buf[3]); jint jWordCount = (jint) wordCount; close(dev); return jWordCount; } // 將 Java 應用的數據,寫入到 /dev/hello_world 裝置 jint Java_com_example_hello_1world_MainActivity_writeDev(JNIEnv* env, jobject this, jstring str) { int dev = open(TARGET_DEV, O_RDWR); char* pStr = jstring_to_point_char(env, str); if (pStr != NULL) { size_t len = strlen(pStr); write(dev, pStr, len); return (jint) len; } close(dev); return (jint) -1; } // 將 Java String 轉為 char 指標 char* jstring_to_point_char(JNIEnv* env, jstring str) { jclass clzString = (*env) -> FindClass(env, "java/lang/String"); jstring strEncode = (*env) -> NewStringUTF(env, "utf-8"); // 取得 String#getBytes(String): byte[] jmethodID getBytesMethodId = (*env) -> GetMethodID(env, clzString, "getBytes", "(Ljava/lang/String;)[B"); // String 呼叫 getBytes() 方法 jbyteArray byteArray = (jbyteArray)((*env) -> CallObjectMethod(env, str, getBytesMethodId, strEncode)); // 計算 string length jsize size = (*env) -> GetArrayLength(env, byteArray); // byteArray 轉 jbyte jbyte* pByte = (*env) -> GetByteArrayElements(env, byteArray, JNI_FALSE); char* pStr = NULL; if (size > 0) { // 動態創建空間,用來保存 Java 應用的字串 pStr = (char*) malloc(size); memcpy(pStr, pByte, size); // pByte 複製 4 byte 到 pStr } return pStr; } ``` :::warning * 無法正常開啟 `hello_world` 裝置? 開啟後 dev 為 `-1`,這可能是因為 App 權限不足的關係,所以無法開啟目標裝置;僅需要使用 `chmod` 指令,將 `/dev/hello_world` 裝置的權限放寬即可使用 ```shell= adb chmod 777 /dev/hello_world ``` ::: 3. **簡單的 Java 應用** ```java= public class MainActivity extends AppCompatActivity { private static String err = ""; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); // Example of a call to a native method binding.readDevBtn.setOnClickListener(view -> { String userInput = binding.textInput.getText().toString(); int writeCount = writeDev(userInput); Toast.makeText(this, "Write count: " + writeCount, Toast.LENGTH_LONG) .show(); StringBuffer stringBuffer = new StringBuffer(); for (int i = 0; i < 4; i ++) { stringBuffer.append("index: " + i + "(" + devBuf(i) + ")").append("\n"); } stringBuffer.append("devOpen: " + devOpen()); binding.showWordCount.setText(stringBuffer + "\nDev word count: " + readDev()); }); binding.showWordCount.setText(err); } public native int devOpen(); public native int readDev(); public native int writeDev(String string); public native int devBuf(int index); static { try { // 讀取 ndk 檔案 System.loadLibrary("ndk_hello_world"); } catch (Throwable t) { err = t.fillInStackTrace().getMessage(); } } } ``` :::info * 讀取 ndk 檔案時檔案為 `libndk_hello_world.so`,須去頭去尾轉為 `ndk_hello_world` > ![image](https://hackmd.io/_uploads/rkouWTTIa.png) ::: > 測試結果 > > ![image](https://hackmd.io/_uploads/BynU1TpUT.png) ## Android Java 程式測試驅動 如果要透過 Java 應用 API 直接讀取驅動,則需要事先 Android Root 裝置、設定驅動權限為 777(所有人皆可操作) ### Android Java 程式 * 如果已經有 Android 裝置的 Root 權限,那就可以直接使用 Java IO 提供的 API 來操作驅動 ```java= public class JavaIOApiActivity extends AppCompatActivity { private static final String DEV_NAME = "/dev/hello_world"; private ActivityMain2Binding binding; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = ActivityMain2Binding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); binding.readDevBtn.setOnClickListener(view -> { writeDev(); readDev(); }); } public void writeDev() { String inputStr = binding.textInput.getText().toString(); try(FileOutputStream fileOutputStream = new FileOutputStream(DEV_NAME)) { fileOutputStream.write(inputStr.getBytes("UTF-8")); } catch (IOException e) { e.printStackTrace(); } } public void readDev() { try(FileInputStream fileInputStream = new FileInputStream(DEV_NAME)) { byte[] buf = new byte[4]; fileInputStream.read(buf); int wordCount = ((int) buf[0]) << 24 | ((int) buf[1]) << 16 | ((int) buf[2]) << 8 | ((int) buf[3]); binding.showWordCount.setText("wordCount: " + wordCount); } catch (IOException e) { e.printStackTrace(); } } } ``` > ![image](https://hackmd.io/_uploads/ry8_Ap6Up.png) ## Appendix & FAQ :::info ::: ###### tags: `設備開發`