Hướng dẫn dành cho nhà phát triển SDK Thời gian chạy

Gửi phản hồi

SDK Thời gian chạy cho phép SDK chạy trong một hộp cát dành riêng, tách biệt với ứng dụng gọi. SDK Thời gian chạy cung cấp các biện pháp bảo vệ nâng cao và đảm bảo việc thu thập dữ liệu người dùng. Việc này được thực hiện thông qua môi trường thực thi đã sửa đổi nhằm giới hạn quyền truy cập dữ liệu và tập hợp các quyền được phép. Tìm hiểu thêm về SDK Thời gian chạy trong đề xuất thiết kế.

Các bước trên trang này sẽ hướng dẫn bạn thực hiện quy trình tạo SDK có thời gian chạy để xác định chế độ xem dựa trên web có thể hiển thị từ xa vào ứng dụng gọi điện.

Trước khi bắt đầu

Trước khi bắt đầu, hãy hoàn thành các bước sau:

  1. Thiết lập môi trường phát triển của bạn cho Hộp cát về quyền riêng tư trên Android.
  2. Hoặc cài đặt hình ảnh hệ thống trên một thiết bị được hỗ trợ hoặc thiết lập một trình mô phỏng bao gồm chức năng hỗ trợ cho Hộp cát về quyền riêng tư trên Android.

Thiết lập dự án trong Android Studio

Để dùng thử SDK Thời gian chạy, hãy sử dụng mô hình tương tự như mô hình máy chủ ứng dụng. Điểm khác biệt chính là các ứng dụng (ứng dụng) và SDK ("máy chủ") chạy trên cùng một thiết bị.

Thêm một mô-đun ứng dụng và một mô-đun SDK vào dự án của bạn. Nhờ đó, bạn có thể chạy và kiểm tra song mã của mình dễ dàng hơn. Bạn nên tạo hai ứng dụng riêng biệt để đảm bảo rằng cả mã ứng dụng và mã SDK đều được tách riêng.

Tùy thuộc vào việc bạn là nhà phát triển SDK hay nhà phát triển ứng dụng, bạn có thể có thiết lập cuối cùng khác với thiết lập được mô tả trong đoạn trước.

Cài đặt SDK vào một thiết bị thử nghiệm, tương tự như cách bạn cài đặt một ứng dụng, bằng cách sử dụng Android Studio hoặc Cầu gỡ lỗi Android (ADB).

Để giúp bạn bắt đầu, chúng tôi đã tạo các ứng dụng mẫu bằng ngôn ngữ lập trình Kotlin và Java . Bạn có thể tìm thấy các ứng dụng này trong kho lưu trữ GitHub này .

Chuẩn bị SDK của bạn

Trong tệp AndroidManifest.xml của ứng dụng SDK, hãy đưa các phần tử <sdk-library><property> vào mục <application>, như minh họa trong đoạn mã sau đây. Sử dụng một tên riêng biệt cho SDK có hỗ trợ thời gian chạy và cung cấp một phiên bản.

<application ...>
  <sdk-library android:name="com.example.privacysandbox.provider"
               android:versionMajor="1" />
  <property
        android:name="android.sdksandbox.PROPERTY_SDK_PROVIDER_CLASS_NAME"
        android:value="com.example.provider.SdkProviderImpl" />

</application>

Điểm bắt đầu cho SDK của bạn sẽ mở rộng SandboxedSdkProvider. Để biên dịch ứng dụng SDK, bạn cần phải ghi đè các phương thức để xử lý vòng đời SDK.

initSdk()
Khởi chạy SDK và thông báo cho ứng dụng gọi khi quá trình khởi chạy hoàn tất và sẵn sàng để sử dụng.
getView()
Tạo và thiết lập chế độ xem cho quảng cáo của bạn, khởi tạo chế độ xem giống như mọi chế độ xem Android khác và trả về chế độ xem để hiển thị từ xa.
onDataReceived()
Xử lý mọi siêu dữ liệu được gửi từ ứng dụng lưu trữ bằng cách sử dụng sendData().

