Android Rust kalıpları

Bu sayfada Android Günlük Kayıtları hakkında bilgi verilmektedir. Ayrıca Rust AIDL örneği, Rust'u C'den nasıl çağıracağınız ve CXX'yi kullanarak Rust/C++ birlikte çalışabilirliği ile ilgili talimatlar yer almaktadır.

Android günlük kaydı

Aşağıdaki örnekte, mesajları logcat (cihaz üzerinde) veya stdout (ana makine üzerinde) nasıl kaydedebileceğiniz gösterilmektedir.

Android.bp modülünüzde liblogger ve liblog_rust değerlerini bağımlılık olarak ekleyin:

rust_binary {
    name: "logging_test",
    srcs: ["src/main.rs"],
    rustlibs: [
        "liblogger",
        "liblog_rust",
    ],
}

Sonra, Rust kaynağınıza şu kodu ekleyin:

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!");
}

Yani, yukarıda gösterilen iki bağımlılığı (liblogger ve liblog_rust) ekleyin, init yöntemini bir kez çağırın (gerekirse birden fazla kez çağırabilirsiniz) ve sağlanan makroları kullanarak mesajları günlüğe kaydedin. Bkz. logger kasası sayfasına bakın.

Günlük kaydedici paketi, neyi günlüğe kaydetmek istediğinizi tanımlamak için bir API sağlar. Şuna bağlı olarak: kodun cihaz üzerinde mi yoksa ana makinede mi (örneğin, ana makine taraflı testi) mesajları, android_logger veya env_logger ekleyin.

Rust AIDL örneği

Bu bölümde, AIDL ile Rust'ın kullanımı hakkında Hello World tarzı bir örnek verilmiştir.

Android Geliştirici Kılavuzu'nu kullanma AIDL'ye Genel Bakış bölümünde başlangıç noktası olarak external/rust/binder_example/aidl/com/example/android/IRemoteService.aidl oluşturun IRemoteService.aidl dosyasında şu içeriğe sahip olmalıdır:

// 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);
}

Ardından, external/rust/binder_example/aidl/Android.bp dosyasında aidl_interface modülü. Rust arka ucu varsayılan olarak etkinleştirilmediğinden özel olarak etkinleştirmeniz gerekir.

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,
        },
    },
}

AIDL arka ucu bir Rust kaynak oluşturucusudur. Bu nedenle, diğer Rust kaynak oluşturucular gibi çalışır ve Rust kitaplığı oluşturur. Oluşturulan Rust kitaplık modülü, diğer Rust modülleri tarafından bağımlılık olarak kullanılabilir. Oluşturulan bu bağımlılığı durumunda, rust_library aşağıdaki gibi tanımlanabilir 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",
    ],
}

rustlibs içinde kullanılan, AIDL tarafından oluşturulan kitaplığın modül adı biçiminin olduğunu unutmayın aidl_interface modülü adı ve ardından -rust; Bu örnekte com.example.android.remoteservice-rust.

AIDL arayüzüne src/lib.rs içinde aşağıdaki şekilde referans verilebilir:

// 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(())
    }
}

Son olarak, hizmeti aşağıda gösterildiği gibi bir Rust ikili programında başlatın:

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()
}

Eş zamansız Rust AIDL örneği

Bu bölümde, AIDL'nin asenkron Rust ile kullanılmasına dair Hello World tarzında bir örnek verilmiştir.

RemoteService örneğine devam edersek, oluşturulan AIDL arka uç kitaplığı, RemoteService AIDL arayüzü için asenkron sunucu uygulaması oluşturmak üzere kullanılabilecek asenkron arayüzler içerir.

Oluşturulan arayüz IRemoteServiceAsyncServer aşağıdaki gibi uygulanabilir:

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(())
    }
}

Eş zamansız sunucu uygulaması şu şekilde başlatılabilir:

#[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();
    });
}

join_thread_pool'nin block_on'u dahili olarak kullanmasına olanak tanıyan asenkron bağlamı terk etmek için block_in_place değerinin gerekli olduğunu unutmayın. Bunun nedeni, #[tokio::main] ürününün kodu sarmalamasıdır. block_on adlı kişiye yapılan aramada, join_thread_pool arayabilir block_on. Çağrı yapılıyor: block_on içinden yapılan block_on paniğe neden oluyor. Bu da tokio çalışma zamanını oluşturarak manuel olarak #[tokio::main] numaralı telefonu kullanmak yerine join_thread_pool numaralı telefonu arayın block_on yönteminin dışında.

Ayrıca, rust arka uç tarafından oluşturulan kitaplık, RemoteService için IRemoteServiceAsync adlı bir eşzamansız istemci uygulamanıza olanak tanıyan bir arayüz içerir. Bu istemci aşağıdaki şekilde uygulanabilir:

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),
    }
}

C'den Rust'ı çağırma

Bu örnekte, C'den Rust'ın nasıl aranacağı gösterilmektedir.

Örnek Rust kitaplığı

external/rust/simple_printer/libsimple_printer.rs içinde libsimple_printer dosyasını tanımlayın şu şekildedir:

//! 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!");
}

Rust kitaplığı, bağımlı C modüllerinin alabileceği üstbilgileri tanımlamalıdır. Bu nedenle, external/rust/simple_printer/simple_printer.h üstbilgisini aşağıdaki gibi tanımlayın:

