---
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`
>
> 
## 源碼 & 核心 - 設置、編譯
以下分別先來編譯 Android 源碼、Android 核心
* 以下使用 ^1.^ Android 內核 `android-goldfish-2.6.29`
> 
* ^2.^ Android 源碼為 `android-4.0.3_r1.1`
> 
>
### 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`… 等等**
> 
而個別架構資料夾之下的 `configs` 資料夾中,又可以看到預設的 config 檔,其中就有 `goldfish_defconfig`
> 
:::
2. **設置 `.config` 為動態加載 ko 檔**:
用於 Android 模擬器的 **goldfish 內核預設 ++不允許++** 動態加載 Linux 驅動模塊,因此需要,在編譯 Linux 內核之前 **進行 `make menuconfig` 配置**
> 代表重新編譯後的 `zImage` 內核可以動態加載、卸載 Linux 驅動模塊
進入 Kernel 根目錄,並運行 `make menuconfig` 命令
```shell=
make menuconfig
```
* 配置 **設定 `Enable loadable module support` 選項**(按下空白)
> 
* 按下 `Enter` 後進入子選項,選擇如下圖的項目(`Forced module loading`、`Module unloading`、`Forced module unloading` 這三項)
> 
* 一直按 `Exit` 退出,直到最後選擇 `save configuration` 為 `Yes`
> 
### Android 內核 - 編譯產出 zImage
* 進行 Android 內核編譯
1. **下載預編譯 toolchain**
有正確的 toolchain 才可以編譯 Android 核心源碼,而不同的核心會需要不同的 toolchain… 目前使用的是 `arm-eabi-4.6`(如下)
> 
```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
```
> 
2. **設置 toolchain 路徑** 到臨時 PATH 路徑
```shell=
# 目的是將 toolchain(剛剛下載的 arm-eabi-4.6/bin) 加入 PATH
# 記得使用 export
export PATH=$PATH:$PWD/arm-eabi-4.6/bin
```
> 
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`
>
> 
### 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` 版本
```
> 
3. `make` 編譯:可以按照自己電腦的 CPU 核心數量來編譯
```shell=
make -j$(nproc)
```
> 
* Android 源碼經過編譯過後,就可以直接使用 `emulator` 命令,並啟動該源碼對應的 Android 模擬器
```shell=
# emulator 命令的位置 `out/host/linux-x86/bin/emulator`
which emulator
# 查看檔案類型
file $(which emulator)
# 運行模擬器
emulator
```
> 
Android 4 模擬器樣式
> 
## 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 系統)
>
> 
### Android 模擬器 - 動態安裝驅動
> 在看這個小節之前請先確保 ^1.^ Android source 已編譯、^2.^ Android Kernel 已編譯(可動態安裝驅動的版本)
>
> 運行模擬器時,使用自己編譯的核心;切換到 About phone 可以看到核心版本為自己設置的版本
> 
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
```
> 
2. **將 `ko` 檔丟入模擬器**:
```shell=
adb push hello_world.ko /data/local/
```
> 
3. **使用 `insmod` 指令安裝驅動**:
```shell=
# 動態安裝驅動 ko 檔
adb shell insmod /data/local/hello_world.ko
# 進入 adb
adb shell
# 查看 Android 系統的 /dev 目錄,並找到 hello_world 裝置
ls -laF /dev
```
> 
4. **對驅動輸入數據,並讀取**:
```shell=
# 在 adb 內
echo "1 2 3 Hello world" > /dev/hello_world
cat /dev/hello_world
```
> 
### 測試 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
```
最終這些檔案會編譯到
> 
3. **透過 Android source 編譯測試驅動的可執行檔案**
```shell=
mmm development/hello_world_test
```
在圖中可以看到,最終可執行檔輸出到 `<Android source>/out/target/product/generic/system/bin/` 資料夾中(`hello_world_test` 為最終的可執行檔)
> 
* **運行可執行檔 `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"
```
從結果可以看出可執行檔會運算輸入到驅動中的單字數量
> 
## 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
```
> 
### 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"
}
```
> 
:::warning
* **運行時發生 `Cannot load library: reloc_library...` 問題**?
那可能是 Android ndk 版本不符… 這要一個一個試試看,目前使用 Android NDK `16.1.4479499` 版本
> 
:::
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`
> 
:::
> 測試結果
>
> 
## 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();
}
}
}
```
> 
## Appendix & FAQ
:::info
:::
###### tags: `設備開發`