ไฟล์สำหรับขยายของ APK

Google Play กำหนดให้ APK ที่บีบอัดซึ่งผู้ใช้ดาวน์โหลดมีขนาดไม่เกิน 100 MB สำหรับแอปส่วนใหญ่ พื้นที่นี้เพียงพอสำหรับโค้ดและชิ้นงานทั้งหมดของแอป อย่างไรก็ตาม แอปบางแอปต้องใช้พื้นที่เพิ่มสำหรับกราฟิกที่มีคุณภาพสูง ไฟล์สื่อ หรือชิ้นงานขนาดใหญ่อื่นๆ ก่อนหน้านี้ หากขนาดการดาวน์โหลดที่บีบอัดของแอปเกิน 100 MB คุณจะต้องโฮสต์และดาวน์โหลดทรัพยากรเพิ่มเติมด้วยตนเองเมื่อผู้ใช้เปิดแอป การโฮสต์และแสดงไฟล์เพิ่มเติมอาจทำให้สิ้นเปลืองค่าใช้จ่าย และประสบการณ์ของผู้ใช้มักไม่เป็นไปตามที่คาดหวัง Google Play อนุญาตให้คุณแนบไฟล์ขยายขนาดใหญ่ 2 ไฟล์เพื่อเสริม APK ของคุณได้ เพื่อช่วยให้คุณดำเนินการได้ง่ายขึ้นและผู้ใช้ได้รับประสบการณ์การใช้งานที่ดีขึ้น

Google Play จะโฮสต์ไฟล์ขยายสำหรับแอปของคุณและแสดงไฟล์เหล่านั้นในอุปกรณ์โดยไม่มีค่าใช้จ่าย ระบบจะบันทึกไฟล์สำหรับขยายลงในตำแหน่งที่จัดเก็บข้อมูลที่ใช้ร่วมกันของอุปกรณ์ (การ์ด SD หรือพาร์ติชันที่เชื่อมต่อได้ด้วย USB หรือที่เรียกว่าพื้นที่เก็บข้อมูล "ภายนอก") ซึ่งแอปของคุณเข้าถึงได้ ในอุปกรณ์ส่วนใหญ่ Google Play จะดาวน์โหลดไฟล์สำหรับขยายไปพร้อมๆ กับที่ดาวน์โหลด APK เพื่อให้แอปมีทุกอย่างที่จำเป็นเมื่อผู้ใช้เปิดแอปเป็นครั้งแรก อย่างไรก็ตาม ในบางกรณี แอปของคุณจะต้องดาวน์โหลดไฟล์จาก Google Play เมื่อแอปเริ่มทำงาน

หากต้องการหลีกเลี่ยงการใช้ไฟล์ส่วนขยายและขนาดการดาวน์โหลดที่บีบอัดของแอปใหญ่กว่า 100 MB คุณควรอัปโหลดแอปโดยใช้App Bundle ของ Android แทน ซึ่งอนุญาตให้มีขนาดการดาวน์โหลดที่บีบอัดได้สูงสุด 200 MB นอกจากนี้ เนื่องจากการใช้ App Bundle จะเลื่อนการสร้างและการลงนาม APK ไปยัง Google Play ผู้ใช้จึงดาวน์โหลด APK ที่เพิ่มประสิทธิภาพซึ่งมีเฉพาะโค้ดและทรัพยากรที่จําเป็นต่อการใช้งานแอป คุณจึงไม่ต้องสร้าง ลงนาม และจัดการ APK หรือไฟล์ Expansion หลายรายการ และผู้ใช้จะดาวน์โหลดไฟล์ที่มีขนาดเล็กลงและเพิ่มประสิทธิภาพมากขึ้น

ภาพรวม

ทุกครั้งที่คุณอัปโหลด APK โดยใช้ Google Play Console คุณจะมีตัวเลือกในการเพิ่มไฟล์สำหรับขยาย 1 หรือ 2 ไฟล์ลงใน APK ไฟล์แต่ละไฟล์มีขนาดไม่เกิน 2 GB และใช้รูปแบบใดก็ได้ แต่เราขอแนะนำให้ใช้ไฟล์ที่บีบอัดเพื่อประหยัดแบนด์วิดท์ระหว่างการดาวน์โหลด ไฟล์เสริมแต่ละไฟล์มีบทบาทแตกต่างกันดังนี้

  • ไฟล์เสริมหลักคือไฟล์เสริมหลักสำหรับทรัพยากรเพิ่มเติมที่แอปของคุณต้องใช้
  • ไฟล์สำหรับขยายแพตช์เป็นไฟล์ที่ไม่บังคับและมีไว้สำหรับการอัปเดตเล็กๆ น้อยๆ ของไฟล์สำหรับขยายหลัก

แม้ว่าคุณจะใช้ไฟล์สำหรับขยาย 2 ไฟล์นี้ในลักษณะใดก็ได้ แต่เราขอแนะนำให้ไฟล์สำหรับขยายหลักส่งชิ้นงานหลักและควรอัปเดตน้อยครั้งหรือไม่อัปเดตเลย ส่วนไฟล์สำหรับขยายแพตช์ควรมีขนาดเล็กลงและทำหน้าที่เป็น "พาหะแพตช์" ซึ่งจะได้รับการอัปเดตพร้อมกับรุ่นหลักแต่ละรุ่นหรือตามความจำเป็น

อย่างไรก็ตาม แม้ว่าการอัปเดตแอปของคุณจะต้องใช้เฉพาะไฟล์สำหรับขยายแพตช์ใหม่ คุณยังคงต้องอัปโหลด APK ใหม่ที่มี versionCode ที่อัปเดตแล้วในไฟล์ Manifest (Play Console ไม่อนุญาตให้คุณอัปโหลดไฟล์สำหรับขยายไปยัง APK ที่มีอยู่)

หมายเหตุ: ไฟล์สำหรับขยายแพตช์มีความหมายเหมือนกับไฟล์สำหรับขยายหลัก คุณจึงใช้ไฟล์แต่ละไฟล์ได้ตามต้องการ

รูปแบบชื่อไฟล์

ไฟล์เสริมแต่ละไฟล์ที่คุณอัปโหลดอาจเป็นรูปแบบใดก็ได้ (ZIP, PDF, MP4 ฯลฯ) นอกจากนี้ คุณยังใช้เครื่องมือ JOBB เพื่อรวมและเข้ารหัสชุดไฟล์ทรัพยากรและแพตช์ชุดถัดไปสำหรับชุดนั้นได้อีกด้วย ไม่ว่าไฟล์จะเป็นประเภทใด Google Play จะถือว่าไฟล์เหล่านั้นเป็นบล็อกไบนารีทึบแสงและจะเปลี่ยนชื่อไฟล์โดยใช้รูปแบบต่อไปนี้

[main|patch].<expansion-version>.<package-name>.obb

รูปแบบนี้มีองค์ประกอบ 3 อย่าง ได้แก่

main หรือ patch
ระบุว่าไฟล์เป็นไฟล์สำหรับขยายหลักหรือไฟล์แพตช์ แต่ละ APK จะมีไฟล์หลักได้เพียงไฟล์เดียวและไฟล์แพตช์ได้เพียงไฟล์เดียว
<expansion-version>
คือจำนวนเต็มซึ่งตรงกับรหัสเวอร์ชันของ APK ที่การขยายครั้งแรกเชื่อมโยงอยู่ (ตรงกับค่า android:versionCode ของแอป)

เราเน้นคำว่า "ครั้งแรก" เนื่องจากแม้ว่า Play Console จะอนุญาตให้คุณใช้ไฟล์สำหรับขยายที่อัปโหลดแล้วกับ APK ใหม่ได้ แต่ชื่อของไฟล์สำหรับขยายจะไม่เปลี่ยนแปลง โดยจะยังคงเป็นเวอร์ชันที่ใช้กับไฟล์เมื่อคุณอัปโหลดไฟล์ครั้งแรก

<package-name>
ชื่อแพ็กเกจสไตล์ Java ของแอป

ตัวอย่างเช่น สมมติว่าเวอร์ชัน APK ของคุณคือ 314159 และชื่อแพ็กเกจคือ com.example.app หากคุณอัปโหลดไฟล์สำหรับขยายหลัก ระบบจะเปลี่ยนชื่อไฟล์เป็น

main.314159.com.example.app.obb

ตำแหน่งพื้นที่เก็บข้อมูล

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

เมธอด getObbDir() จะแสดงตําแหน่งเฉพาะสำหรับไฟล์การขยายในรูปแบบต่อไปนี้

<shared-storage>/Android/obb/<package-name>/
  • <shared-storage> คือเส้นทางไปยังพื้นที่เก็บข้อมูลที่ใช้ร่วมกัน ซึ่งพร้อมใช้งานจาก getExternalStorageDirectory()
  • <package-name> คือชื่อแพ็กเกจสไตล์ Java ของแอป ซึ่งรับได้จาก getPackageName()

แต่ละแอปจะมีไฟล์ขยายได้ไม่เกิน 2 ไฟล์ในไดเรกทอรีนี้ ไฟล์หนึ่งคือไฟล์เสริมหลัก และอีกไฟล์หนึ่งคือไฟล์เสริมแพตช์ (หากจำเป็น) ระบบจะเขียนทับเวอร์ชันก่อนหน้าเมื่อคุณอัปเดตแอปด้วยไฟล์สำหรับขยายใหม่ ตั้งแต่ Android 4.4 (API ระดับ 19) เป็นต้นไป แอปจะอ่านOBBไฟล์เสริมได้โดยไม่ต้องมีสิทธิ์เข้าถึงพื้นที่เก็บข้อมูลภายนอก อย่างไรก็ตาม การใช้งานบางอย่างใน Android 6.0 (API ระดับ 23) ขึ้นไปยังคงต้องใช้สิทธิ์ดังกล่าว คุณจึงต้องประกาศสิทธิ์ READ_EXTERNAL_STORAGE ในไฟล์ Manifest ของแอปและขอสิทธิ์ในรันไทม์ ดังนี้

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

