Android Paslanma 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 (barındırıcı üzerinde) nasıl kaydedebileceğiniz gösterilmektedir.

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

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

Ardından 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. Olası yapılandırma seçeneklerinin listesi için logger paketine göz atın.

Günlük kaydedici kasası, günlüğe kaydetmek istediğinizi tanımlamak için bir API sağlar. Kodun cihazda mı yoksa ana makinede mi çalıştığına (ör. ana makine tarafında yapılan testin bir parçası olarak) bağlı olarak mesajlar, android_logger veya env_logger kullanılarak günlüğe kaydedilir.

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'nun AIDL'ye Genel Bakış bölümünü başlangıç noktası olarak kullanıp IRemoteService.aidl dosyasında aşağıdaki içerikleri içeren external/rust/binder_example/aidl/com/example/android/IRemoteService.aidl dosyasını 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);
}

Ardından, external/rust/binder_example/aidl/Android.bp dosyasında aidl_interface modülünü tanımlayın. 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şturucu olduğundan diğer Rust kaynak jeneratörleri gibi çalışır ve bir Rust kitaplığı üretir. Üretilen Rust kitaplık modülü, diğer Rust modülleri tarafından bağımlılık olarak kullanılabilir. Üretilen kitaplığın bağımlılık olarak kullanılmasına bir örnek olarak rust_library, external/rust/binder_example/Android.bp içinde 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 içinde kullanılan AIDL tarafından oluşturulan kitaplığın modül adı biçiminin, aidl_interface modül adının ardından -rust geldiğini unutmayın. Bu durumda, com.example.android.remoteservice-rust.

Daha sonra 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'in asenkron Rust ile kullanılmasına dair Hello World tarzında bir örnek verilmiştir.

RemoteService örneğinden devam edelim. Oluşturulan AIDL arka uç kitaplığı, RemoteService AIDL arayüzü için eşzamansız sunucu uygulamasını uygulamak için kullanılabilen eşzamansız arayüzler içermektedir.

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

Eş zamansız bağlamı bırakmak için block_in_place gereklidir. Bu sayede join_thread_pool, block_on öğesini dahili olarak kullanabilir. Bunun nedeni, #[tokio::main] ürününün, block_on çağrısında kodu sarmalaması ve join_thread_pool ürününün gelen bir işlemi işlerken block_on çağrısı yapabilmesidir. block_on içinden bir block_on çağrısı yapmak paniğe yol açar. Bu durum, #[tokio::main] kullanmak yerine tokio çalışma zamanını manuel olarak oluşturarak ve ardından join_thread_pool yöntemini block_on yönteminin dışında çağırarak da önlenebilir.

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'ı ara

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

Örnek Rust kitaplığı

external/rust/simple_printer/libsimple_printer.rs içindeki libsimple_printer dosyasını 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ığı, bağımlı C modüllerinin alabileceği başlıkları tanımlamalıdır. 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

external/rust/simple_printer/Android.bp değerini 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 dosyası

external/rust/c_hello_rust/main.c öğesini şu şekilde tanımlayın:

#include "simple_printer.h"

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

external/rust/c_hello_rust/Android.bp öğesini şu şekilde tanımlayın:

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

Son olarak, m c_hello_rust yöntemini çağırarak derleme yapın.

Rust-Java birlikte çalışabilirliği

jni kasası, Java Yerel Arayüzü (JNI) aracılığıyla Java ile Rust birlikte çalışabilirliği sağlar. 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 üzerinden kod oluşturma işlemi gerçekleştiren C++ bağlamalarının aksine, JNI üzerinden Java birlikte çalışabilirliği, derleme sırasında kod oluşturma adımı gerektirmez. Bu nedenle, özel bir derleme sistemi desteğine ihtiyaç duymaz. Java kodu, Rust tarafından sağlanan cdylib öğesini diğer tüm yerel kitaplıklar gibi yükler.

Kullanım

Hem Rust hem de Java kodunda kullanım, jni kasa belgelerinde ele alınmıştır. Lütfen burada verilen Başlarken örneğini izleyin. src/lib.rs yazdıktan sonra Android'in derleme sistemiyle kitaplığı nasıl oluşturacağınızı öğrenmek için bu sayfaya dönün.

Derleme tanımı

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

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ı bir required bağımlılığı olarak listeler. Böylece, derleme zamanına bağımlı olmasa da dosyanın Java kitaplığıyla birlikte cihaza yüklenmesi sağlanır:

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

Alternatif olarak, Rust kitaplığını bir AndroidManifest.xmldosyasına dahil etmeniz gerekirse kitaplığı uses_libs'a aşağıdaki gibi ekleyin:

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

CXX kullanarak Rust-C++ birlikte çalışabilirliği

CXX paketi, Rust ile C++'nun bir alt kümesi arasında güvenli FFI sağlar. CXX dokümanlarında, genel olarak nasıl çalıştığına dair iyi örnekler verilmiştir. Kitaplığa ve C++ ile Rust arasında köprü kurmasına aşina olmak için önce bunları okumanızı öneririz. Aşağıdaki örnekte, bu özelliğin Android'de nasıl kullanılacağı gösterilmektedir.

CXX'in, Rust'ın çağırdığı C++ kodunu oluşturmasını sağlamak için, CXX'i çağırmak üzere bir genrule ve bunu bir kitaplıkta gruplandırmak için bir cc_library_static tanımlayın. 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"],
}

cxxbridge aracı, köprünün C++ tarafını oluşturmak için yukarıda kullanılır. Şimdi, Rust yürütülebilir dosyamıza bağımlılık olarak libcxx_test_cpp statik kitaplığı kullanılacak:

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 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ş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;
}