Android Rust kalıpları

Bu sayfada Android Logging hakkında bilgiler yer alır, Rust AIDL örneği sunulur, C'den Rust'ı nasıl çağıracağınız anlatılır ve CXX kullanarak Rust/C++ birlikte çalışabilirliği ile ilgili talimatlar verilir.

Android günlük kaydı

Aşağıdaki örnekte, mesajları logcat (cihaz üzerinde) veya stdout (ana makine üzerinde) nasıl kaydedebileceğiniz gösterilmektedir.

Android.bp modülünüze liblogger ve liblog_rust bağımlılıklarını ekleyin:

rust_binary {
    name: "logging_test",
    srcs: ["src/main.rs"],
    rustlibs: [
        "liblogger",
        "liblog_rust",
    ],
}

Ardından, Rust kaynağınıza şu kodu ekleyin:

use log::{debug, error, LevelFilter};

fn main() {
    let _init_success = logger::init(
        logger::Config::default()
            .with_tag_on_device("mytag")
            .with_max_level(LevelFilter::Trace),
    );
    debug!("This is a debug message.");
    error!("Something went wrong!");
}

Yani, yukarıda gösterilen iki bağımlılığı (liblogger ve liblog_rust) ekleyin, init yöntemini bir kez çağırın (gerekirse birden fazla kez çağırabilirsiniz) ve sağlanan makroları kullanarak mesajları kaydedin. Olası yapılandırma seçeneklerinin listesi için logger crate'e bakın.

Logger sandığı, neyi günlüğe kaydetmek istediğinizi tanımlamak için bir API sağlar. Kodun cihazda mı yoksa ana makinede mi (ör. ana makine tarafı testinin bir parçası olarak) çalışmasına bağlı olarak mesajlar android_logger veya env_logger kullanılarak kaydedilir.

Rust AIDL örneği

Bu bölümde, Rust ile AIDL kullanımına ilişkin bir "Hello World" tarzı örnek verilmektedir.

Başlangıç noktası olarak Android Geliştirici Kılavuzu'ndaki AIDL'ye Genel Bakış bölümünü kullanarak external/rust/binder_example/aidl/com/example/android/IRemoteService.aidl oluşturun. IRemoteService.aidl dosyasında aşağıdaki içerikler yer almalıdır:

// IRemoteService.aidl
package com.example.android;

// Declare any non-default types here with import statements

/** Example service interface */
interface IRemoteService {
    /** Request the process ID of this service, to do evil things with it. */
    int getPid();

    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}

Ardından, external/rust/binder_example/aidl/Android.bp dosyasında aidl_interface modülünü tanımlayın. Varsayılan olarak etkinleştirilmediğinden Rust arka ucunu açıkça etkinleştirmeniz gerekir.

aidl_interface {
    name: "com.example.android.remoteservice",
    srcs: [ "aidl/com/example/android/*.aidl", ],
    unstable: true, // Add during development until the interface is stabilized.
    backend: {
        rust: {
            // By default, the Rust backend is not enabled
            enabled: true,
        },
    },
}

AIDL arka ucu, Rust kaynak oluşturucudur. Bu nedenle, diğer Rust kaynak oluşturucular gibi çalışır ve bir Rust kitaplığı oluşturur. Oluşturulan Rust kitaplık modülü, diğer Rust modülleri tarafından bağımlılık olarak kullanılabilir. Üretilen kitaplığı bağımlılık olarak kullanma örneği olarak, rust_library, external/rust/binder_example/Android.bp içinde aşağıdaki gibi tanımlanabilir:

rust_library {
    name: "libmyservice",
    srcs: ["src/lib.rs"],
    crate_name: "myservice",
    rustlibs: [
        "com.example.android.remoteservice-rust",
        "libbinder_rs",
    ],
}

rustlibs içinde kullanılan AIDL tarafından oluşturulan kitaplığın modül adı biçiminin, aidl_interface modül adından sonra -rust ile ayrılmış com.example.android.remoteservice-rust olduğunu unutmayın.

AIDL arayüzüne daha sonra src/lib.rs içinde aşağıdaki şekilde referansta bulunulabilir:

