หัวข้อขั้นสูง

ส่วนเหล่านี้มีไว้สำหรับใช้อ้างอิง และคุณไม่จำเป็นต้องอ่านตั้งแต่ต้นจนจบ

ใช้ API ของเฟรมเวิร์ก

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

การติดตั้งใช้งานนั้นง่ายมาก หากโต้ตอบได้ ก็ดำเนินการได้เลย หากไม่ได้ แต่คุณขอได้ ให้แสดงพรอมต์/แบนเนอร์/เคล็ดลับเครื่องมือ/ฯลฯ แก่ผู้ใช้ หากผู้ใช้ตกลงที่จะไปที่การตั้งค่า ให้สร้าง Intent คำขอและใช้ Context#startActivity เพื่อส่งผู้ใช้ไปที่นั่น คุณสามารถใช้การออกอากาศเพื่อตรวจหาเมื่อความสามารถนี้เปลี่ยนแปลง หรือจะตรวจสอบอีกครั้งเมื่อผู้ใช้กลับมาก็ได้

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

อภิธานศัพท์

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

การกําหนดค่าข้ามโปรไฟล์

การกําหนดค่าข้ามโปรไฟล์จะจัดกลุ่มผู้ให้บริการข้ามโปรไฟล์ที่เกี่ยวข้องไว้ด้วยกัน และระบุการกําหนดค่าทั่วไปสําหรับฟีเจอร์ข้ามโปรไฟล์ โดยปกติแล้วจะมีคำอธิบายประกอบ @CrossProfileConfiguration 1 รายการต่อโค้ดเบส แต่ในบางแอปพลิเคชันที่ซับซ้อนอาจมีหลายรายการ

เครื่องมือเชื่อมต่อโปรไฟล์

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

คลาสผู้ให้บริการข้ามโปรไฟล์

คลาสผู้ให้บริการข้ามโปรไฟล์จะจัดกลุ่มประเภทข้ามโปรไฟล์ที่เกี่ยวข้องไว้ด้วยกัน

Mediator

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

ประเภทโปรไฟล์ข้าม

ประเภทข้ามโปรไฟล์คือคลาสหรืออินเทอร์เฟซที่มีเมธอดที่มีคำอธิบายประกอบ @CrossProfile โค้ดประเภทนี้ไม่จำเป็นต้องรับรู้โปรไฟล์และควรดำเนินการกับข้อมูลในเครื่องเท่านั้น

ประเภทโปรไฟล์

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

ตัวระบุโปรไฟล์

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

คู่มือนี้จะอธิบายโครงสร้างที่แนะนําในการสร้างฟังก์ชันการทำงานข้ามโปรไฟล์ที่มีประสิทธิภาพและดูแลรักษาได้ภายในแอป Android

แปลง CrossProfileConnector เป็น Singleton

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

แทรกหรือส่งอินสแตนซ์ Profile ที่สร้างขึ้นไปยังคลาสเมื่อคุณทำการเรียกใช้แทนการสร้างในเมธอด

ซึ่งจะช่วยให้คุณส่งอินสแตนซ์ FakeProfile ที่สร้างขึ้นโดยอัตโนมัติในยูนิตเทสต์ในภายหลังได้

ลองใช้รูปแบบสื่อกลาง

รูปแบบที่พบบ่อยนี้คือการทำให้ API ที่มีอยู่รายการใดรายการหนึ่ง (เช่น getEvents()) รับรู้โปรไฟล์สำหรับผู้เรียก API ทั้งหมด ในกรณีนี้ API ที่มีอยู่สามารถเปลี่ยนเป็นเมธอดหรือคลาส "สื่อกลาง" ที่มีการเรียกใช้ใหม่เพื่อโค้ดข้ามโปรไฟล์ที่สร้างขึ้น

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

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

ซึ่งทำงานร่วมกับเฟรมเวิร์กการฉีดข้อมูลอย่างลงตัว

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

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

ข้ามโปรไฟล์

ส่วนนี้จะอธิบายวิธีสร้างการโต้ตอบข้ามโปรไฟล์ของคุณเอง

โปรไฟล์หลัก

การเรียกใช้ส่วนใหญ่ในตัวอย่างในเอกสารนี้มีวิธีการที่ชัดเจนเกี่ยวกับโปรไฟล์ที่จะเรียกใช้ ซึ่งรวมถึงโปรไฟล์งาน โปรไฟล์ส่วนตัว และทั้ง 2 โปรไฟล์

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

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

profileCalendarDatabase.primary().getEvents();

profileCalendarDatabase.secondary().getEvents();

// Runs on all profiles if running on the primary, or just
// on the current profile if running on the secondary.
profileCalendarDatabase.suppliers().getEvents();

ประเภทข้อมูลข้ามโปรไฟล์

คลาสและอินเทอร์เฟซที่มีเมธอดที่มีคำอธิบายประกอบ @CrossProfile จะเรียกว่าประเภทข้ามโปรไฟล์

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

ตัวอย่างประเภทโปรไฟล์ข้าม

public class Calculator {
  @CrossProfile
  public int add(int a, int b) {
    return a + b;
  }
}

คําอธิบายประกอบของชั้นเรียน

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

@CrossProfile(connector=MyProfileConnector.class)
public class Calculator {
  @CrossProfile
  public int add(int a, int b) {
    return a + b;
  }
}

ตัวเลือกนี้ไม่บังคับ แต่จะทำให้ API ที่สร้างขึ้นมีความเฉพาะเจาะจงมากขึ้นเกี่ยวกับประเภทและการตรวจสอบเวลาคอมไพล์จะเข้มงวดมากขึ้น