Đoạn mã sau đây minh họa cách ghi đè các phương thức này:

Kotlin

class SdkProviderImpl : SandboxedSdkProvider() {
    override fun initSdk(
        sandboxedSdkContext: SandboxedSdkContext, params: Bundle,
        executor: Executor, initSdkCallback: InitSdkCallback
    ) {
        // Update the callback with optional data to show that initialization
        // is complete.
        executor.execute { initSdkCallback.onInitSdkFinished(Bundle()) }
    }

    override fun getView(windowContext: Context, bundle: Bundle): View {
        val webView = WebView(windowContext)
        webView.loadUrl("https://developer.android.com/privacy-sandbox")
        return webView
    }

    override fun onDataReceived(bundle: Bundle, dataReceivedCallback: DataReceivedCallback) {
        // Update the callback with optional data to show that the receiving
        // or processing of data is complete
        dataReceivedCallback.onDataReceivedSuccess(Bundle())
    }
}

Java

public class SdkProviderImpl extends SandboxedSdkProvider {
    @Override
    public void initSdk(SandboxedSdkContext sandboxedSdkContext, Bundle params,
            Executor executor, InitSdkCallback initSdkCallback) {
        // Update the callback with optional data to show that the
        // initialization is complete.
        executor.execute(() -> initSdkCallback.onInitSdkFinished(new Bundle()));
    }

    @Override
    public View getView(Context windowContext, Bundle bundle) {
        WebView webView = new WebView(windowContext);
        webView.loadUrl("https://developer.android.com/privacy-sandbox");
        return webView;
    }

    @Override
    public void onDataReceived(Bundle bundle, DataReceivedCallback callback) {
        // Update the callback with optional data to show that the receiving or
        // processing of data is complete
        dataReceivedCallback.onDataReceivedSuccess(new Bundle());
    }
}

Thử nghiệm trình phát video trong SDK Thời gian chạy

Ngoài việc hỗ trợ quảng cáo biểu ngữ, Hộp cát về quyền riêng tư còn cam kết hỗ trợ các trình phát video chạy trong SDK Thời gian chạy.

Quy trình thử nghiệm trình phát video tương tự như thử nghiệm quảng cáo biểu ngữ. Thay đổi phương thức getView() của điểm truy cập SDK của bạn để đưa trình phát video vào đối tượng View được trả về. Thử nghiệm tất cả các luồng trình phát video mà bạn mong muốn hộp cát về quyền riêng tư hỗ trợ. Vui lòng lưu ý việc giao tiếp giữa SDK và ứng dụng khách về vòng đời của video hiện nằm ngoài phạm vi, do đó bạn chưa cần phải phản hồi về việc này.

Thử nghiệm và phản hồi của bạn phải đảm bảo SDK thời gian chạy hỗ trợ tất cả các trường hợp sử dụng của trình phát video ưa thích của bạn.

Đoạn mã sau đây minh họa cách trả về một lượt xem video đơn giản tải từ một URL.

Kotlin

class SdkProviderImpl : SandboxedSdkProvider() {

    override fun getView(windowContext: Context, params: Bundle): View {
        val videoView = VideoView(windowContext)
        videoView.setVideoURI(Uri.parse("https://test.website/video.mp4"))
        videoView.setOnPreparedListener { mp -> mp.start() }
        return videoView
    }
}

Java

public class SdkProviderImpl extends SandboxedSdkProvider {

    @Override
    public View getView(Context windowContext, Bundle params) {
        VideoView videoView = new VideoView(windowContext);
        videoView.setVideoURI(Uri.parse("https://test.website/video.mp4"));
        videoView.setOnPreparedListener(mp -> {
            mp.start();
        });
        return videoView;
    }
}

Sử dụng API lưu trữ trong SDK của bạn

