고급 주제

이 섹션은 참고용이며 꼭 맨 위부터 읽을 필요는 없습니다.

프레임워크 API를 사용합니다.

이러한 API는 더 일관된 API 노출 영역(예: UserHandle 객체 방지)을 위해 SDK에서 래핑되지만 지금은 이를 직접 호출할 수 있습니다.

구현은 간단합니다. 상호작용할 수 있으면 계속 진행합니다. 그렇지 않은 경우 요청 후 사용자 프롬프트/배너/도움말 등을 표시할 수 있습니다. 사용자가 설정으로 이동하여 요청 인텐트를 만들고 Context#startActivity를 호출하여 사용자를 해당 페이지로 이동시킵니다. 브로드캐스트를 사용하여 이 기능이 변경될 때 감지하거나 사용자가 다시 돌아왔을 때 다시 확인하면 됩니다.

이를 테스트하려면 직장 프로필에서 TestDPC를 열어야 합니다. 하단의 연결된 앱 허용 목록에 패키지 이름을 추가하도록 선택합니다. 이렇게 하면 관리자가 앱을 '허용 목록'에 추가한 것처럼 보입니다.

용어 설명

이 섹션에서는 교차 프로필 개발 개발과 관련된 핵심 용어를 정의합니다.

교차 프로필 구성

교차 프로필 구성은 관련 교차 프로필 제공업체 클래스를 그룹화하고 교차 프로필 기능에 대한 일반 구성을 제공합니다. 일반적으로 코드베이스당 하나의 @CrossProfileConfiguration 주석이 있지만 일부 복잡한 애플리케이션에서는 여러 개가 있을 수 있습니다.

프로필 커넥터

커넥터는 프로필 간의 연결을 관리합니다. 일반적으로 각 교차 프로필 유형이 특정 커넥터를 가리키게 됩니다. 모든 교차 프로필 유형을 동일한 커넥터를 사용해야 합니다

교차 프로필 제공자 클래스

교차 프로필 제공업체 클래스는 관련 교차 프로필 유형을 그룹화합니다.

중재자

미디에이터는 상위 코드와 하위 코드 사이에 위치하여 올바른 프로필에 호출을 배포하고 결과를 병합합니다. 이는 프로필을 인식해야 하는 유일한 코드입니다. 이것은 아키텍처 개념이라기보다는 생성합니다.

교차 프로필 유형

교차 프로필 유형은 주석이 지정된 메서드를 포함하는 클래스 또는 인터페이스입니다. @CrossProfile 이 유형의 코드는 프로필을 인식할 필요가 없으며 로컬 데이터에만 작동하는 것이 이상적입니다.

프로필 유형

프로필 종류
현재실행 중인 활성 프로필입니다.
기타(있는 경우) 실행하지 않는 프로필입니다.
개인사용자 0은 이(가) 꺼져 있습니다.
업무일반적으로 사용자 10이지만 그보다 높을 수 있으며 사용 설정하고 사용 중지되어 직장 앱과 데이터가 포함됩니다.
기본애플리케이션에서 선택적으로 정의합니다. 두 프로필의 병합된 보기를 보여주는 프로필입니다.
보조기본이 정의되면 보조 프로필이 이(가) 기본이 아닙니다.
공급업체기본 프로필의 공급자는 두 프로필 모두이고 보조 프로필의 공급자는 보조 프로필 자체뿐입니다.

프로필 식별자

프로필 유형(개인 또는 직장)을 나타내는 클래스입니다. 이 작업은 여러 프로필에서 실행되고 더 많은 리소스를 실행하는 데 사용할 수 있는 메서드에서 반환 해당 프로필에 적용됩니다. 편의를 위해 int로 직렬화할 수 있습니다. 사용할 수 있습니다

이 가이드에서는 효율적이고 효율적으로 빌드하기 위한 권장 구조에 대해 간략히 설명합니다. 교차 프로필 기능을 구현해야 합니다.

CrossProfileConnector를 싱글톤으로 변환

수명 주기 동안 하나의 인스턴스만 사용해야 함 그렇지 않으면 병렬 연결을 만듭니다 Dagger와 같은 종속 항목 삽입 프레임워크를 사용하거나 새 클래스 또는 기존 클래스에서 기존 싱글톤 패턴을 사용하여 이를 실행할 수 있습니다.