อินเทอร์เฟซ

การกำกับเนื้อหาประกอบวิธีการในอินเทอร์เฟซเป็น @CrossProfile หมายความว่าคุณระบุว่ามีการใช้งานวิธีการนี้บางอย่างที่ควรเข้าถึงได้ทั่วทั้งโปรไฟล์

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

ผู้ให้บริการข้ามโปรไฟล์

ประเภทข้ามโปรไฟล์ทุกประเภทต้องระบุโดยเมธอดที่มีคำอธิบายประกอบ @CrossProfileProvider ระบบจะเรียกใช้เมธอดเหล่านี้ทุกครั้งที่มีการเรียกใช้ข้ามโปรไฟล์ ดังนั้นเราขอแนะนำให้คุณดูแลรักษา Singleton สำหรับแต่ละประเภท

ผู้ผลิต

ผู้ให้บริการต้องมีตัวสร้างแบบสาธารณะที่ไม่รับอาร์กิวเมนต์หรือรับอาร์กิวเมนต์ Context รายการเดียว

วิธีการของผู้ให้บริการ

เมธอดของผู้ให้บริการต้องไม่มีอาร์กิวเมนต์หรือมีอาร์กิวเมนต์ Context รายการเดียว

Dependency Injection

หากคุณใช้เฟรมเวิร์กการฉีดข้อมูล Dependency เช่น Dagger เพื่อจัดการ Dependency เราขอแนะนำให้คุณให้เฟรมเวิร์กดังกล่าวสร้างประเภทข้ามโปรไฟล์ตามปกติ แล้วจึงฉีดประเภทเหล่านั้นลงในคลาสผู้ให้บริการ จากนั้นเมธอด @CrossProfileProvider จะแสดงอินสแตนซ์ที่แทรกเหล่านั้น

เครื่องมือเชื่อมต่อโปรไฟล์

การกําหนดค่าข้ามโปรไฟล์แต่ละรายการต้องมีตัวเชื่อมต่อโปรไฟล์รายการเดียว ซึ่งมีหน้าที่จัดการการเชื่อมต่อกับโปรไฟล์อื่น

ตัวเชื่อมต่อโปรไฟล์เริ่มต้น

หากมีการกำหนดค่าข้ามโปรไฟล์เพียงรายการเดียวในโค้ดเบส คุณก็หลีกเลี่ยงการสร้างเครื่องมือเชื่อมต่อโปรไฟล์ของคุณเองได้และใช้com.google.android.enterprise.connectedapps.CrossProfileConnector ซึ่งเป็นค่าเริ่มต้นที่ใช้หากไม่ได้ระบุ

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

  • บริการผู้ดำเนินการตามกำหนดการ

    หากต้องการควบคุมเธรดที่ SDK สร้างขึ้น ให้ใช้ #setScheduledExecutorService()

  • แฟ้ม

    หากมีความต้องการเฉพาะเกี่ยวกับการเชื่อมโยงโปรไฟล์ ให้ใช้ #setBinder ผู้ใช้ประเภทนี้น่าจะเป็นผู้ควบคุมนโยบายอุปกรณ์เท่านั้น

เครื่องมือเชื่อมต่อโปรไฟล์ที่กำหนดเอง

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

เมื่อสร้าง ProfileConnector ลักษณะควรเป็นดังนี้

@GeneratedProfileConnector
public interface MyProfileConnector extends ProfileConnector {
  public static MyProfileConnector create(Context context) {
    // Configuration can be specified on the builder
    return GeneratedMyProfileConnector.builder(context).build();
  }
}
  • serviceClassName

    หากต้องการเปลี่ยนชื่อบริการที่สร้างขึ้น (ซึ่งควรอ้างอิงใน AndroidManifest.xml) ให้ใช้ serviceClassName=

  • primaryProfile

    หากต้องการระบุโปรไฟล์หลัก ให้ใช้ primaryProfile

  • availabilityRestrictions

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

เครื่องมือควบคุมนโยบายด้านอุปกรณ์

หากแอปของคุณเป็นเครื่องมือควบคุมนโยบายด้านอุปกรณ์ คุณต้องระบุอินสแตนซ์ของ DpcProfileBinder ที่อ้างอิง DeviceAdminReceiver

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

@GeneratedProfileConnector
public interface DpcProfileConnector extends ProfileConnector {
  public static DpcProfileConnector get(Context context) {
    return GeneratedDpcProfileConnector.builder(context).setBinder(new
DpcProfileBinder(new ComponentName("com.google.testdpc",
"AdminReceiver"))).build();
  }
}

หรือใช้ CrossProfileConnector เริ่มต้น

CrossProfileConnector connector =
CrossProfileConnector.builder(context).setBinder(new DpcProfileBinder(new
ComponentName("com.google.testdpc", "AdminReceiver"))).build();

การกําหนดค่าข้ามโปรไฟล์

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

@CrossProfileConfiguration(providers = {TestProvider.class})
public abstract class TestApplication {
}

ซึ่งจะตรวจสอบว่าประเภทโปรไฟล์ข้ามทั้งหมดมีเครื่องมือเชื่อมต่อโปรไฟล์เดียวกันหรือไม่ระบุเครื่องมือเชื่อมต่อ

  • serviceSuperclass

    โดยค่าเริ่มต้น บริการที่สร้างขึ้นจะใช้ android.app.Service เป็นซุปเปอร์คลาส หากต้องการใช้คลาสอื่น (ซึ่งต้องเป็นคลาสย่อยของ android.app.Service) เป็นซุปเปอร์คลาส ให้ระบุ serviceSuperclass=

  • serviceClass

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

