APK Caching

เอกสารนี้อธิบายการออกแบบโซลูชันแคช APK สำหรับการติดตั้งแอพที่โหลดไว้ล่วงหน้าอย่างรวดเร็วบนอุปกรณ์ที่รองรับพาร์ติชัน A/B

OEM สามารถวางโหลดล่วงหน้าและแอปยอดนิยมในแคช APK ที่จัดเก็บไว้ในพาร์ติชัน B ที่ว่างส่วนใหญ่บนอุปกรณ์ ที่แบ่งพาร์ติชัน A/B ใหม่ โดยไม่กระทบต่อพื้นที่ข้อมูลที่ผู้ใช้ต้องเผชิญ การมีแคช APK บนอุปกรณ์ อุปกรณ์ใหม่หรือที่เพิ่งรีเซ็ตเป็นค่าจากโรงงานจะพร้อมใช้งานเกือบจะในทันที โดยไม่จำเป็นต้องดาวน์โหลดไฟล์ APK จาก Google Play

กรณีการใช้งาน

  • จัดเก็บแอปที่โหลดไว้ล่วงหน้าไว้ในพาร์ติชัน B เพื่อการตั้งค่าที่เร็วขึ้น
  • จัดเก็บแอพยอดนิยมในพาร์ติชั่น B เพื่อการกู้คืนที่รวดเร็วยิ่งขึ้น

ข้อกำหนดเบื้องต้น

หากต้องการใช้คุณสมบัตินี้ อุปกรณ์จำเป็นต้องมี:

  • ติดตั้ง Android 8.1 (O MR1) แล้ว
  • ใช้พาร์ติชัน A/B แล้ว

เนื้อหาที่โหลดไว้ล่วงหน้าสามารถคัดลอกได้เฉพาะในระหว่างการบูตครั้งแรกเท่านั้น เนื่องจากบนอุปกรณ์ที่รองรับการอัปเดตระบบ A/B พาร์ติชัน B จะไม่จัดเก็บไฟล์อิมเมจระบบจริงๆ แต่จะโหลดเนื้อหาไว้ล่วงหน้า เช่น ทรัพยากรสาธิตการขายปลีก ไฟล์ OAT และแคช APK แทน หลังจากคัดลอกทรัพยากรไปยังพาร์ติชัน /data แล้ว (สิ่งนี้เกิดขึ้นเมื่อบูตครั้งแรก) พาร์ติชัน B จะถูกนำมาใช้โดย การอัพเดตแบบ over-the-air (OTA) เพื่อดาวน์โหลดเวอร์ชันอัปเดตของอิมเมจระบบ

ดังนั้นแคช APK จึงไม่สามารถอัปเดตผ่าน OTA ได้ สามารถโหลดล่วงหน้าได้ที่โรงงานเท่านั้น การรีเซ็ตเป็นค่าจากโรงงานจะมีผลกับพาร์ติชัน /data เท่านั้น พาร์ติชันระบบ B ยังคงมีเนื้อหาที่โหลดไว้ล่วงหน้าจนกว่าจะดาวน์โหลดอิมเมจ OTA หลังจากรีเซ็ตเป็นค่าจากโรงงาน ระบบจะทำการบู๊ตครั้งแรกอีกครั้ง ซึ่งหมายความว่าแคช APK จะไม่สามารถใช้งานได้หากดาวน์โหลดอิมเมจ OTA ลงในพาร์ติชัน B จากนั้นอุปกรณ์จะถูกรีเซ็ตเป็นค่าเริ่มต้นจากโรงงาน

การนำไปปฏิบัติ

วิธีที่ 1. เนื้อหาบนพาร์ติชัน system_other

Pro : เนื้อหาที่โหลดไว้ล่วงหน้าจะไม่สูญหายหลังจากการรีเซ็ตเป็นค่าจากโรงงาน - เนื้อหาจะถูกคัดลอกจากพาร์ติชัน B หลังจากรีบูต

Con : ต้องการพื้นที่บนพาร์ติชัน B การบูตหลังจากการรีเซ็ตเป็นค่าจากโรงงานต้องใช้เวลาเพิ่มเติมในการคัดลอกเนื้อหาที่โหลดไว้ล่วงหน้า

เพื่อให้โหลดล่วงหน้าถูกคัดลอกในระหว่างการบูตครั้งแรก ระบบจะเรียกสคริปต์ใน /system/bin/preloads_copy.sh สคริปต์ถูกเรียกด้วยอาร์กิวเมนต์เดียว (เส้นทางไปยังจุดเมานต์แบบอ่านอย่างเดียวสำหรับพาร์ติชัน system_b ):