메서드에서 생성하는 대신 호출 시 클래스에 생성된 Profile 인스턴스를 삽입하거나 전달합니다.

이렇게 하면 자동으로 생성된 FakeProfile 인스턴스를 나중에 단위 테스트를 수행할 수 있습니다

미디에이터 패턴 고려

이러한 일반적인 패턴은 기존 API 중 하나 (예: getEvents())를 만드는 것입니다. 프로필을 인식합니다. 이 경우 기존 API는 생성된 교차 프로필 코드에 대한 새 호출이 포함된 '미디에이터' 메서드 또는 클래스가 될 수 있습니다.

이렇게 하면 모든 발신자에게 교차 프로필 호출을 하라는 메시지를 강요하지 않아도 됩니다. API의 일부가 됩니다

제공자에서 구현 클래스를 노출하지 않도록 인터페이스 메서드에 @CrossProfile 주석을 달지 고려하세요.

이는 종속 항목 삽입 프레임워크와 잘 작동합니다.

교차 프로필 호출에서 데이터를 수신하는 경우 데이터가 출처인 프로필을 참조하는 입력란을 추가할지 여부를 고려하세요.

UI 레이어에서 이를 알고 싶을 수 있으므로(예: 작업 항목에 배지 아이콘 추가) 좋은 관행일 수 있습니다. 패키지 이름과 같이 데이터 식별자가 이 없이 더 이상 고유하지 않은 경우에도 필요할 수 있습니다.

교차 프로필

이 섹션에서는 교차 프로필 상호작용을 직접 빌드하는 방법을 간략히 설명합니다.

기본 프로필

이 문서의 예시에서 대부분의 호출에는 직장, 개인, 둘 다를 포함하여 실행할 프로필에 관한 명시적인 안내가 포함되어 있습니다.

실제로 하나의 프로필에서만 병합된 환경이 있는 앱의 경우 실행 중인 프로필에 따라 이 결정을 내리고자 하므로 유사한 편리한 방법이기도 합니다. 코드베이스가 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 주석이 달렸습니다. 이러한 메서드는 교차 프로필 호출이 실행될 때마다 호출되므로 각 유형에 대해 싱글톤을 유지하는 것이 좋습니다.

생성자

제공자는 인수를 사용하지 않거나 단일 Context 인수

제공자 메서드

제공자 메서드는 인수를 사용하지 않거나 단일 Context 인수를 사용해야 합니다.

종속 항목 삽입

Dagger와 같은 종속 항목 삽입 프레임워크를 사용하여 종속 항목 삽입 프레임워크를 기본 종속 항목 간에 교차하도록 하려면 프레임워크에서 그런 다음 이러한 유형을 provider 클래스입니다. 그러면 @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

    제한사항을 변경하는 방법 연결 및 프로필 가용성에 대해 알아보려면 availabilityRestrictions입니다.

기기 정책 컨트롤러

앱이 기기 정책 컨트롤러인 경우 DeviceAdminReceiver를 참조하는 DpcProfileBinder 인스턴스를 지정해야 합니다.

자체 프로필 커넥터를 구현하는 경우:

@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 주석이 추가된 클래스는 애플리케이션에 사용되는 모든 제공자를 볼 수 있어야 합니다.

동기 호출

연결된 앱 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개인 단일 메서드가 포함된 경우 한 번에 여러 프로필을 호출하는 데도 사용할 수 있습니다.

콜백을 사용하여 원하는 수의 값을 전달할 수 있지만 연결은 첫 번째 값에 대해서만 열린 상태로 있습니다. 더 많은 값을 수신하기 위해 연결을 열어 두는 방법에 관한 자세한 내용은 연결 홀더를 참고하세요.

콜백을 사용한 동기 메서드

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를 사용하여 호출되면 동일한 방식으로 작동하지 않습니다. 현재 설치 메서드가 '이 출력 셋째" 출력됩니다. SDK에서 비동기식으로 표시된 메서드를 사용하는 경우 메서드가 호출되는 시점에 관해 가정해서는 안 됩니다.

간단한 콜백

'간단한 콜백'은 교차 프로필 호출 시 추가 기능을 허용하는 더 제한적인 형태의 콜백입니다. 단순한 인터페이스는 에는 0개 또는 1개의 매개변수를 사용할 수 있는 단일 메서드가 포함됩니다.

