Ta strona zawiera informacje na temat rejestrowania w Androidzie, przykład Rust AIDL, wyjaśnia, Zadzwoń do Rust z C i podaje instrukcje dla współpracy Rust/C++ z 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 liblogger
i liblog_rust
jako zależności:
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!");
}
Musisz dodać 2 zależności wymienione powyżej (liblogger
i liblog_rust
):
wywołaj metodę init
raz (w razie potrzeby możesz ją wywołać więcej niż raz) i
komunikatów logu za pomocą dostarczonych makr. 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
aidl_interface
. Musisz jednoznacznie włączyć backend Rust, ponieważ
nie jest domyślnie włączone.
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 źródła Rust, więc działa jak inne źródło 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 w bibliotece wygenerowanej przez AIDL używany w rustlibs
to nazwa modułu aidl_interface
, po którym następuje ciąg -rust
; W tym przypadku
com.example.android.remoteservice-rust
Interfejs AIDL może zostać następnie odwołany w src/lib.rs
w następujący 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 asynchronicznej wersji Rust AIDL
Ta sekcja zawiera przykład użycia AIDL w stylu Hello World z asynchronicznym systemem Rust.
Kontynuując przykład z RemoteService
, wygenerowana biblioteka backendu AIDL
zawiera interfejsy asynchroniczne, które mogą służyć do implementacji serwera asynchronicznego
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 rozpocząć w następujący 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że to też spowodować
można uniknąć, tworząc środowisko wykonawcze tokio
ręcznie
zamiast używać #[tokio::main]
, a następnie wywołaj join_thread_pool
poza metodą block_on
.
Co więcej, generowana przez Rust biblioteka backendu zawiera interfejs, który umożliwia
implementuję klienta asynchronicznego IRemoteServiceAsync
dla środowiska RemoteService
, który może
należy wdrożyć w następujący 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
Określ plik libsimple_printer
w: external/rust/simple_printer/libsimple_printer.rs
w następujący 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
Podaj definicję słowa external/rust/c_hello_rust/main.c
w ten sposób:
#include "simple_printer.h"
int main() {
print_c_hello_rust();
return 0;
}
Podaj definicję słowa 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). Definiuje niezbędne definicje typów, które Rust może wygenerować
biblioteka Rust cdylib
, która podłącza się bezpośrednio do JNI Javy (JNIEnv
, JClass
,
JString
i tak dalej). W przeciwieństwie do wiązań C++, które generują codegen za pomocą cxx
,
Interoperacyjność Javy przez JNI nie wymaga kroku generowania kodu
podczas budowy. Nie wymaga więc specjalnej pomocy w systemie kompilacji. Jawa
wczytuje cdylib
udostępniony przez Rust tak samo jak każda inna biblioteka natywna.
Wykorzystanie
Użytkowanie w kodzie Rust i Javy jest opisane w dokumencie
jni
Utwórz dokumentację. Proszę
postępuj zgodnie z instrukcjami w artykule Pierwsze kroki.
podany na stronie. 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 Song jest taka:
rust_ffi_shared {
name: "libhello_jni",
crate_name: "hello_jni",
srcs: ["src/lib.rs"],
// The jni crate is required
rustlibs: ["libjni"],
}
W bibliotece Java jest wymieniona biblioteka Rust jako zależność required
.
Dzięki temu będzie ona zainstalowana na urządzeniu wraz z biblioteką Java, chociaż
nie jest to zależność od czasu kompilacji.
java_library {
name: "libhelloworld",
[...]
required: ["libhellorust"]
[...]
}
Możesz też dodać bibliotekę Rust do biblioteki AndroidManifest.xml
dodaj ją do uses_libs
w ten sposób:
java_library {
name: "libhelloworld",
[...]
uses_libs: ["libhellorust"]
[...]
}
Interoperacyjność Rust–C++ wykorzystująca CXX
Konto CXX zapewnia bezpieczne FFI między Rust a podzbiorem języka C++. Dokumentacja CXX zawiera dobre przykłady jej działania. Zalecamy najpierw zapoznanie się z nią. aby zapoznać się z biblioteką i sposobem, w jaki łączy ona języki C++ i Rust. 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
aby wywoływać kod C++ w języku Rust lub używać typów wspólnych między językami C++ i Rust, zdefiniuj
drugi 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"],
}
Narzędzie cxxbridge
jest używany powyżej do wygenerowania strony mostu w języku C++. libcxx_test_cpp
Biblioteka statyczna jest używana jako zależność dla naszego pliku wykonywalnego w wersji 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.
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);
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;
}