Chủ đề nâng cao

Những phần này chỉ để tham khảo và bạn không bắt buộc phải đọc từ trên xuống dưới.

Sử dụng API khung:

Các API này sẽ được bao bọc trong SDK để tạo nên khu vực API nhất quán hơn (ví dụ: tránh các đối tượng UserHandle), nhưng hiện tại, bạn có thể gọi trực tiếp các đối tượng này.

Quy trình triển khai rất đơn giản: nếu bạn có thể tương tác, hãy tiếp tục. Nếu không, nhưng bạn có thể yêu cầu, sau đó hiển thị lời nhắc/biểu ngữ/tooltip/v.v. dành cho người dùng. Nếu người dùng đồng ý chuyển đến phần Cài đặt, tạo ý định yêu cầu và sử dụng Context#startActivity để chuyển người dùng đến đó. Bạn có thể dùng chế độ phát sóng để phát hiện khi nào khả năng này thay đổi hoặc chỉ kiểm tra lại khi người dùng đến quay lại.

Để kiểm tra điều này, bạn cần mở TestDPC trong hồ sơ công việc của mình, chuyển đến ở dưới cùng rồi chọn thêm tên gói của bạn vào danh sách cho phép ứng dụng đã kết nối. Thao tác này mô phỏng việc quản trị viên "cho phép đưa vào danh sách" ứng dụng của bạn.

Bảng thuật ngữ

Phần này định nghĩa các thuật ngữ chính liên quan đến việc phát triển hoạt động phát triển hồ sơ chéo.

Cấu hình trên nhiều hồ sơ

Cấu hình trên nhiều hồ sơ nhóm các Nhà cung cấp hồ sơ trên nhiều hồ sơ có liên quan lại với nhau Lớp và cung cấp cấu hình chung cho các tính năng giữa nhiều hồ sơ. Thường sẽ có một chú giải @CrossProfileConfiguration cho mỗi cơ sở mã , nhưng trong một số ứng dụng phức tạp, có thể có nhiều loại mã.

Trình kết nối hồ sơ

Trình kết nối quản lý kết nối giữa các cấu hình. Thông thường, mỗi hồ sơ chéo sẽ trỏ đến một Trình kết nối cụ thể. Mọi loại hồ sơ trong một phải sử dụng cùng một Trình kết nối.

Lớp nhà cung cấp hồ sơ chéo

Lớp nhà cung cấp hồ sơ chéo sẽ nhóm các Loại hồ sơ trên nhiều hồ sơ có liên quan lại với nhau.

Mediator

Người hoà giải đóng vai trò giữa bộ mã cấp cao và cấp thấp, phân phối các lệnh gọi đến chính xác hồ sơ và hợp nhất các kết quả. Đây là mã duy nhất cần được nhận biết hồ sơ. Đây là một khái niệm kiến trúc chứ không phải là một tính năng được tích hợp sẵn SDK.

Loại hồ sơ chéo

Loại hồ sơ chéo là một lớp hoặc giao diện chứa các phương thức được chú giải @CrossProfile. Mã thuộc loại này không cần nhận biết hồ sơ và thì tốt nhất là bạn chỉ cần hành động dựa trên dữ liệu cục bộ.

Các loại hồ sơ

Loại hồ sơ
Hiện tạiHồ sơ đang hoạt động mà chúng tôi đang thực thi.
Khác(nếu có) Hồ sơ mà chúng tôi hiện không thực thi.
Cá nhânNgười dùng 0, hồ sơ trên thiết bị không thể đã tắt.
Số nơi làm việcThông thường là người dùng 10 nhưng có thể cao hơn, họ có thể bật và tắt tắt, dùng để chứa dữ liệu và ứng dụng công việc.
ChínhDo ứng dụng xác định tuỳ ý. Hồ sơ hiển thị chế độ xem hợp nhất của cả hai hồ sơ.
Cấp haiNếu chính được xác định, phụ là hồ sơ không phải là chính.
Nhà cung cấpNhà cung cấp cho hồ sơ chính là cả hai hồ sơ, nhà cung cấp cho hồ sơ phụ chỉ là chính hồ sơ phụ.

Số nhận dạng hồ sơ

Một lớp đại diện cho một loại hồ sơ (cá nhân hoặc công việc). Đây sẽ là được trả về bằng các phương thức chạy trên nhiều cấu hình và có thể được dùng để chạy nhiều cấu hình khác mã trên các hồ sơ đó. Các mã này có thể được chuyển đổi tuần tự thành int để thuận tiện bộ nhớ.