สำหรับ Android เวอร์ชัน 6 ขึ้นไป จะต้องขอสิทธิ์เข้าถึงพื้นที่เก็บข้อมูลภายนอกขณะรันไทม์ อย่างไรก็ตาม การใช้งาน Android บางเวอร์ชันไม่จําเป็นต้องขอสิทธิ์ในการอ่านไฟล์ OBB ข้อมูลโค้ดต่อไปนี้แสดงวิธีตรวจสอบสิทธิ์การอ่านก่อนที่จะขอสิทธิ์เข้าถึงพื้นที่เก็บข้อมูลภายนอก

Kotlin

val obb = File(obb_filename)
var open_failed = false

try {
    BufferedReader(FileReader(obb)).also { br ->
        ReadObbFile(br)
    }
} catch (e: IOException) {
    open_failed = true
}

if (open_failed) {
    // request READ_EXTERNAL_STORAGE permission before reading OBB file
    ReadObbFileWithPermission()
}

Java

File obb = new File(obb_filename);
 boolean open_failed = false;

 try {
     BufferedReader br = new BufferedReader(new FileReader(obb));
     open_failed = false;
     ReadObbFile(br);
 } catch (IOException e) {
     open_failed = true;
 }

 if (open_failed) {
     // request READ_EXTERNAL_STORAGE permission before reading OBB file
     ReadObbFileWithPermission();
 }

หากต้องแตกไฟล์ในไฟล์ขยาย อย่าลบไฟล์OBBขยายหลังจากนั้น และอย่าบันทึกข้อมูลที่แตกไฟล์แล้วในไดเรกทอรีเดียวกัน คุณควรบันทึกไฟล์ที่แตกไฟล์แล้วในไดเรกทอรีที่ระบุโดย getExternalFilesDir() อย่างไรก็ตาม หากเป็นไปได้ เราขอแนะนำให้ใช้รูปแบบไฟล์การขยายที่ให้คุณอ่านจากไฟล์ได้โดยตรงแทนที่จะต้องแตกไฟล์ ตัวอย่างเช่น เราได้จัดเตรียมโปรเจ็กต์ไลบรารีที่เรียกว่า APK Expansion Zip Library ซึ่งจะอ่านข้อมูลจากไฟล์ ZIP โดยตรง

ข้อควรระวัง: ผู้ใช้และแอปอื่นๆ จะอ่านไฟล์ที่บันทึกไว้ในพื้นที่เก็บข้อมูลที่แชร์ได้ ซึ่งแตกต่างจากไฟล์ APK

เคล็ดลับ: หากกำลังจัดแพ็กเกจไฟล์สื่อเป็น ZIP คุณสามารถใช้การเรียกใช้การเล่นสื่อในไฟล์ที่มีการควบคุมระยะถ่วงและความยาว (เช่น MediaPlayer.setDataSource() และ SoundPool.load()) โดยไม่ต้องแตกไฟล์ ZIP คุณต้องไม่ทำการบีบอัดเพิ่มเติมในไฟล์สื่อเมื่อสร้างแพ็กเกจ ZIP เพื่อให้การดําเนินการนี้ได้ผล ตัวอย่างเช่น เมื่อใช้เครื่องมือ zip คุณควรใช้ตัวเลือก -n เพื่อระบุนามสกุลไฟล์ที่ไม่ควรบีบอัด ดังนี้
zip -n .mp4;.ogg main_expansion media_files

กระบวนการดาวน์โหลด

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

กระบวนการดาวน์โหลดโดยสังเขปมีดังนี้

  1. ผู้ใช้เลือกติดตั้งแอปของคุณจาก Google Play
  2. หาก Google Play ดาวน์โหลดไฟล์สำหรับขยายได้ (ซึ่งอุปกรณ์ส่วนใหญ่ทำได้) ก็จะดาวน์โหลดไฟล์ดังกล่าวไปพร้อมกับ APK

    หาก Google Play ดาวน์โหลดไฟล์สำหรับขยายไม่ได้ ก็จะดาวน์โหลด APK เท่านั้น

  3. เมื่อผู้ใช้เปิดแอป แอปของคุณต้องตรวจสอบว่าไฟล์เสริมบันทึกอยู่ในอุปกรณ์แล้วหรือยัง
    1. หากใช่ แอปของคุณก็พร้อมใช้งานแล้ว
    2. หากไม่มี แอปของคุณจะต้องดาวน์โหลดไฟล์ขยายผ่าน HTTP จาก Google Play แอปของคุณต้องส่งคำขอไปยังไคลเอ็นต์ Google Play โดยใช้บริการการอนุญาตให้ใช้แอปของ Google Play ซึ่งจะตอบสนองด้วยชื่อ ขนาดไฟล์ และ URL ของไฟล์ขยายแต่ละไฟล์ เมื่อทราบข้อมูลนี้แล้ว คุณสามารถดาวน์โหลดไฟล์และบันทึกลงในตำแหน่งพื้นที่เก็บข้อมูลที่เหมาะสม

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

รายการตรวจสอบการพัฒนา

ต่อไปนี้เป็นสรุปของงานที่คุณควรทำเพื่อใช้ไฟล์สำหรับขยายกับแอป

  1. ก่อนอื่นให้พิจารณาว่าขนาดการดาวน์โหลดที่บีบอัดของแอปต้องมากกว่า 100 MB หรือไม่ พื้นที่เก็บข้อมูลมีคุณค่ามาก คุณจึงควรทำให้ขนาดการดาวน์โหลดทั้งหมดมีขนาดเล็กที่สุดเท่าที่จะเป็นไปได้ หากแอปใช้พื้นที่มากกว่า 100 MB เพื่อให้บริการชิ้นงานกราฟิกหลายเวอร์ชันสำหรับความหนาแน่นของหน้าจอหลายแบบ ให้พิจารณาเผยแพร่APK หลายรายการแทน โดยแต่ละ APK จะมีเฉพาะชิ้นงานที่จําเป็นสําหรับหน้าจอที่กําหนดเป้าหมาย หากต้องการให้ได้ผลลัพธ์ที่ดีที่สุดเมื่อเผยแพร่ไปยัง Google Play ให้อัปโหลด Android App Bundle ซึ่งรวมโค้ดและทรัพยากรที่คอมไพล์แล้วทั้งหมดของแอป แต่เลื่อนการสร้างและ Signing APK ไปยัง Google Play
  2. เลือกทรัพยากรของแอปที่จะแยกออกจาก APK และแพ็กเกจไว้ในไฟล์เพื่อใช้เป็นไฟล์เสริมหลัก

    ปกติแล้ว คุณควรใช้ไฟล์สำหรับขยายแพตช์ที่ 2 เฉพาะเมื่อทำการอัปเดตไฟล์สำหรับขยายหลักเท่านั้น อย่างไรก็ตาม หากทรัพยากรเกินขีดจำกัด 2 GB สำหรับไฟล์สำหรับขยายหลัก คุณจะใช้ไฟล์แพตช์สำหรับเนื้อหาที่เหลือได้

  3. พัฒนาแอปให้ใช้ทรัพยากรจากไฟล์ขยายในตำแหน่งพื้นที่เก็บข้อมูลที่แชร์ของอุปกรณ์

    โปรดทราบว่าคุณต้องไม่ลบ ย้าย หรือเปลี่ยนชื่อไฟล์สำหรับขยาย

    หากแอปของคุณไม่ได้กำหนดรูปแบบที่เฉพาะเจาะจง เราขอแนะนำให้คุณสร้างไฟล์ ZIP สำหรับไฟล์สำหรับขยาย แล้วอ่านโดยใช้คลัง ZIP สำหรับขยาย APK

  4. เพิ่มตรรกะลงในกิจกรรมหลักของแอปเพื่อตรวจสอบว่าไฟล์ขยายอยู่ในอุปกรณ์เมื่อเริ่มต้นหรือไม่ หากไฟล์ไม่ได้อยู่ในอุปกรณ์ ให้ใช้บริการการอนุญาตให้ใช้สิทธิแอปของ Google Play เพื่อขอ URL ของไฟล์ขยาย จากนั้นดาวน์โหลดและบันทึกไฟล์

    หากต้องการลดจำนวนโค้ดที่ต้องเขียนลงอย่างมากและช่วยให้ผู้ใช้ได้รับประสบการณ์ที่ดีขณะดาวน์โหลด เราขอแนะนำให้คุณใช้คลังดาวน์โหลดเพื่อใช้ลักษณะการดาวน์โหลด

    หากคุณสร้างบริการดาวน์โหลดของคุณเองแทนการใช้คลัง โปรดทราบว่าคุณต้องไม่เปลี่ยนชื่อไฟล์ส่วนขยายและต้องบันทึกไฟล์เหล่านั้นลงในตำแหน่งพื้นที่เก็บข้อมูลที่เหมาะสม

เมื่อพัฒนาแอปเสร็จแล้ว ให้ทำตามคำแนะนำในการทดสอบไฟล์ Expansion

กฎและข้อจำกัด