다음 메서드를 지정하여 콜백 인터페이스가 유지되도록 할 수 있습니다. @CrossProfileCallback 주석의 simple=true

간단한 콜백은 .both(), .suppliers() 등 다양한 메서드에서 사용할 수 있습니다.

연결 홀더

비동기 호출(콜백 또는 future 사용)을 실행하면 호출 시 연결 홀더가 추가되고 예외 또는 값이 전달되면 삭제됩니다.

콜백을 사용하여 둘 이상의 결과가 전달될 것으로 예상되는 경우 콜백을 연결 홀더로 수동으로 추가합니다.

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
}

콜백 또는 미래로 전화를 걸면 연결이 열린 상태로 유지됩니다. 결과가 전달될 때까지만 허용됩니다. 결과가 전달되지 않는다고 판단될 경우 연결 홀더로서 콜백 또는 future를 삭제해야 합니다.

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

자세한 내용은 연결 홀더를 확인하세요.

Future

SDK는 Futures도 기본적으로 지원합니다. 기본적으로 지원되는 유일한 Future 유형은 ListenableFuture이지만 custom future가 있습니다. 유형은 사용됩니다. future를 사용하려면 지원되는 Future 유형을 교차 프로필 메서드의 반환 유형으로 선언한 다음 평소와 같이 사용하면 됩니다.

이는 콜백과 동일한 '비정상적인 기능'을 갖습니다. future를 반환하는 동기 메서드(예: immediateFuture 사용)는 현재 프로필에서 실행될 때와 다른 프로필에서 실행될 때 다르게 동작합니다. SDK에서 비동기식으로 표시된 메서드를 사용하는 경우 메서드가 호출되는 시점에 관해 가정해서는 안 됩니다.

스레드

기본 스레드에서 교차 프로필 future 또는 콜백의 결과를 차단하지 마세요. 스레드 이렇게 하면 경우에 따라 코드가 무기한으로 유지됩니다. 이는 다른 프로필과의 연결도 메인 스레드에서 설정되기 때문입니다. 교차 프로필 결과를 기다리는 동안 차단되면 연결이 설정되지 않습니다.

가용성

사용 가능 여부 리스너는 사용 가능 여부 상태가 변경될 때 알리는 데 사용할 수 있으며 connector.utils().isAvailable는 다른 프로필을 사용할 수 있는지 확인하는 데 사용할 수 있습니다. 예를 들면 다음과 같습니다.

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

연결 홀더

연결 홀더는 교차 프로필 연결에 대한 관심

기본적으로 비동기 호출을 실행하면 호출이 시작될 때 연결 홀더가 추가되고 결과나 오류가 발생하면 삭제됩니다.

연결 홀더를 수동으로 추가 및 삭제하여 연결을 더 세부적으로 제어할 수도 있습니다. 연결 보유자는 다음을 사용하여 추가할 수 있습니다. connector.addConnectionHolder, 다음을 사용하여 삭제함 connector.removeConnectionHolder

연결 홀더가 하나 이상 추가되면 SDK는 연결을 유지하려고 시도합니다. 연결 홀더가 추가되지 않은 경우 연결을 닫을 수 있습니다.

추가하는 연결 홀더에 대한 참조를 유지하고 삭제해야 합니다. 더 이상 관련이 없을 때

동기 호출

동기 호출을 실행하기 전에 연결 홀더를 추가해야 합니다. 이렇게 하면 객체를 사용하여 수행할 수 있지만 해당 객체를 추적해야 더 이상 동기 호출을 할 필요가 없으면 삭제됩니다.

비동기 호출

비동기 호출을 할 때 연결 보유자가 자동으로 관리됩니다. 호출과 첫 번째 응답 또는 오류 사이에 연결이 열립니다. 그 이후에도 연결이 유지되어야 하는 경우(예: 단일 콜백을 사용하여 여러 응답을 수신하는 경우) 콜백 자체를 연결 홀더로 추가하고 더 이상 데이터를 수신할 필요가 없으면 삭제해야 합니다.

오류 처리