// Note carefully the AIDL crates structure:
// * the AIDL module name: "com_example_android_remoteservice"
// * next "::aidl"
// * next the AIDL package name "::com::example::android"
// * the interface: "::IRemoteService"
// * finally, the 'BnRemoteService' and 'IRemoteService' submodules

//! This module implements the IRemoteService AIDL interface
use com_example_android_remoteservice::aidl::com::example::android::{
  IRemoteService::{BnRemoteService, IRemoteService}
};
use binder::{
    BinderFeatures, Interface, Result as BinderResult, Strong,
};

/// This struct is defined to implement IRemoteService AIDL interface.
pub struct MyService;

impl Interface for MyService {}

impl IRemoteService for MyService {
    fn getPid(&self) -> BinderResult<i32> {
        Ok(42)
    }

    fn basicTypes(&self, _: i32, _: i64, _: bool, _: f32, _: f64, _: &str) -> BinderResult<()> {
        // Do something interesting...
        Ok(())
    }
}

Son olarak, hizmeti aşağıda gösterildiği gibi bir Rust ikilisinde başlatın:

use myservice::MyService;

fn main() {
    // [...]
    let my_service = MyService;
    let my_service_binder = BnRemoteService::new_binder(
        my_service,
        BinderFeatures::default(),
    );
    binder::add_service("myservice", my_service_binder.as_binder())
        .expect("Failed to register service?");
    // Does not return - spawn or perform any work you mean to do before this call.
    binder::ProcessState::join_thread_pool()
}

Eş zamansız Rust AIDL örneği

Bu bölümde, AIDL'nin asenkron Rust ile kullanımına ilişkin "Hello World" tarzında bir örnek verilmektedir.

RemoteService örneğinden devam edersek oluşturulan AIDL arka uç kitaplığı, RemoteService AIDL arayüzü için eşzamansız bir sunucu uygulaması oluşturmak üzere kullanılabilecek eşzamansız arayüzler içerir.

Oluşturulan asenkron sunucu arayüzü IRemoteServiceAsyncServer aşağıdaki gibi uygulanabilir:

use com_example_android_remoteservice::aidl::com::example::android::IRemoteService::{
    BnRemoteService, IRemoteServiceAsyncServer,
};
use binder::{BinderFeatures, Interface, Result as BinderResult};

/// This struct is defined to implement IRemoteServiceAsyncServer AIDL interface.
pub struct MyAsyncService;

impl Interface for MyAsyncService {}

#[async_trait]
impl IRemoteServiceAsyncServer for MyAsyncService {
    async fn getPid(&self) -> BinderResult<i32> {
        //Do something interesting...
        Ok(42)
    }

    async fn basicTypes(&self, _: i32, _: i64, _: bool, _: f32, _: f64,_: &str,) -> BinderResult<()> {
        //Do something interesting...
        Ok(())
    }
}

Asenkron sunucu uygulaması aşağıdaki gibi başlatılabilir:

#[tokio::main(flavor = "multi_thread", worker_threads = 2)]
async fn main() {
    binder::ProcessState::start_thread_pool();

    let my_service = MyAsyncService;
    let my_service_binder = BnRemoteService::new_async_binder(
        my_service,
        TokioRuntime(Handle::current()),
        BinderFeatures::default(),
    );

    binder::add_service("myservice", my_service_binder.as_binder())
        .expect("Failed to register service?");

    task::block_in_place(move || {
        binder::ProcessState::join_thread_pool();
    });
}

join_thread_pool'ın dahili olarak block_on kullanmasına olanak tanıyan eşzamansız bağlamdan çıkmak için block_in_place'in gerekli olduğunu unutmayın. Bunun nedeni, #[tokio::main]'nın kodu block_on çağrısı içinde sarmalaması ve join_thread_pool'ın gelen bir işlemi işlerken block_on'ı çağırmasıdır. block_on içinden block_on çağırmak panik durumuna neden oluyor. Bu durum, #[tokio::main] yerine tokio çalışma zamanı manuel olarak oluşturularak ve ardından join_thread_pool, block_on yöntemi dışında çağrılarak da önlenebilir.

