Cette page contient des informations sur Android Logging. fournit un exemple Rust AIDL, vous explique comment appeler Rust à partir de C et fournit des instructions pour l'interopérabilité Rust/C++ à l'aide de CXX.
Journalisation Android
L'exemple suivant montre comment consigner des messages dans logcat
(sur l'appareil) ou
stdout
(sur l'hôte).
Dans votre module Android.bp
, ajoutez liblogger
et liblog_rust
comme dépendances:
rust_binary {
name: "logging_test",
srcs: ["src/main.rs"],
rustlibs: [
"liblogger",
"liblog_rust",
],
}
Ensuite, dans votre source Rust, ajoutez ce code:
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!");
}
Autrement dit, ajoutez les deux dépendances indiquées ci-dessus (liblogger
et liblog_rust
),
Appelez la méthode init
une seule fois (vous pouvez l'appeler plusieurs fois si nécessaire).
les messages de journal à l'aide des macros fournies. Consultez le
caisse enregistreuse
pour consulter la liste des options de configuration possibles.
La caisse enregistreur fournit une API pour définir ce que vous souhaitez consigner. En fonction de si le code s'exécute sur l'appareil ou sur l'hôte (par exemple, dans une (test côté hôte), les messages sont enregistrés à l'aide d'android_logger. ou env_logger.
Exemple Rust AIDL
Cette section fournit un exemple d'utilisation d'AIDL avec Rust, semblable à "Hello World".
Utiliser le guide du développeur Android Présentation d'AIDL
comme point de départ, créez external/rust/binder_example/aidl/com/example/android/IRemoteService.aidl
avec le contenu suivant dans le fichier 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);
}
Ensuite, dans le fichier external/rust/binder_example/aidl/Android.bp
, définissez
aidl_interface
. Vous devez activer explicitement le backend Rust, car il
n'est pas activé par défaut.
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,
},
},
}
Le backend AIDL est un générateur de sources Rust et fonctionne donc comme toute autre source Rust
des générateurs et produit
une bibliothèque Rust. Le module de bibliothèque Rust produit peut être
utilisé par d'autres modules Rust comme dépendance. Pour vous donner un exemple d'utilisation
bibliothèque en tant que dépendance, un rust_library
peut être défini comme suit dans
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",
],
}
Notez que le format du nom de module de la bibliothèque générée par AIDL utilisée dans rustlibs
est le nom du module aidl_interface
suivi de -rust
. dans ce cas,
com.example.android.remoteservice-rust
L'interface AIDL peut ensuite être référencée dans src/lib.rs
comme suit:
// 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(())
}
}
Enfin, démarrez le service dans un binaire Rust, comme indiqué ci-dessous:
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()
}
Exemple Async Rust AIDL
Cette section fournit un exemple d'utilisation d'AIDL avec Rust asynchrone (de type "Hello World").
En poursuivant l'exemple RemoteService
, la bibliothèque backend AIDL générée
inclut des interfaces asynchrones qui peuvent être utilisées pour implémenter un serveur asynchrone
pour l'interface AIDL RemoteService
.
L'interface de serveur asynchrone IRemoteServiceAsyncServer
générée peut être
implémenté comme suit:
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(())
}
}
La mise en œuvre du serveur asynchrone peut être lancée comme suit:
#[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();
});
}
Notez que
block_in_place
est nécessaire pour conserver le contexte asynchrone, ce qui permet à join_thread_pool
d'utiliser
block_on en interne. En effet, #[tokio::main]
encapsule le code.
lors d'un appel à block_on
, et join_thread_pool
pourrait appeler
block_on
lors du traitement d'une transaction entrante. Appeler un
block_on
à partir d'une block_on
entraîne une panique. Cela pourrait également
à éviter en créant l'environnement d'exécution tokio
manuellement
au lieu d'utiliser #[tokio::main]
, puis appelez join_thread_pool
en dehors de la méthode block_on
.
De plus, la bibliothèque générée par le backend
rust inclut une interface qui permet
implémenter un client asynchrone IRemoteServiceAsync
pour RemoteService
, ce qui peut
être implémenté comme suit:
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),
}
}
Appeler Rust depuis C
Cet exemple montre comment appeler Rust à partir de C.
Exemple de bibliothèque Rust
Définir le fichier libsimple_printer
dans external/rust/simple_printer/libsimple_printer.rs
comme suit:
//! 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 bibliothèque Rust doit définir les en-têtes que les modules C dépendants peuvent extraire,
Définissez donc l'en-tête external/rust/simple_printer/simple_printer.h
comme suit:
#ifndef SIMPLE_PRINTER_H
#define SIMPLE_PRINTER_H
void print_c_hello_rust();
#endif
Définissez external/rust/simple_printer/Android.bp
comme indiqué ici:
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: ["."],
}
Exemple de binaire C
Définissez external/rust/c_hello_rust/main.c
comme suit :
#include "simple_printer.h"
int main() {
print_c_hello_rust();
return 0;
}
Définissez external/rust/c_hello_rust/Android.bp
comme suit :
cc_binary {
name: "c_hello_rust",
srcs: ["main.c"],
shared_libs: ["libsimple_c_printer"],
}
Enfin, appelez m c_hello_rust
pour la compilation.
Interopérabilité de Rust-Java
La caisse jni
offre une interopérabilité Rust avec Java via le langage Java natif
(JNI). Il définit les définitions de type nécessaires pour que Rust produise
Une bibliothèque cdylib
Rust qui se branche directement au JNI de Java (JNIEnv
, JClass
,
JString
, etc.). Contrairement aux liaisons C++ qui exécutent le codegen via cxx
,
L'interopérabilité Java via JNI ne nécessite pas d'étape de génération de code
pendant la compilation. Il n'a donc pas besoin de prise en charge spéciale du système de compilation. L'API Java
charge le cdylib
fourni par Rust comme n'importe quelle autre bibliothèque native.
Utilisation
L'utilisation dans le code Rust et Java est expliquée dans le
Documentation sur la caisse jni
Veuillez
suivez le guide Premiers pas
fourni ici. Après avoir écrit src/lib.rs
, revenez à cette page pour
apprendre à créer la bibliothèque
avec le système de compilation d'Android.
Définition de la compilation
Java exige que la bibliothèque Rust soit fournie en tant que cdylib
pour qu'elle puisse être
de manière dynamique. La définition de la bibliothèque Rust dans Soong est la suivante:
rust_ffi_shared {
name: "libhello_jni",
crate_name: "hello_jni",
srcs: ["src/lib.rs"],
// The jni crate is required
rustlibs: ["libjni"],
}
La bibliothèque Java répertorie la bibliothèque Rust en tant que dépendance required
.
cela garantit qu'il est installé sur l'appareil avec la bibliothèque Java, même si
il ne s'agit pas d'une dépendance au moment de la compilation:
java_library {
name: "libhelloworld",
[...]
required: ["libhellorust"]
[...]
}
Si vous devez inclure la bibliothèque Rust dans un élément AndroidManifest.xml
, ajoutez la bibliothèque à uses_libs
comme suit:
java_library {
name: "libhelloworld",
[...]
uses_libs: ["libhellorust"]
[...]
}
Interopérabilité Rust-C++ avec CXX
La caisse CXX fournit des FFI sécurisées entre Rust et un sous-ensemble de C++. La documentation CXX donne de bons exemples de fonctionnement général, et nous vous conseillons de le lire en premier. pour vous familiariser avec la bibliothèque et la façon dont elle établit un pont avec C++ et Rust. La l'exemple suivant montre comment l'utiliser sous Android.
Pour que CXX génère le code C++ que Rust appelle, définissez un genrule
pour
appelez CXX et un cc_library_static
pour les regrouper dans une bibliothèque. Si vous prévoyez
appeler C++ du code Rust, ou utiliser des types partagés entre C++ et Rust, définissez
deuxième genrule (pour générer un en-tête C++ contenant les liaisons 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"],
}
L'outil cxxbridge
est utilisé ci-dessus pour générer le côté C++ du pont. libcxx_test_cpp
La bibliothèque statique est ensuite utilisée comme dépendance pour notre exécutable Rust:
rust_binary {
name: "cxx_test",
srcs: ["lib.rs"],
rustlibs: ["libcxx"],
static_libs: ["libcxx_test_cpp"],
}
Dans les fichiers .cpp
et .hpp
, définissez les fonctions C++ comme vous le souhaitez.
à l'aide des types de wrappers CXX selon vos besoins.
Par exemple, une définition cxx_test.hpp
contient les éléments suivants:
#pragma once
#include "rust/cxx.h"
#include "lib.rs.h"
int greet(rust::Str greetee);
Si cxx_test.cpp
contient
#include "cxx_test.hpp"
#include "lib.rs.h"
#include <iostream>
int greet(rust::Str greetee) {
std::cout << "Hello, " << greetee << std::endl;
return get_num();
}
Pour l'utiliser à partir de Rust, définissez un pont CXX comme ci-dessous dans 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;
}