ตั้งค่า Android
หน้านี้รวบรวมโน้ต Android build ที่เคยกระจายอยู่หลายที่ใน repo
ตำแหน่งของแอป
- Mobile POS app:
apps/mobile - POS terminal app:
apps/pos-terminal
คำสั่งหลัก
รันจาก monorepo root:
yarn mobile:start
yarn mobile:android
yarn mobile:apk
yarn mobile:aab
yarn terminal:start
yarn terminal:android
คำสั่ง Android run, APK, AAB และ clean ใน workspace จะเรียก script แก้
compatibility ของ native dependencies ก่อนรัน React Native หรือ Gradle ทุกครั้ง
จึงรองรับกรณีคืน dependencies จาก cache โดยไม่ได้รัน postinstall ด้วย
หากเรียก ./gradlew โดยตรง ให้รัน
node scripts/patch-react-native-dependencies.js จาก repo root ก่อน
Mobile ใช้ Metro port 8081 ส่วนคำสั่ง start/run ของ Terminal ใช้ 8082 เป็นค่าเริ่มต้น
คำสั่งสำหรับเครื่องจริงคือ yarn mobile:android:8081 และ
yarn terminal:android:8082 ซึ่งเลือก 0074490208420196 และ
A65C009P00400034 ตามลำดับ โดยต้องเชื่อมต่อและอนุญาต USB debugging
ของเครื่องเป้าหมายก่อน
Desktop emulator สำหรับ Terminal (1920×1080)
yarn terminal:emu # เปิดเฉพาะ emulator
yarn terminal:android:8082:emu # เปิด รอ boot แล้ว build/install Terminal
ทั้งสองคำสั่งใช้ AVD Large_Desktop ที่มีอยู่แล้ว (1920×1080 แนวนอน 160 dpi)
และค้นหา serial ของ emulator ตัวนี้อัตโนมัติ จึงใช้ได้แม้ emulator อื่นครอง
emulator-5554 อยู่ หรือเสียบเครื่องจริงพร้อมกัน helper จะปลุกจอ ปิดหน้าล็อก
และตั้งพักจอเป็น 30 นาที หลังคำสั่งจบ emulator จะยังเปิดอยู่
log การเปิด emulator อยู่ที่ bsr-terminal-emulator.log ใน temporary directory ของระบบ
เมื่อเริ่ม emulator process ใหม่ helper จะใช้ DNS 1.1.1.1,8.8.8.8 เพื่อไม่ให้
emulator ส่งคำขอ DNS ไปยัง router เดิมเมื่อคอมเปลี่ยน Wi-Fi หากเครือข่ายต้องใช้
DNS อื่น ให้ตั้ง POS_TERMINAL_DNS_SERVERS แล้วปิด emulator และรันคำสั่งใหม่
เพื่อใช้ค่าที่เปลี่ยน โดยข้อมูลแอปยังอยู่ครบ หาก API ขึ้น Network Error
ให้ตรวจว่า Android เชื่อม AndroidWifi และแปลงชื่อโดเมน API กับ Firestore ได้
หากเครื่องอื่นยังไม่มี AVD นี้ ให้สร้างชื่อ Large_Desktop ใน Android Studio
Device Manager โดยใช้ hardware profile แบบ desktop ขนาด 1920×1080, 160 dpi
และเลือก system image ที่ตรงกับ CPU ของเครื่อง หากต้องการใช้ AVD อื่นที่มีอยู่แล้ว:
POS_TERMINAL_AVD=My_Desktop yarn terminal:android:8082:emu
helper จะ build เฉพาะ CPU architecture ของ emulator ที่ใช้อยู่เพื่อลดเวลารอ
ส่ง option ของ React Native เพิ่มได้ เช่น --no-packager โดย helper จะเลือก
device และ Metro port 8082 ให้ บน macOS หากไม่ได้ตั้ง JAVA_HOME และเรียก
java ไม่ได้ helper จะใช้ JDK ที่มาพร้อม Android Studio
เมื่อโปรเจกต์อื่นใช้พอร์ต 8081/8082 อยู่
พอร์ตเริ่มต้นยังคงเดิม หากรันพร้อมโปรเจกต์ React Native อื่น เช่น SSK Smart City ให้ใช้คำสั่งพอร์ตทางเลือก โดยเปิดคำสั่ง Metro และ Android ของแต่ละแอปจากคนละ terminal:
# Mobile: Metro และ Android ต้องใช้ 8091 ตรงกัน
yarn mobile:start:8091
yarn mobile:android:8091
# POS Terminal: Metro และ Android ต้องใช้ 8092 ตรงกัน
yarn terminal:start:8092
yarn terminal:android:8092
# หรือใช้ emulator-5554:
yarn terminal:android:8092:emu
คำสั่ง Android ทางเลือกเลือกเครื่องเดียวกับคำสั่ง 8081/8082
หากใช้เครื่องอื่น ให้รัน
ANDROID_SERIAL=<serial> yarn mobile:android --device <serial> --port 8091
หรือ ANDROID_SERIAL=<serial> yarn terminal:android --device <serial> --port 8092
คำสั่ง Android จะสร้าง ADB reverse forwarding ให้ตรงกับพอร์ตที่ระบุ
ตรวจว่า Metro ที่ใช้พอร์ตอยู่เป็นของโปรเจกต์ใดก่อนใช้งาน:
lsof -nP -iTCP:8081 -iTCP:8082 -sTCP:LISTEN
curl -i http://127.0.0.1:8081/status
curl -g -i 'http://[::1]:8081/status'
header X-React-Native-Project-Root ต้องชี้ไปยังแอปที่ต้องการ
IPv4 และ IPv6 อาจเชื่อมต่อ Metro คนละโปรเซสแม้ใช้เลขพอร์ตเดียวกัน
จึงดูเพียง packager-status:running ไม่ได้ว่าเครื่อง Android จะได้รับ bundle จากโปรเจกต์ใด
หากที่อยู่ใดเป็นโปรเจกต์อื่น ให้เลือกพอร์ตทางเลือก
รัน Mobile และ Terminal บน Android 2 เครื่อง
เมื่อเสียบ Android พร้อมกัน 2 เครื่อง ให้ระบุเครื่องเป้าหมายของแต่ละคำสั่งด้วย
--device และ ANDROID_SERIAL
ดู serial ของเครื่องที่ต่ออยู่ก่อน:
adb devices
ตัวอย่าง:
List of devices attached
R58M123456A device
ZY22ABCDE9 device
รัน Mobile POS บนเครื่องหนึ่ง และ POS Terminal บนอีกเครื่องจากคนละ terminal:
ANDROID_SERIAL=R58M123456A yarn mobile:android --device R58M123456A
ANDROID_SERIAL=ZY22ABCDE9 yarn terminal:android --device ZY22ABCDE9
คำสั่งเดียวกันเมื่อเรียกผ่าน workspace โดยตรง:
yarn workspace @bsr/mobile android --device R58M123456A
yarn workspace @bsr/pos-terminal android --device ZY22ABCDE9
--device กำหนดเครื่องเป้าหมายให้ขั้นตอนเปิดแอปและส่งต่อ port ของ React Native ด้วย
จึงไม่ควรใช้ ANDROID_SERIAL เพียงอย่างเดียวเมื่อเชื่อมต่อหลายเครื่อง
script แยก Mobile ไว้ที่ 8081 และ Terminal ที่ 8082 อยู่แล้ว
หากต้องสร้าง port forwarding ใหม่ด้วยตัวเอง ให้รัน:
adb -s R58M123456A reverse tcp:8081 tcp:8081
adb -s ZY22ABCDE9 reverse tcp:8082 tcp:8082
รันจากใน mobile app เมื่อจำเป็นต้องใช้ Gradle โดยตรง:
cd apps/mobile/android
./gradlew clean assembleRelease
./gradlew clean bundleRelease
ติดตั้ง Mobile release APK ที่ build ไว้แล้วจาก root ของ repository โดยระบุ
เครื่องให้ชัดเจน (แทน serial ตัวอย่างด้วยค่าจาก adb devices):
adb -s R58M123456A install -r apps/mobile/android/app/build/outputs/apk/release/app-release.apk
สำหรับ Terminal ให้ใช้ serial ของเครื่อง Terminal และเปลี่ยน path เป็น
apps/pos-terminal/ โดยใช้ตำแหน่ง output เดียวกัน
เครื่องมือที่ต้องมีในเครื่อง
- Node.js ที่รองรับ
>=22.11.0 - Yarn Classic
1.22.x - Java และ
JAVA_HOMEที่ชี้ไป JDK ที่ถูกต้อง - Android SDK ที่ Gradle เข้าถึงได้
- Android build-tools และ platform SDK ตามที่โปรเจกต์ต้องใช้
- Android NDK version ตามที่ Gradle คาดหวัง
adbอยู่ในPATH
ค่า signing สำหรับ release
Android app อ่านค่า release signing จาก Gradle properties:
MYAPP_UPLOAD_STORE_FILEMYAPP_UPLOAD_STORE_PASSWORDMYAPP_UPLOAD_KEY_ALIASMYAPP_UPLOAD_KEY_PASSWORD
ให้เช็กว่าค่าเหล่านี้มาจากที่ไหนในแต่ละเครื่อง:
~/.gradle/gradle.properties- project
gradle.properties - secret injection ของ CI/CD
ปัญหา APK signature ไม่ตรงตอน install
ถ้าการติดตั้ง APK ล้มเหลวด้วยข้อความ:
INSTALL_FAILED_UPDATE_INCOMPATIBLE: Existing package th.co.boonsiri.pos signatures do not match newer version
แปลว่า device มี th.co.boonsiri.pos ติดตั้งอยู่แล้ว แต่ถูก sign ด้วย key
คนละตัวกับ APK ใหม่ Android จึงไม่ยอม update ทับ
ถ้าต้องเก็บข้อมูลเดิม ให้ใช้ build ที่ลงนามด้วย key เดิมหรือทดสอบบน emulator แยกต่างหาก ถอนแอปได้เมื่อได้รับอนุญาตให้ลบข้อมูลในเครื่องแล้วเท่านั้น:
adb -s R58M123456A uninstall th.co.boonsiri.pos
adb -s R58M123456A install -r apps/mobile/android/app/build/outputs/apk/release/app-release.apk
Terminal ใช้ package th.co.boonsiri.posterminal ให้ระบุ serial และ APK ของ
Terminal โดยตรง การถอนแอปจะลบข้อมูล local และต้องเข้าสู่ระบบใหม่
R8 สำหรับ Release
Release ของ Mobile และ Terminal เปิด minifyEnabled และ shrinkResources
โดยใช้ proguard-android-optimize.txt ร่วมกับ proguard-rules.pro ของแต่ละแอป
เพื่อแก้รายงาน DEX optimization ต่ำ / obfuscation 1% ของ Mobile 0.5.24 ใน Play Console
R8 ปรับโค้ด Java/Kotlin ใน DEX การตั้งค่านี้ไม่ได้ทดแทน JavaScript bundling
หรือการตรวจ native library สำหรับ memory page ขนาด 16 KB
Keep rules ของ Time4J และ date-picker
ทั้งสองแอปเก็บ rules เฉพาะส่วนดังนี้:
-keep public class net.time4j.android.ApplicationStarter
-keep public class net.time4j.PrettyTime
-keep class net.time4j.android.spi.AndroidResourceLoader {
public <init>();
}
ระหว่างทดสอบ Release พบแอปปิดตอนเริ่มต้น พร้อมข้อความ
react-native-date-picker is not installed correctly สาเหตุจริงคือ
AndroidResourceLoader ไม่มี constructor แบบไม่รับ argument เพราะ Time4J
สร้าง loader ผ่าน reflection และ consumer rule ที่เก็บเฉพาะ class ยังไม่พอ
สำหรับ R8 full mode ใน build นี้ จึงต้องเก็บ constructor โดยตรง
ไม่แก้ด้วยการซ่อน error หรือปิด R8
Build และตรวจสอบ
จาก root ของ repository ให้รัน dependency patches แล้วเลือก build แอป:
node scripts/patch-react-native-dependencies.js
cd apps/pos-terminal/android
./gradlew :app:bundleRelease :app:assembleRelease
Mobile ใช้ apps/mobile/android ไฟล์ AAB และ APK ที่ลงนามแล้วอยู่ที่
app/build/outputs/bundle/release/app-release.aab และ
app/build/outputs/apk/release/app-release.apk ภายใต้ Android directory ของแอปนั้น
เก็บ app/build/outputs/mapping/release/mapping.txt คู่กับแต่ละ Release
สำหรับแปลง crash stack trace กลับเป็นชื่อโค้ดเดิม
ใน AAB ต้องมี BUNDLE-METADATA/com.android.tools/r8.json และ
BUNDLE-METADATA/com.android.tools.build.obfuscation/proguard.map
ตรวจ options และ stats ใน r8.json โดย coverage ที่เปิดใช้งานคือ
100 - no*Percentage ค่า coverage เป็นคนละค่ากับเปอร์เซ็นต์ขนาด DEX ที่ลดลงจริง
AAB ติดตั้งด้วย adb install โดยตรงไม่ได้ ให้ใช้
bundletool ของ Google
สร้างชุด APK ที่ลงนามจาก AAB นั้นด้วย build-apks --connected-device --device-id=...
แล้วติดตั้งด้วย install-apks --apks=... --device-id=... ระบุ release keystore
ของโปรเจกต์และอ่านรหัสผ่านจากไฟล์ส่วนตัว ไม่ใส่รหัสผ่านใน command arguments
หรือ log หากลายเซ็นไม่ตรง ให้ทำตามขั้นตอนด้านบน
ผลตรวจวันที่ 2026-09-28
| ค่าจากไฟล์ build ในเครื่อง | Mobile | Terminal |
|---|---|---|
| DEX ของ AAB เดิมในเครื่อง (bytes) | 32,714,316 | 32,905,052 |
| DEX ของ AAB หลัง optimize (bytes) | 6,208,192 | 6,299,520 |
| Optimization coverage | 80.64% | 80.66% |
| Obfuscation coverage | 81.18% | 81.19% |
| Shrinking coverage | 81.21% | 81.22% |
ทั้งสองแอป build Release AAB/APK ที่ลงนามด้วย R8 9.2.14 ผ่าน release lint
และการตรวจลายเซ็น APK พร้อม R8 metadata และ mapping ครบ ตัวเลขนี้เป็นผลจาก
ไฟล์ในเครื่อง ยังไม่ใช่ผลประเมินหลังอัปโหลดเข้า Play Console
- Mobile 525 / 0.5.25: ทดสอบเปิดแอปใหม่สองรอบ แสดงเวอร์ชันจาก native ตรวจกรอก login ไม่ครบ และสลับธีมบน Android 17 emulator ที่ใช้ page 16,384 bytes ต่อมาแปลง AAB 526 / 0.5.26 แล้วติดตั้งบนมือถือ ตรวจหน้า login และเวอร์ชันผ่าน ถอนแอปเดิมที่ลายเซ็นไม่ตรงหลังได้รับอนุญาตให้ลบข้อมูลแล้ว
- Terminal 423 / 0.4.23: ทดสอบ Release เปิดถึงหน้าเลือกสาขา และแสดงโฆษณา ด้วย native Customer Display บนจอที่สองจำลอง โดยใช้ page 16,384 bytes เช่นกัน ไม่พบ ReactNativeJS/AndroidRuntime error ใน process สุดท้ายที่ตรวจ
- หลัง push งาน Terminal รัน
yarn terminal:android:8082เพื่อ build และติดตั้ง Debug บน C9D และเชื่อม Metro ที่เปิดอยู่บน port8082การติดตั้ง Debug เป็นคนละการตรวจจาก R8 Release ซึ่งใช้ JavaScript ที่ bundle มาโดยไม่ต้องใช้ Metro - ยังต้องตรวจงานขายหลังเข้าสู่ระบบ การกด date-picker สแกน พิมพ์ การแจ้งเตือน ธุรกรรมครบทั้งสองจอ และผลประเมินบน Play Console
- ยังมี warning ที่ไม่หยุด build จาก
play-services-auth:21.5.0(stack maps) และ Time4J (ไม่ใช้-useuniqueclassmembernames) ให้ตรวจซ้ำเมื่ออัปเดต dependency
อ้างอิง implementation: Mobile 3245dc66, เพิ่มเวอร์ชัน 25b2fa23, merge
เข้า uat e343b482 และ Terminal บน uat 26f222ae
โน้ตการอัปเกรด React Native core
Android baseline ปัจจุบันของทั้งสองแอปคือ:
- React Native
0.87.1โดยเปิด new architecture และ Hermes - Build Tools และ compile SDK
37; target SDK36; minimum SDK24 - NDK
28.2.13676358, Kotlin2.2.10และ Gradle9.4.1 - Node
>=22.11.0; การทดสอบบนเครื่องจริงรอบนี้ใช้ Node26.7.0ผ่าน nvm
เมื่ออัปเกรด React Native core ให้ใช้ React Native Upgrade Helper เป็น baseline ของ template diff เช่น:
https://react-native-community.github.io/upgrade-helper/?from=0.85.2&to=0.87.1
ห้าม copy template ทับ repo นี้ตรง ๆ เพราะ mobile app มี native configuration
เฉพาะโปรเจกต์ที่ต้องรักษาไว้ เช่น monorepo paths ใน android/app/build.gradle,
explicit Node/Hermes paths, font linking ของ react-native-vector-icons,
Firebase Google Services, release signing, app versioning และ Gradle task
workaround ของ Reanimated/Worklets
React Native 0.87 ใช้ Android Gradle Plugin 9 ให้คง
android.builtInKotlin=false และ android.newDsl=false ไว้ จนกว่า native
dependencies ที่เหลือจะเลิกใช้ Kotlin และ variant API รุ่นเก่า