這些章節僅供參考,您不需要閱讀 由上至下。
要求使用者同意
使用架構 API:
CrossProfileApps.canInteractAcrossProfiles()
CrossProfileApps.canRequestInteractAcrossProfiles()
CrossProfileApps.createRequestInteractAcrossProfilesIntent()
CrossProfileApps.ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED
這些 API 會包裝在 SDK 中,以取得更一致的 API 介面 (例如 避免使用 UserHandle 物件),但您目前可以直接呼叫這些物件。
導入作業非常簡單,如果您可以互動,請繼續操作。如果不是
但您可以要求之後顯示使用者提示/橫幅/工具提示等如果使用者
同意前往「設定」,建立要求意圖並使用
Context#startActivity
即可將使用者帶往該處。你可以使用
即可偵測這項功能的變更時間,或只在使用者嘗試存取
返回。
如要進行測試,您必須在工作資料夾中開啟 TestDPC,前往 頁面,然後選取將套件名稱加入連結應用程式的許可清單。這個 模仿管理員「加入許可清單」
詞彙解釋
本節定義與開發跨設定檔相關的重要詞彙。
跨設定檔設定
跨設定檔設定將相關的跨設定檔供應商歸為一組
類別並提供跨設定檔功能的一般設定。
通常每個程式碼集都會有一個 @CrossProfileConfiguration
註解
但在某些複雜的應用程式中,可能還會有多種。
設定檔連接器
連接器用來管理設定檔之間的連線。通常是每個跨設定檔 類型會指向特定的「連接器」。單一跨設定檔類型 設定必須使用相同的連接器。
跨設定檔供應商類別
跨設定檔供應商類別將相關的跨設定檔類型歸為一組。
Mediator
中介者位於高階和低階程式碼之間,將呼叫分配至 正確的設定檔並合併結果您只需要讀取這張投影片 或個人檔案感知此為架構概念,而非內建功能 SDK。
跨設定檔類型
跨設定檔類型是包含註解方法的類別或介面。
@CrossProfile
。這類程式碼不需具備設定檔感知功能,
最好只處理本機資料
設定檔類型
設定檔類型 | |
---|---|
目前 | 正在執行的有效設定檔。 |
其他 | (如果有的話) 系統執行該設定檔的設定檔。 |
個人 | 使用者 0,裝置中的設定檔無法 已關閉。 |
公司 | 通常是使用者 10 (但可能會更高),可切換開啟和關閉,用於容納工作應用程式和資料。 |
主要 | 視需要由應用程式定義。要變更設定檔的設定檔 會顯示兩個設定檔的合併檢視畫面。 |
次要 | 如果已定義主要值,次要則是次要設定檔 則不是主要資源 |
供應商 | 主要付款資料的供應商包括 次要付款資料的供應商只是次要付款資料本身。 |
設定檔 ID
代表個人資料類型 (個人或工作) 的類別。這些會
能由多個設定檔執行的方法所傳回,並可用來執行
加上該設定檔的程式碼這些項目可序列化為 int
,方便您使用
如果 30 天內讀取資料不到一次
建議使用 Coldline Storage
架構建議解決方案
本指南將概述在 Android 應用程式中建構高效且可維護的跨設定檔功能的建議結構。
將 CrossProfileConnector
轉換為單例模式
在應用程式的生命週期中,您只能使用一個執行個體 否則就會建立平行連線都可以 使用 Dagger 等依附元件插入架構 經典單例模式 圖案、 在新類別中或現有類別中
在發出呼叫時,將產生的設定檔例項插入或傳入您的類別,而非透過方法建立
這樣您就能傳入自動產生的 FakeProfile
例項,
稍後將建立單元測試
思考中介模式
這個常見的模式是將其中一個現有 API 設為現有 API (例如 getEvents()
)
可對所有呼叫端建立設定檔感知功能在這種情況下,您現有的 API
成為「中介者」方法或類別,內含要產生的新呼叫
跨設定檔程式碼
如此一來,您就不必強制所有呼叫端呼叫跨設定檔呼叫 作為 API 的一部分
請考慮是否要將介面方法加註為 @CrossProfile
,以免在供應器中公開實作類別
這個做法可與依附元件插入架構完美搭配運作。
如果您從跨設定檔呼叫接收任何資料,請考慮是否要新增欄位來參照來源設定檔
這可能是不錯的做法,因為您可能想在 UI 層知道這點 (例如在工作內容中加入徽章圖示)。如有任何資料 和 ID 等 ID 就不再是唯一的 ID
跨設定檔
本節將概述如何建立自己的「跨設定檔」互動。
主要設定檔
本文件範例中的大多數呼叫都包含明確指示 和/或個人資料夾
實際上,如果應用程式只將一個個人資料合併成一個個人資料,您可能很可能會 希望這項決策取決於要執行的設定檔 為避免您的 導致程式碼集與 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
的類別和介面為
稱為「跨設定檔類型」
導入跨設定檔類型時,應不受設定檔影響, 設定 Pod他們可以呼叫其他方法和 一般來說,這些功能的運作方式應該就像在單一設定檔上運作。他們將 只能在各自的設定檔中存取狀態。
跨設定檔類型範例:
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 等依附元件插入架構
建議您讓該架構
然後建立在
提供者類別接著,@CrossProfileProvider
方法可以傳回這些
插入的執行個體
設定檔連接器
每個跨設定檔設定都必須有一個設定檔連接器, 負責管理其他設定檔的連結。
預設設定檔連接器
如果程式碼集中只有一個跨設定檔設定,則您可以
不需建立專屬的設定檔連接器,在
com.google.android.enterprise.connectedapps.CrossProfileConnector
。這是
如未指定,則會採用預設值。
建構跨設定檔連接器時,您可以指定 建立工具:
已排定執行程式服務
如要控管 SDK 建立的執行緒,請使用
#setScheduledExecutorService()
,繫結機制
如有特定設定檔繫結需求,請使用
#setBinder
。這個 只有裝置政策控制器才能使用。
自訂設定檔連接器
您需要自訂設定檔連接器,才能進行某些設定
(使用 CustomProfileConnector
),需要一個
整合多個連接器 (舉例來說,如果您有多個程序,
每個程序建議一個連接器)。
建立 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
。
裝置政策控制器
如果您的應用程式是 Device Policy Controller,則必須指定
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
註解的類別必須能查看
應用程式所使用的供應商
同步呼叫
已連結的應用程式 SDK 支援同步 (封鎖) 呼叫, 因為這些限制是難以避免的然而,使用 AI 開發工具 (例如長時間封鎖來電), 建議您盡量避免同步呼叫。使用 非同步呼叫請參閱非同步呼叫 呼叫 。
連接夾
如果您使用的是同步呼叫,則務必確認 在執行跨設定檔呼叫之前註冊連線容器,否則 系統會擲回例外狀況詳情請參閱連線保留器。
如要新增連線持有者,請呼叫 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 標示為非同步方法的使用,都必須 不就呼叫方法的時間做出任何假設。
簡易回呼
「簡易回呼」則是限制更為嚴格的回呼形式 進行跨設定檔通話時的額外功能簡單介面必須 僅包含單一方法,可接收零或一個參數。
您可以藉由指定
@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);
如需更多資訊,請參閱連線保留器。
Futures
SDK 也原生支援 Future。唯一原生支援的
未來類型為 ListenableFuture
,但自訂 Future
類型
。如要使用 Future ,您只須宣告支援的 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
。
加入至少一個連線容器後,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 {
}
如此一來,您就能將 此外還會從 0 自動調整資源配置 您完全不必調整資源調度設定
在測試中建立這些假例項:
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[]>>
是
有效的類型
Futures
以下類型僅支援傳回類型:
com.google.common.util.concurrent.ListenableFuture
自訂 Parcelable 包裝函式
如果您的類型不在前一個清單中,請先考慮是否可以設為
並正確實作 android.os.Parcelable
或 java.io.Serializable
。如果
然後就無法看到 parcelable
包裝函式
增加對資料類型的支援。
自訂未來包裝函式
如要使用先前清單未列出的未來類型,請參閱「未來」 包裝函式來新增支援功能。
Parcelable 包裝函式
SDK 支援 Parcelable 包裝函式。 無法修改的類型SDK 包含許多 types 當中並未含有您需要的類型,因此必須自行編寫。
Parcelable 包裝函式是一種用來納入其他類別的類別, parcelable。根據定義的靜態合約,向 SDK 註冊 因此可用於將指定類型轉換為 parcel 類型 從 parcelable 類型中擷取該類型
註解
parcelable 包裝函式類別必須加註 @CustomParcelableWrapper
。
將包裝的類別指定為 originalType
。例如:
@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];
}
};
}
透過 SDK 註冊
建立完成後,您需要註冊自訂 parcelable 包裝函式。 與 SDK 整合
方法是在parcelableWrappers={YourParcelableWrapper.class}
類別上的 CustomProfileConnector
註解或 CrossProfile
註解。
未來包裝函式
SDK 會透過日後的包裝函式來新增對設定檔的未來的支援。
SDK 預設支援 ListenableFuture
,但適用於其他 Future
可以自行新增支援的類型
Future Wrapper 是專門用來包裝特定 Future 型別的類別, 可用的所有來源。須遵守已定義的靜態合約, 已向 SDK 註冊。
註解
未來的包裝函式類別必須加上 @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 註冊
建立後,如要使用自訂未來包裝函式,您需要向該包裝函式註冊 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
已開啟
。
屆時,我們會告知供應情形,以便 個人資料呼叫 (前提是其他設定檔未解鎖)。您的責任 確保通話只會存取裝置加密儲存空間。