Các SDK trong SDK Thời gian chạy không còn truy cập, đọc hoặc ghi trong bộ nhớ trong của ứng dụng nữa và ngược lại. SDK Thời gian chạy sẽ được phân bổ vào khu vực lưu trữ nội bộ riêng, đảm bảo tách biệt với ứng dụng.

Trong bộ nhớ trong riêng biệt cho mỗi SDK Runtime, mỗi SDK sẽ được cung cấp các thư mục lưu trữ của riêng chúng, được gọi là bộ nhớ trên mỗi SDK. Bộ nhớ trên mỗi SDK là sự phân tách hợp lý của bộ nhớ trong thuộc SDK Thời gian chạy, giúp tính toán cân nhắc dung lượng bộ nhớ mà mỗi SDK sử dụng.

Mỗi SDK có thể sử dụng bộ nhớ trên mỗi SDK của nó bằng các API lưu trữ tệp trên đối tượng SandboxedSdkContext mà SDK nhận được trong phương thức initSdk(). Bộ nhớ trên mỗi SDK sẽ được duy trì cho đến khi ứng dụng khách được gỡ cài đặt hoặc khi xóa dữ liệu ứng dụng khách.

SDK chỉ có thể sử dụng bộ nhớ trong. Do đó, chỉ có các API lưu trữ nội bộ, chẳng hạn như SandboxedSdkContext.getFilesDir() hoặc SandboxedSdkContext.getCacheDir() mới hoạt động. Vui lòng tham khảo thêm ví dụ ở bài viết Quyền truy cập từ bộ nhớ trong.

Không hỗ trợ quyền truy cập vào bộ nhớ ngoài từ SDK Thời gian chạy. Việc gọi các API để truy cập bộ nhớ ngoài sẽ gửi một ngoại lệ hoặc trả về giá trị null. Dưới đây là một số ví dụ:

Bạn phải sử dụng SandboxedSdkContext đã cung cấp để lưu trữ. Việc sử dụng API lưu trữ tệp trên bất kỳ phiên bản đối tượng Context nào khác, chẳng hạn như ngữ cảnh ứng dụng, sẽ không tận dụng được bộ nhớ trong của mỗi SDK, cũng như không đảm bảo sẽ hoạt động trong mọi tình huống hiện tại hoặc tương lai.

Đoạn mã sau đây minh họa cách sử dụng bộ nhớ trong SDK Thời gian chạy:

Java

public class SdkProviderImpl extends SandboxedSdkProvider {
    private SandboxedSdkContext mContext;

    @Override
    public void initSdk(SandboxedSdkContext sandboxedSdkContext, Bundle params,
            Executor executor, InitSdkCallback initSdkCallback) {
        // Store reference to the SandboxedSdkContext for later usage
        mContext = sandboxedSdkContext;
    }

    @Override
    public void onDataReceived(Bundle data, DataReceivedCallback callback) {
        // Use the SandboxedSdkContext to use storage
        final filename = "extraData";
        final String fileContents = data.getString("extraData", "");
        try (FileOutputStream fos = mContext.openFileOutput(filename, Context.MODE_PRIVATE)) {
            fos.write(fileContents.toByteArray());
            callback.onDataReceivedSuccess();
        } catch (Exception e) {
            callback.onDataReceivedError("Unable to process data.");
        }
    }
}

Kotlin

class SdkProviderImpl : SandboxedSdkProvider() {
    private lateinit var mContext: SandboxedSdkContext

    override fun initSdk(
        sandboxedSdkContext: SandboxedSdkContext, params: Bundle,
        executor: Executor, initSdkCallback: InitSdkCallback
    ) {
        // Store reference to the SandboxedContext for later usage
        mContext = sandboxedSdkContext;
    }

