Ta strona zawiera informacje o logowaniu w Androidzie, przykład pliku AIDL w Rust, instrukcje dotyczące wywołania Rust z języka C oraz instrukcje dotyczące interoperacyjności Rust/C++ za pomocą CXX.
Logowanie w Androidzie
Poniższy przykład pokazuje, jak zarejestrować wiadomości w logcat
(na urządzeniu) lub
stdout
(na serwerze).
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. Zobacz
skrzynka na dziennik
, aby zobaczyć listę możliwych opcji konfiguracji.
Skrzynia z rejestratorem udostępnia interfejs API, który określa, co ma być zapisywane. W zależności od czy kod działa na urządzeniu czy na hoście (np. testu po stronie hosta), wiadomości są rejestrowane za pomocą tagu android_logger. lub env_logger.
Przykład Rust AIDL
Ta sekcja zawiera przykład użycia AIDL w stylu Hello World w środowisku Rust.
Korzystanie z Przewodnika dla programistów aplikacji na Androida Omówienie AIDL
jako punktu wyjścia, utwórz external/rust/binder_example/aidl/com/example/android/IRemoteService.aidl
z tą zawartością w pliku 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. Wytworzony moduł biblioteki Rust można
używane przez inne moduły Rusta jako zależność. Jako przykład wykorzystania wygenerowanego
jako zależność, funkcję rust_library
można zdefiniować w ten sposób
external/rust/binder_example/Android.bp
:
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<i32> {
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
Ta sekcja zawiera przykład użycia AIDL w stylu Hello World z asynchronicznym systemem Rust.
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że być
są zaimplementowane w następujący 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<i32> {
//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 parametr
block_in_place
jest konieczne, aby opuścić kontekst asynchroniczny, który umożliwia usłudze join_thread_pool
korzystanie z
block_on. Wynika to z faktu, że kod #[tokio::main]
opakowuje kod
w trakcie połączenia z numerem block_on
, a join_thread_pool
może zadzwonić
block_on
podczas obsługi transakcji przychodzącej. Wywołuję
block_on
z poziomu block_on
wywołuje 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<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),
}
}
Zadzwoń do Rust z C
Ten przykład pokazuje, jak zadzwonić do Rusta z aplikacji 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 określać nagłówki, które zależne moduły C mogą pobierać,
więc zdefiniuj nagłówek external/rust/simple_printer/simple_printer.h
w taki sposób:
#ifndef SIMPLE_PRINTER_H
#define SIMPLE_PRINTER_H
void print_c_hello_rust();
#endif
Podaj definicję słowa external/rust/simple_printer/Android.bp
tak jak tutaj:
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ład pliku binarnego 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 kompiluj, wywołując funkcję m c_hello_rust
.
Interoperacyjność Rust-Java
Kontener jni
zapewnia interoperacyjność platformy Rust z Javą dzięki systemowi natywnemu w Javie
Interfejs (JNI). Określa ona niezbędne definicje typów w Rust, 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. Gdy napiszesz „src/lib.rs
”, wróć na tę stronę, aby:
dowiedz się, jak utworzyć bibliotekę w systemie kompilacji Androida.
Definicja kompilacji
Java wymaga udostępnienia biblioteki Rust jako biblioteki cdylib
, aby można było ją
ładowane 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"]
[...]
}
Interoperacyjność Rust–C++ wykorzystująca 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. Ten przykład pokazuje, jak używać tego narzędzia na Androidzie.
Aby umożliwić CXX wygenerowanie kodu C++ używanego przez Rust, zdefiniuj genrule
do
wywołaj CXX i cc_library_static
, by połączyć to w 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. libcxx_test_cpp
Biblioteka statyczna jest używana jako zależność dla naszego pliku wykonywalnego Rust:
rust_binary {
name: "cxx_test",
srcs: ["lib.rs"],
rustlibs: ["libcxx"],
static_libs: ["libcxx_test_cpp"],
}
W plikach .cpp
i .hpp
zdefiniuj funkcje C++ według własnego uznania,
za pomocą typów otoki CXX.
Definicja cxx_test.hpp
zawiera na przykład:
#pragma once
#include "rust/cxx.h"
#include "lib.rs.h"
int greet(rust::Str greetee);
Chociaż cxx_test.cpp
zawiera
#include "cxx_test.hpp"
#include "lib.rs.h"
#include <iostream>
int greet(rust::Str greetee) {
std::cout << "Hello, " << greetee << std::endl;
return get_num();
}
Aby użyć tego z platformy Rust, zdefiniuj most CXX w sposób opisany poniżej w narzędziu lib.rs
:
#[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;
}