public final class TestProfileConnector_Service extends Service {
  private Stub binder = new Stub() {
    private final TestProfileConnector_Service_Dispatcher dispatcher = new
TestProfileConnector_Service_Dispatcher();

    @Override
    public void prepareCall(long callId, int blockId, int numBytes, byte[] params)
{
      dispatcher.prepareCall(callId, blockId, numBytes, params);
    }

    @Override
    public byte[] call(long callId, int blockId, long crossProfileTypeIdentifier,
int methodIdentifier, byte[] params,
    ICrossProfileCallback callback) {
      return dispatcher.call(callId, blockId, crossProfileTypeIdentifier,
methodIdentifier, params, callback);
    }

    @Override
    public byte[] fetchResponse(long callId, int blockId) {
      return dispatcher.fetchResponse(callId, blockId);
  };

  @Override
  public Binder onBind(Intent intent) {
    return binder;
  }
}

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

  • ผู้ให้บริการเชื่อมต่อ

    หากคุณใช้ตัวเชื่อมต่ออื่นที่ไม่ใช่ CrossProfileConnector เริ่มต้น คุณต้องระบุตัวเชื่อมต่อนั้นโดยใช้ connector=

ระดับการแชร์

ทุกส่วนของแอปพลิเคชันซึ่งโต้ตอบข้ามโปรไฟล์ต้องมองเห็นเครื่องมือเชื่อมต่อโปรไฟล์

@CrossProfileConfigurationคลาสที่มีคำอธิบายประกอบต้องเห็นผู้ให้บริการทุกรายที่ใช้ในแอปพลิเคชัน

การเรียกแบบซิงโครนัส

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

ตัวยึดการเชื่อมต่อ

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

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

หากคุณไม่มีสิทธิ์ข้ามโปรไฟล์ที่เหมาะสมเมื่อเรียกใช้ ProfileConnector#addConnectionHolder(Object) หรือไม่มีโปรไฟล์ที่พร้อมเชื่อมต่อ ระบบจะไม่แสดงข้อผิดพลาด แต่จะไม่เรียกใช้การเรียกกลับที่เชื่อมต่อ หากได้รับสิทธิ์ในภายหลังหรือโปรไฟล์อื่นพร้อมใช้งานแล้ว ระบบจะเชื่อมต่อและเรียกใช้การติดต่อกลับ

หรือ ProfileConnector#connect(Object) เป็นเมธอดการบล็อกที่จะเพิ่มออบเจ็กต์เป็นผู้ถือการเชื่อมต่อและสร้างการเชื่อมต่อหรือแสดง UnavailableProfileException เรียกเมธอดนี้จาก เธรด UI ไม่ได้

การเรียกใช้ ProfileConnector#connect(Object) และ ProfileConnector#connect ที่คล้ายกันจะแสดงผลออบเจ็กต์ที่ปิดโดยอัตโนมัติ ซึ่งจะนำตัวยึดการเชื่อมต่อออกโดยอัตโนมัติเมื่อปิด ซึ่งช่วยให้ใช้ในกรณีต่อไปนี้ได้

try (ProfileConnectionHolder p = connector.connect()) {
  // Use the connection
}

เมื่อเรียกใช้การเรียกแบบซิงค์เสร็จแล้ว คุณควรเรียกใช้ ProfileConnector#removeConnectionHolder(Object) เมื่อนำผู้ถือการเชื่อมต่อทั้งหมดออกแล้ว การเชื่อมต่อจะปิดลง

การเชื่อมต่อ

คุณสามารถใช้ตัวรับฟังการเชื่อมต่อเพื่อรับแจ้งเมื่อสถานะการเชื่อมต่อมีการเปลี่ยนแปลง และสามารถใช้ connector.utils().isConnected เพื่อระบุว่ามีการเชื่อมต่ออยู่หรือไม่ เช่น

// Only use this if using synchronous calls instead of Futures.
crossProfileConnector.connect(this);
crossProfileConnector.registerConnectionListener(() -> {
  if (crossProfileConnector.utils().isConnected()) {
    // Make cross-profile calls.
  }
});

การเรียกแบบอะซิงโครนัส

วิธีการทั้งหมดที่แสดงในโปรไฟล์ต้องได้รับการระบุว่าเป็นการบล็อก (แบบซิงค์) หรือไม่บล็อก (แบบแอซิงค์) เมธอดใดก็ตามที่แสดงผลประเภทข้อมูลแบบแอซิงโครนัส (เช่น ListenableFuture) หรือยอมรับพารามิเตอร์การเรียกกลับจะได้รับการทําเครื่องหมายว่าไม่ใช่แบบบล็อก ส่วนวิธีการอื่นๆ ทั้งหมดจะทําเครื่องหมายเป็นการบล็อก

เราขอแนะนำให้ใช้การเรียกแบบอะซิงโครนัส หากต้องใช้การเรียกแบบซิงค์ โปรดดูการเรียกแบบซิงค์

การติดต่อกลับ

การเรียกแบบไม่บล็อกประเภทพื้นฐานที่สุดคือเมธอด void ซึ่งยอมรับอินเทอร์เฟซที่มีเมธอดที่จะเรียกใช้พร้อมกับผลลัพธ์เป็นหนึ่งในพารามิเตอร์ หากต้องการให้อินเทอร์เฟซเหล่านี้ทํางานร่วมกับ SDK อินเทอร์เฟซต้องได้รับการอธิบายประกอบ @CrossProfileCallback เช่น

@CrossProfileCallback
public interface InstallationCompleteListener {
  void installationComplete(int state);
}

จากนั้นอินเทอร์เฟซนี้จะใช้เป็นพารามิเตอร์ใน@CrossProfileเมธอดที่มีคำอธิบายประกอบได้ และเรียกใช้ได้ตามปกติ เช่น

@CrossProfile
public void install(String filename, InstallationCompleteListener callback) {
  // Do something on a separate thread and then:
  callback.installationComplete(1);
}

// In the mediator
profileInstaller.work().install(filename, (status) -> {
  // Deal with callback
}, (exception) -> {
  // Deal with possibility of profile unavailability
});

หากอินเทอร์เฟซนี้มีเมธอดเดียวซึ่งใช้พารามิเตอร์ 0 หรือ 1 รายการ ก็จะสามารถใช้ในการเรียกใช้โปรไฟล์หลายรายการพร้อมกันได้

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

เมธอดแบบซิงโครนัสที่มี Callback

ฟีเจอร์ที่ผิดปกติอย่างหนึ่งของการใช้คอลแบ็กกับ SDK คือคุณสามารถเขียนเมธอดแบบซิงค์ที่ใช้คอลแบ็กได้ ดังนี้

public void install(InstallationCompleteListener callback) {
  callback.installationComplete(1);
}

ในกรณีนี้ เมธอดเป็นแบบซิงค์ แม้ว่าจะมีคอลแบ็กก็ตาม โค้ดนี้จะทํางานอย่างถูกต้อง

System.out.println("This prints first");
installer.install(() -> {
        System.out.println("This prints second");
});
System.out.println("This prints third");

อย่างไรก็ตาม เมื่อเรียกใช้โดยใช้ SDK การดำเนินการนี้จะทำงานไม่เหมือนกัน ไม่มีการรับประกันว่าจะมีการเรียกใช้วิธีการติดตั้งก่อนที่จะพิมพ์ "This prints third" การใช้เมธอดที่ SDK ทำเครื่องหมายเป็น "แบบไม่พร้อมกัน" ต้องไม่คาดเดาว่าระบบจะเรียกใช้เมธอดเมื่อใด

Callback แบบง่าย

"การเรียกกลับแบบง่าย" เป็นการเรียกกลับแบบจํากัดมากขึ้น ซึ่งอนุญาตให้ใช้ฟีเจอร์เพิ่มเติมเมื่อทําการเรียกข้ามโปรไฟล์ อินเทอร์เฟซแบบง่ายต้องมีเมธอดเดียว ซึ่งใช้พารามิเตอร์ได้ 0 หรือ 1 รายการ

คุณสามารถบังคับให้อินเทอร์เฟซการเรียกกลับต้องยังคงอยู่ได้โดยระบุ simple=true ในการกำกับเนื้อหา @CrossProfileCallback

คุณสามารถใช้งาน Callback แบบง่ายกับเมธอดต่างๆ เช่น .both(), .suppliers() และอื่นๆ

ตัวยึดการเชื่อมต่อ

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

หากคาดว่าจะมีการส่งผลลัพธ์มากกว่า 1 รายการโดยใช้การเรียกกลับ คุณควรเพิ่มการเรียกกลับด้วยตนเองเป็นผู้ถือการเชื่อมต่อ ดังนี้

MyCallback b = //...
connector.addConnectionHolder(b);