Hướng dẫn này trình bày các cấu trúc được đề xuất để xây dựng một cách hiệu quả và có thể duy trì trên nhiều hồ sơ trong ứng dụng Android của bạn.

Chuyển đổi CrossProfileConnector thành một singleton

Bạn chỉ nên sử dụng một thực thể duy nhất trong suốt vòng đời của hoặc tạo các kết nối song song. Có thể làm được dùng một khung chèn phụ thuộc như Dagger hoặc bằng cách dùng một Singleton cổ điển mẫu, trong một lớp mới hoặc một lớp hiện có.

Chèn hoặc truyền thực thể Hồ sơ đã tạo vào lớp để khi bạn thực hiện lệnh gọi, thay vì tạo thực thể đó trong phương thức

Thao tác này cho phép bạn truyền thực thể FakeProfile được tạo tự động vào kiểm thử đơn vị của bạn sau.

Cân nhắc mẫu trung gian

Mẫu hình phổ biến này là để tạo một trong các API hiện có của bạn (ví dụ: getEvents()) nhận biết hồ sơ đối với tất cả phương thức gọi. Trong trường hợp này, API hiện tại của bạn chỉ có thể trở thành "người hoà giải" phương thức hoặc lớp chứa lệnh gọi mới đến được tạo mã hồ sơ chéo.

Bằng cách này, bạn không buộc mọi người gọi phải biết để thực hiện cuộc gọi giữa nhiều hồ sơ trở thành một phần trong API của bạn.

Cân nhắc xem có chú thích một phương thức giao diện là @CrossProfile hay không để tránh phải hiển thị các lớp triển khai của bạn trong một trình cung cấp

Tính năng này hoạt động tốt với các khung chèn phần phụ thuộc.

Nếu bạn nhận được bất kỳ dữ liệu nào từ một cuộc gọi giữa nhiều hồ sơ, hãy cân nhắc xem có nên thêm một trường tham chiếu đến hồ sơ mà dữ liệu đó đến từ hồ sơ nào

Đây có thể là một phương pháp hay vì bạn cần biết điều này ở lớp giao diện người dùng (ví dụ: thêm biểu tượng huy hiệu vào nội dung công việc). Cũng có thể bắt buộc phải có dữ liệu giá trị nhận dạng không còn là duy nhất nếu không có, chẳng hạn như tên gói.

Trên nhiều hồ sơ

Phần này trình bày cách tạo tương tác Hồ sơ chéo của riêng bạn.

Hồ sơ chính

Hầu hết các lệnh gọi trong các ví dụ trên tài liệu này đều chứa hướng dẫn rõ ràng về hồ sơ nào cần chạy, bao gồm hồ sơ công việc, hồ sơ cá nhân và cả hai.

Trong thực tế, đối với những ứng dụng có trải nghiệm hợp nhất chỉ trên một hồ sơ, bạn có thể muốn quyết định này phụ thuộc vào hồ sơ mà bạn đang chạy, vì vậy, là các phương pháp thuận tiện tương tự cũng tính đến điều này, để tránh cơ sở mã có nhiều điều kiện của hồ sơ if-else.

Khi tạo bản sao trình kết nối, bạn có thể chỉ định loại cấu hình là "chính" của bạn (ví dụ: "CÔNG VIỆC"). Thao tác này cho phép các tuỳ chọn bổ sung, chẳng hạn như sau:

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();

Các loại hồ sơ chéo

Các lớp và giao diện chứa một phương thức được chú thích @CrossProfile là được gọi là Loại hồ sơ chéo.

Việc triển khai Loại hồ sơ chéo phải độc lập với hồ sơ, hồ sơ người dùng mà họ đang chạy. Chúng được phép thực hiện lệnh gọi đến các phương thức khác và nói chung sẽ hoạt động giống như khi chạy trên một hồ sơ duy nhất. Chúng sẽ chỉ có quyền truy cập vào trạng thái trong hồ sơ của riêng họ.

Ví dụ về Loại hồ sơ chéo:

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

Chú giải về lớp

Để cung cấp API mạnh nhất, bạn nên chỉ định trình kết nối cho mỗi hồ sơ, như sau:

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

Điều này là không bắt buộc, nhưng có nghĩa là API được tạo sẽ cụ thể hơn về các loại và nghiêm ngặt hơn về việc kiểm tra thời gian biên dịch.

Giao diện

Bằng cách chú thích các phương thức trên giao diện dưới dạng @CrossProfile, bạn cho biết rằng bạn có thể áp dụng một số cách triển khai phương thức này, trên các hồ sơ.