การเพิ่มไฟล์ขยาย APK เป็นฟีเจอร์ที่ใช้ได้เมื่อคุณอัปโหลดแอปโดยใช้ Play Console เมื่ออัปโหลดแอปเป็นครั้งแรกหรืออัปเดตแอปที่ใช้ไฟล์เสริม คุณต้องคำนึงถึงกฎและข้อจำกัดต่อไปนี้

  1. ไฟล์ขยายแต่ละไฟล์ต้องมีขนาดไม่เกิน 2 GB
  2. ผู้ใช้ต้องดาวน์โหลดแอปจาก Google Play จึงจะดาวน์โหลดไฟล์ส่วนขยายจาก Google Play ได้ Google Play จะไม่ระบุ URL ของไฟล์สำหรับขยายหากติดตั้งแอปด้วยวิธีอื่น
  3. เมื่อทำการดาวน์โหลดจากภายในแอป URL ที่ Google Play ให้ไว้สำหรับแต่ละไฟล์จะไม่ซ้ำกันสำหรับการดาวน์โหลดแต่ละครั้ง และแต่ละ URL จะหมดอายุไม่นานหลังจากที่ส่งให้กับแอป
  4. หากอัปเดตแอปด้วย APK ใหม่หรืออัปโหลด APK หลายรายการสำหรับแอปเดียวกัน คุณสามารถเลือกไฟล์สำหรับขยายที่คุณอัปโหลดไว้สำหรับ APK รายการก่อนหน้าได้ ชื่อไฟล์ส่วนขยายจะไม่เปลี่ยนแปลง โดยไฟล์จะยังคงเป็นเวอร์ชันที่ได้รับจาก APK ที่เชื่อมโยงกับไฟล์นั้นในตอนแรก
  5. หากคุณใช้ไฟล์ขยายร่วมกับ APK หลายรายการเพื่อระบุไฟล์ขยายที่แตกต่างกันสำหรับอุปกรณ์แต่ละเครื่อง คุณยังคงต้องอัปโหลด APK แยกต่างหากสำหรับแต่ละอุปกรณ์เพื่อระบุค่า versionCode ที่ไม่ซ้ำกันและประกาศตัวกรองที่แตกต่างกันสำหรับแต่ละ APK
  6. คุณไม่สามารถอัปเดตแอปโดยเปลี่ยนไฟล์สำหรับขยายเพียงอย่างเดียว คุณต้องอัปโหลด APK ใหม่เพื่ออัปเดตแอป หากการเปลี่ยนแปลงเกี่ยวข้องกับเนื้อหาในไฟล์สำหรับขยายเท่านั้น คุณสามารถอัปเดต APK ได้โดยเปลี่ยน versionCode (และอาจเปลี่ยน versionName ด้วย)
  7. อย่าบันทึกข้อมูลอื่นๆ ลงในobb/ ไดเรกทอรี หากต้องแตกไฟล์ข้อมูล ให้บันทึกข้อมูลดังกล่าวไว้ในตำแหน่งที่ getExternalFilesDir() ระบุ
  8. อย่าลบหรือเปลี่ยนชื่อไฟล์สำหรับขยาย .obb (เว้นแต่คุณจะทำการอัปเดต) เนื่องจากจะทำให้ Google Play (หรือแอปของคุณเอง) ดาวน์โหลดไฟล์สำหรับขยายซ้ำๆ
  9. เมื่ออัปเดตไฟล์สำหรับขยายด้วยตนเอง คุณต้องลบไฟล์สำหรับขยายก่อนหน้านี้

การดาวน์โหลดไฟล์สำหรับขยาย

ในกรณีส่วนใหญ่ Google Play จะดาวน์โหลดและบันทึกไฟล์สำหรับขยายลงในอุปกรณ์ในเวลาเดียวกันกับที่ติดตั้งหรืออัปเดต APK วิธีนี้จะทำให้ไฟล์ขยายพร้อมใช้งานเมื่อแอปเปิดขึ้นเป็นครั้งแรก อย่างไรก็ตาม ในบางกรณี แอปของคุณจะต้องดาวน์โหลดไฟล์เสริมด้วยตนเองโดยขอไฟล์จาก URL ที่ระบุไว้ในคําตอบจากบริการการอนุญาตให้ใช้แอปของ Google Play

ตรรกะพื้นฐานที่คุณต้องใช้ในการดาวน์โหลดไฟล์เสริมมีดังนี้

  1. เมื่อแอปเริ่มทำงาน ให้มองหาไฟล์ขยายในตำแหน่งที่จัดเก็บข้อมูลที่ใช้ร่วมกัน (ในไดเรกทอรี Android/obb/<package-name>/)
    1. หากมีไฟล์ขยายอยู่ ทุกอย่างก็พร้อมแล้วและแอปจะดำเนินการต่อได้
    2. หากไฟล์สำหรับขยายไม่อยู่ ให้ทำดังนี้
      1. ส่งคำขอโดยใช้การอนุญาตให้ใช้สิทธิของแอปของ Google Play เพื่อดูชื่อ ขนาด และ URL ของไฟล์ขยายของแอป
      2. ใช้ URL ที่ Google Play ระบุเพื่อดาวน์โหลดไฟล์สำหรับขยายและบันทึกไฟล์สำหรับขยาย คุณต้องบันทึกไฟล์ลงในตำแหน่งพื้นที่เก็บข้อมูลที่ใช้ร่วมกัน (Android/obb/<package-name>/) และใช้ชื่อไฟล์ที่ตรงกันทุกประการกับที่ระบุไว้ในคําตอบของ Google Play

        หมายเหตุ: URL ที่ Google Play ระบุสำหรับไฟล์เสริมของคุณจะไม่ซ้ำกันสำหรับการดาวน์โหลดแต่ละครั้ง และแต่ละ URL จะหมดอายุไม่นานหลังจากที่ส่งไปยังแอป

หากแอปของคุณเป็นแบบไม่มีค่าใช้จ่าย (ไม่ใช่แอปที่ต้องซื้อ) คุณอาจไม่ได้ใช้บริการการอนุญาตให้ใช้สิทธิแอป โดยหลักแล้ว ฟีเจอร์นี้ออกแบบมาเพื่อให้คุณบังคับใช้นโยบายการอนุญาตให้ใช้สิทธิสำหรับแอปและตรวจสอบว่าผู้ใช้มีสิทธิ์ใช้แอปของคุณ (ผู้ใช้ชำระเงินอย่างถูกต้องใน Google Play) เราได้ปรับปรุงบริการอนุญาตให้ใช้สิทธิเพื่อให้การตอบสนองแก่แอปของคุณมี URL ของไฟล์ส่วนขยายของแอปที่โฮสต์ใน Google Play เพื่ออำนวยความสะดวกในฟังก์ชันการทำงานของไฟล์ส่วนขยาย ดังนั้น แม้ว่าแอปของคุณจะให้บริการแก่ผู้ใช้แบบไม่มีค่าใช้จ่าย คุณก็จะต้องใส่ไฟล์ LVL (License Verification Library) เพื่อใช้ไฟล์สำหรับขยายของ APK แน่นอนว่าหากแอปของคุณไม่มีค่าใช้จ่าย คุณไม่จำเป็นต้องบังคับใช้การยืนยันใบอนุญาต เพียงแค่ต้องมีไลบรารีเพื่อดำเนินการตามคำขอที่แสดงผล URL ของไฟล์ส่วนขยาย

หมายเหตุ: ไม่ว่าแอปของคุณจะฟรีหรือไม่ Google Play จะแสดง URL ของไฟล์ขยายเฉพาะในกรณีที่ผู้ใช้ดาวน์โหลดแอปจาก Google Play เท่านั้น

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

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

เราได้สร้างคลังโปรแกรมดาวน์โหลดเพื่อลดความซับซ้อนของงานนี้ให้คุณ ซึ่งจะขอ URL ของไฟล์ขยายผ่านบริการอนุญาตให้ใช้สิทธิ ดาวน์โหลดไฟล์ขยาย ทำงานทั้งหมดที่ระบุไว้ข้างต้น และแม้แต่หยุดกิจกรรมชั่วคราวและดาวน์โหลดต่อ การเพิ่มคลังเครื่องมือดาวน์โหลดและฮุกโค้ด 2-3 รายการลงในแอปจะทําให้ระบบเขียนโค้ดเกือบทั้งหมดสําหรับการดาวน์โหลดไฟล์ส่วนขยายให้คุณแล้ว ดังนั้น เราขอแนะนำให้คุณใช้คลังโปรแกรมดาวน์โหลดเพื่อดาวน์โหลดไฟล์ส่วนขยายเพื่อให้ผู้ใช้ได้รับประสบการณ์การใช้งานที่ดีที่สุดโดยที่คุณไม่ต้องทำอะไรมากนัก ข้อมูลในส่วนต่อไปนี้อธิบายวิธีผสานรวมคลังเข้ากับแอป

หากต้องการพัฒนาโซลูชันของคุณเองเพื่อดาวน์โหลดไฟล์ขยายโดยใช้ URL ของ Google Play คุณต้องทําตามเอกสารประกอบการอนุญาตให้ใช้แอปเพื่อส่งคําขอใบอนุญาต จากนั้นดึงข้อมูลชื่อ ไฟล์ขยาย ขนาด และ URL จากข้อมูลเพิ่มเติมของคำตอบ คุณควรใช้คลาส APKExpansionPolicy (รวมอยู่ในคลังการยืนยันใบอนุญาต) เป็นนโยบายการอนุญาตให้ใช้สิทธิ ซึ่งจะบันทึกชื่อ ขนาดใหญ่ และ URL ของไฟล์ขยายจากบริการอนุญาตให้ใช้สิทธิ

เกี่ยวกับคลังโปรแกรมดาวน์โหลด

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

หากต้องการใช้การดาวน์โหลดไฟล์สำหรับขยายโดยใช้คลังเครื่องมือดาวน์โหลด คุณก็เพียงทำดังนี้

  • ขยายService ซับคลาสและBroadcastReceiver ซับคลาสพิเศษที่แต่ละรายการต้องใช้โค้ดเพียงไม่กี่บรรทัด
  • เพิ่มตรรกะบางอย่างลงในกิจกรรมหลักที่ตรวจสอบว่าดาวน์โหลดไฟล์ขยายไปแล้วหรือยัง หากยัง ให้เรียกใช้กระบวนการดาวน์โหลดและแสดง UI ความคืบหน้า
  • ใช้อินเทอร์เฟซการเรียกกลับที่มีเมธอด 2-3 รายการในกิจกรรมหลักซึ่งรับข้อมูลอัปเดตเกี่ยวกับความคืบหน้าของการดาวน์โหลด

ส่วนต่อไปนี้จะอธิบายวิธีตั้งค่าแอปโดยใช้คลังโปรแกรมดาวน์โหลด

การเตรียมพร้อมใช้งานคลังโปรแกรมดาวน์โหลด

หากต้องการใช้ไลบรารีโปรแกรมดาวน์โหลด คุณต้องดาวน์โหลดแพ็กเกจ 2 รายการจากเครื่องมือจัดการ SDK และเพิ่มไลบรารีที่เหมาะสมลงในแอป

ก่อนอื่น ให้เปิด Android SDK Manager (เครื่องมือ > SDK Manager) และในส่วนลักษณะที่ปรากฏและลักษณะการทํางาน > การตั้งค่าระบบ > Android SDK ให้เลือกแท็บเครื่องมือ SDK เพื่อเลือกและดาวน์โหลดรายการต่อไปนี้

  • แพ็กเกจคลังใบอนุญาตของ Google Play
  • แพ็กเกจ Google Play Expansion Library สำหรับ APK

