Android Rust kalıpları

Bu sayfada Android Günlük Kaydı hakkında bilgiler bulunur, bir Rust AIDL örneği sunulur, Rust'u C'den nasıl çağıracağınız anlatılır ve CXX Kullanarak Rust/C++ Birlikte Çalışma talimatları sağlanır.

Android günlüğü

Aşağıdaki örnek, mesajları logcat (aygıt üzerinde) veya stdout (ana bilgisayar üzerinde) nasıl kaydedebileceğinizi gösterir.

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

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

Daha sonra Rust kaynağınıza şu kodu ekleyin:

use log::{debug, error, Level};

fn main() {
    let init_success = logger::init(
        logger::Config::default()
            .with_tag_on_device("mytag")
            .with_min_level(Level::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. Olası yapılandırma seçeneklerinin listesi için kaydedici kasasına bakın.

Kaydedici sandığı, neyi günlüğe kaydetmek istediğinizi tanımlamak için bir API sağlar. Kodun cihazda mı yoksa ana bilgisayarda mı (ana bilgisayar tarafı testinin bir parçası gibi) çalıştığına bağlı olarak mesajlar, android_logger veya env_logger kullanılarak günlüğe kaydedilir.

Rust AIDL örneği

Bu bölümde AIDL'nin Rust ile kullanımına ilişkin Merhaba Dünya tarzı bir örnek sunulmaktadır.

Android Geliştirici Kılavuzu AIDL'ye Genel Bakış bölümünü bir başlangıç ​​noktası olarak kullanarak, IRemoteService.aidl dosyasında aşağıdaki içeriklerle external/rust/binder_example/aidl/com/example/android/IRemoteService.aidl oluşturun:

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

Daha sonra, external/rust/binder_example/aidl/Android.bp dosyası içinde aidl_interface modülünü tanımlayın. Varsayılan olarak etkin olmadığından Rust arka ucunu açıkça 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şturucudur, dolayısıyla diğer Rust kaynak oluşturucuları gibi çalışır ve bir Rust kitaplığı oluşturur. Üretilen Rust kütüphane modülü diğer Rust modülleri tarafından bağımlılık olarak kullanılabilir. Üretilen kütüphaneyi bağımlılık olarak kullanmanın bir örneği olarak, bir rust_library external/rust/binder_example/Android.bp dosyasında aşağıdaki gibi tanımlanabilir:

rust_library {
    name: "libmyservice",
    srcs: ["src/lib.rs"],
    crate_name: "myservice",
    rustlibs: [
        "com.example.android.remoteservice-rust",
        "libbinder_rs",
    ],
}

rustlibs kullanılan AIDL tarafından oluşturulan kitaplığın modül adı formatının aidl_interface modül adı ve ardından -rust geldiğini unutmayın; bu durumda com.example.android.remoteservice-rust .

AIDL arayüzüne daha sonra src/lib.rs dosyasında şu şekilde başvurulabilir:

// 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 dosyası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 eşzamansız Rust ile kullanımına ilişkin Merhaba Dünya tarzı bir örnek sunulmaktadır.

RemoteService örneğine devam edersek, oluşturulan AIDL arka uç kitaplığı, RemoteService AIDL arabirimi için bir eşzamansız sunucu uygulamasını uygulamak için kullanılabilen eşzamansız arabirimler içerir.

Oluşturulan eşzamansız sunucu 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 dahili olarak blok_on kullanmasına izin veren asenkron bağlamdan ayrılmak için blockchain_in_place'in gerekli olduğunu unutmayın. Bunun nedeni #[tokio::main] kodunun block_on çağrısına sarılması ve join_thread_pool gelen bir işlemi gerçekleştirirken block_on çağırabilmesidir. Bir block_on içinden bir block_on çağrısı paniğe yol açar. Bu, #[tokio::main] kullanmak yerine tokio çalışma zamanını manuel olarak oluşturarak ve ardından join_thread_pool block_on yönteminin dışında çağırarak da önlenebilir.

Ayrıca, Rustik arka uç tarafından oluşturulan kitaplık, RemoteService için IRemoteServiceAsync asenkron istemcisinin uygulanmasına izin veren ve aşağıdaki şekilde uygulanabilen bir arayüz içerir:

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::get_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ğırmak

Bu örnekte Rust'un C'den nasıl çağrılacağı gösterilmektedir.

Örnek Rust Kitaplığı

libsimple_printer dosyasını external/rust/simple_printer/libsimple_printer.rs dosyasında aşağıdaki gibi tanımlayın:

//! 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ığının, bağımlı C modüllerinin alabileceği başlıkları tanımlaması gerekir; bu nedenle external/rust/simple_printer/simple_printer.h başlığını aşağıdaki gibi tanımlayın:

#ifndef SIMPLE_PRINTER_H
#define SIMPLE_PRINTER_H

void print_c_hello_rust();


#endif

Burada gördüğünüz gibi external/rust/simple_printer/Android.bp tanımlayın:

rust_ffi {
    name: "libsimple_c_printer",
    crate_name: "simple_c_printer",
    srcs: ["libsimple_c_printer.rs"],

    // Define include_dirs so cc_binary knows where the headers are.
    include_dirs: ["."],
}

Örnek C ikilisi

external/rust/c_hello_rust/main.c 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 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ğırarak derleyin.

Rust – Java birlikte çalışması

jni kutusu, Java Yerel Arayüzü (JNI) aracılığıyla Rust'un Java ile birlikte çalışabilmesini sağlar. Rust'un doğrudan Java'nın JNI'sine ( JNIEnv , JClass , JString vb.) bağlanan bir Rust cdylib kütüphanesi üretmesi için gerekli tür tanımlarını tanımlar. cxx aracılığıyla codegen gerçekleştiren C++ bağlamalarının aksine, JNI aracılığıyla Java birlikte çalışabilirliği, derleme sırasında bir kod oluşturma adımı gerektirmez. Bu nedenle özel yapı sistemi desteğine ihtiyaç duymaz. Java kodu, Rust tarafından sağlanan cdylib diğer yerel kütüphaneler gibi yükler.

Kullanım

Hem Rust hem de Java kodundaki kullanım, jni sandık belgelerinde ele alınmıştır. Lütfen orada verilen Başlarken örneğini takip edin. src/lib.rs yazdıktan sonra, Android'in derleme sistemiyle kitaplığın nasıl oluşturulacağını öğrenmek için bu sayfaya dönün.

Derleme tanımı

Java, Rust kütüphanesinin dinamik olarak yüklenebilmesi için cdylib olarak sağlanmasını gerektirir. Soong'daki Rust kütüphanesinin 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 bir bağımlılık olarak listeler; bu, derleme zamanı bağımlılığı olmasa bile Java kitaplığının yanında cihaza yüklenmesini sağlar:

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

Alternatif olarak, Rust kitaplığını bir AndroidManifest.xml dosyasına eklemeniz gerekiyorsa kitaplığı uses_libs aşağıdaki şekilde ekleyin:

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

CXX Kullanarak Rust–C++ Birlikte Çalışma

CXX kasası, Rust ile C++'ın bir alt kümesi arasında güvenli FFI sağlar. CXX belgeleri genel olarak nasıl çalıştığına dair iyi örnekler verir ve kitaplığa ve C++ ile Rust arasında nasıl köprü kurduğuna aşina olmak için önce bu belgeyi okumanızı öneririz. Aşağıdaki örnek, Android'de nasıl kullanılacağını gösterir.

CXX'in, Rust'un çağırdığı C++ kodunu oluşturmasını sağlamak için, CXX'i çağıracak bir genrule ve bunu bir kitaplıkta paketleyecek bir cc_library_static tanımlayın. C++'ın Rust kodunu çağırmasını veya C++ ile Rust arasında paylaşılan türleri kullanmayı planlıyorsanız, ikinci bir tür tanımlayın (Rust bağlamalarını içeren bir C++ başlığı 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ıldı. Daha sonra libcxx_test_cpp statik kitaplığı Rust yürütülebilir dosyamıza bağımlılık olarak kullanılır:

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

.cpp ve .hpp dosyalarında CXX sarmalayıcı türlerini istediğiniz gibi kullanarak C++ işlevlerini istediğiniz gibi tanımlayın. Ö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 şunları 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 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;
}