Bạn có thể trả về bất kỳ cách triển khai giao diện Hồ sơ chéo nào trong một hàm Cross Nhà cung cấp hồ sơ và khi làm vậy, bạn cho biết rằng Quy trình triển khai này phải có thể truy cập được trên nhiều hồ sơ. Bạn không cần chú thích các lớp triển khai.

Trình cung cấp hồ sơ chéo

Mỗi Loại hồ sơ chéo phải được cung cấp theo một phương pháp có chú thích @CrossProfileProvider. Các phương thức này sẽ được gọi mỗi khi một lệnh gọi giữa nhiều hồ sơ được thực hiện, vì vậy bạn nên duy trì singleton cho từng loại.

Hàm dựng

Trình cung cấp phải có một hàm khởi tạo công khai không chứa đối số hoặc đối số Context duy nhất.

Phương thức nhà cung cấp

Phương thức của trình cung cấp không được nhận đối số hoặc chỉ có một đối số Context.

Chèn phần phụ thuộc

Nếu bạn đang sử dụng một khung chèn phần phụ thuộc như Dagger để quản lý bạn nên thiết lập các đoạn mã phụ thuộc như bạn thường làm, sau đó chèn các loại đó vào lớp nhà cung cấp khác. Các phương thức @CrossProfileProvider sau đó có thể trả về những kết quả đó thực thể được chèn.

Trình kết nối hồ sơ

Mỗi Cấu hình hồ sơ chéo phải có một Trình kết nối hồ sơ duy nhất, chịu trách nhiệm quản lý kết nối đến hồ sơ khác.

Trình kết nối hồ sơ mặc định

Nếu chỉ có một Cấu hình hồ sơ chéo trong một cơ sở mã, thì bạn có thể tránh tạo Trình kết nối hồ sơ của riêng bạn và sử dụng com.google.android.enterprise.connectedapps.CrossProfileConnector. Đây là mặc định được sử dụng nếu không có giá trị nào được chỉ định.

Khi tạo Trình kết nối cấu hình chéo, bạn có thể chỉ định một số tuỳ chọn trên trình tạo:

  • Dịch vụ người thực thi theo lịch

    Nếu bạn muốn kiểm soát các luồng do SDK tạo, hãy sử dụng #setScheduledExecutorService(),

  • Keo dán

    Nếu bạn có nhu cầu cụ thể về liên kết hồ sơ, hãy sử dụng #setBinder. Chiến dịch này có thể chỉ Đơn vị kiểm soát chính sách thiết bị mới sử dụng.

Trình kết nối hồ sơ tùy chỉnh

Bạn sẽ cần trình kết nối hồ sơ tuỳ chỉnh để có thể đặt một số cấu hình (đang sử dụng CustomProfileConnector) và sẽ cần một mã nếu bạn cần nhiều mã trình kết nối trong một cơ sở mã duy nhất (ví dụ: nếu bạn có nhiều quy trình, chúng tôi đề xuất một trình kết nối cho mỗi quy trình).

Khi tạo ProfileConnector, mã này sẽ có dạng như sau:

@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

    Để thay đổi tên của dịch vụ được tạo (cần được tham chiếu trong AndroidManifest.xml), hãy sử dụng serviceClassName=.

  • primaryProfile

    Để chỉ định hồ sơ chính, hãy dùng primaryProfile.

  • availabilityRestrictions

    Cách thay đổi các quy định hạn chế SDK đặt thông tin về kết nối và khả năng sử dụng hồ sơ, hãy sử dụng availabilityRestrictions.

Trình kiểm soát chính sách thiết bị

Nếu ứng dụng của bạn là một Trình kiểm soát chính sách thiết bị, thì bạn phải chỉ định một phiên bản của DpcProfileBinder tham chiếu đến DeviceAdminReceiver của bạn.

Nếu bạn đang triển khai trình kết nối hồ sơ của riêng mình:

@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();
  }
}

hoặc sử dụng CrossProfileConnector mặc định:

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

Cấu hình trên nhiều hồ sơ

Chú thích @CrossProfileConfiguration được dùng để liên kết tất cả các các cấu hình khác bằng cách sử dụng trình kết nối để gửi chính xác các lệnh gọi phương thức. Người nhận để thực hiện việc này, chúng ta sẽ chú thích một lớp bằng @CrossProfileConfiguration trỏ tới mọi nhà cung cấp, chẳng hạn như:

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