หากต้องการใช้คุณลักษณะนี้ ให้ทำการเปลี่ยนแปลงเฉพาะอุปกรณ์เหล่านี้ นี่คือตัวอย่างจาก Marlin:

  1. เพิ่มสคริปต์ที่ทำการคัดลอกไปยังไฟล์ device-common.mk (ในกรณีนี้คือ device/google/marlin/device-common.mk ) เช่น:
    # Script that copies preloads directory from system_other to data partition
    PRODUCT_COPY_FILES += \
        device/google/marlin/preloads_copy.sh:system/bin/preloads_copy.sh
    
    ค้นหาแหล่งสคริปต์ตัวอย่างที่: device/google/marlin /preloads_copy.sh
  2. แก้ไขไฟล์ init.common.rc เพื่อให้สร้างไดเร็กทอรี /data/preloads และไดเร็กทอรีย่อยที่จำเป็น:
    mkdir /data/preloads 0775 system system
    mkdir /data/preloads/media 0775 system system
    mkdir /data/preloads/demo 0775 system system
    
    ค้นหาตัวอย่างแหล่งที่มาของไฟล์ init ที่: device/google/marlin/init.common.rc
  3. กำหนดโดเมน SELinux ใหม่ในไฟล์ preloads_copy.te :
    type preloads_copy, domain, coredomain;
    type preloads_copy_exec, exec_type, vendor_file_type, file_type;
    
    init_daemon_domain(preloads_copy)
    
    allow preloads_copy shell_exec:file rx_file_perms;
    allow preloads_copy toolbox_exec:file rx_file_perms;
    allow preloads_copy preloads_data_file:dir create_dir_perms;
    allow preloads_copy preloads_data_file:file create_file_perms;
    allow preloads_copy preloads_media_file:dir create_dir_perms;
    allow preloads_copy preloads_media_file:file create_file_perms;
    
    # Allow to copy from /postinstall
    allow preloads_copy system_file:dir r_dir_perms;
    
    ค้นหาไฟล์โดเมน SELinux ตัวอย่างได้ที่: /device/google/marlin/+/main/sepolicy/preloads_copy.te
  4. จดทะเบียนโดเมนในรูปแบบใหม่ /sepolicy/file_contexts file:
    /system/bin/preloads_copy\.sh     u:object_r:preloads_copy_exec:s0
    
    ค้นหาตัวอย่างไฟล์บริบท SELinux ได้ที่: device/google/marlin/sepolicy/preloads_copy.te
  5. ณ เวลาสร้าง ไดเรกทอรีที่มีเนื้อหาที่โหลดไว้ล่วงหน้าจะต้องถูกคัดลอกไปยังพาร์ติชัน system_other :
    # Copy contents of preloads directory to system_other partition
    PRODUCT_COPY_FILES += \
        $(call find-copy-subdir-files,*,vendor/google_devices/marlin/preloads,system_other/preloads)
    
    นี่คือตัวอย่างของการเปลี่ยนแปลงใน Makefile ที่อนุญาตให้คัดลอกทรัพยากรแคช APK จากที่เก็บ Git ของผู้ขาย (ในกรณีของเราคือ vendor/google_devices/ marlin/preloads) ไปยังตำแหน่งบนพาร์ติชัน system_other ซึ่งจะถูกคัดลอกไปยัง /data/preloads ในภายหลังเมื่ออุปกรณ์บู๊ตเป็นครั้งแรก สคริปต์นี้รันในเวลา build เพื่อเตรียมอิมเมจ system_other คาดว่าเนื้อหาที่โหลดไว้ล่วงหน้าจะพร้อมใช้งานใน vendor/google_devices/marlin/preloads OEM มีอิสระในการเลือกชื่อ/เส้นทางพื้นที่เก็บข้อมูลจริง
  6. แคช APK อยู่ใน /data/preloads/file_cache และมีเค้าโครงต่อไปนี้:
    /data/preloads/file_cache/
        app.package.name.1/
              file1
              fileN
        app.package.name.N/
    
    นี่คือโครงสร้างไดเรกทอรีสุดท้ายบนอุปกรณ์ OEM มีอิสระในการเลือกแนวทางการใช้งานใดๆ ตราบใดที่โครงสร้างไฟล์สุดท้ายจำลองแบบที่อธิบายไว้ข้างต้น