สร้างโมดูลไลบรารีใหม่สําหรับไลบรารีการยืนยันใบอนุญาตและ Downloader Library สำหรับคลังแต่ละแห่ง

  1. เลือก File > New > New Module
  2. ในหน้าต่างสร้างข้อบังคับใหม่ ให้เลือกคลัง Android แล้วเลือกถัดไป
  3. ระบุชื่อแอป/ไลบรารี เช่น "ไลบรารีใบอนุญาต Google Play" และ "ไลบรารีโปรแกรมดาวน์โหลด Google Play" เลือกระดับ SDK ขั้นต่ำ แล้วเลือกเสร็จสิ้น
  4. เลือกไฟล์ > โครงสร้างโปรเจ็กต์
  5. เลือกแท็บพร็อพเพอร์ตี้ และในส่วนคลัง ที่เก็บ ให้ป้อนคลังจากไดเรกทอรี <sdk>/extras/google/ (play_licensing/ สำหรับคลังการยืนยันใบอนุญาตหรือ play_apk_expansion/downloader_library/ สำหรับคลังโปรแกรมดาวน์โหลด)
  6. เลือกตกลงเพื่อสร้างข้อบังคับใหม่

หมายเหตุ: ไลบรารีโปรแกรมดาวน์โหลดจะขึ้นอยู่กับไลบรารีการยืนยันใบอนุญาต อย่าลืมเพิ่มไลบรารีการยืนยันใบอนุญาตลงในพร็อพเพอร์ตี้โปรเจ็กต์ของไลบรารีโปรแกรมดาวน์โหลด

หรืออัปเดตโปรเจ็กต์จากบรรทัดคำสั่งให้รวมไลบรารีต่อไปนี้

  1. เปลี่ยนไดเรกทอรีเป็นไดเรกทอรี <sdk>/tools/
  2. เรียกใช้ android update project ด้วยตัวเลือก --library เพื่อเพิ่มทั้ง LVL และไลบรารี Downloader ลงในโปรเจ็กต์ เช่น
    android update project --path ~/Android/MyApp \
    --library ~/android_sdk/extras/google/market_licensing \
    --library ~/android_sdk/extras/google/market_apk_expansion/downloader_library
    

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

เคล็ดลับ: แพ็กเกจการขยาย Apk มีตัวอย่างแอปที่แสดงวิธีใช้ไลบรารี Downloader ในแอป ตัวอย่างนี้ใช้ไลบรารีของบุคคลที่สามซึ่งมีอยู่ในแพ็กเกจการขยาย Apk ที่เรียกว่า APK Expansion Zip Library หากวางแผนที่จะใช้ไฟล์ ZIP สำหรับไฟล์สำหรับขยาย เราขอแนะนำให้คุณเพิ่มไลบรารี ZIP ส่วนขยาย APK ลงในแอปด้วย ดูข้อมูลเพิ่มเติมได้ที่ส่วนการใช้ไลบรารี ZIP ส่วนขยาย APK

การประกาศสิทธิ์ของผู้ใช้

หากต้องการดาวน์โหลดไฟล์เสริม ไลบรารี Downloader จะต้องใช้สิทธิ์หลายรายการที่คุณจะต้องประกาศในไฟล์ Manifest ของแอป ดังนี้

<manifest ...>
    <!-- Required to access Google Play Licensing -->
    <uses-permission android:name="com.android.vending.CHECK_LICENSE" />

    <!-- Required to download files from Google Play -->
    <uses-permission android:name="android.permission.INTERNET" />

    <!-- Required to keep CPU alive while downloading files
        (NOT to keep screen awake) -->
    <uses-permission android:name="android.permission.WAKE_LOCK" />

    <!-- Required to poll the state of the network connection
        and respond to changes -->
    <uses-permission
        android:name="android.permission.ACCESS_NETWORK_STATE" />

    <!-- Required to check whether Wi-Fi is enabled -->
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>

    <!-- Required to read and write the expansion files on shared storage -->
    <uses-permission
        android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    ...
</manifest>

หมายเหตุ: โดยค่าเริ่มต้น ไลบรารี Downloader ต้องใช้ API ระดับ 4 แต่ไลบรารี ZIP การขยาย APK ต้องใช้ API ระดับ 5

การใช้บริการดาวน์โหลด

หากต้องการทำการดาวน์โหลดในเบื้องหลัง ไลบรารี Downloader จะมีService ซับคลาสของตัวเองชื่อ DownloaderService ที่คุณควรขยาย นอกเหนือจากการดาวน์โหลดไฟล์ส่วนขยายให้คุณแล้ว DownloaderService ยังทําสิ่งต่อไปนี้ด้วย

  • ลงทะเบียน BroadcastReceiver ที่คอยฟังการเปลี่ยนแปลงการเชื่อมต่อเครือข่ายของอุปกรณ์ (การออกอากาศ CONNECTIVITY_ACTION) เพื่อหยุดการดาวน์โหลดชั่วคราวเมื่อจำเป็น (เช่น การเชื่อมต่อขาดหายไป) และกลับมาดาวน์โหลดต่อเมื่อเป็นไปได้ (มีการเชื่อมต่อ)
  • ตั้งเวลาการปลุก RTC_WAKEUP เพื่อลองดาวน์โหลดอีกครั้งสำหรับกรณีที่บริการถูกหยุด
  • สร้าง Notification ที่กําหนดเองซึ่งแสดงความคืบหน้าการดาวน์โหลด รวมถึงข้อผิดพลาดหรือการเปลี่ยนแปลงสถานะ
  • อนุญาตให้แอปหยุดการดาวน์โหลดชั่วคราวและดาวน์โหลดต่อด้วยตนเอง
  • ตรวจสอบว่าพื้นที่เก็บข้อมูลที่ใช้ร่วมกันนั้นพร้อมใช้งานและได้รับการต่อเชื่อมแล้ว ไม่มีไฟล์ดังกล่าวอยู่แล้ว และมีพื้นที่เพียงพอ ก่อนที่จะดาวน์โหลดไฟล์ขยาย จากนั้นจะแจ้งให้ผู้ใช้ทราบหากข้อมูลข้างต้นไม่ถูกต้อง

สิ่งที่ต้องทำมีเพียงสร้างคลาสในแอปที่ขยายคลาส DownloaderService และลบล้างเมธอด 3 รายการเพื่อระบุรายละเอียดแอปที่เฉพาะเจาะจง ดังนี้

getPublicKey()
การดำเนินการนี้ต้องแสดงผลสตริงที่เป็นคีย์สาธารณะ RSA ที่เข้ารหัส Base64 สำหรับบัญชีผู้เผยแพร่ของคุณ ซึ่งดูได้จากหน้าโปรไฟล์ใน Play Console (ดูการตั้งค่าสำหรับการอนุญาตให้ใช้สิทธิ)
getSALT()
การดำเนินการนี้ต้องแสดงผลอาร์เรย์ไบต์แบบสุ่มที่ Policy ที่ใช้เพื่อสร้าง Obfuscator เกลือช่วยให้มั่นใจว่าไฟล์ SharedPreferences ที่มีการสร้างความสับสนซึ่งบันทึกข้อมูลการอนุญาตให้ใช้สิทธิจะไม่เหมือนใครและไม่สามารถค้นพบได้
getAlarmReceiverClassName()
การดำเนินการนี้ต้องแสดงผลชื่อคลาสของ BroadcastReceiver ในแอปของคุณที่ควรได้รับการแจ้งเตือนว่าควรเริ่มการดาวน์โหลดอีกครั้ง (ซึ่งอาจเกิดขึ้นหากบริการดาวน์โหลดหยุดทำงานโดยไม่คาดคิด)

ตัวอย่างเช่น ต่อไปนี้คือการใช้งาน DownloaderService ที่สมบูรณ์

Kotlin

// You must use the public key belonging to your publisher account
const val BASE64_PUBLIC_KEY = "YourLVLKey"
// You should also modify this salt
val SALT = byteArrayOf(
        1, 42, -12, -1, 54, 98, -100, -12, 43, 2,
        -8, -4, 9, 5, -106, -107, -33, 45, -1, 84
)

class SampleDownloaderService : DownloaderService() {

    override fun getPublicKey(): String = BASE64_PUBLIC_KEY

    override fun getSALT(): ByteArray = SALT

    override fun getAlarmReceiverClassName(): String = SampleAlarmReceiver::class.java.name
}

Java

public class SampleDownloaderService extends DownloaderService {
    // You must use the public key belonging to your publisher account
    public static final String BASE64_PUBLIC_KEY = "YourLVLKey";
    // You should also modify this salt
    public static final byte[] SALT = new byte[] { 1, 42, -12, -1, 54, 98,
            -100, -12, 43, 2, -8, -4, 9, 5, -106, -107, -33, 45, -1, 84
    };

    @Override
    public String getPublicKey() {
        return BASE64_PUBLIC_KEY;
    }

    @Override
    public byte[] getSALT() {
        return SALT;
    }

    @Override
    public String getAlarmReceiverClassName() {
        return SampleAlarmReceiver.class.getName();
    }
}

หมายเหตุ: คุณต้องอัปเดตค่า BASE64_PUBLIC_KEY เป็นคีย์สาธารณะของบัญชีผู้เผยแพร่โฆษณา คุณดูคีย์ได้ใน Developer Console ในส่วนข้อมูลโปรไฟล์ ซึ่งจำเป็นต้องทำแม้กระทั่งเมื่อทดสอบการดาวน์โหลด

อย่าลืมประกาศบริการในไฟล์ Manifest

<app ...>
    <service android:name=".SampleDownloaderService" />
    ...
</app>

การติดตั้งใช้งานเครื่องรับสัญญาณเตือน