Điều này sẽ xác thực rằng đối với tất cả các tuỳ chọn Hồ sơ chéo Loại có cùng trình kết nối hồ sơ hoặc không có trình kết nối nào được chỉ định.

  • serviceSuperclass

    Theo mặc định, dịch vụ được tạo sẽ sử dụng android.app.Service làm lớp cao cấp. Nếu bạn cần một lớp khác (lớp này phải là lớp con android.app.Service) làm lớp cấp cao, sau đó chỉ định serviceSuperclass=.

  • serviceClass

    Nếu bạn chỉ định thì sẽ không có dịch vụ nào được tạo. Tên này phải khớp với serviceClassName trong trình kết nối hồ sơ mà bạn đang sử dụng. Tùy chỉnh của bạn dịch vụ sẽ điều phối cuộc gọi bằng cách sử dụng lớp _Dispatcher đã tạo dưới dạng như:

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;
  }
}

Bạn có thể sử dụng lựa chọn này nếu cần thực hiện thêm hành động trước hoặc sau cuộc gọi giữa nhiều hồ sơ.

  • Trình kết nối

    Nếu bạn đang sử dụng trình kết nối không phải là CrossProfileConnector mặc định, thì bạn phải chỉ định mã đó bằng connector=.

Chế độ hiển thị

Mọi phần của ứng dụng tương tác giữa các hồ sơ phải có thể thấy Trình kết nối hồ sơ của bạn.

Lớp được chú thích @CrossProfileConfiguration của bạn phải có thể xem mọi nhà cung cấp được sử dụng trong ứng dụng của bạn.

Cuộc gọi đồng bộ

SDK ứng dụng đã kết nối hỗ trợ các lệnh gọi đồng bộ (chặn) trong trường hợp chúng là không thể tránh khỏi. Tuy nhiên, việc sử dụng cũng có một số nhược điểm những cuộc gọi này (chẳng hạn như khả năng các cuộc gọi bị chặn trong một thời gian dài). bạn nên tránh các lệnh gọi đồng bộ khi có thể. Để sử dụng lệnh gọi không đồng bộ sẽ thấy Không đồng bộ cuộc gọi .

Kìm giắc cắm

Nếu đang sử dụng lệnh gọi đồng bộ, bạn phải đảm bảo rằng có chủ kết nối đã đăng ký trước khi thực hiện lệnh gọi chéo hồ sơ, nếu không thì hệ thống sẽ gửi ngoại lệ. Để biết thêm thông tin, hãy xem phần Chủ chốt giữ kết nối.

Để thêm trình giữ kết nối, hãy gọi ProfileConnector#addConnectionHolder(Object) với bất kỳ đối tượng nào (có thể là thực thể đối tượng đang khiến cho cuộc gọi giữa nhiều hồ sơ). Thao tác này sẽ ghi nhận rằng đối tượng này đang sử dụng và sẽ cố tạo kết nối. Hàm này phải được gọi trước bất kỳ lệnh gọi đồng bộ nào được thực hiện. Đây là lệnh gọi không chặn nên có thể kết nối sẽ chưa sẵn sàng (hoặc có thể không khả thi) vào thời điểm bạn thực hiện cuộc gọi của mình, trong trường hợp đó, hành vi xử lý lỗi thông thường sẽ được áp dụng.

Nếu bạn thiếu các quyền thích hợp giữa các hồ sơ khi bạn gọi ProfileConnector#addConnectionHolder(Object) hoặc không có hồ sơ nào cho kết nối, thì sẽ không có lỗi nào xảy ra nhưng lệnh gọi lại được kết nối sẽ không bao giờ được có tên. Nếu sau đó quyền này được cấp hoặc hồ sơ khác trở nên thì kết nối sẽ được thực hiện và lệnh gọi lại sẽ được gọi.

Ngoài ra, ProfileConnector#connect(Object) là một phương thức chặn sẽ thêm đối tượng làm phần tử giữ kết nối và thiết lập kết nối hoặc gửi một UnavailableProfileException. Không thể gọi phương thức này từ Luồng giao diện người dùng.

Lệnh gọi đến ProfileConnector#connect(Object) và các lệnh tương tự ProfileConnector#connect trả về các đối tượng đóng tự động. Các đối tượng này sẽ tự động hãy tháo phần giữ kết nối sau khi đóng. Điều này cho phép sử dụng như:

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

Sau khi thực hiện xong lệnh gọi đồng bộ, bạn nên gọi ProfileConnector#removeConnectionHolder(Object). Sau khi tất cả các chủ sở hữu kết nối bị xoá, kết nối sẽ bị đóng.

Kết nối

Bạn có thể dùng trình nghe kết nối để nhận thông báo khi trạng thái kết nối thay đổi và connector.utils().isConnected có thể được dùng để xác định xem hiện có kết nối. Ví dụ:

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

Lệnh gọi không đồng bộ