    override fun onDataReceived(data: Bundle, callback: DataReceivedCallback) {
        // Use the SandboxedSdkContext to use storage
        val filename = "myfile"
        val fileContents = data.getString("extraData", "")
        try {
            mContext.openFileOutput(filename, Context.MODE_PRIVATE).use {
                it.write(fileContents.toByteArray())
                callback.onDataReceivedSuccess()
        } catch (e: Exception) {
            callback.onDataReceivedError("Unable to process data.");
        }
    }
}

Cập nhật ứng dụng khách

Để gọi vào một SDK đang chạy trong SDK Thời gian chạy, hãy thực hiện những thay đổi sau đây đối với ứng dụng khách dùng để gọi:

  1. Trong hoạt động ứng dụng bao gồm một quảng cáo, hãy khai báo thông tin tham chiếu đến SdkSandboxManager, boolean để biết liệu SDK có được tải hay không và Đối tượng SurfaceView để hiển thị từ xa:

    Kotlin

    private lateinit var mSdkSandboxManager: SdkSandboxManager
    private lateinit var mClientView: SurfaceView
    private var mSdkLoaded = false
    
    companion object {
        private const val SDK_NAME = "com.example.privacysandbox.provider"
    }

    Java

    private static final String SDK_NAME = "com.example.privacysandbox.provider";
    
    private SdkSandboxManager mSdkSandboxManager;
    private SurfaceView mClientView;
    private boolean mSdkLoaded = false;
  2. Xác định một lớp gọi lại bằng cách triển khai LoadSdkCallback để tương tác với SDK trong thời gian chạy:

    Kotlin

    private inner class RemoteSdkCallbackImpl() : RemoteSdkCallback {
        override fun onLoadSdkSuccess(params: Bundle) {
            mSdkLoaded = true
        }
    
        override fun onLoadSdkFailure(errorCode: Int, errorMessage: String) {
            // log/show error
        }
    }
    

    Java

    private class RemoteSdkCallbackImpl implements RemoteSdkCallback {
        private RemoteSdkCallbackImpl() {}
    
        @Override
        public void onLoadSdkSuccess(Bundle params) {
            mSdkLoaded = true;
        }
    
        @Override
        public void onLoadSdkFailure(int errorCode, String errorMessage) {
            // log/show error
        }
    }
    

Để quay lại chế độ xem từ xa từ SDK trong thời gian chạy khi gọi requestSurfacePackage(), hãy xác định lớp gọi lại RequestSurfacePackageCallback:

Kotlin

private inner class RequestSurfacePackageCallbackImpl() : RequestSurfacePackageCallback {

    // Loads the remote view specified in the SDK's getView() method.
    override fun onSurfacePackageReady(
        surfacePackage: SurfacePackage,
        surfacePackageId: Int, params: Bundle
    ) {
        Handler(Looper.getMainLooper()).post {
            mClientView.setChildSurfacePackage(surfacePackage)
            mClientView.visibility = View.VISIBLE
        }
    }

    override fun onSurfacePackageError(errorCode: Int, errorMessage: String) {
        // log/show error
    }
}

Java

private class RequestSurfacePackageCallbackImpl implements RequestSurfacePackageCallback {

    // Loads the remote view specified in the SDK's getView() method.
    @Override
    public void onSurfacePackageReady(SurfacePackage surfacePackage,
            int surfacePackageId, Bundle params) {
        new Handler(Looper.getMainLooper()).post(() -> {
            mClientView.setChildSurfacePackage(surfacePackage);
            mClientView.setVisibility(View.VISIBLE);
        });
    }

    @Override
    public void onSurfacePackageError(int errorCode, String errorMessage) {
        // log/show error
    }
}

Để theo dõi trạng thái của sendData() và nhận lại mọi dữ liệu do SDK trả về, hãy xác định lớp gọi lại SendDataCallback.

Kotlin

private inner class SendDataCallbackImpl() : SendDataCallback {

    override fun onSendDataSuccess(
        params: Bundle
    ) {
        // Use the data returned by the SDK if required.
    }

    override fun onSendDataError(errorCode: Int, errorMessage: String) {
        // log/show error
    }
}

Java

private class SendDataCallbackImpl implements SendDataCallback {

