การแคช APK

เอกสารนี้อธิบายการออกแบบโซลูชันการแคช 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 (ซึ่งจะเกิดขึ้นในการบูตครั้งแรก) แล้ว การอัปเดตผ่านอากาศ (OTA) จะใช้พาร์ติชัน B เพื่อดาวน์โหลดอิมเมจระบบเวอร์ชันที่อัปเดตแล้ว

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

การใช้งาน

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

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

ข้อเสีย: ต้องมีพื้นที่ในพาร์ติชัน 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/+/android16-release/sepolicy/preloads_copy.te
  4. ลงทะเบียนโดเมนใน/sepolicy/file_contexts ไฟล์ใหม่โดยทำดังนี้
    /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 ในภายหลังเมื่ออุปกรณ์บูตเป็นครั้งแรก สคริปต์นี้จะทำงานในเวลาบิลด์เพื่อเตรียมอิมเมจ 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. ทดสอบแอป หลังจากติดตั้งแอปและสร้างไดเรกทอรี file_cache สำหรับการทดสอบแล้ว ให้เปิดแอป ApkCacheTest แอปควรแสดงไฟล์ test.txt 1 ไฟล์และเนื้อหาของไฟล์ ดูภาพหน้าจอนี้เพื่อดูว่าผลลัพธ์เหล่านี้ปรากฏในอินเทอร์เฟซผู้ใช้อย่างไร

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