기본적으로 다른 프로필이 연결되지 않을 때 다른 프로필에 대한 모든 호출은 사용 가능한 경우 UnavailableProfileException이 발생합니다 (또는 Future로 전달되거나 비동기 호출의 경우 오류 콜백)에 전달됩니다.

이를 방지하기 위해 개발자는 #both() 또는 #suppliers()를 사용하고 처리하여 결과 목록에서 원하는 수의 항목을 처리하는 코드 다른 프로필을 사용할 수 없는 경우 또는 사용 가능한 경우 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[]>>: 올바른 유형인지 확인합니다

Future

다음 유형은 반환 유형으로만 지원됩니다.

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

맞춤 Parcelable 래퍼

유형이 위 목록에 없는 경우 먼저 android.os.Parcelable 또는 java.io.Serializable를 올바르게 구현하도록 만들 수 있는지 고려합니다. 그러면 유형에 대한 지원을 추가할 parcelable 래퍼를 볼 수 없습니다.

맞춤 Future 래퍼

이전 목록에 없는 future 유형을 사용하려면 future 래퍼를 참고하여 지원을 추가하세요.

Parcelable 래퍼

Parcelable 래퍼는 SDK가 수정할 수 없는 비 Parcelable 유형에 대한 지원을 추가하는 방법입니다. SDK에는 여러 SDK를 위한 유형이지만 포함되어 있지 않으므로 직접 작성해야 합니다.

Parcelable 래퍼는 다른 클래스를 래핑하고 parcelable을 적용할 수 있습니다. 정의된 정적 계약을 따르고 SDK에 등록되므로 지정된 유형을 parcelable 유형으로 변환하고 parcelable 유형에서 해당 유형을 추출하는 데 사용할 수 있습니다.

Annotation

parcelable 래퍼 클래스는 @CustomParcelableWrapper 주석을 달고 래핑된 클래스를 originalType로 지정해야 합니다. 예를 들면 다음과 같습니다.

@CustomParcelableWrapper(originalType=ImmutableList.class)

형식

Parcelable 래퍼는 Parcelable를 올바르게 구현해야 하며 래핑된 유형과W of(Bundler, BundlerType, T) 래핑된 유형을 반환하는 비정적 T get() 메서드입니다.

SDK는 이러한 메서드를 사용하여 유형을 원활하게 지원합니다.

Bundler

제네릭 유형(예: 목록 및 맵) 래핑을 허용하기 위해 of 메서드에는 지원되는 모든 유형을 Parcel에 읽기(#readFromParcel 사용) 및 쓰기(#writeToParcel 사용)할 수 있는 Bundler와 쓰려는 선언된 유형을 나타내는 BundlerType가 전달됩니다.

BundlerBundlerType 인스턴스는 자체적으로 parcelable이며 parcelable 래퍼를 재구성할 때 사용할 수 있도록 parcelable 래퍼의 parceling의 일부로 작성해야 합니다.

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에 등록

맞춤 parcelable 래퍼를 만들었다면 SDK에 등록해야 사용할 수 있습니다.

이렇게 하려면parcelableWrappers={YourParcelableWrapper.class} 클래스의 CustomProfileConnector 주석 또는 CrossProfile 주석

Future 래퍼(Future Wrapper)

Future Wrapper는 SDK가 프로필 전체에 futures 지원을 추가하는 방법입니다. SDK에는 기본적으로 ListenableFuture 지원이 포함되어 있지만 다른 Future 유형의 경우 직접 지원을 추가할 수 있습니다.

Future 래퍼는 특정 Future 유형을 래핑하고 SDK에서 사용할 수 있도록 설계된 클래스입니다. 정의된 정적 계약을 따르며 SDK에 등록되어 있어야 합니다

Annotation

향후 래퍼 클래스는 @CustomFutureWrapper 주석을 달고 래핑된 클래스를 originalType로 설정합니다. 예를 들면 다음과 같습니다.

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

SDK에 등록

생성된 후에 커스텀 future 래퍼를 사용하려면 래퍼를 생성합니다.

이렇게 하려면 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 사용 확인할 수 있습니다

이번 변경으로 다른 프로필이 잠금 해제되지 않은 경우 사용 가능 여부가 표시되고 교차 프로필 호출을 할 수 있습니다. 크리에이터의 책임 통화 시 기기 암호화 저장소에만 액세스할 수 있습니다.