Questa pagina contiene informazioni su Android Logging, fornisce un esempio di Rust AIDL e spiega come chiama Rust dal C e ti fornisce le istruzioni per Interoperabilità di Rust/C++ con CXX.
Logging di Android
L'esempio seguente mostra come registrare i messaggi su logcat
(sul dispositivo) o
stdout
(sull'organizzatore).
Nel modulo Android.bp
, aggiungi liblogger
e liblog_rust
come dipendenze:
rust_binary {
name: "logging_test",
srcs: ["src/main.rs"],
rustlibs: [
"liblogger",
"liblog_rust",
],
}
Quindi, nel codice sorgente Rust, aggiungi questo codice:
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!");
}
Vale a dire, aggiungi le due dipendenze mostrate sopra (liblogger
e liblog_rust
),
chiamare il metodo init
una volta (puoi chiamarlo più di una volta, se necessario) e
registrare i messaggi utilizzando le macro fornite. Consulta le
cassa registratore
per un elenco di possibili opzioni di configurazione.
La crate del logger fornisce un'API per definire ciò che vuoi registrare. In base a se il codice è in esecuzione sul dispositivo o sull'host (ad esempio, nell'ambito di un test lato host), i messaggi vengono registrati utilizzando android_logger o env_logger.
Esempio di AIDL Rust
Questa sezione fornisce un esempio in stile Hello World dell'utilizzo di AIDL con Rust.
Utilizzando la Guida per gli sviluppatori Android Panoramica AIDL
come punto di partenza, crea external/rust/binder_example/aidl/com/example/android/IRemoteService.aidl
con i seguenti contenuti nel file 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);
}
Quindi, all'interno del file external/rust/binder_example/aidl/Android.bp
, definisci
Modulo aidl_interface
. Devi abilitare esplicitamente il backend Rust perché
non è abilitato per impostazione predefinita.
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,
},
},
}
Il backend AIDL è un generatore di sorgenti Rust, quindi funziona come le altre sorgenti Rust
generatori e produce una libreria Rust. Il modulo della libreria Rust prodotto può essere
usato da altri moduli Rust come dipendenza. Come esempio di utilizzo della proprietà
libreria come dipendenza, un rust_library
può essere definito come segue
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",
],
}
Tieni presente che il formato del nome del modulo per la libreria generata da AIDL utilizzata in rustlibs
è il nome del modulo aidl_interface
seguito da -rust
; in questo caso,
com.example.android.remoteservice-rust
.
È quindi possibile fare riferimento all'interfaccia AIDL in src/lib.rs
come segue:
// 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(())
}
}
Infine, avvia il servizio in un file binario Rust come mostrato di seguito:
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()
}
Esempio di Async Rust AIDL
Questa sezione fornisce un esempio in stile Hello World dell'utilizzo di AIDL con Rust asincrono.
Proseguendo con l'esempio RemoteService
, la libreria di backend AIDL generata
include interfacce asincrone che possono essere utilizzate per implementare un server asincrono
implementazione per l'interfaccia AIDL RemoteService
.
L'interfaccia del server asincrona generata IRemoteServiceAsyncServer
può essere
implementate nel seguente modo:
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(())
}
}
L'implementazione del server asincrono può essere avviata come segue:
#[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();
});
}
Tieni presente che
blocco_in_luogo
è necessario uscire dal contesto asincrono, che consente a join_thread_pool
di utilizzare
block_on internamente. Questo perché #[tokio::main]
aggrega il codice
in una chiamata a block_on
, e join_thread_pool
potrebbe chiamare
block_on
durante la gestione di una transazione in entrata. Chiamata a un
block_on
da un block_on
provoca il panico. Potrebbe anche essere
da evitare creando il runtime di
manualmente
invece di utilizzare #[tokio::main]
e poi chiama join_thread_pool
al di fuori del metodo block_on
.
Inoltre, la libreria generata dal backend rust include un'interfaccia che consente
implementare un client asincrono IRemoteServiceAsync
per RemoteService
che può
essere implementate nel seguente modo:
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),
}
}
Chiama Rust da C
Questo esempio mostra come chiamare Rust da C.
Esempio di libreria Rust
Definisci il file libsimple_printer
in external/rust/simple_printer/libsimple_printer.rs
come segue:
//! 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!");
}
La libreria Rust deve definire le intestazioni che i moduli C dipendenti possono inserire,
quindi definisci l'intestazione external/rust/simple_printer/simple_printer.h
come segue:
#ifndef SIMPLE_PRINTER_H
#define SIMPLE_PRINTER_H
void print_c_hello_rust();
#endif
Definisci external/rust/simple_printer/Android.bp
come mostrato qui:
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: ["."],
}
Esempio binario C
Definisci external/rust/c_hello_rust/main.c
come segue:
#include "simple_printer.h"
int main() {
print_c_hello_rust();
return 0;
}
Definisci external/rust/c_hello_rust/Android.bp
come segue:
cc_binary {
name: "c_hello_rust",
srcs: ["main.c"],
shared_libs: ["libsimple_c_printer"],
}
Infine, esegui la creazione chiamando m c_hello_rust
.
Interoperabilità Rust-Java
La cassa jni
offre l'interoperabilità di Rust con Java tramite Java Native
o l'interfaccia JNI. Definisce le definizioni del tipo necessarie che Rust produca
una libreria cdylib
Rust che si collega direttamente alla JNI di Java (JNIEnv
, JClass
,
JString
e così via). A differenza delle associazioni C++ che eseguono il codegen mediante cxx
,
L'interoperabilità Java tramite JNI non richiede un passaggio di generazione del codice
durante una build. Pertanto non ha bisogno di un supporto speciale per il sistema di compilazione. Java
carica il codice cdylib
fornito da Rust come qualsiasi altra libreria nativa.
Utilizzo
L'utilizzo nel codice Rust e Java è coperto nel
Documentazione relativa alla cassa di jni
. Non dimenticare di apporre
segui le istruzioni della Guida introduttiva
dell'esempio fornito. Dopo aver scritto src/lib.rs
, torna in questa pagina all'indirizzo
scopri come creare la libreria con il sistema di compilazione di Android.
Definizione build
Java richiede che la libreria Rust sia fornita come cdylib
per poter essere
vengono caricati in modo dinamico. La definizione della libreria Rust in Presto è la seguente:
rust_ffi_shared {
name: "libhello_jni",
crate_name: "hello_jni",
srcs: ["src/lib.rs"],
// The jni crate is required
rustlibs: ["libjni"],
}
La libreria Java elenca la libreria Rust come dipendenza required
.
questo ne garantisce l'installazione sul dispositivo insieme alla libreria Java, anche se
non c'è una dipendenza in tempo di build:
java_library {
name: "libhelloworld",
[...]
required: ["libhellorust"]
[...]
}
In alternativa, se devi includere la libreria Rust in un AndroidManifest.xml
aggiungi la libreria a uses_libs
in questo modo:
java_library {
name: "libhelloworld",
[...]
uses_libs: ["libhellorust"]
[...]
}
Interoperabilità Rust-C++ con CXX
La cassa CXX offre un FFI sicuro tra Rust e un sottoinsieme di C++. La documentazione di CXX fornisce buoni esempi di come funziona in generale e ti consigliamo di leggerli prima per acquisire familiarità con la libreria e il modo in cui collega C++ e Rust. La nell'esempio seguente viene mostrato come utilizzarla in Android.
Per fare in modo che CXX generi il codice C++ in cui Rust chiama, definisci un genrule
in
richiamare CXX e cc_library_static
per includerli in una raccolta. Se prevedi
per fare in modo che C++ chiami codice Rust o utilizzare tipi condivisi tra C++ e Rust, definisci un
seconda genrule (per generare un'intestazione C++ contenente le associazioni 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"],
}
Lo strumento cxxbridge
viene utilizzato in alto per generare il lato C++ del bridge. libcxx_test_cpp
la libreria statica viene poi utilizzata come dipendenza per l'eseguibile Rust:
rust_binary {
name: "cxx_test",
srcs: ["lib.rs"],
rustlibs: ["libcxx"],
static_libs: ["libcxx_test_cpp"],
}
Nei file .cpp
e .hpp
, definisci le funzioni C++ come preferisci,
utilizzando i tipi di wrapper CXX come preferisci.
Ad esempio, una definizione di cxx_test.hpp
contiene quanto segue:
#pragma once
#include "rust/cxx.h"
#include "lib.rs.h"
int greet(rust::Str greetee);
Mentre cxx_test.cpp
contiene
#include "cxx_test.hpp"
#include "lib.rs.h"
#include <iostream>
int greet(rust::Str greetee) {
std::cout << "Hello, " << greetee << std::endl;
return get_num();
}
Per utilizzarlo da Rust, definisci un bridge CXX come mostrato di seguito in 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;
}