DownloaderService จะตั้งเวลาการปลุก RTC_WAKEUP เพื่อส่ง Intent ไปยัง BroadcastReceiver ในแอปของคุณ เพื่อตรวจสอบความคืบหน้าของการดาวน์โหลดไฟล์และเริ่มดาวน์โหลดอีกครั้งหากจําเป็น คุณต้องกําหนด BroadcastReceiver เพื่อเรียก API จากคลังโปรแกรมดาวน์โหลดที่จะตรวจสอบสถานะการดาวน์โหลดและเริ่มดาวน์โหลดอีกครั้งหากจําเป็น

คุณเพียงต้องลบล้างเมธอด onReceive() เพื่อเรียกใช้ DownloaderClientMarshaller.startDownloadServiceIfRequired()

เช่น

Kotlin

class SampleAlarmReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        try {
            DownloaderClientMarshaller.startDownloadServiceIfRequired(
                    context,
                    intent,
                    SampleDownloaderService::class.java
            )
        } catch (e: PackageManager.NameNotFoundException) {
            e.printStackTrace()
        }
    }
}

Java

public class SampleAlarmReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        try {
            DownloaderClientMarshaller.startDownloadServiceIfRequired(context,
                intent, SampleDownloaderService.class);
        } catch (NameNotFoundException e) {
            e.printStackTrace();
        }
    }
}

โปรดทราบว่านี่คือคลาสที่คุณจะต้องแสดงชื่อในเมธอด getAlarmReceiverClassName() ของบริการ (ดูส่วนก่อนหน้า)

อย่าลืมประกาศผู้รับในไฟล์ Manifest

<app ...>
    <receiver android:name=".SampleAlarmReceiver" />
    ...
</app>

กำลังเริ่มดาวน์โหลด

กิจกรรมหลักในแอป (กิจกรรมที่เริ่มต้นโดยไอคอนตัวเปิดแอป) จะมีหน้าที่รับผิดชอบในการยืนยันว่ามีไฟล์ขยายอยู่ในอุปกรณ์หรือไม่ และเริ่มการดาวน์โหลดหากไม่มี

คุณต้องทําตามขั้นตอนต่อไปนี้เพื่อเริ่มการดาวน์โหลดโดยใช้คลังโปรแกรมดาวน์โหลด

  1. ตรวจสอบว่าระบบดาวน์โหลดไฟล์แล้วหรือยัง

    ไลบรารี Downloader มี API บางรายการในคลาส Helper เพื่อช่วยในกระบวนการนี้

    • getExpansionAPKFileName(Context, c, boolean mainFile, int versionCode)
    • doesFileExist(Context c, String fileName, long fileSize)

    ตัวอย่างเช่น แอปตัวอย่างที่ให้มาในแพ็กเกจการขยาย Apk จะเรียกใช้วิธีต่อไปนี้ในเมธอด onCreate() ของกิจกรรมเพื่อตรวจสอบว่ามีไฟล์ขยายอยู่ในอุปกรณ์แล้วหรือยัง

    Kotlin

    fun expansionFilesDelivered(): Boolean {
        xAPKS.forEach { xf ->
            Helpers.getExpansionAPKFileName(this, xf.isBase, xf.fileVersion).also { fileName ->
                if (!Helpers.doesFileExist(this, fileName, xf.fileSize, false))
                    return false
            }
        }
        return true
    }

    Java

    boolean expansionFilesDelivered() {
        for (XAPKFile xf : xAPKS) {
            String fileName = Helpers.getExpansionAPKFileName(this, xf.isBase,
                xf.fileVersion);
            if (!Helpers.doesFileExist(this, fileName, xf.fileSize, false))
                return false;
        }
        return true;
    }

    ในกรณีนี้ ออบเจ็กต์ XAPKFile แต่ละรายการจะมีหมายเลขเวอร์ชันและขนาดไฟล์ของไฟล์เสริมที่รู้จัก รวมถึงบูลีนว่าไฟล์นั้นเป็นไฟล์เสริมหลักหรือไม่ (ดูรายละเอียดได้ที่คลาส SampleDownloaderActivity ของแอปตัวอย่าง)

    หากเมธอดนี้แสดงผลเป็นเท็จ แอปจะต้องเริ่มการดาวน์โหลด

  2. เริ่มการดาวน์โหลดโดยเรียกใช้เมธอดแบบคงที่ DownloaderClientMarshaller.startDownloadServiceIfRequired(Context c, PendingIntent notificationClient, Class<?> serviceClass)

    โดยเมธอดนี้ใช้พารามิเตอร์ต่อไปนี้

    • context: Context ของแอป
    • notificationClient: PendingIntent เพื่อเริ่มกิจกรรมหลัก ซึ่งจะใช้ใน Notification ที่ DownloaderServiceสร้างขึ้นเพื่อแสดงความคืบหน้าในการดาวน์โหลด เมื่อผู้ใช้เลือกการแจ้งเตือน ระบบจะเรียกใช้ PendingIntent ที่คุณระบุที่นี่และควรเปิดกิจกรรมที่แสดงความคืบหน้าการดาวน์โหลด (โดยปกติจะเป็นกิจกรรมเดียวกันกับที่เริ่มการดาวน์โหลด)
    • serviceClass: ออบเจ็กต์ Class สำหรับการติดตั้งใช้งาน DownloaderService ซึ่งจําเป็นต่อการเริ่มบริการและเริ่มการดาวน์โหลด หากจําเป็น

    เมธอดจะแสดงผลจำนวนเต็มที่ระบุว่าจำเป็นต้องดาวน์โหลดหรือไม่ ค่าที่เป็นไปได้มีดังนี้

    • NO_DOWNLOAD_REQUIRED: แสดงผลหากไฟล์มีอยู่แล้วหรือมีการดาวน์โหลดอยู่
    • LVL_CHECK_REQUIRED: แสดงผลหากจำเป็นต้องมีการยืนยันใบอนุญาตเพื่อรับ URL ของไฟล์ขยาย
    • DOWNLOAD_REQUIRED: แสดงผลหากระบบทราบ URL ของไฟล์ขยายอยู่แล้ว แต่ยังไม่ได้ดาวน์โหลด

    โดยทั่วไปแล้ว LVL_CHECK_REQUIRED และ DOWNLOAD_REQUIRED มีลักษณะการทำงานเหมือนกัน และคุณไม่จําเป็นต้องกังวลเกี่ยวกับค่าเหล่านี้ ในกิจกรรมหลักที่เรียก startDownloadServiceIfRequired() คุณสามารถตรวจสอบได้ว่าคําตอบคือ NO_DOWNLOAD_REQUIRED หรือไม่ หากคำตอบเป็นค่าอื่นที่ไม่ใช่ NO_DOWNLOAD_REQUIRED แสดงว่าไลบรารีโปรแกรมดาวน์โหลดจะเริ่มการดาวน์โหลดและคุณควรอัปเดต UI ของกิจกรรมเพื่อแสดงความคืบหน้าของการดาวน์โหลด (ดูขั้นตอนถัดไป) หากคำตอบคือ NO_DOWNLOAD_REQUIRED แสดงว่าไฟล์พร้อมใช้งานและแอปของคุณจะเริ่มทำงานได้

    เช่น

    Kotlin

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    
        // Check if expansion files are available before going any further
        if (!expansionFilesDelivered()) {
            val pendingIntent =
                    // Build an Intent to start this activity from the Notification
                    Intent(this, MainActivity::class.java).apply {
                        flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
                    }.let { notifierIntent ->
                        PendingIntent.getActivity(
                                this,
                                0,
                                notifierIntent,
                                PendingIntent.FLAG_UPDATE_CURRENT
                        )
                    }
    
    
            // Start the download service (if required)
            val startResult: Int = DownloaderClientMarshaller.startDownloadServiceIfRequired(
                    this,
                    pendingIntent,
                    SampleDownloaderService::class.java
            )
            // If download has started, initialize this activity to show
            // download progress
            if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) {
                // This is where you do set up to display the download
                // progress (next step)
                ...
                return
            } // If the download wasn't necessary, fall through to start the app
        }
        startApp() // Expansion files are available, start the app
    }

    Java

    @Override
    public void onCreate(Bundle savedInstanceState) {
        // Check if expansion files are available before going any further
        if (!expansionFilesDelivered()) {
            // Build an Intent to start this activity from the Notification
            Intent notifierIntent = new Intent(this, MainActivity.getClass());
            notifierIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
                                    Intent.FLAG_ACTIVITY_CLEAR_TOP);
            ...
            PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
                    notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT);
    
            // Start the download service (if required)
            int startResult =
                DownloaderClientMarshaller.startDownloadServiceIfRequired(this,
                            pendingIntent, SampleDownloaderService.class);
            // If download has started, initialize this activity to show
            // download progress
            if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) {
                // This is where you do set up to display the download
                // progress (next step)
                ...
                return;
            } // If the download wasn't necessary, fall through to start the app
        }
        startApp(); // Expansion files are available, start the app
    }
  3. เมื่อเมธอด startDownloadServiceIfRequired() แสดงผลเป็นค่าอื่นที่ไม่ใช่ NO_DOWNLOAD_REQUIRED ให้สร้างอินสแตนซ์ของ IStub โดยการเรียกใช้ DownloaderClientMarshaller.CreateStub(IDownloaderClient client, Class<?> downloaderService) IStub มีการเชื่อมโยงระหว่างกิจกรรมกับบริการดาวน์โหลดเพื่อให้กิจกรรมได้รับการเรียกกลับเกี่ยวกับความคืบหน้าของการดาวน์โหลด

    หากต้องการสร้างอินสแตนซ์ IStub โดยการเรียกใช้ CreateStub() คุณต้องส่งผ่านการใช้งานอินเทอร์เฟซ IDownloaderClient และการใช้งาน DownloaderService ส่วนถัดไปเกี่ยวกับการรับความคืบหน้าการดาวน์โหลดจะกล่าวถึงอินเทอร์เฟซ IDownloaderClient ซึ่งโดยปกติแล้วคุณควรนำไปใช้ในคลาส Activity เพื่อให้อัปเดต UI ของกิจกรรมได้เมื่อสถานะการดาวน์โหลดมีการเปลี่ยนแปลง

    เราขอแนะนำให้คุณเรียก CreateStub() เพื่อสร้างอินสแตนซ์ IStub ในระหว่างเมธอด onCreate() ของกิจกรรม หลังจากที่ startDownloadServiceIfRequired() เริ่มการดาวน์โหลด

    ตัวอย่างเช่น ในตัวอย่างโค้ดก่อนหน้าสําหรับ onCreate() คุณสามารถตอบสนองต่อผลการค้นหา startDownloadServiceIfRequired() ดังนี้

    Kotlin

            // Start the download service (if required)
            val startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(
                    this@MainActivity,
                    pendingIntent,
                    SampleDownloaderService::class.java
            )
            // If download has started, initialize activity to show progress
            if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) {
                // Instantiate a member instance of IStub
                downloaderClientStub =
                        DownloaderClientMarshaller.CreateStub(this, SampleDownloaderService::class.java)
                // Inflate layout that shows download progress
                setContentView(R.layout.downloader_ui)
                return
            }

    Java

            // Start the download service (if required)
            int startResult =
                DownloaderClientMarshaller.startDownloadServiceIfRequired(this,
                            pendingIntent, SampleDownloaderService.class);
            // If download has started, initialize activity to show progress
            if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) {
                // Instantiate a member instance of IStub
                downloaderClientStub = DownloaderClientMarshaller.CreateStub(this,
                        SampleDownloaderService.class);
                // Inflate layout that shows download progress
                setContentView(R.layout.downloader_ui);
                return;
            }

    หลังจากเมธอด onCreate() แสดงผลแล้ว กิจกรรมของคุณจะได้รับคําเรียก onResume() ซึ่งคุณควรเรียก connect() ใน IStub โดยส่ง Context ของแอป ในทางกลับกัน คุณควรเรียกใช้ disconnect() ใน onStop() callback ของกิจกรรม

    Kotlin

    override fun onResume() {
        downloaderClientStub?.connect(this)
        super.onResume()
    }
    
    override fun onStop() {
        downloaderClientStub?.disconnect(this)
        super.onStop()
    }

    Java

    @Override
    protected void onResume() {
        if (null != downloaderClientStub) {
            downloaderClientStub.connect(this);
        }
        super.onResume();
    }
    
    @Override
    protected void onStop() {
        if (null != downloaderClientStub) {
            downloaderClientStub.disconnect(this);
        }
        super.onStop();
    }

    การเรียกใช้ connect() ใน IStub จะเชื่อมโยงกิจกรรมของคุณกับ DownloaderService เพื่อให้กิจกรรมได้รับการเรียกกลับเกี่ยวกับการเปลี่ยนแปลงสถานะการดาวน์โหลดผ่านอินเทอร์เฟซ IDownloaderClient