Ayrıca, Rust arka ucu tarafından oluşturulan kitaplık, RemoteService için IRemoteServiceAsync eşzamansız istemcisinin uygulanmasına olanak tanıyan bir arayüz içerir. Bu arayüz aşağıdaki gibi uygulanabilir:

use com_example_android_remoteservice::aidl::com::example::android::IRemoteService::IRemoteServiceAsync;
use binder_tokio::Tokio;

#[tokio::main(flavor = "current_thread")]
async fn main() {
    let binder_service = binder_tokio::wait_for_interface::<dyn IRemoteServiceAsync<Tokio>>("myservice");

    let my_client = binder_service.await.expect("Cannot find Remote Service");

    let result = my_client.getPid().await;

    match result {
        Err(err) => panic!("Cannot get the process id from Remote Service {:?}", err),
        Ok(p_id) => println!("PID = {}", p_id),
    }
}

C'den Rust'ı çağırma

Bu örnekte, C'den Rust'ın nasıl çağrılacağı gösterilmektedir.

Örnek Rust kitaplığı

libsimple_printer dosyasını external/rust/simple_printer/libsimple_printer.rs içinde aşağıdaki şekilde tanımlayın:

//! A simple hello world example that can be called from C

#[no_mangle]
/// Print "Hello Rust!"
pub extern fn print_c_hello_rust() {
    println!("Hello Rust!");
}

Rust kitaplığı, bağımlı C modüllerinin çekebileceği başlıkları tanımlamalıdır. Bu nedenle, external/rust/simple_printer/simple_printer.h başlığını aşağıdaki gibi tanımlayın:

#ifndef SIMPLE_PRINTER_H
#define SIMPLE_PRINTER_H

void print_c_hello_rust();


#endif

external/rust/simple_printer/Android.bp öğesini burada gördüğünüz gibi tanımlayın:

rust_ffi {
    name: "libsimple_c_printer",
    crate_name: "simple_c_printer",
    srcs: ["libsimple_c_printer.rs"],

    // Define export_include_dirs so cc_binary knows where the headers are.
    export_include_dirs: ["."],
}

Örnek C ikilisi

external/rust/c_hello_rust/main.c öğesini aşağıdaki şekilde tanımlayın:

#include "simple_printer.h"

int main() {
  print_c_hello_rust();
  return 0;
}

external/rust/c_hello_rust/Android.bp öğesini aşağıdaki şekilde tanımlayın:

cc_binary {
    name: "c_hello_rust",
    srcs: ["main.c"],
    shared_libs: ["libsimple_c_printer"],
}

Son olarak, m c_hello_rust numaralı telefonu arayarak oluşturun.

Rust-Java birlikte çalışabilirlik

jni sandığı, Java Native Interface (JNI) aracılığıyla Java ile Rust birlikte çalışabilirliği sağlar. Rust'ın doğrudan Java'nın JNI'sine (JNIEnv, JClass, JString vb.) bağlanan bir Rust cdylib kitaplığı oluşturması için gerekli tür tanımlarını belirler. cxx aracılığıyla kod oluşturma işlemi gerçekleştiren C++ bağlamalarının aksine, JNI aracılığıyla Java birlikte çalışabilirliği, derleme sırasında kod oluşturma adımı gerektirmez. Bu nedenle özel bir derleme sistemi desteği gerekmez. Java kodu, Rust tarafından sağlanan cdylib öğesini diğer yerel kitaplıklar gibi yükler.

Kullanım

Hem Rust hem de Java kodundaki kullanım, jni crate belgelerinde açıklanmıştır. Lütfen Başlangıç bölümünde verilen örneği uygulayın. src/lib.rs yazdıktan sonra, Android'in derleme sistemiyle kitaplığı nasıl oluşturacağınızı öğrenmek için bu sayfaya dönün.

Derleme tanımı

Java, Rust kitaplığının dinamik olarak yüklenebilmesi için cdylib olarak sağlanmasını gerektirir. Soong'daki Rust kitaplığı tanımı şöyledir:

rust_ffi_shared {
    name: "libhello_jni",
    crate_name: "hello_jni",
    srcs: ["src/lib.rs"],

    // The jni crate is required
    rustlibs: ["libjni"],
}