Mọi phương thức hiển thị trên đường phân chia hồ sơ phải được chỉ định là chặn (đồng bộ) hoặc không chặn (không đồng bộ). Bất kỳ phương thức nào trả về một loại dữ liệu không đồng bộ (ví dụ: ListenableFuture) hoặc chấp nhận lệnh gọi lại được đánh dấu là không chặn. Tất cả các phương pháp khác được đánh dấu là chặn.

Bạn nên sử dụng các lệnh gọi không đồng bộ. Nếu bạn phải sử dụng các lệnh gọi đồng bộ, hãy xem Đồng bộ Cuộc gọi.

Lệnh gọi lại

Loại lệnh gọi không chặn cơ bản nhất là phương thức trống, chấp nhận làm một của tham số, một giao diện chứa một phương thức được gọi bằng phương thức kết quả. Để các giao diện này hoạt động với SDK, giao diện phải chú thích @CrossProfileCallback. Ví dụ:

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

Sau đó, giao diện này có thể được dùng làm tham số trong chú giải @CrossProfile và được gọi như thường lệ. Ví dụ:

@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
});

Nếu giao diện này chứa một phương thức duy nhất, phương thức này sẽ nhận giá trị 0 hoặc 1 tham số, thì nó cũng có thể được sử dụng trong các lệnh gọi đến nhiều cấu hình cùng một lúc.

Có thể truyền số lượng giá trị bất kỳ bằng cách sử dụng lệnh gọi lại, nhưng kết nối sẽ chỉ được giữ mở cho giá trị đầu tiên. Xem phần Giữ kết nối để biết thông tin về giữ kết nối mở để nhận thêm giá trị.

Phương thức đồng bộ với lệnh gọi lại

Một tính năng bất thường của việc sử dụng lệnh gọi lại với SDK là bạn có thể về mặt kỹ thuật, phương thức đồng bộ này sử dụng lệnh gọi lại:

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

Trong trường hợp này, phương thức này thực sự đồng bộ, bất kể lệnh gọi lại. Chiến dịch này mã sẽ thực thi chính xác:

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

Tuy nhiên, khi được gọi bằng SDK, mã này sẽ không hoạt động theo cách tương tự. Có không đảm bảo rằng phương thức cài đặt sẽ được gọi trước khi "Bản in này ngày 3" sẽ được in. Mọi lần sử dụng phương thức được SDK đánh dấu là không đồng bộ đều phải không đưa ra giả định nào về thời điểm phương thức sẽ được gọi.

Lệnh gọi lại đơn giản

"Lệnh gọi lại đơn giản" là một hình thức gọi lại hạn chế hơn, cho phép các tính năng bổ sung khi thực hiện cuộc gọi giữa nhiều hồ sơ. Giao diện đơn giản phải chứa một phương thức duy nhất có thể nhận tham số 0 hoặc 1.

Bạn có thể thực thi việc duy trì giao diện gọi lại bằng cách chỉ định simple=true trong chú thích @CrossProfileCallback.

Bạn có thể sử dụng các lệnh gọi lại đơn giản với nhiều phương thức như .both(), .suppliers(), và các nguồn khác.

Kìm giắc cắm

Khi thực hiện lệnh gọi không đồng bộ (sử dụng lệnh gọi lại hoặc tương lai), phần tử giữ kết nối sẽ được thêm vào khi thực hiện cuộc gọi và bị xoá khi một trong hai ngoại lệ hoặc một giá trị được thông qua.

Nếu muốn truyền đi nhiều kết quả bằng lệnh gọi lại, bạn nên thêm lệnh gọi lại làm trình giữ kết nối theo cách thủ công:

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

  profileMyClass.other().registerListener(b);

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

Bạn cũng có thể sử dụng thuộc tính này với khối try-with-resources:

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

  // Other things running while we expect results
}

Nếu chúng ta gọi lại hoặc thực hiện cuộc gọi trong tương lai, kết nối sẽ được giữ lại cho đến khi có kết quả được thông qua. Nếu chúng tôi xác định rằng kết quả sẽ không được thông qua, thì chúng tôi sẽ loại bỏ lệnh gọi lại hoặc tương lai với vai trò chủ thể kết nối:

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

Để biết thêm thông tin, hãy xem bài viết Chủ chốt kết nối.

Futures

Tương lai cũng được SDK hỗ trợ sẵn. Tuỳ chọn gốc duy nhất được hỗ trợ Loại tương lai là ListenableFuture, mặc dù tương lai tuỳ chỉnh các loại có thể để sử dụng. Để sử dụng dữ liệu tương lai, bạn chỉ cần khai báo một loại dữ liệu Future được hỗ trợ làm dữ liệu trả về của một phương thức hồ sơ chéo, sau đó sử dụng phương thức đó như bình thường.