แนวทางที่ 2 เนื้อหาบนภาพข้อมูลผู้ใช้จะกะพริบที่โรงงาน

วิธีอื่นนี้จะถือว่าเนื้อหาที่โหลดไว้ล่วงหน้ารวมอยู่ในไดเร็กทอรี /data/preloads บนพาร์ติชัน /data แล้ว

มือโปร : ใช้งานได้ทันที - ไม่จำเป็นต้องปรับแต่งอุปกรณ์เพื่อคัดลอกไฟล์ในการบู๊ตครั้งแรก เนื้อหาอยู่ในพาร์ติชัน /data แล้ว

ข้อเสีย : เนื้อหาที่โหลดไว้ล่วงหน้าจะหายไปหลังจากการรีเซ็ตเป็นค่าจากโรงงาน แม้ว่าสิ่งนี้อาจยอมรับได้สำหรับบางคน แต่ก็อาจไม่ได้ผลเสมอไปสำหรับ OEM ที่รีเซ็ตอุปกรณ์เป็นค่าเริ่มต้นจากโรงงานหลังจากทำการตรวจสอบการควบคุมคุณภาพแล้ว

มีการเพิ่มเมธอด @SystemApi ใหม่ getPreloadsFileCache() ใน android.content.Context โดยจะส่งคืนเส้นทางที่แน่นอนไปยังไดเร็กทอรีเฉพาะแอปพลิเคชันในแคชที่โหลดไว้ล่วงหน้า

มีการเพิ่มเมธอดใหม่ IPackageManager.deletePreloadsFileCache ซึ่งอนุญาตให้ลบไดเร็กทอรีโหลดล่วงหน้าเพื่อเรียกคืนพื้นที่ทั้งหมด วิธีการนี้สามารถเรียกได้โดยแอปที่มี SYSTEM_UID เท่านั้น เช่น เซิร์ฟเวอร์ระบบหรือการตั้งค่า

การเตรียมแอพ

เฉพาะแอปพลิเคชันที่ได้รับสิทธิ์เท่านั้นที่สามารถเข้าถึงไดเร็กทอรีแคชที่โหลดไว้ล่วงหน้า สำหรับการเข้าถึงดังกล่าว ต้องติดตั้งแอปในไดเร็กทอรี /system/priv-app

การตรวจสอบ

  • หลังจากการบูตครั้งแรก อุปกรณ์ควรมีเนื้อหาอยู่ในไดเร็กทอรี /data/preloads/file_cache
  • เนื้อหาในไดเร็กทอรี file_cache/ จะต้องถูกลบหากอุปกรณ์มีพื้นที่เก็บข้อมูลเหลือน้อย

ใช้ตัวอย่างแอป ApkCacheTest เพื่อทดสอบแคช APK

  1. สร้างแอปโดยการรันคำสั่งนี้จากไดเร็กทอรีราก:
    make ApkCacheTest
    
  2. ติดตั้งแอปเป็นแอปพลิเคชันพิเศษ (โปรดจำไว้ว่า เฉพาะแอปที่ได้รับสิทธิพิเศษเท่านั้นที่สามารถเข้าถึงแคช APK ได้) ซึ่งต้องใช้อุปกรณ์ที่รูท:
    adb root && adb remount
    adb shell mkdir /system/priv-app/ApkCacheTest
    adb push $ANDROID_PRODUCT_OUT/data/app/ApkCacheTest/ApkCacheTest.apk /system/priv-app/ApkCacheTest/
    adb shell stop && adb shell start
    
  3. จำลองไดเร็กทอรีแคชของไฟล์และเนื้อหาหากจำเป็น (ยังต้องการสิทธิ์รูทด้วย):
    adb shell mkdir -p /data/preloads/file_cache/com.android.apkcachetest
    adb shell restorecon -r /data/preloads
    adb shell "echo "Test File" > /data/preloads/file_cache/com.android.apkcachetest/test.txt"
    
  4. ทดสอบแอป หลังจากติดตั้งแอปและสร้างไดเร็กทอรี test file_cache ให้เปิดแอป ApkCacheTest ควรแสดงไฟล์ test.txt หนึ่งไฟล์และเนื้อหาในไฟล์ ดูภาพหน้าจอนี้เพื่อดูว่าผลลัพธ์เหล่านี้ปรากฏในอินเทอร์เฟซผู้ใช้อย่างไร

    รูปที่ 1 ผลการทดสอบ ApkCacheTest