ความคืบหน้าในการรับการดาวน์โหลด

หากต้องการรับข้อมูลอัปเดตเกี่ยวกับความคืบหน้าของการดาวน์โหลดและโต้ตอบกับ DownloaderService คุณต้องติดตั้งใช้งานอินเทอร์เฟซ IDownloaderClient ของ Downloader Library โดยปกติแล้ว กิจกรรมที่คุณใช้เพื่อเริ่มการดาวน์โหลดควรใช้อินเทอร์เฟซนี้เพื่อแสดงความคืบหน้าของการดาวน์โหลดและส่งคําขอไปยังบริการ

วิธีการอินเทอร์เฟซที่จําเป็นสําหรับ IDownloaderClient มีดังนี้

onServiceConnected(Messenger m)
หลังจากสร้างอินสแตนซ์ IStub ในกิจกรรมแล้ว คุณจะได้รับคําเรียกใช้เมธอดนี้ ซึ่งจะส่งออบเจ็กต์ Messenger ที่เชื่อมต่อกับอินสแตนซ์ DownloaderService หากต้องการส่งคำขอไปยังบริการ เช่น หยุดชั่วคราวและดาวน์โหลดต่อ คุณต้องเรียกใช้ DownloaderServiceMarshaller.CreateProxy() เพื่อรับอินเทอร์เฟซ IDownloaderService ที่เชื่อมต่อกับบริการ

การติดตั้งใช้งานที่แนะนํามีลักษณะดังนี้

Kotlin

private var remoteService: IDownloaderService? = null
...

override fun onServiceConnected(m: Messenger) {
    remoteService = DownloaderServiceMarshaller.CreateProxy(m).apply {
        downloaderClientStub?.messenger?.also { messenger ->
            onClientUpdated(messenger)
        }
    }
}

Java

private IDownloaderService remoteService;
...

@Override
public void onServiceConnected(Messenger m) {
    remoteService = DownloaderServiceMarshaller.CreateProxy(m);
    remoteService.onClientUpdated(downloaderClientStub.getMessenger());
}

เมื่อเริ่มต้นออบเจ็กต์ IDownloaderService แล้ว คุณจะส่งคําสั่งไปยังบริการดาวน์โหลดได้ เช่น หยุดชั่วคราวและดาวน์โหลดต่อ (requestPauseDownload() และ requestContinueDownload())

onDownloadStateChanged(int newState)
บริการดาวน์โหลดจะเรียกใช้เมธอดนี้เมื่อมีการเปลี่ยนแปลงสถานะการดาวน์โหลด เช่น การดาวน์โหลดเริ่มต้นขึ้นหรือเสร็จสมบูรณ์

ค่า newState จะเป็นหนึ่งในค่าที่เป็นไปได้หลายค่าที่ระบุไว้ในค่าคงที่ STATE_* ของคลาส IDownloaderClient

หากต้องการแสดงข้อความที่เป็นประโยชน์ต่อผู้ใช้ คุณสามารถขอสตริงที่เกี่ยวข้องสำหรับสถานะแต่ละรายการได้โดยเรียกใช้ Helpers.getDownloaderStringResourceIDFromState() ซึ่งจะแสดงรหัสทรัพยากรของสตริงใดสตริงหนึ่งที่อยู่ในชุดหนังสือของโปรแกรมดาวน์โหลด เช่น สตริง "การดาวน์โหลดหยุดชั่วคราวเนื่องจากคุณกำลังโรมมิ่ง" สอดคล้องกับ STATE_PAUSED_ROAMING

onDownloadProgress(DownloadProgressInfo progress)
บริการดาวน์โหลดจะเรียกใช้เพื่อส่งออบเจ็กต์ DownloadProgressInfo ซึ่งจะอธิบายข้อมูลต่างๆ เกี่ยวกับความคืบหน้าของการดาวน์โหลด รวมถึงเวลาที่เหลือโดยประมาณ ความเร็วปัจจุบัน ความคืบหน้าโดยรวม และจำนวนทั้งหมดเพื่อให้คุณอัปเดต UI ความคืบหน้าของการดาวน์โหลดได้

เคล็ดลับ: ดูตัวอย่างการเรียกกลับเหล่านี้ที่อัปเดต UI ความคืบหน้าการดาวน์โหลดได้ที่ SampleDownloaderActivity ในแอปตัวอย่างที่มาพร้อมกับแพ็กเกจการขยาย Apk

เมธอดสาธารณะบางรายการสำหรับอินเทอร์เฟซ IDownloaderService ที่อาจเป็นประโยชน์มีดังนี้

requestPauseDownload()
หยุดการดาวน์โหลดชั่วคราว
requestContinueDownload()
ดาวน์โหลดต่อจากตอนที่หยุดไว้
setDownloadFlags(int flags)
ตั้งค่ากำหนดของผู้ใช้สำหรับประเภทเครือข่ายที่อนุญาตให้ดาวน์โหลดไฟล์ได้ การใช้งานปัจจุบันรองรับ Flag 1 รายการเท่านั้น ซึ่งก็คือ FLAGS_DOWNLOAD_OVER_CELLULAR แต่คุณเพิ่ม Flag อื่นๆ ได้ โดยค่าเริ่มต้น ระบบจะไม่เปิดใช้ Flag นี้ ดังนั้นผู้ใช้จึงต้องใช้ Wi-Fi เพื่อดาวน์โหลดไฟล์ส่วนขยาย คุณอาจต้องระบุค่ากำหนดของผู้ใช้เพื่อเปิดใช้การดาวน์โหลดผ่านเครือข่ายมือถือ ในกรณีนี้ คุณสามารถโทรหาหมายเลขต่อไปนี้

Kotlin

remoteService = DownloaderServiceMarshaller.CreateProxy(m).apply {
    ...
    setDownloadFlags(IDownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR)
}

Java

remoteService
    .setDownloadFlags(IDownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR);

การใช้ APKExpansionPolicy

หากตัดสินใจที่จะสร้างบริการดาวน์โหลดของคุณเองแทนที่จะใช้คลังเครื่องมือดาวน์โหลดของ Google Play คุณควรใช้ APKExpansionPolicy ที่ระบุไว้ในคลังการยืนยันใบอนุญาต คลาส APKExpansionPolicy เกือบจะเหมือนกับ ServerManagedPolicy (มีอยู่ใน Google Play License Verification Library) แต่มีการจัดการเพิ่มเติมสำหรับข้อมูลเพิ่มเติมในการตอบกลับไฟล์การขยาย APK

หมายเหตุ: หากคุณใช้คลังโปรแกรมดาวน์โหลดตามที่ได้อธิบายไว้ในส่วนก่อนหน้า คลังจะดำเนินการโต้ตอบกับ APKExpansionPolicy ทั้งหมด คุณจึงไม่ต้องใช้คลาสนี้โดยตรง

คลาสนี้มีเมธอดที่จะช่วยให้คุณได้รับข้อมูลที่จําเป็นเกี่ยวกับไฟล์ส่วนขยายที่ใช้ได้ ดังนี้

  • getExpansionURLCount()
  • getExpansionURL(int index)
  • getExpansionFileName(int index)
  • getExpansionFileSize(int index)

ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีใช้ APKExpansionPolicy เมื่อไม่ได้ใช้คลังเครื่องมือดาวน์โหลดได้ที่เอกสารประกอบสำหรับการเพิ่มการอนุญาตให้ใช้สิทธิในแอป ซึ่งอธิบายวิธีใช้นโยบายการอนุญาตให้ใช้สิทธิเช่นนี้

การอ่านไฟล์สำหรับขยาย