Có cùng một "tính năng bất thường" làm lệnh gọi lại, trong đó phương thức đồng bộ trả về một tương lai (ví dụ: sử dụng immediateFuture) sẽ hoạt động theo cách khác khi chạy trên hồ sơ hiện tại so với chạy trên một hồ sơ khác. Mọi trường hợp sử dụng phương thức được SDK đánh dấu là không đồng bộ không được giả định về thời điểm sẽ được gọi.

Luồng

Đừng chặn do một tương lai giữa nhiều hồ sơ hoặc lệnh gọi lại trên máy tính chính luồng. Nếu bạn làm như vậy, thì trong một số trường hợp, mã của bạn sẽ chặn vô thời hạn. Điều này là do kết nối với hồ sơ khác cũng được thiết lập trên luồng chính. Điều này sẽ không bao giờ xảy ra nếu bị chặn đang chờ xử lý kết quả trên nhiều hồ sơ.

Phạm vi cung cấp

Bạn có thể dùng trình nghe tình trạng rảnh/bận để nhận thông báo khi trạng thái rảnh/bận thay đổi và connector.utils().isAvailable có thể được dùng để xác định xem hồ sơ có sẵn để sử dụng. Ví dụ:

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

Kìm giắc cắm

Phần tử giữ kết nối là các đối tượng tuỳ ý được ghi lại là có và mối quan tâm đến việc thiết lập và duy trì kết nối giữa các hồ sơ.

Theo mặc định, khi thực hiện các lệnh gọi không đồng bộ, phần tử giữ kết nối sẽ được thêm vào khi lệnh gọi bắt đầu và bị xoá khi xảy ra bất kỳ kết quả hoặc lỗi nào.

Bạn cũng có thể thêm và tháo đế kết nối theo cách thủ công để kiểm soát tốt hơn qua kết nối. Bạn có thể thêm phần tử giữ kết nối bằng connector.addConnectionHolder và xóa bằng connector.removeConnectionHolder.

Khi có ít nhất một phần tử giữ kết nối được thêm, SDK sẽ cố gắng duy trì kết nối. Khi không có chủ kết nối nào được thêm vào, kết nối có thể bị đóng.

Bạn phải duy trì tham chiếu đến mọi chủ sở hữu kết nối mà bạn thêm vào và xoá tham chiếu đó khi không còn phù hợp.

Lệnh gọi đồng bộ

Trước khi thực hiện lệnh gọi đồng bộ, bạn phải thêm phần tử giữ kết nối. Điều này có thể bằng cách sử dụng bất kỳ đối tượng nào, mặc dù bạn phải theo dõi đối tượng đó để nó có thể sẽ bị xoá khi bạn không cần thực hiện lệnh gọi đồng bộ nữa.

Lệnh gọi không đồng bộ

Khi thực hiện các lệnh gọi không đồng bộ, phần tử giữ kết nối sẽ tự động được quản lý để kết nối mở giữa cuộc gọi và phản hồi hoặc lỗi đầu tiên. Nếu bạn cần kết nối để vượt qua giới hạn này (ví dụ: để nhận nhiều kết nối bằng cách sử dụng một lệnh gọi lại), bạn nên thêm chính lệnh gọi lại đó làm một phần tử giữ kết nối và xoá nó khi bạn không cần nhận thêm .

Xử lý lỗi

Theo mặc định, mọi cuộc gọi được thực hiện tới hồ sơ kia khi hồ sơ khác không được sẽ dẫn đến việc gửi UnavailableProfileException (hoặc được truyền vào Tương lai hoặc lệnh gọi lại lỗi đối với lệnh gọi không đồng bộ).

Để tránh điều này, nhà phát triển có thể sử dụng #both() hoặc #suppliers() và ghi để xử lý số lượng mục bất kỳ trong danh sách kết quả (giá trị này sẽ là 1 nếu hồ sơ còn lại không hoạt động hoặc 2 nếu có).

Ngoại lệ