    @Override
    public void onSendDataSuccess(Bundle params) {
        // Use the data returned by the SDK if required.
    }

    @Override
    public void onSendDataError(int errorCode, String errorMessage) {
        // log/show error
    }
}
  1. Trong onCreate(), hãy khởi tạo SdkSandboxManager, các lệnh gọi lại cần thiết, và sau đó đưa ra yêu cầu hiển thị chế độ xem từ xa:

    Kotlin

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mSdkSandboxManager = applicationContext.getSystemService(
                SdkSandboxManager::class.java
        )
    
        mClientView = findViewById(R.id.rendered_view)
        mClientView.setZOrderOnTop(true)
    
        val loadSdkCallback = LoadSdkCallbackImpl()
        mSdkSandboxManager.loadSdk(
                SDK_NAME, Bundle(), { obj: Runnable -> obj.run() }, loadSdkCallback
        )
    
        val sendDataCallback = SendDataCallback()
        // Send some data to the SDK if needed
        mSdkSandboxManager.sendData(SDK_NAME, Bundle(),{ obj: Runnable -> obj.run() }, sendDataCallback)
    
        val requestSurfacePackageCallback = RequestSurfacePackageCallback()
        Handler(Looper.getMainLooper()).post {
            val bundle = Bundle()
            mSdkSandboxManager.requestSurfacePackage(
                    SDK_NAME, display!!.displayId,
                    mClientView.width, mClientView.height,
                    bundle, requestSurfacePackageCallback
            )
        }
    }
    

    Java

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    
        mSdkSandboxManager = getApplicationContext().getSystemService(
                SdkSandboxManager.class);
    
        mClientView = findViewById(R.id.rendered_view);
        mClientView.setZOrderOnTop(true);
    
        LoadSdkCallbackImpl loadSdkCallback = new LoadSdkCallbackImpl();
        mSdkSandboxManager.loadSdk(
                SDK_NAME, new Bundle(), Runnable::run, loadSdkCallback);
    
        SendDataCallback sendDataCallback = new SendDataCallback();
        // Send some data to the SDK if needed
        mSdkSandboxManager.sendData(SDK_NAME, new Bundle(), Runnable::run, sendDataCallback);
    
        RequestSurfacePackageCallback requestSurfacePackageCallback = new RequestSurfacePackageCallback();
        new Handler(Looper.getMainLooper()).post(() -> {
            Bundle bundle = new Bundle();
            mSdkSandboxManager.requestSurfacePackage(
                    SDK_NAME, getDisplay().getDisplayId(),
                    mClientView.getWidth(), mClientView.getHeight(), bundle, requestSurfacePackageCallback);
        });
    }
    
  2. Chỉ định thông báo chứng nhận theo cách thủ công. Để tìm thông báo chứng chỉ của bạn, hãy trích xuất chứng chỉ đó từ tệp kho khóa gỡ lỗi bằng keytool. Mật khẩu mặc định là android.

    keytool -list -keystore ~/.android/debug.keystore
    
  3. Trong phần tử <application> trong tệp kê khai của ứng dụng, hãy thêm phần tử <uses-sdk-library>. Trong phần tử này, hãy đặt thuộc tính android:certDigest thành đầu ra từ bước trước đó:

    <application ...>
      <uses-sdk-library
          android:name="com.example.basicsandboxservice"
          android:versionMajor="1"
          android:certDigest="27:76:B1:2D:...:B1:BE:E0:28:5E" />
    </application>
    

    Lưu ý: Nếu bạn nhập giá trị không chính xác cho android:certDigest, thì lỗi sau đây sẽ xảy ra:

    Installation failed due to: 'Failed to commit install session \
    SESSION_ID with command cmd package install-commit SESSION_ID. \
    Error: INSTALL_FAILED_MISSING_SHARED_LIBRARY: Reconciliation \
    failed...: Reconcile failed: Package PACKAGE_NAME \
    requires differently signed sdk library; failing!'