เมื่อบันทึกไฟล์ขยายของ APK ไว้ในอุปกรณ์แล้ว วิธีอ่านไฟล์จะขึ้นอยู่กับประเภทไฟล์ที่คุณใช้ ดังที่ได้กล่าวไว้ในภาพรวม ไฟล์ขยายอาจเป็นไฟล์ประเภทใดก็ได้ตามต้องการ แต่ระบบจะเปลี่ยนชื่อโดยใช้รูปแบบชื่อไฟล์ที่เฉพาะเจาะจงและบันทึกลงใน<shared-storage>/Android/obb/<package-name>/

ไม่ว่าคุณจะอ่านไฟล์ด้วยวิธีใด คุณควรตรวจสอบก่อนเสมอว่าพื้นที่เก็บข้อมูลภายนอกพร้อมให้อ่าน ผู้ใช้อาจต่อพื้นที่เก็บข้อมูลกับคอมพิวเตอร์ผ่าน USB หรือนำการ์ด SD ออกแล้ว

หมายเหตุ: เมื่อแอปเริ่มทำงาน คุณควรตรวจสอบเสมอว่าพื้นที่เก็บข้อมูลภายนอกพร้อมใช้งานและอ่านได้หรือไม่โดยเรียกใช้ getExternalStorageState() ซึ่งจะแสดงผลสตริงที่เป็นไปได้ 1 รายการจากหลายรายการซึ่งแสดงสถานะของพื้นที่เก็บข้อมูลภายนอก ค่าที่แสดงผลต้องเป็น MEDIA_MOUNTED เพื่อให้แอปอ่านได้

การรับชื่อไฟล์

ตามที่อธิบายไว้ในภาพรวม ระบบจะบันทึกไฟล์เสริมของ APK โดยใช้รูปแบบชื่อไฟล์ที่เฉพาะเจาะจง ดังนี้

[main|patch].<expansion-version>.<package-name>.obb

หากต้องการทราบตำแหน่งและชื่อของไฟล์ขยาย คุณควรใช้เมธอด getExternalStorageDirectory() และ getPackageName() เพื่อสร้างเส้นทางไปยังไฟล์

ต่อไปนี้คือเมธอดที่คุณใช้ในแอปเพื่อรับอาร์เรย์ที่มีเส้นทางที่สมบูรณ์ไปยังไฟล์ขยายทั้ง 2 ไฟล์

Kotlin

fun getAPKExpansionFiles(ctx: Context, mainVersion: Int, patchVersion: Int): Array<String> {
    val packageName = ctx.packageName
    val ret = mutableListOf<String>()
    if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
        // Build the full path to the app's expansion files
        val root = Environment.getExternalStorageDirectory()
        val expPath = File(root.toString() + EXP_PATH + packageName)

        // Check that expansion file path exists
        if (expPath.exists()) {
            if (mainVersion > 0) {
                val strMainPath = "$expPath${File.separator}main.$mainVersion.$packageName.obb"
                val main = File(strMainPath)
                if (main.isFile) {
                    ret += strMainPath
                }
            }
            if (patchVersion > 0) {
                val strPatchPath = "$expPath${File.separator}patch.$mainVersion.$packageName.obb"
                val main = File(strPatchPath)
                if (main.isFile) {
                    ret += strPatchPath
                }
            }
        }
    }
    return ret.toTypedArray()
}

Java

// The shared path to all app expansion files
private final static String EXP_PATH = "/Android/obb/";

static String[] getAPKExpansionFiles(Context ctx, int mainVersion,
      int patchVersion) {
    String packageName = ctx.getPackageName();
    Vector<String> ret = new Vector<String>();
    if (Environment.getExternalStorageState()
          .equals(Environment.MEDIA_MOUNTED)) {
        // Build the full path to the app's expansion files
        File root = Environment.getExternalStorageDirectory();
        File expPath = new File(root.toString() + EXP_PATH + packageName);

        // Check that expansion file path exists
        if (expPath.exists()) {
            if ( mainVersion > 0 ) {
                String strMainPath = expPath + File.separator + "main." +
                        mainVersion + "." + packageName + ".obb";
                File main = new File(strMainPath);
                if ( main.isFile() ) {
                        ret.add(strMainPath);
                }
            }
            if ( patchVersion > 0 ) {
                String strPatchPath = expPath + File.separator + "patch." +
                        mainVersion + "." + packageName + ".obb";
                File main = new File(strPatchPath);
                if ( main.isFile() ) {
                        ret.add(strPatchPath);
                }
            }
        }
    }
    String[] retArray = new String[ret.size()];
    ret.toArray(retArray);
    return retArray;
}

คุณสามารถเรียกใช้เมธอดนี้ได้โดยส่งContext แอปของคุณและเวอร์ชันของไฟล์สำหรับขยายที่ต้องการ

คุณระบุหมายเลขเวอร์ชันของไฟล์ขยายได้หลายวิธี วิธีง่ายๆ วิธีหนึ่งคือการบันทึกเวอร์ชันในไฟล์ SharedPreferences เมื่อการดาวน์โหลดเริ่มต้นขึ้น โดยค้นหาชื่อไฟล์ส่วนขยายด้วยเมธอด getExpansionFileName(int index) ของคลาส APKExpansionPolicy จากนั้นคุณจะได้รับรหัสเวอร์ชันโดยการอ่านไฟล์ SharedPreferences เมื่อต้องการเข้าถึงไฟล์ส่วนขยาย

ดูข้อมูลเพิ่มเติมเกี่ยวกับการอ่านจากพื้นที่เก็บข้อมูลที่ใช้ร่วมกันได้ในเอกสารประกอบพื้นที่เก็บข้อมูล

การใช้คลัง ZIP สำหรับขยาย APK

แพ็กเกจการขยาย Apk ของ Google Market มีไลบรารีที่เรียกว่า APK Expansion Zip Library (อยู่ใน <sdk>/extras/google/google_market_apk_expansion/zip_file/) ซึ่งเป็นไลบรารีที่ไม่บังคับซึ่งจะช่วยคุณอ่านไฟล์สำหรับขยายเมื่อบันทึกเป็นไฟล์ ZIP การใช้ไลบรารีนี้ช่วยให้คุณอ่านทรัพยากรจากไฟล์ ZIP ที่ขยายเป็นระบบไฟล์เสมือนได้อย่างง่ายดาย

ไลบรารี ZIP ของส่วนขยาย APK มีคลาสและ API ต่อไปนี้

APKExpansionSupport
ระบุวิธีการเข้าถึงชื่อไฟล์ขยายและไฟล์ ZIP ดังนี้
getAPKExpansionFiles()
วิธีการเดียวกันกับที่แสดงด้านบนซึ่งแสดงเส้นทางไฟล์ที่สมบูรณ์ไปยังไฟล์ขยายทั้ง 2 ไฟล์
getAPKExpansionZipFile(Context ctx, int mainVersion, int patchVersion)
แสดงผล ZipResourceFile ที่แสดงผลรวมของทั้งไฟล์หลักและไฟล์แพตช์ กล่าวคือ หากคุณระบุทั้ง mainVersion และ patchVersion ระบบจะแสดงผล ZipResourceFile ที่ให้สิทธิ์การอ่านข้อมูลทั้งหมด โดยผสานข้อมูลของไฟล์แพตช์ไว้ด้านบนของไฟล์หลัก
ZipResourceFile
แสดงไฟล์ ZIP ในพื้นที่เก็บข้อมูลที่ใช้ร่วมกันและดำเนินการทั้งหมดเพื่อให้ระบบไฟล์เสมือนตามไฟล์ ZIP คุณรับอินสแตนซ์ได้โดยใช้ APKExpansionSupport.getAPKExpansionZipFile() หรือ ZipResourceFile โดยส่งเส้นทางไปยังไฟล์การขยาย คลาสนี้มีเมธอดที่มีประโยชน์มากมาย แต่โดยทั่วไปแล้วคุณไม่จำเป็นต้องเข้าถึงเมธอดส่วนใหญ่ วิธีการสําคัญ 2 วิธี ได้แก่
getInputStream(String assetPath)
ระบุ InputStream เพื่ออ่านไฟล์ภายในไฟล์ ZIP assetPath ต้องเป็นเส้นทางไปยังไฟล์ที่ต้องการ โดยสัมพันธ์กับรูทของเนื้อหาไฟล์ ZIP
getAssetFileDescriptor(String assetPath)
ระบุ AssetFileDescriptor สำหรับไฟล์ภายในไฟล์ ZIP assetPath ต้องเป็นเส้นทางไปยังไฟล์ที่ต้องการ โดยสัมพันธ์กับรูทของเนื้อหาไฟล์ ZIP ซึ่งจะเป็นประโยชน์สําหรับ API ของ Android บางรายการที่ต้องใช้ AssetFileDescriptor เช่น API ของ MediaPlayer บางรายการ
APEZProvider
แอปส่วนใหญ่ไม่จำเป็นต้องใช้คลาสนี้ คลาสนี้กำหนด ContentProvider ที่จัดระเบียบข้อมูลจากไฟล์ ZIP ผ่านผู้ให้บริการเนื้อหา Uri เพื่อให้สิทธิ์เข้าถึงไฟล์สำหรับ Android API บางรายการที่คาดหวังสิทธิ์เข้าถึง Uri สำหรับไฟล์สื่อ เช่น คำสั่งนี้มีประโยชน์ในกรณีที่คุณต้องการเล่นวิดีโอด้วย VideoView.setVideoURI()

การข้ามการบีบอัดไฟล์สื่อเป็น ZIP

หากคุณใช้ไฟล์สำหรับขยายเพื่อจัดเก็บไฟล์สื่อ ไฟล์ ZIP จะยังคงให้คุณใช้การเรียกใช้การเล่นสื่อของ Android ที่มีการควบคุมการเลื่อนและความยาว (เช่น MediaPlayer.setDataSource() และ SoundPool.load()) ได้ คุณต้องไม่ทำการบีบอัดไฟล์สื่อเพิ่มเติมเมื่อสร้างแพ็กเกจ ZIP เพื่อให้การดำเนินการนี้ทำงานได้ เช่น เมื่อใช้เครื่องมือ zip คุณควรใช้ตัวเลือก -nเพื่อระบุนามสกุลไฟล์ที่ไม่ควรบีบอัด