Bất kỳ ngoại lệ nào chưa được kiểm tra xảy ra sau khi bạn gọi đến hồ sơ hiện tại sẽ sẽ được truyền như bình thường. Điều này áp dụng bất kể phương pháp dùng để tạo (#current(), #personal, #both, v.v.).

Các ngoại lệ không được chọn xảy ra sau khi gọi đến hồ sơ khác sẽ dẫn đến trong ProfileRuntimeException được gửi với ngoại lệ ban đầu là giá trị nguyên nhân. Điều này áp dụng bất kể phương thức dùng để thực hiện lệnh gọi là gì (#other(), #personal, #both, v.v.).

ifAvailable

Thay vì phát hiện và xử lý UnavailableProfileException có thể sử dụng phương thức .ifAvailable() để cung cấp giá trị mặc định sẽ được trả về thay vì gửi một UnavailableProfileException.

Ví dụ:

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

Thử nghiệm

Để mã của bạn có thể kiểm thử được, bạn nên chèn các thực thể của hồ sơ trình kết nối với bất kỳ mã nào sử dụng trình kết nối này (để kiểm tra tính sẵn có của hồ sơ, để kết nối theo cách thủ công, v.v.). Bạn cũng nên chèn các bản sao hồ sơ của mình biết loại mã được sử dụng ở đâu.

Chúng tôi cung cấp các loại trình kết nối và loại trình kết nối giả mạo có thể dùng trong các bài kiểm thử.

Trước tiên, hãy thêm các phần phụ thuộc kiểm thử:

  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'

Sau đó, chú thích lớp kiểm thử của bạn bằng @CrossProfileTest, xác định @CrossProfileConfiguration lớp được chú giải cần kiểm tra:

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

}

Điều này sẽ dẫn đến việc tạo ra thông tin giả cho tất cả các loại và trình kết nối được sử dụng trong .

Tạo các thực thể của những lần giả mạo đó trong kiểm thử của bạn:

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();

Thiết lập trạng thái hồ sơ:

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

Truyền trình kết nối giả và lớp hồ sơ chéo vào mã của bạn đang được kiểm thử và sau đó thực hiện cuộc gọi.

Cuộc gọi sẽ được chuyển đến đúng đích – và các trường hợp ngoại lệ sẽ được gửi khi đang gọi đến những hồ sơ đã ngắt kết nối hoặc không hoạt động.

Loại được hỗ trợ

Các loại sau đây được hỗ trợ mà bạn không cần phải làm gì thêm. Những URL này có thể được sử dụng làm đối số hoặc loại trả về cho tất cả các lệnh gọi giữa nhiều hồ sơ.

  • Nguyên gốc (byte, short, int, long, float, double, char, boolean),
  • Nguyên tắc đóng hộp (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,
  • Mọi thứ triển khai android.os.Parcelable,
  • Mọi thứ triển khai java.io.Serializable,
  • Mảng không gốc một phương diện,
  • java.util.Optional,
  • java.util.Collection,
  • java.util.List,
  • java.util.Map,
  • java.util.Set,
  • android.util.Pair,
  • com.google.common.collect.ImmutableMap.

Mọi loại chung được hỗ trợ (ví dụ: java.util.Collection) có thể có bất kỳ được hỗ trợ làm tham số loại của chúng. Ví dụ:

java.util.Collection<java.util.Map<java.lang.String,MySerializableType[]>> là loại hợp lệ.

Futures

Các loại sau đây chỉ được hỗ trợ làm loại dữ liệu trả về:

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

Trình bao bọc theo gói tuỳ chỉnh

Nếu loại hình của bạn không có trong danh sách trên, trước tiên hãy cân nhắc xem có thể gửi loại giấy tờ đó cho hãy triển khai android.os.Parcelable hoặc java.io.Serializable đúng cách. Nếu sau đó bạn sẽ không thấy thông báo Parceable trình bao bọc thành hỗ trợ thêm cho loại của bạn.

Trình bao bọc tuỳ chỉnh trong tương lai

Nếu muốn sử dụng một kiểu dữ liệu trong tương lai mà không có trong danh sách trước đó, hãy xem phần tương lai trình bao bọc để hỗ trợ thêm.

Trình bao bọc có thể đóng gói

Trình bao bọc có thể đóng gói là cách mà SDK hỗ trợ thêm cho các trình bao bọc không theo gói không thể sửa đổi được. SDK có trình bao bọc cho nhiều loại nhưng nếu loại bạn cần sử dụng không được bao gồm, bạn phải tự viết.

Trình bao bọc Parcelable là một lớp được thiết kế để gói một lớp khác và khiến nó theo gói. Ứng dụng tuân theo một hợp đồng tĩnh xác định và được đăng ký với SDK để nó có thể được dùng để chuyển đổi một loại dữ liệu nhất định thành một loại theo gói, cũng như trích xuất loại đó từ loại theo gói.

Annotation

Bạn phải chú thích cho lớp trình bao bọc theo gói @CustomParcelableWrapper, chỉ định lớp được gói là originalType. Ví dụ:

@CustomParcelableWrapper(originalType=ImmutableList.class)
``` ###
Format

Parcelable wrappers must implement `Parcelable` correctly, and must have a
static `W of(Bundler, BundlerType, T)` method which wraps the wrapped type and a
non-static `T get()` method which returns the wrapped type.

The SDK will use these methods to provide seamless support for the type.

### Bundler

To allow for wrapping generic types (such as lists and maps), the `of` method is
passed a `Bundler` which is capable of reading (using `#readFromParcel`) and
writing (using `#writeToParcel`) all supported types to a `Parcel`, and a
`BundlerType` which represents the declared type to be written.

`Bundler` and `BundlerType` instances are themselves parcelable, and should be
written as part of the parcelling of the parcelable wrapper, so that it can be
used when reconstructing the parcelable wrapper.

If the `BundlerType` represents a generic type, the type variables can be found
by calling `.typeArguments()`. Each type argument is itself a `BundlerType`.

For an example, see `ParcelableCustomWrapper`:

```java
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];
      }
    };
}

Đăng ký bằng SDK

Sau khi tạo xong, để sử dụng trình bao bọc theo gói tuỳ chỉnh, bạn cần đăng ký trình bao bọc đó với SDK.

Để thực hiện việc này, hãy chỉ định parcelableWrappers={YourParcelableWrapper.class} trong chú thích CustomProfileConnector hoặc chú thích CrossProfile trên một lớp.

Trình bao bọc trong tương lai

Trình bao bọc trong tương lai là cách SDK hỗ trợ thêm cho tương lai trên các hồ sơ. Chiến lược phát hành đĩa đơn Theo mặc định, SDK có hỗ trợ ListenableFuture, nhưng đối với các Tương lai khác mà bạn có thể tự thêm hỗ trợ.

Trình bao bọc tương lai là một lớp được thiết kế để gói một kiểu tương lai cụ thể và làm cho loại đó có sẵn cho SDK. Thoả thuận này tuân theo một hợp đồng cố định được xác định và phải đã đăng ký với SDK.

Annotation

Lớp trình bao bọc trong tương lai phải được chú thích @CustomFutureWrapper, chỉ định lớp được gói là originalType. Ví dụ:

@CustomFutureWrapper(originalType=SettableFuture.class)
``` ### Format

Future wrappers must extend
`com.google.android.enterprise.connectedapps.FutureWrapper`.

Future wrappers must have a static `W create(Bundler, BundlerType)` method which
creates an instance of the wrapper. At the same time this should create an
instance of the wrapped future type. This should be returned by a non-static `T`
`getFuture()` method. The `onResult(E)` and `onException(Throwable)` methods
must be implemented to pass the result or throwable to the wrapped future.

Future wrappers must also have a static `void writeFutureResult(Bundler,`
`BundlerType, T, FutureResultWriter<E>)` method. This should register with the
passed in future for results, and when a result is given, call
`resultWriter.onSuccess(value)`. If an exception is given,
`resultWriter.onFailure(exception)` should be called.

Finally, future wrappers must also have a static `T<Map<Profile, E>>`
`groupResults(Map<Profile, T<E>> results)` method which converts a map from
profile to future, into a future of a map from profile to result.
`CrossProfileCallbackMultiMerger` can be used to make this logic easier.

For example:

```java
/** A very simple 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;
  }
}

Đăng ký bằng SDK

Sau khi tạo, để sử dụng trình bao bọc tuỳ chỉnh trong tương lai, bạn sẽ cần đăng ký trình bao bọc đó với SDK.

Để thực hiện việc này, hãy chỉ định futureWrappers={YourFutureWrapper.class} trong Chú giải CustomProfileConnector hoặc chú thích CrossProfile trên một lớp.

Chế độ Khởi động trực tiếp

Nếu ứng dụng của bạn hỗ trợ tính năng khởi động trực tiếp chế độ , thì bạn có thể cần thực hiện cuộc gọi giữa nhiều hồ sơ trước khi hồ sơ được mở khoá. Theo mặc định, SDK chỉ cho phép kết nối khi hồ sơ khác được mở khoá.

Để thay đổi hành vi này, nếu bạn đang sử dụng trình kết nối hồ sơ tùy chỉnh, bạn phải chỉ định 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();
  }
}

Nếu bạn đang sử dụng CrossProfileConnector, hãy sử dụng .setAvailabilityRestrictions(AvailabilityRestrictions.DIRECT_BOOT _AWARE đang bật trình tạo.

Với thay đổi này, bạn sẽ được thông báo về tình trạng còn hàng và có thể thực hiện cuộc gọi hồ sơ, khi hồ sơ khác không được mở khoá. Bạn có trách nhiệm để đảm bảo cuộc gọi của bạn chỉ truy cập vào bộ nhớ được mã hoá của thiết bị.