Triển khai ứng dụng

Trước khi chạy ứng dụng, hãy cài đặt ứng dụng SDK và ứng dụng khách vào thiết bị thử nghiệm bằng cách sử dụng Android Studio hoặc dòng lệnh.

Triển khai thông qua Android Studio

Khi triển khai thông qua Android Studio, hãy hoàn thành các bước sau:

  1. Mở dự án Android Studio cho ứng dụng SDK của bạn.
  2. Chuyển đến Chạy > Chỉnh sửa cấu hình. Cửa sổ Cấu hình chạy/gỡ lỗi sẽ xuất hiện.
  3. Trong Tùy chọn khởi chạy, hãy đặt Chạy thành Không có, vì không có hoạt động nào để bắt đầu.
  4. Nhấp vào Áp dụng và sau đó nhấp vào OK.
  5. Nhấp vào Chạy để cài đặt ứng dụng SDK trên thiết bị thử nghiệm của bạn.
  6. Trong cửa sổ công cụ Dự án, hãy chuyển đến mô-đun ứng dụng ứng dụng.
  7. Chuyển đến Chạy > Chỉnh sửa cấu hình. Cửa sổ Cấu hình chạy/gỡ lỗi sẽ xuất hiện.
  8. Đặt Tùy chọn phát hành thành hoạt động chính của ứng dụng.
  9. Nhấp vào Áp dụng và sau đó nhấp vào OK.
  10. Nhấp vào Chạy để cài đặt ứng dụng đã cài đặt trên thiết bị thử nghiệm của bạn.

Triển khai trên dòng lệnh

Khi triển khai bằng dòng lệnh, hãy hoàn tất các bước trong danh sách sau. Phần này giả định rằng tên mô-đun ứng dụng SDK của bạn là sdk-app và tên của mô-đun ứng dụng khách là client-app.

  1. Triển khai ứng dụng SDK:

    ./gradlew sdk-app:installDebug
    
  2. Triển khai ứng dụng máy khách:

    ./gradlew client-app:installDebug && \
      # Start the app's activity. This example uses the sample app.
      adb shell am start -n \
      com.example.privacysandbox.client/com.example.client.MainActivity
    

Gỡ lỗi ứng dụng

Để gỡ lỗi ứng dụng máy khách, hãy nhấp vào nút Gỡ lỗi trong Android Studio.

Để gỡ lỗi ứng dụng SDK, hãy chuyển đến Chạy > Đính kèm quy trình. Thao tác này sẽ cho bạn thấy màn hình bật lên (hình 1). Đánh dấu vào hộp Hiển thị tất cả quy trình. Trong danh sách xuất hiện, hãy tìm quy trình có tên sdk_sandbox_CLIENT_APP_UID. Chọn tùy chọn này và thêm các điểm ngắt trong mã của ứng dụng SDK để bắt đầu gỡ lỗi SDK của bạn.

Quy trình ứng dụng SDK xuất hiện trong chế độ xem danh sách gần cuối
  hộp thoại
Hình 1. Màn hình Chọn quy trình để chọn ứng dụng SDK để gỡ lỗi

Các hạn chế

Để biết danh sách các tính năng đang tiến hành cho Thời gian chạy SDK, vui lòng xem ghi chú phát hành.

Mã mẫu

TrangKho lưu trữ API SDK Thời gian chạy và Quyền riêng tư trên GitHub chứa một tập hợp các dự án Android Studio riêng lẻ nhằm giúp bạn bắt đầu sử dụng, bao gồm cả các mẫu minh họa cách khởi chạy và gọi SDK Thời gian chạy.

Báo cáo lỗi và vấn đề

Ý kiến phản hồi của bạn có vai trò quan trọng trong Hộp cát về quyền riêng tư trên Android! Hãy cho chúng tôi biết mọi vấn đề bạn tìm thấy hoặc ý tưởng để cải thiện Hộp cát về quyền riêng tư trên Android.