#ifndef SIMPLE_PRINTER_H
#define SIMPLE_PRINTER_H

void print_c_hello_rust();


#endif

external/rust/simple_printer/Android.bp terimini burada gösterildiği gibi tanımlayın:

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: ["."],
}

Örnek C ikili programı

external/rust/c_hello_rust/main.c değerini aşağıdaki gibi tanımlayın:

#include "simple_printer.h"

int main() {
  print_c_hello_rust();
  return 0;
}

external/rust/c_hello_rust/Android.bp değerini aşağıdaki gibi tanımlayın:

cc_binary {
    name: "c_hello_rust",
    srcs: ["main.c"],
    shared_libs: ["libsimple_c_printer"],
}

Son olarak, m c_hello_rust çağrısını yaparak derleyin.

Rust-Java birlikte çalışabilirliği

jni kasası, Java Yerel aracılığıyla Java ile Rust birlikte çalışabilirliği sağlar Arayüz (JNI). Rust'ın doğrudan Java'nın JNI'sine (JNIEnv, JClass, JString vb.) takılan bir Rust cdylib kitaplığı oluşturması için gerekli tür tanımlarını tanımlar. cxx aracılığıyla kod oluşturma işlemi gerçekleştiren C++ bağlamalarının aksine, JNI üzerinden Java birlikte çalışabilirlik için kod oluşturma adımı gerekmez inceleyeceğiz. Bu nedenle, özel bir derleme sistemi desteğine ihtiyaç duymaz. Java kodu, Rust tarafından sağlanan cdylib kitaplığını diğer yerel kitaplıklar gibi yükler.

Kullanım

Hem Rust hem de Java kodundaki kullanım jni sandık belgeleri. Lütfen burada verilen Başlangıç örneğini uygulayın. src/lib.rs yazdıktan sonra bu sayfaya dönerek için Android'in derleme sistemiyle kitaplığı nasıl oluşturacağınızı öğrenin.

Derleme tanımı

Java, Rust kitaplığının dinamik olarak yüklenebilmesi için cdylib olarak sağlanmasını gerektirir. Shortg'daki Rust kitaplığının tanımı aşağıdaki gibidir:

rust_ffi_shared {
    name: "libhello_jni",
    crate_name: "hello_jni",
    srcs: ["src/lib.rs"],

    // The jni crate is required
    rustlibs: ["libjni"],
}

Java kitaplığı, Rust kitaplığını required bağımlılığı olarak listeler. Bu, derleme zamanı bağımlılığı olmasa bile Rust kitaplığının Java kitaplığıyla birlikte cihaza yüklenmesini sağlar:

java_library {
        name: "libhelloworld",
        [...]
        required: ["libhellorust"]
        [...]
}

Alternatif olarak, Rust kitaplığını bir AndroidManifest.xml dosyasında, kitaplığı aşağıdaki gibi uses_libs öğesine ekleyin:

java_library {
        name: "libhelloworld",
        [...]
        uses_libs: ["libhellorust"]
        [...]
}

CXX kullanarak Rust–C++ birlikte çalışabilirlik

CXX kasası güvenli FFI sağlar C++ alt kümesi arasında 30 kilometre kaldı. CXX belgeleri işin geneline dair güzel örnekler veriyor. Önce okumanızı öneririz. kitaplığını ve C++ ile Rust'ı birbirine nasıl bağladığını öğrenin. İlgili içeriği oluşturmak için kullanılan aşağıdaki örnekte, bunun Android'de nasıl kullanılacağı gösterilmektedir.

CXX'nin Rust'ın çağırdığı C++ kodunu oluşturmasını sağlamak için bir genrule tanımlayın CXX ve cc_library_static çağrılarını bir kitaplıkta gruplandırmak için kullanılır. C++'nun Rust kodunu çağırmasını veya C++ ile Rust arasında paylaşılan türleri kullanmasını istiyorsanız ikinci bir genrule tanımlayın (Rust bağlamalarını içeren bir C++ üstbilgisi oluşturmak için).

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"],
}

Köprünün C++ tarafını oluşturmak için yukarıda cxxbridge aracı kullanılmıştır. Ardından, libcxx_test_cpp statik kitaplığı Rust yürütülebilir dosyamızın bağımlılığı olarak kullanılır:

rust_binary {
    name: "cxx_test",
    srcs: ["lib.rs"],
    rustlibs: ["libcxx"],
    static_libs: ["libcxx_test_cpp"],
}

.cpp ve .hpp dosyalarında C++ işlevlerini istediğiniz gibi tanımlayın. CXX sarmalayıcı türlerini tercih edebilirsiniz. Örneğin, bir cxx_test.hpp tanımı aşağıdakileri içerir:

#pragma once

#include "rust/cxx.h"
#include "lib.rs.h"

int greet(rust::Str greetee);

cxx_test.cppşunu içerirken:

#include "cxx_test.hpp"
#include "lib.rs.h"

#include <iostream>

int greet(rust::Str greetee) {
  std::cout << "Hello, " << greetee << std::endl;
  return get_num();
}

Bunu Rust'tan kullanmak için lib.rs içinde aşağıdaki gibi bir CXX köprüsü tanımlayın:

#[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;
}