  profileMyClass.other().registerListener(b);

  // Now the connection will be held open indefinitely, once finished:
  connector.removeConnectionHolder(b);

สามารถใช้กับบล็อก try-with-resources ได้ด้วย

MyCallback b = //...
try (ProfileConnectionHolder p = connector.addConnectionHolder(b)) {
  profileMyClass.other().registerListener(b);

  // Other things running while we expect results
}

หากเราโทรด้วยระบบการโทรกลับหรือในอนาคต การเชื่อมต่อจะเปิดอยู่จนกว่าจะได้ผลลัพธ์ หากเราพิจารณาว่าผลลัพธ์จะไม่ผ่าน เราควรนําการเรียกกลับหรืออนาคตออกในฐานะผู้ถือการเชื่อมต่อ

connector.removeConnectionHolder(myCallback);
connector.removeConnectionHolder(future);

ดูข้อมูลเพิ่มเติมได้ที่ผู้ถือการเชื่อมต่อ

สัญญาซื้อขายล่วงหน้า

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

การดำเนินการนี้มี "ฟีเจอร์ที่ผิดปกติ" เช่นเดียวกับการเรียกกลับ โดยเมธอดแบบซิงค์ที่แสดงผลลัพธ์ในอนาคต (เช่น การใช้ immediateFuture) จะทํางานแตกต่างกันเมื่อเรียกใช้ในโปรไฟล์ปัจจุบันกับเมื่อเรียกใช้ในโปรไฟล์อื่น การใช้เมธอดที่ SDK ทำเครื่องหมายว่าเป็นแบบไม่พร้อมกันต้องไม่คาดเดาว่าระบบจะเรียกใช้เมธอดเมื่อใด

ชุดข้อความ

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

ความพร้อมใช้งาน

คุณสามารถรับฟังความพร้อมเพื่อรับแจ้งเมื่อสถานะความพร้อมมีการเปลี่ยนแปลงได้ และสามารถใช้ connector.utils().isAvailable เพื่อระบุว่ามีโปรไฟล์อื่นให้ใช้งานหรือไม่ เช่น

crossProfileConnector.registerAvailabilityListener(() -> {
  if (crossProfileConnector.utils().isAvailable()) {
    // Show cross-profile content
  } else {
    // Hide cross-profile content
  }
});

ตัวยึดการเชื่อมต่อ

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

โดยค่าเริ่มต้น เมื่อทำการเรียกแบบไม่พร้อมกัน ระบบจะเพิ่มตัวเก็บการเชื่อมต่อเมื่อการเรียกเริ่มต้นขึ้น และนำออกเมื่อมีผลลัพธ์หรือข้อผิดพลาดเกิดขึ้น

นอกจากนี้ คุณยังเพิ่มและนำผู้ถือการเชื่อมต่อออกด้วยตนเองได้เพื่อควบคุมการเชื่อมต่อได้มากขึ้น คุณสามารถเพิ่มผู้ถือการเชื่อมต่อได้โดยใช้ connector.addConnectionHolder และนำออกได้โดยใช้ connector.removeConnectionHolder

เมื่อเพิ่มผู้ถือการเชื่อมต่ออย่างน้อย 1 รายแล้ว SDK จะพยายามรักษาการเชื่อมต่อไว้ เมื่อไม่มีการเพิ่มผู้ถือการเชื่อมต่อ ระบบจะปิดการเชื่อมต่อได้

คุณต้องเก็บข้อมูลอ้างอิงเกี่ยวกับผู้ถือการเชื่อมต่อที่คุณเพิ่มไว้ และนำข้อมูลอ้างอิงดังกล่าวออกเมื่อไม่เกี่ยวข้องแล้ว

การเรียกใช้แบบซิงโครนัส

ก่อนทำการเรียกแบบซิงค์ คุณควรเพิ่มตัวยึดการเชื่อมต่อ ซึ่งทําได้โดยใช้ออบเจ็กต์ใดก็ได้ แต่คุณต้องติดตามออบเจ็กต์นั้นเพื่อนำออกเมื่อไม่จําเป็นต้องเรียกใช้แบบซิงค์อีกต่อไป

การเรียกใช้แบบอะซิงโครนัส

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

การจัดการข้อผิดพลาด

โดยค่าเริ่มต้น การเรียกใช้โปรไฟล์อื่นเมื่อโปรไฟล์นั้นไม่พร้อมใช้งานจะส่งผลให้ระบบแสดงข้อผิดพลาด UnavailableProfileException (หรือส่งไปยัง Future หรือ Callback ข้อผิดพลาดสําหรับการเรียกใช้แบบแอสซิงค์)

หากต้องการหลีกเลี่ยงปัญหานี้ นักพัฒนาแอปสามารถใช้ #both() หรือ #suppliers() และเขียนโค้ดเพื่อจัดการกับรายการในรายการผลลัพธ์ที่มีจำนวนเท่าใดก็ได้ (ค่านี้จะเท่ากับ 1 หากโปรไฟล์อื่นไม่พร้อมใช้งาน หรือ 2 หากพร้อมใช้งาน)

ข้อยกเว้น

ข้อยกเว้นที่ไม่ได้เลือกซึ่งเกิดขึ้นหลังจากการเรียกใช้โปรไฟล์ปัจจุบันจะได้รับการนำไปใช้งานตามปกติ การดำเนินการนี้จะมีผลไม่ว่าคุณจะใช้วิธีการใดในการเรียกใช้ (#current(), #personal, #both ฯลฯ)

ข้อยกเว้นที่ตรวจไม่พบซึ่งเกิดขึ้นหลังจากการเรียกใช้โปรไฟล์อื่นจะส่งผลให้มีการโยน ProfileRuntimeException โดยมีข้อยกเว้นเดิมเป็นสาเหตุ การดำเนินการนี้จะมีผลไม่ว่าคุณจะใช้วิธีใดในการโทร (#other(), #personal, #both ฯลฯ)

ifAvailable

นอกเหนือจากการจับและจัดการกับอินสแตนซ์ UnavailableProfileExceptionแล้ว คุณสามารถใช้เมธอด .ifAvailable() เพื่อระบุค่าเริ่มต้นซึ่งระบบจะแสดงผลแทนการโยน UnavailableProfileException

เช่น

profileNotesDatabase.other().ifAvailable().getNumberOfNotes(/* defaultValue= */ 0);

การทดสอบ

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

เรามีตัวเชื่อมต่อและประเภทจำลองซึ่งสามารถใช้ในการทดสอบ

ก่อนอื่น ให้เพิ่มการพึ่งพาการทดสอบ

  testAnnotationProcessor
'com.google.android.enterprise.connectedapps:connectedapps-processor:1.1.2'
  testCompileOnly
'com.google.android.enterprise.connectedapps:connectedapps-testing-annotations:1.1.2'
  testImplementation
'com.google.android.enterprise.connectedapps:connectedapps-testing:1.1.2'

จากนั้นกำกับเนื้อหาในคลาสทดสอบด้วย @CrossProfileTest โดยระบุคลาสที่มีคำอธิบายประกอบ @CrossProfileConfiguration ที่จะทดสอบ

@CrossProfileTest(configuration = MyApplication.class)
@RunWith(RobolectricTestRunner.class)
public class NotesMediatorTest {

}

ซึ่งจะทำให้เกิดการสร้างไฟล์จำลองสำหรับขั้วต่อทุกประเภทที่ใช้ในการกำหนดค่า

สร้างอินสแตนซ์ของรายการจำลองเหล่านั้นในการทดสอบ

private final FakeCrossProfileConnector connector = new
FakeCrossProfileConnector();
private final NotesManager personalNotesManager = new NotesManager(); //
real/mock/fake
private final NotesManager workNotesManager = new NotesManager(); // real/mock/fake

private final FakeProfileNotesManager profileNotesManager =
  FakeProfileNotesManager.builder()
    .personal(personalNotesManager)
    .work(workNotesManager)
    .connector(connector)
    .build();

ตั้งค่าสถานะโปรไฟล์

connector.setRunningOnProfile(PERSONAL);
connector.createWorkProfile();
connector.turnOffWorkProfile();

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

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

ประเภทที่รองรับ

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

  • องค์ประกอบพื้นฐาน (byte, short, int, long, float, double, char, boolean)
  • รูปแบบพื้นฐานในกล่อง (java.lang.Byte, java.lang.Short, java.lang.Integer, java.lang.Long, java.lang.Float, java.lang.Double, java.lang.Character, java.lang.Boolean, java.lang.Void)
  • java.lang.String
  • ทุกอย่างที่ใช้ android.os.Parcelable
  • ทุกอย่างที่ใช้ java.io.Serializable
  • อาร์เรย์ที่ไม่ใช่แบบพื้นฐานมิติข้อมูลเดียว
  • java.util.Optional
  • java.util.Collection
  • java.util.List
  • java.util.Map
  • java.util.Set
  • android.util.Pair
  • com.google.common.collect.ImmutableMap

ประเภททั่วไปที่รองรับ (เช่น java.util.Collection) อาจมีประเภทที่รองรับใดก็ได้เป็นพารามิเตอร์ประเภท เช่น

java.util.Collection<java.util.Map<java.lang.String,MySerializableType[]>> เป็นประเภทที่ถูกต้อง

สัญญาซื้อขายล่วงหน้า

ระบบรองรับประเภทต่อไปนี้เป็นประเภทผลลัพธ์เท่านั้น

  • com.google.common.util.concurrent.ListenableFuture

Wrapper ของ Parcelable ที่กําหนดเอง

หากประเภทของคุณไม่อยู่ในรายการก่อนหน้านี้ ให้พิจารณาก่อนว่าสามารถทำให้ประเภทนั้นใช้ android.os.Parcelable หรือ java.io.Serializable ได้อย่างถูกต้องหรือไม่ หากไม่เห็นตัวห่อที่แยกส่วนได้เพื่อเพิ่มการรองรับประเภทของคุณ

Wrapper ของอนาคตที่กําหนดเอง

หากต้องการใช้ประเภทในอนาคตซึ่งไม่อยู่ในรายการก่อนหน้านี้ โปรดดูWrapper ในอนาคตเพื่อเพิ่มการรองรับ

Wrapper ที่แยกส่วนได้

Wrapper แบบแยกส่วนได้คือวิธีที่ SDK เพิ่มการรองรับประเภทที่ไม่แยกส่วนได้ซึ่งแก้ไขไม่ได้ SDK มี Wrapper หลายประเภท แต่หากไม่มีประเภทที่คุณต้องการใช้ คุณต้องเขียน Wrapper ของคุณเอง

Wrapper ที่แยกส่วนได้คือคลาสที่ออกแบบมาเพื่อรวมคลาสอื่นเข้าด้วยกันและทำให้แยกส่วนได้ โดยเป็นไปตามสัญญาแบบคงที่ที่กําหนดไว้และจดทะเบียนกับ SDK เพื่อให้ใช้แปลงประเภทหนึ่งๆ เป็นประเภทที่แยกเป็นแพ็กเกจได้ รวมถึงดึงประเภทนั้นออกจากประเภทที่แยกเป็นแพ็กเกจได้

หมายเหตุ

ต้องใส่คำอธิบายประกอบ @CustomParcelableWrapper ให้กับคลาส Wrapper ที่แยกส่วนได้ โดยระบุคลาสที่รวมไว้เป็น originalType เช่น

@CustomParcelableWrapper(originalType=ImmutableList.class)

รูปแบบ

Wrapper ของ Parcelable ต้องใช้ Parcelable อย่างถูกต้อง และต้องมีทั้งวิธี W of(Bundler, BundlerType, T) แบบคงที่ซึ่งรวมประเภทที่รวมไว้ และวิธี T get() แบบไม่คงที่ซึ่งแสดงผลประเภทที่รวมไว้

SDK จะใช้วิธีการเหล่านี้เพื่อรองรับประเภทดังกล่าวอย่างราบรื่น

Bundler

หากต้องการอนุญาตให้ตัดข้อมูลประเภททั่วไป (เช่น รายการและแผนที่) ระบบจะส่ง Bundler ไปยังเมธอด of ซึ่งสามารถอ่าน (โดยใช้ #readFromParcel) และเขียน (โดยใช้ #writeToParcel) ประเภทที่รองรับทั้งหมดลงใน Parcel และ BundlerType ซึ่งแสดงถึงประเภทที่ประกาศไว้ที่จะเขียน

อินสแตนซ์ Bundler และ BundlerType เองก็แยกเป็นแพ็กเกจได้ และควรเขียนเป็นส่วนหนึ่งของการแยกเป็นแพ็กเกจของ Wrapper ที่แยกเป็นแพ็กเกจได้ เพื่อให้นำไปใช้ได้เมื่อสร้าง Wrapper ที่แยกเป็นแพ็กเกจได้อีกครั้ง

หาก BundlerType แสดงถึงประเภททั่วไป ตัวแปรประเภทจะพบได้ด้วยการเรียกใช้ .typeArguments() อาร์กิวเมนต์ประเภทแต่ละรายการจะเป็น BundlerType เอง

โปรดดูตัวอย่างที่ ParcelableCustomWrapper

public class CustomWrapper<F> {
  private final F value;

  public CustomWrapper(F value) {
    this.value = value;
  }
  public F value() {
    return value;
  }
}

@CustomParcelableWrapper(originalType = CustomWrapper.class)
public class ParcelableCustomWrapper<E> implements Parcelable {

  private static final int NULL = -1;
  private static final int NOT_NULL = 1;

  private final Bundler bundler;
  private final BundlerType type;
  private final CustomWrapper<E> customWrapper;

  /**
  *   Create a wrapper for a given {@link CustomWrapper}.
  *
  *   <p>The passed in {@link Bundler} must be capable of bundling {@code F}.
  */
  public static <F> ParcelableCustomWrapper<F> of(
      Bundler bundler, BundlerType type, CustomWrapper<F> customWrapper) {
    return new ParcelableCustomWrapper<>(bundler, type, customWrapper);
  }

  public CustomWrapper<E> get() {
    return customWrapper;
  }

  private ParcelableCustomWrapper(
      Bundler bundler, BundlerType type, CustomWrapper<E> customWrapper) {
    if (bundler == null || type == null) {
      throw new NullPointerException();
    }
    this.bundler = bundler;
    this.type = type;
    this.customWrapper = customWrapper;
  }

  private ParcelableCustomWrapper(Parcel in) {
    bundler = in.readParcelable(Bundler.class.getClassLoader());

    int presentValue = in.readInt();

    if (presentValue == NULL) {
      type = null;
      customWrapper = null;
      return;
    }

    type = (BundlerType) in.readParcelable(Bundler.class.getClassLoader());
    BundlerType valueType = type.typeArguments().get(0);

    @SuppressWarnings("unchecked")
    E value = (E) bundler.readFromParcel(in, valueType);

    customWrapper = new CustomWrapper<>(value);
  }

  @Override
  public void writeToParcel(Parcel dest, int flags) {
    dest.writeParcelable(bundler, flags);

    if (customWrapper == null) {
      dest.writeInt(NULL);
      return;
    }

    dest.writeInt(NOT_NULL);
    dest.writeParcelable(type, flags);
    BundlerType valueType = type.typeArguments().get(0);
    bundler.writeToParcel(dest, customWrapper.value(), valueType, flags);
  }

  @Override
  public int describeContents() {
    return 0;
  }

  @SuppressWarnings("rawtypes")
  public static final Creator<ParcelableCustomWrapper> CREATOR =
    new Creator<ParcelableCustomWrapper>() {
      @Override
      public ParcelableCustomWrapper createFromParcel(Parcel in) {
        return new ParcelableCustomWrapper(in);
      }

      @Override
      public ParcelableCustomWrapper[] newArray(int size) {
        return new ParcelableCustomWrapper[size];
      }
    };
}

ลงทะเบียนกับ SDK

เมื่อสร้างแล้ว หากต้องการใช้ Wrapper ที่แยกชิ้นส่วนได้ซึ่งกําหนดเอง คุณจะต้องลงทะเบียน Wrapper นั้นกับ SDK

โดยระบุ parcelableWrappers={YourParcelableWrapper.class} ในคำอธิบายประกอบ CustomProfileConnector หรือคำอธิบายประกอบ CrossProfile ในคลาส

Wrapper ในอนาคต

Wrapper ในอนาคตคือวิธีที่ SDK เพิ่มการรองรับฟีเจอร์ใหม่ในโปรไฟล์ต่างๆ SDK จะรองรับ ListenableFuture โดยค่าเริ่มต้น แต่คุณเพิ่มการรองรับประเภทอื่นๆ ในอนาคตได้

Wrapper ของ Future คือคลาสที่ออกแบบมาเพื่อรวม Future ประเภทหนึ่งๆ และทำให้ SDK ใช้งานได้ โดยเป็นไปตามสัญญาแบบคงที่ที่กําหนดไว้และต้องลงทะเบียนกับ SDK

หมายเหตุ

คลาส Wrapper ในอนาคตต้องมีการกำกับเนื้อหา @CustomFutureWrapper โดยระบุคลาสที่รวมไว้เป็น originalType เช่น

@CustomFutureWrapper(originalType=SettableFuture.class)

รูปแบบ

Wrapper ในอนาคตต้องขยาย com.google.android.enterprise.connectedapps.FutureWrapper

Wrapper ในอนาคตต้องมีเมธอด W create(Bundler, BundlerType) แบบคงที่ซึ่งสร้างอินสแตนซ์ของ Wrapper ในขณะเดียวกัน การดำเนินการนี้ควรสร้างอินสแตนซ์ของประเภทอนาคตที่รวมไว้ ผลลัพธ์นี้ควรแสดงโดยเมธอด T getFuture() ที่ไม่คงที่ ต้องใช้เมธอด onResult(E) และ onException(Throwable) เพื่อส่งผลลัพธ์หรือรายการที่ทำให้เกิดข้อยกเว้นไปยัง Future ที่รวมไว้

Wrapper ในอนาคตต้องมีเมธอด void writeFutureResult(Bundler, BundlerType, T, FutureResultWriter<E>) แบบคงที่ด้วย ซึ่งควรลงทะเบียนกับ passed in future สำหรับผลลัพธ์ และเมื่อได้ผลลัพธ์แล้ว ให้เรียกใช้ resultWriter.onSuccess(value) หากมีข้อยกเว้น ก็ควรเรียกใช้ resultWriter.onFailure(exception)

สุดท้าย Wrapper ในอนาคตต้องมีเมธอด T<Map<Profile, E>> groupResults(Map<Profile, T<E>> results) แบบคงที่ด้วย ซึ่งจะแปลงการแมปจากโปรไฟล์เป็นอนาคตเป็นการแมปจากโปรไฟล์ไปยังผลลัพธ์ในอนาคต คุณใช้ CrossProfileCallbackMultiMerger เพื่อทําให้ตรรกะนี้ง่ายขึ้นได้

เช่น

/** A basic implementation of the future pattern used to test custom future
wrappers. */
public class SimpleFuture<E> {
  public static interface Consumer<E> {
    void accept(E value);
  }
  private E value;
  private Throwable thrown;
  private final CountDownLatch countDownLatch = new CountDownLatch(1);
  private Consumer<E> callback;
  private Consumer<Throwable> exceptionCallback;

  public void set(E value) {
    this.value = value;
    countDownLatch.countDown();
    if (callback != null) {
      callback.accept(value);
    }
  }

  public void setException(Throwable t) {
    this.thrown = t;
    countDownLatch.countDown();
    if (exceptionCallback != null) {
      exceptionCallback.accept(thrown);
    }
  }

  public E get() {
    try {
      countDownLatch.await();
    } catch (InterruptedException e) {
      eturn null;
    }
    if (thrown != null) {
      throw new RuntimeException(thrown);
    }
    return value;
  }

  public void setCallback(Consumer<E> callback, Consumer<Throwable>
exceptionCallback) {
    if (value != null) {
      callback.accept(value);
    } else if (thrown != null) {
      exceptionCallback.accept(thrown);
    } else {
      this.callback = callback;
      this.exceptionCallback = exceptionCallback;
    }
  }
}
/** Wrapper for adding support for {@link SimpleFuture} to the Connected Apps SDK.
*/
@CustomFutureWrapper(originalType = SimpleFuture.class)
public final class SimpleFutureWrapper<E> extends FutureWrapper<E> {

  private final SimpleFuture<E> future = new SimpleFuture<>();

  public static <E> SimpleFutureWrapper<E> create(Bundler bundler, BundlerType
bundlerType) {
    return new SimpleFutureWrapper<>(bundler, bundlerType);
  }

  private SimpleFutureWrapper(Bundler bundler, BundlerType bundlerType) {
    super(bundler, bundlerType);
  }

  public SimpleFuture<E> getFuture() {
    return future;
  }

  @Override
  public void onResult(E result) {
    future.set(result);
  }

  @Override
  public void onException(Throwable throwable) {
    future.setException(throwable);
  }

  public static <E> void writeFutureResult(
      SimpleFuture<E> future, FutureResultWriter<E> resultWriter) {

    future.setCallback(resultWriter::onSuccess, resultWriter::onFailure);
  }

  public static <E> SimpleFuture<Map<Profile, E>> groupResults(
      Map<Profile, SimpleFuture<E>> results) {
    SimpleFuture<Map<Profile, E>> m = new SimpleFuture<>();

    CrossProfileCallbackMultiMerger<E> merger =
        new CrossProfileCallbackMultiMerger<>(results.size(), m::set);
    for (Map.Entry<Profile, SimpleFuture<E>> result : results.entrySet()) {
      result
        .getValue()
        .setCallback(
          (value) -> merger.onResult(result.getKey(), value),
          (throwable) -> merger.missingResult(result.getKey()));
    }
    return m;
  }
}

ลงทะเบียนกับ SDK

เมื่อสร้างแล้ว หากต้องการใช้ Wrapper ในอนาคตที่กําหนดเอง คุณจะต้องลงทะเบียน Wrapper กับ SDK

โดยระบุ futureWrappers={YourFutureWrapper.class} ในคำอธิบายประกอบ CustomProfileConnector หรือคำอธิบายประกอบ CrossProfile ในชั้นเรียน

โหมดการบูตโดยตรง

หากแอปรองรับโหมดการบูตโดยตรง คุณอาจต้องโทรข้ามโปรไฟล์ก่อนจึงจะปลดล็อกโปรไฟล์ได้ โดยค่าเริ่มต้น SDK จะอนุญาตการเชื่อมต่อก็ต่อเมื่อโปรไฟล์อื่นปลดล็อกอยู่เท่านั้น

หากต้องการเปลี่ยนลักษณะการทำงานนี้เมื่อใช้ตัวเชื่อมต่อโปรไฟล์ที่กำหนดเอง คุณควรระบุavailabilityRestrictions=AvailabilityRestrictions.DIRECT_BOOT_AWAREข้อมูลต่อไปนี้

@GeneratedProfileConnector
@CustomProfileConnector(availabilityRestrictions=AvailabilityRestrictions.DIRECT_BO
OT_AWARE)
public interface MyProfileConnector extends ProfileConnector {
  public static MyProfileConnector create(Context context) {
    return GeneratedMyProfileConnector.builder(context).build();
  }
}

หากคุณใช้ CrossProfileConnector ให้ใช้ .setAvailabilityRestrictions(AvailabilityRestrictions.DIRECT_BOOT _AWARE ในเครื่องมือสร้าง

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