Java kitaplığı, Rust kitaplığını required bağımlılığı olarak listeler. Bu, derleme zamanı bağımlılığı olmasa da Java kitaplığıyla birlikte cihaza yüklenmesini sağlar:

java_library {
        name: "libhelloworld",
        [...]
        required: ["libhellorust"]
        [...]
}

Alternatif olarak, Rust kitaplığını bir AndroidManifest.xml dosyasına eklemeniz gerekiyorsa kitaplığı uses_libs dosyasına aşağıdaki şekilde ekleyin:

java_library {
        name: "libhelloworld",
        [...]
        uses_libs: ["libhellorust"]
        [...]
}

CXX kullanarak Rust ile C++ arasında birlikte çalışabilirlik

CXX sandığı, Rust ile C++'ın bir alt kümesi arasında güvenli FFI sağlar. CXX belgelerinde, genel olarak nasıl çalıştığına dair iyi örnekler verilmektedir. Kitaplığa ve C++ ile Rust arasındaki köprüye aşina olmak için önce bu belgeleri okumanızı öneririz. Aşağıdaki örnekte, Android'de nasıl kullanılacağı gösterilmektedir.

CXX'in, Rust'ın çağırdığı C++ kodunu oluşturması için CXX'i çağırmak üzere bir genrule ve bunu bir kitaplıkta paketlemek için bir cc_library_static tanımlayın. C++'ın Rust kodunu çağırmasını veya C++ ile Rust arasında paylaşılan türleri kullanmayı planlıyorsanız ikinci bir genrule tanımlayın (Rust bağlamalarını içeren bir C++ üstbilgisi oluşturmak için).

cc_library_static {
    name: "libcxx_test_cpp",
    srcs: ["cxx_test.cpp"],
    generated_headers: [
        "cxx-bridge-header",
        "libcxx_test_bridge_header"
    ],
    generated_sources: ["libcxx_test_bridge_code"],
}

// Generate the C++ code that Rust calls into.
genrule {
    name: "libcxx_test_bridge_code",
    tools: ["cxxbridge"],
    cmd: "$(location cxxbridge) $(in) > $(out)",
    srcs: ["lib.rs"],
    out: ["libcxx_test_cxx_generated.cc"],
}

// Generate a C++ header containing the C++ bindings
// to the Rust exported functions in lib.rs.
genrule {
    name: "libcxx_test_bridge_header",
    tools: ["cxxbridge"],
    cmd: "$(location cxxbridge) $(in) --header > $(out)",
    srcs: ["lib.rs"],
    out: ["lib.rs.h"],
}

Köprünün C++ tarafını oluşturmak için yukarıda cxxbridge aracı kullanılmıştır. Ardından, Rust yürütülebilir dosyamız için bağımlılık olarak libcxx_test_cpp statik kitaplığı kullanılır:

rust_binary {
    name: "cxx_test",
    srcs: ["lib.rs"],
    rustlibs: ["libcxx"],
    static_libs: ["libcxx_test_cpp"],
}

.cpp ve .hpp dosyalarında, C++ işlevlerini istediğiniz gibi tanımlayın. İstediğiniz gibi CXX sarmalayıcı türlerini kullanın. Örneğin, bir cxx_test.hpp tanımı şunları içerir:

#pragma once

#include "rust/cxx.h"
#include "lib.rs.h"

int greet(rust::Str greetee);

cxx_test.cpp şunları içerir:

#include "cxx_test.hpp"
#include "lib.rs.h"

#include <iostream>

int greet(rust::Str greetee) {
  std::cout << "Hello, " << greetee << std::endl;
  return get_num();
}

Bunu Rust'tan kullanmak için lib.rs içinde aşağıdaki gibi bir CXX köprüsü tanımlayın:

#[cxx::bridge]
mod ffi {
    unsafe extern "C++" {
        include!("cxx_test.hpp");
        fn greet(greetee: &str) -> i32;
    }
    extern "Rust" {
        fn get_num() -> i32;
    }
}

fn main() {
    let result = ffi::greet("world");
    println!("C++ returned {}", result);
}

fn get_num() -> i32 {
    return 42;
}