Na tej stronie znajdziesz informacje o logowaniu w Androidzie, przykład AIDL w Rust, instrukcje wywoływania kodu Rust z C oraz instrukcje dotyczące współdziałania Rust i C++ za pomocą CXX.
Logowanie na Androidzie
Poniższy przykład pokazuje, jak rejestrować wiadomości w logcat
(na urządzeniu) lub stdout
(na hoście).
W module Android.bp
dodaj liblogger
i liblog_rust
jako zależności:
rust_binary {
name: "logging_test",
srcs: ["src/main.rs"],
rustlibs: [
"liblogger",
"liblog_rust",
],
}
Następnie dodaj ten kod do źródła Rust:
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 musisz dodać 2 zależności pokazane powyżej (liblogger
i liblog_rust
), wywołać metodę init
raz (w razie potrzeby możesz wywołać ją więcej razy) i rejestrować wiadomości za pomocą podanych makr. Listę możliwych opcji konfiguracji znajdziesz w logger crate.
Pakiet logger udostępnia interfejs API do określania, 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 przy użyciu funkcji android_logger lub env_logger.
Przykład AIDL w Rust
W tej sekcji znajdziesz przykład użycia AIDL z Rust w stylu „Hello World”.
Zacznij od sekcji AIDL Overview (Omówienie AIDL) w przewodniku dla deweloperów Androida i utwórz plik external/rust/binder_example/aidl/com/example/android/IRemoteService.aidl
z następującą treś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 w Rust, więc działa jak inne generatory kodu źródłowego w Rust i tworzy bibliotekę w Rust. Wygenerowany moduł biblioteki Rust może być używany przez inne moduły Rust jako zależność. Jako przykład użycia wygenerowanej biblioteki jako zależności w pliku external/rust/binder_example/Android.bp
można zdefiniować rust_library
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 w przypadku biblioteki wygenerowanej przez AIDL używanej w rustlibs
to aidl_interface
nazwa modułu, po której następuje -rust
; w tym przypadku jest to com.example.android.remoteservice-rust
.
Do interfejsu AIDL można się odwołać w 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 binarnym pliku 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 AIDL w Rust
W tej sekcji znajdziesz przykład użycia AIDL z asynchronicznym Rustem w stylu „Hello World”.
W przypadku przykładu RemoteService
wygenerowana biblioteka backendu AIDL zawiera interfejsy asynchroniczne, których można użyć do wdrożenia asynchronicznego serwera dla interfejsu AIDL RemoteService
.
Wygenerowany asynchroniczny interfejs serwera 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<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 block_in_place jest potrzebny do opuszczenia kontekstu asynchronicznego, co pozwala join_thread_pool
na wewnętrzne użycie 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 transakcji przychodzącej. Dzwonienie do block_on
z poziomu block_on
wywołuje panikę. Można tego uniknąć, ręcznie tworząc środowisko wykonawcze tokio zamiast używać #[tokio::main]
, a następnie wywołując join_thread_pool
poza metodą block_on
.
Biblioteka wygenerowana przez backend Rust zawiera interfejs, który umożliwia implementację klienta asynchronicznego IRemoteServiceAsync
dla RemoteService
. Można to zrobić 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),
}
}
Wywoływanie kodu w Rust z C
Ten przykład pokazuje, jak wywołać kod Rust z C.
Przykładowa biblioteka Rust
Zdefiniuj plik libsimple_printer
wexternal/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 definiować nagłówki, które mogą być używane przez 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
Zdefiniuj external/rust/simple_printer/Android.bp
w sposób widoczny poniżej:
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 C w formacie binarnym
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 polecenie m c_hello_rust
.
Współdziałanie języków Rust i Java
Pakiet jni
zapewnia interoperacyjność Rusta z Javą za pomocą interfejsu Java Native Interface (JNI). Określa niezbędne definicje typów dla Rusta, aby wygenerować bibliotekę Rusta cdylib
, która jest bezpośrednio podłączana do JNI Javy (JNIEnv
, JClass
, JString
itp.). W odróżnieniu od powiązań C++, które wykonują generowanie kodu za pomocą cxx
, interoperacyjność Javy za pomocą JNI nie wymaga kroku generowania kodu podczas kompilacji. Dlatego nie wymaga specjalnej obsługi przez system kompilacji. Kod Java
wczytuje cdylib
dostarczony przez Rusta jak każdą inną bibliotekę natywną.
Wykorzystanie
Użycie w kodzie Rust i Java jest opisane w jni
dokumentacji pakietu. Postępuj zgodnie z przykładem Wprowadzenie. Po napisaniu src/lib.rs
wróć na tę stronę, aby dowiedzieć się, jak utworzyć bibliotekę za pomocą systemu kompilacji Androida.
Definicja kompilacji
Java wymaga, aby biblioteka Rust była udostępniana jako cdylib
, dzięki czemu można ją dynamicznie wczytywać. Definicja biblioteki Rust w Soong wygląda tak:
rust_ffi_shared {
name: "libhello_jni",
crate_name: "hello_jni",
srcs: ["src/lib.rs"],
// The jni crate is required
rustlibs: ["libjni"],
}
Biblioteka Java zawiera bibliotekę Rust jako zależność required
. Dzięki temu jest ona instalowana na urządzeniu razem z biblioteką Java, mimo że nie jest zależnością w czasie kompilacji:
java_library {
name: "libhelloworld",
[...]
required: ["libhellorust"]
[...]
}
Jeśli musisz dołączyć bibliotekę Rust w pliku AndroidManifest.xml
, dodaj ją do uses_libs
w ten sposób:
java_library {
name: "libhelloworld",
[...]
uses_libs: ["libhellorust"]
[...]
}
Współdziałanie Rust–C++ za pomocą CXX
Pakiet CXX zapewnia bezpieczny interfejs FFI między Rustem a podzbiorem C++. Dokumentacja CXX zawiera dobre przykłady jego działania. Zalecamy najpierw zapoznać się z nią, aby poznać bibliotekę i sposób, w jaki łączy ona C++ i Rust. Poniższy przykład pokazuje, jak używać tego interfejsu w Androidzie.
Aby CXX wygenerował kod C++, do którego wywołania Rust używa, zdefiniuj genrule
, aby wywołać CXX, i cc_library_static
, aby połączyć to w bibliotekę. Jeśli planujesz, że kod C++ będzie wywoływać kod Rust lub używać typów wspólnych dla C++ i Rust, zdefiniuj drugą regułę genrule (aby wygenerować nagłówek C++ zawierający 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"],
}
Powyżej użyto cxxbridge
narzędzia do wygenerowania części mostu w C++. Następnie używamy libcxx_test_cpp
biblioteki statycznej jako zależności 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++ w dowolny sposób, używając w razie potrzeby typów opakowania CXX.
Na przykład definicja cxx_test.hpp
zawiera te elementy:
#pragma once
#include "rust/cxx.h"
#include "lib.rs.h"
int greet(rust::Str greetee);
Gdy 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ć tej funkcji w Rust, zdefiniuj most CXX w sposób podany poniżej w 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;
}