Ta strona zawiera informacje o logowaniu w Androidzie, przykład Rust AIDL, instrukcje dotyczące wywołania Rust z C oraz instrukcje dotyczące interoperacyjności Rust/C++ za pomocą CXX.
Logowanie w Androidzie
Ten przykład pokazuje, jak rejestrować wiadomości do logcat
(na urządzeniu) lub stdout
(na hoście).
W module Android.bp
dodaj jako zależności pliki liblogger
i liblog_rust
:
rust_binary {
name: "logging_test",
srcs: ["src/main.rs"],
rustlibs: [
"liblogger",
"liblog_rust",
],
}
Następnie w źródle Rust dodaj ten kod:
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!");
}
Oznacza to, że należy dodać 2 zależności widoczne powyżej (liblogger
i liblog_rust
), wywołać metodę init
raz (w razie potrzeby można ją wywołać więcej niż raz) oraz rejestrować wiadomości za pomocą podanych makro. Listę możliwych opcji konfiguracji znajdziesz w modułu logger crate.
Zbiór logger udostępnia interfejs API do definiowania tego, co chcesz rejestrować. W zależności od tego, czy kod jest uruchamiany na urządzeniu, czy na hoście (np. w ramach testu po stronie hosta), wiadomości są rejestrowane za pomocą funkcji android_logger lub env_logger.
Przykład Rust AIDL
Ta sekcja zawiera przykład użycia AIDL w Rust w stylu Hello World.
Na podstawie sekcji Przegląd AIDL w Przewodniku dla deweloperów Androida utwórz plik external/rust/binder_example/aidl/com/example/android/IRemoteService.aidl
z treścią:IRemoteService.aidl
// 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);
}
Następnie w pliku external/rust/binder_example/aidl/Android.bp
zdefiniuj moduł aidl_interface
. Musisz wyraźnie włączyć backend Rust, ponieważ nie jest on domyślnie włączony.
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,
},
},
}
Backend AIDL to generator kodu źródłowego Rust, więc działa jak inne generatory kodu źródłowego Rust i tworzy bibliotekę Rust. Wygenerowany moduł biblioteki Rust może być używany przez inne moduły Rust jako zależność. Przykład użycia wygenerowanej biblioteki jako zależności: w funkcji rust_library
można zdefiniować external/rust/binder_example/Android.bp
w ten sposób:
rust_library {
name: "libmyservice",
srcs: ["src/lib.rs"],
crate_name: "myservice",
rustlibs: [
"com.example.android.remoteservice-rust",
"libbinder_rs",
],
}
Pamiętaj, że format nazwy modułu biblioteki wygenerowanej za pomocą AIDL używanej w rustlibs
to nazwa modułu aidl_interface
, po której następuje -rust
. W tym przypadku jest to com.example.android.remoteservice-rust
.
Do interfejsu AIDL można się odwoływać w pliku src/lib.rs
w ten sposób:
// 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 {
Ok(42)
}
fn basicTypes(&self, _: i32, _: i64, _: bool, _: f32, _: f64, _: &str) -> BinderResult<()> {
// Do something interesting...
Ok(())
}
}
Na koniec uruchom usługę w pliku binarnym Rust, jak pokazano poniżej:
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()
}
Przykład asynchronicznego Rust AIDL
W tej sekcji znajdziesz przykład użycia AIDL w stylu Hello World z asynchronicznym Rustem.
Wracając do przykładu RemoteService
, wygenerowana biblioteka backendowa AIDL zawiera interfejsy asynchroniczne, które można wykorzystać do implementacji asynchronicznej implementacji serwera dla interfejsu AIDL RemoteService
.
Wygenerowany interfejs serwera asynchronicznego IRemoteServiceAsyncServer
można zaimplementować w ten sposób:
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 {
//Do something interesting...
Ok(42)
}
async fn basicTypes(&self, _: i32, _: i64, _: bool, _: f32, _: f64,_: &str,) -> BinderResult<()> {
//Do something interesting...
Ok(())
}
}
Implementację serwera asynchronicznego można uruchomić w ten sposób:
#[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();
});
}
Pamiętaj, że funkcja block_in_place jest potrzebna, aby pozostawić kontekst asynchroniczny, który pozwala funkcji join_thread_pool
używać wewnętrznie funkcji block_on. Dzieje się tak, ponieważ #[tokio::main]
otacza kod wywołaniem funkcji block_on
, a join_thread_pool
może wywoływać block_on
podczas obsługi przychodzącej transakcji. Wywołanie funkcji block_on
w ramach block_on
powoduje panikę. Można tego uniknąć, budując środowisko wykonawcze Tokio ręcznie zamiast używać #[tokio::main]
, a potem wywoływać join_thread_pool
poza metodą block_on
.
Ponadto biblioteka wygenerowana przez backend Rust zawiera interfejs, który umożliwia implementację klienta asynchronicznego IRemoteServiceAsync
dla RemoteService
. Można go zaimplementować w ten sposób:
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 >("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),
}
}
Wykonywanie funkcji Rust z poziomu C
Ten przykład pokazuje, jak wywołać Rust z C.
Przykładowa biblioteka Rust
Zdefiniuj plik libsimple_printer
w pliku external/rust/simple_printer/libsimple_printer.rs
w ten sposób:
//! 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!");
}
Biblioteka Rust musi zdefiniować nagłówki, które mogą pobrać zależne moduły C, więc zdefiniuj nagłówek external/rust/simple_printer/simple_printer.h
w ten sposób:
#ifndef SIMPLE_PRINTER_H
#define SIMPLE_PRINTER_H
void print_c_hello_rust();
#endif
Określ external/rust/simple_printer/Android.bp
w ten sposób:
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: ["."],
}
Przykładowy plik binarne C
Zdefiniuj external/rust/c_hello_rust/main.c
w ten sposób:
#include "simple_printer.h"
int main() {
print_c_hello_rust();
return 0;
}
Zdefiniuj external/rust/c_hello_rust/Android.bp
w ten sposób:
cc_binary {
name: "c_hello_rust",
srcs: ["main.c"],
shared_libs: ["libsimple_c_printer"],
}
Na koniec utwórz projekt, wywołując funkcję m c_hello_rust
.
Współdziałanie Rust-Java
Pakiet jni
zapewnia interoperacyjność Rust z Javą za pomocą interfejsu Java Native (JNI). Określa ona niezbędne definicje typów dla Rusta, aby wygenerować bibliotekę Rust cdylib
, która łączy się bezpośrednio z JNI w Javie (JNIEnv
, JClass
,
JString
itd.). W odróżnieniu od połączeń C++, które wykonują generowanie kodu za pomocą cxx
, współdziałanie Java za pomocą JNI nie wymaga generowania kodu podczas kompilacji. Dlatego nie wymaga specjalnego wsparcia systemu kompilacji. Kod Java wczytuje cdylib
udostępniony przez Rust, tak jak każdą inną natywną bibliotekę.
Wykorzystanie
Zastosowanie w kodzie Rust i Java jest opisane w dokumentacji jni
crate. Postępuj zgodnie z podanym tam opisem procesu rozpoczęcia. Po napisaniu src/lib.rs
wróć na tę stronę, aby dowiedzieć się, jak skompilować bibliotekę za pomocą systemu kompilacji Androida.
Definicja kompilacji
Java wymaga, aby biblioteka Rust była dostarczana jako cdylib
, aby można było ją wczytać dynamicznie. Definicja biblioteki Rust w Soong:
rust_ffi_shared {
name: "libhello_jni",
crate_name: "hello_jni",
srcs: ["src/lib.rs"],
// The jni crate is required
rustlibs: ["libjni"],
}
Biblioteka Java wymienia bibliotekę Rust jako zależność required
. Dzięki temu zostanie ona zainstalowana na urządzeniu wraz z biblioteką Java, mimo że nie jest zależnością w czasie kompilacji:
java_library {
name: "libhelloworld",
[...]
required: ["libhellorust"]
[...]
}
Jeśli musisz uwzględnić bibliotekę Rust w pliku AndroidManifest.xml
, dodaj ją do pliku uses_libs
w ten sposób:
java_library {
name: "libhelloworld",
[...]
uses_libs: ["libhellorust"]
[...]
}
Współpraca Rust–C++ za pomocą CXX
Pakiet CXX zapewnia bezpieczne FFI między Rustem a podzbiorem C++. Dokumentacja CXX zawiera dobre przykłady ogólnego działania tego pakietu. Zalecamy zapoznanie się z nią, aby poznać bibliotekę i sposób, w jaki łączy ona C++ z Rustem. Poniższy przykład pokazuje, jak z niego korzystać na urządzeniu z Androidem.
Aby CXX wygenerował kod C++, który jest wywoływany przez Rust, zdefiniuj genrule
, aby wywołać CXX, oraz cc_library_static
, aby utworzyć z niego bibliotekę. Jeśli planujesz wywoływać kod Rust z poziomu kodu C++, lub używać typów wspólnych dla C++ i Rust, zdefiniuj drugą regułę generowania (aby wygenerować nagłówek C++, który zawiera powiązania Rust).
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"],
}
Narzędzia cxxbridge
używa się powyżej do wygenerowania strony C++ mostu. Biblioteka statyczna libcxx_test_cpp
jest następnie używana jako zależność naszego wykonywalnego pliku Rust:
rust_binary {
name: "cxx_test",
srcs: ["lib.rs"],
rustlibs: ["libcxx"],
static_libs: ["libcxx_test_cpp"],
}
W plikach .cpp
i .hpp
zdefiniuj funkcje C++, używając typów opakowania CXX.
Definicja cxx_test.hpp
zawiera na przykład:
#pragma once
#include "rust/cxx.h"
#include "lib.rs.h"
int greet(rust::Str greetee);
Podczas gdy cxx_test.cpp
zawiera
#include "cxx_test.hpp"
#include "lib.rs.h"
#include
int greet(rust::Str greetee) {
std::cout << "Hello, " << greetee << std::endl;
return get_num();
}
Aby użyć tego w Rust, zdefiniuj most CXX w pliku lib.rs
w ten sposób:
#[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;
}