zip -n .mp4;.ogg main_expansion media_files

การอ่านจากไฟล์ ZIP

เมื่อใช้ไลบรารี ZIP ของ APK Expansion การอ่านไฟล์จาก ZIP มักจะต้องใช้สิ่งต่อไปนี้

Kotlin

// Get a ZipResourceFile representing a merger of both the main and patch files
val expansionFile =
        APKExpansionSupport.getAPKExpansionZipFile(appContext, mainVersion, patchVersion)

// Get an input stream for a known file inside the expansion file ZIPs
expansionFile.getInputStream(pathToFileInsideZip).use {
    ...
}

Java

// Get a ZipResourceFile representing a merger of both the main and patch files
ZipResourceFile expansionFile =
    APKExpansionSupport.getAPKExpansionZipFile(appContext,
        mainVersion, patchVersion);

// Get an input stream for a known file inside the expansion file ZIPs
InputStream fileStream = expansionFile.getInputStream(pathToFileInsideZip);

โค้ดด้านบนให้สิทธิ์เข้าถึงไฟล์ที่อยู่ในไฟล์สำหรับขยายหลักหรือไฟล์สำหรับขยายแพตช์ โดยการอ่านจากแผนที่ที่ผสานไฟล์ทั้งหมดจากทั้ง 2 ไฟล์ สิ่งที่คุณจำเป็นต้องระบุในเมธอด getAPKExpansionFile() คือ android.content.Context ของแอปและหมายเลขเวอร์ชันของทั้งไฟล์สำหรับขยายหลักและไฟล์สำหรับขยายแพตช์

หากต้องการอ่านจากไฟล์สำหรับขยายที่เฉพาะเจาะจง ให้ใช้คอนสตรัคเตอร์ ZipResourceFile พร้อมเส้นทางไปยังไฟล์สำหรับขยายที่ต้องการ ดังนี้

Kotlin

// Get a ZipResourceFile representing a specific expansion file
val expansionFile = ZipResourceFile(filePathToMyZip)

// Get an input stream for a known file inside the expansion file ZIPs
expansionFile.getInputStream(pathToFileInsideZip).use {
    ...
}

Java

// Get a ZipResourceFile representing a specific expansion file
ZipResourceFile expansionFile = new ZipResourceFile(filePathToMyZip);

// Get an input stream for a known file inside the expansion file ZIPs
InputStream fileStream = expansionFile.getInputStream(pathToFileInsideZip);

ดูข้อมูลเพิ่มเติมเกี่ยวกับการใช้ไลบรารีนี้สำหรับไฟล์ขยายได้จากคลาส SampleDownloaderActivity ของแอปตัวอย่าง ซึ่งมีโค้ดเพิ่มเติมเพื่อตรวจสอบไฟล์ที่ดาวน์โหลดโดยใช้ CRC โปรดทราบว่าหากใช้ตัวอย่างนี้เป็นพื้นฐานสําหรับการใช้งานของคุณเอง คุณจะต้องประกาศขนาดไบต์ของไฟล์ขยายในอาร์เรย์ xAPKS

การทดสอบไฟล์สำหรับขยาย

ก่อนเผยแพร่แอป คุณควรทดสอบ 2 อย่าง ได้แก่ การอ่านไฟล์เสริมและการดาวน์โหลดไฟล์

การทดสอบการอ่านไฟล์

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

  1. ในอุปกรณ์ ให้สร้างไดเรกทอรีที่เหมาะสมในพื้นที่เก็บข้อมูลที่ใช้ร่วมกันซึ่ง Google Play จะบันทึกไฟล์

    เช่น หากชื่อแพ็กเกจคือ com.example.android คุณจะต้องสร้างไดเรกทอรี Android/obb/com.example.android/ ในพื้นที่เก็บข้อมูลที่ใช้ร่วมกัน (เสียบอุปกรณ์ทดสอบเข้ากับคอมพิวเตอร์เพื่อต่อเชื่อมพื้นที่เก็บข้อมูลที่ใช้ร่วมกันและสร้างไดเรกทอรีนี้ด้วยตนเอง)

  2. เพิ่มไฟล์สำหรับขยายลงในไดเรกทอรีดังกล่าวด้วยตนเอง อย่าลืมเปลี่ยนชื่อไฟล์ให้ตรงกับรูปแบบชื่อไฟล์ที่ Google Play จะใช้

    ตัวอย่างเช่น ไฟล์เสริมหลักสำหรับแอป com.example.android ควรเป็น main.0300110.com.example.android.obb ไม่ว่าไฟล์จะเป็นประเภทใดก็ตาม รหัสเวอร์ชันจะเป็นค่าใดก็ได้ตามต้องการ โปรดทราบว่า

    • ไฟล์เสริมหลักจะขึ้นต้นด้วย main เสมอ และไฟล์แพตช์จะขึ้นต้นด้วย patch
    • ชื่อแพ็กเกจต้องตรงกับชื่อของ APK ที่แนบไฟล์ใน Google Play เสมอ
  3. เมื่อไฟล์สำหรับขยายอยู่ในอุปกรณ์แล้ว คุณสามารถติดตั้งและเรียกใช้แอปเพื่อทดสอบไฟล์สำหรับขยายได้

โปรดทราบข้อมูลต่อไปนี้เกี่ยวกับการจัดการไฟล์ขยาย

  • อย่าลบหรือเปลี่ยนชื่อไฟล์ขยาย .obb (แม้ว่าคุณจะแตกไฟล์ข้อมูลไปยังตำแหน่งอื่นก็ตาม) เนื่องจากการดำเนินการดังกล่าวจะทำให้ Google Play (หรือแอปของคุณเอง) ดาวน์โหลดไฟล์สำหรับขยายซ้ำๆ
  • อย่าบันทึกข้อมูลอื่นๆ ลงในobb/ ไดเรกทอรี หากต้องแตกไฟล์ข้อมูล ให้บันทึกข้อมูลดังกล่าวไว้ในตำแหน่งที่ getExternalFilesDir() ระบุ

การทดสอบการดาวน์โหลดไฟล์

เนื่องจากบางครั้งแอปของคุณจะต้องดาวน์โหลดไฟล์เสริมด้วยตนเองเมื่อเปิดแอปเป็นครั้งแรก คุณจึงควรทดสอบกระบวนการนี้เพื่อให้แน่ใจว่าแอปสามารถค้นหา URL, ดาวน์โหลดไฟล์ และบันทึกลงในอุปกรณ์ได้

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

หมายเหตุ: ก่อนหน้านี้คุณสามารถทดสอบแอปโดยการอัปโหลดเวอร์ชัน "ฉบับร่าง" ที่ไม่ได้เผยแพร่ ระบบไม่รองรับฟังก์ชันนี้อีกต่อไป แต่ต้องเผยแพร่แอปไปยังแทร็กการทดสอบภายใน แบบปิด หรือแบบเปิดแทน ดูข้อมูลเพิ่มเติมได้ที่หัวข้อระบบไม่รองรับแอปฉบับร่างอีกต่อไป

การอัปเดตแอป

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

ไฟล์สำหรับขยายแพตช์นั้นในทางเทคนิคแล้วเหมือนกับไฟล์สำหรับขยายหลัก และทั้งระบบ Android และ Google Play จะไม่ทำการแพตช์จริงระหว่างไฟล์สำหรับขยายหลักและไฟล์สำหรับขยายแพตช์ โค้ดแอปของคุณต้องทำการแพตช์ที่จำเป็นด้วยตนเอง

หากคุณใช้ไฟล์ ZIP เป็นไฟล์สำหรับขยาย คลัง ZIP สำหรับขยาย APK ที่รวมอยู่ในแพ็กเกจการขยาย APK จะสามารถผสานไฟล์แพตช์เข้ากับไฟล์สำหรับขยายหลักได้

หมายเหตุ: แม้ว่าจะต้องทำการเปลี่ยนแปลงเฉพาะในไฟล์แพตช์ Expansion แต่คุณก็ยังต้องอัปเดต APK เพื่อให้ Google Play ทำการอัปเดต หากไม่ต้องการเปลี่ยนแปลงโค้ดในแอป คุณก็เพียงแค่อัปเดต versionCode ในไฟล์ Manifest

ตราบใดที่คุณไม่เปลี่ยนไฟล์สำหรับขยายหลักที่เชื่อมโยงกับ APK ใน Play Console ผู้ใช้ที่เคยติดตั้งแอปของคุณจะไม่ดาวน์โหลดไฟล์สำหรับขยายหลัก ผู้ใช้ปัจจุบันจะได้รับเฉพาะ APK ที่อัปเดตแล้วและไฟล์แพตช์สำหรับขยายเวอร์ชันใหม่ (เก็บไฟล์สำหรับขยายหลักเวอร์ชันก่อนหน้าไว้)

ปัญหาที่ควรทราบเกี่ยวกับการอัปเดตไฟล์ส่วนขยายมีดังนี้

  • แอปของคุณจะมีไฟล์เสริมได้เพียง 2 ไฟล์พร้อมกัน ไฟล์เสริมหลัก 1 ไฟล์และไฟล์เสริมแพตช์ 1 ไฟล์ ในระหว่างการอัปเดตไฟล์ Google Play จะลบเวอร์ชันก่อนหน้า (และแอปของคุณก็จะต้องดำเนินการเช่นเดียวกันเมื่อทำการอัปเดตด้วยตนเอง)
  • เมื่อเพิ่มไฟล์สำหรับขยายที่เป็นแพตช์ ระบบ Android จะไม่แพตช์แอปหรือไฟล์สำหรับขยายหลัก คุณต้องออกแบบแอปให้รองรับข้อมูลแพตช์ อย่างไรก็ตาม แพ็กเกจการขยาย Apk มีไลบรารีสำหรับใช้ไฟล์ ZIP เป็นไฟล์สำหรับขยาย ซึ่งจะผสานข้อมูลจากไฟล์แพตช์ลงในไฟล์สำหรับขยายหลักเพื่อให้คุณอ่านข้อมูลไฟล์สำหรับขยายทั้งหมดได้อย่างง่ายดาย