分类目录归档:未分类

Android Activity requestPermissions

https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/Activity.java

        if (mHasCurrentPermissionsRequest) {
            Log.w(TAG, "Can request only one set of permissions at a time");
            // Dispatch the callback with empty arrays which means a cancellation.
            onRequestPermissionsResult(requestCode, new String[0], new int[0]);
            return;
        }

崩溃的地方, 一次只能请求一个权限集合

android native线程调用jni找不到Java Class

线程

所有线程(包括Java线程)都是 Linux 线程,由内核调度。

线程通常从受管理代码(Java代码)启动(使用 Thread.start()),但也可以在其他位置创建,然后附加到 JavaVM。

例如,可以使用 AttachCurrentThread() 或 AttachCurrentThreadAsDaemon() 函数附加通过 pthread_create() 或 std::thread 启动的线程。在附加之前,线程不包含任何 JNIEnv,也无法调用 JNI。

通常,最好使用 Thread.start() 创建需要调用 Java 代码的任何线程

这样做可以确保您有足够的堆栈空间、属于正确的 ThreadGroup 且与您的 Java 代码使用相同的 ClassLoader

而且,设置线程名称以在 Java 中进行调试也比通过原生代码更容易(如果您有 pthread_t 或 thread_t,请参阅 pthread_setname_np();如果您有 std::thread 且需要 pthread_t,请参阅 std::thread::native_handle())。

附加原生创建的线程会构建 java.lang.Thread 对象并将其添加到“主”ThreadGroup,从而使调试程序能够看到它。在已附加的线程上调用 AttachCurrentThread() 属于空操作。

Android 不会挂起执行原生代码的线程。如果正在进行垃圾回收,或者调试程序已发出挂起请求,则在线程下次调用 JNI 时,Android 会将其挂起。

通过 JNI 附加的线程在退出之前必须调用 DetachCurrentThread()。如果直接对此进行编码会很棘手,在 Android 2.0 (Eclair) 及更高版本中,您可以先使用 pthread_key_create() 定义将在线程退出之前调用的析构函数,之后再调用 DetachCurrentThread()。(将该键与 pthread_setspecific() 搭配使用,将 JNIEnv 存储在线程本地存储中;这样一来,该键将作为参数传递到您的析构函数中。)

===========
运行时可以通过两种方式找到您的原生方法。您可以使用 RegisterNatives 显示注册原生方法,也可以让运行时使用 dlsym 进行动态查找。RegisterNatives 的优势在于,您可以预先检查符号是否存在,而且还可以通过只导出 JNI_OnLoad 来获得规模更小、速度更快的共享库。
让运行时发现函数的优势在于,要编写的代码稍微少一些。

如需使用 RegisterNatives,请执行以下操作:
1) 提供 JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) 函数。
2) 在 JNI_OnLoad 中,使用 RegisterNatives 注册所有原生方法。
3) 使用 -fvisibility=hidden 进行构建,以便只从您的库中导出您的 JNI_OnLoad。这将生成速度更快且规模更小的代码,并避免与加载到应用中的其他库发生潜在冲突(但如果应用在原生代码中崩溃,则创建的堆栈轨迹用处不大)。

从 JNI_OnLoad 进行的任何 FindClass 调用都会在用于加载共享库的类加载器的上下文中解析类。
从其他上下文调用时,FindClass 会使用与 Java 堆栈顶部的方法相关联的类加载器,如果没有(因为调用来自刚刚附加的原生线程),则会使用“系统”类加载器。

由于系统类加载器不知道应用的类,因此您将无法在该上下文中使用 FindClass 查找您自己的类

这使得 JNI_OnLoad 成为查找和缓存类的便捷位置:一旦有了有效的 jclass,您就可以从任何附加的线程使用它。

==============
常见问题解答:为什么 FindClass 找不到我的类?

如果类名称形式正确,则可能是您遇到了类加载器问题。FindClass 需要在与您的代码关联的类加载器中启动类搜索。它会检查调用堆栈,如下所示:

    Foo.myfunc(Native Method)
        Foo.main(Foo.java:10)

最顶层的方法是 Foo.myfunc。
FindClass 会查找与 Foo 类关联的 ClassLoader 对象并使用它。

采用这种方法通常会完成您想要执行的操作。如果您自行创建线程(可能通过调用 pthread_create,然后使用 AttachCurrentThread 进行附加),可能会遇到麻烦。现在您的应用中没有堆栈帧。如果从此线程调用 FindClass,JavaVM 会在“系统”类加载器(而不是与应用关联的类加载器)中启动,因此尝试查找特定于应用的类将失败。

您可以通过以下几种方法来解决此问题:

1. 在 JNI_OnLoad 中执行一次 FindClass 查找,然后缓存类引用以供日后使用。在执行 JNI_OnLoad 过程中发出的任何 FindClass 调用都会使用与调用 System.loadLibrary 的函数关联的类加载器(这是一条特殊规则,用于更方便地进行库初始化)。如果您的应用代码要加载库,FindClass 会使用正确的类加载器。

2. 通过声明原生方法来获取 Class 参数,然后传入 Foo.class,从而将类的实例传递给需要它的函数。

3. 在某个便捷位置缓存对 ClassLoader 对象的引用,然后直接发出 loadClass 调用。这需要花费一些精力来完成。

android udp操作权限

https://developer.android.com/training/basics/network-ops/connecting
若要在您的应用中执行网络操作,您的清单必须包含以下权限

uses-permission android:name="android.permission.INTERNET"
uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"

Operation not permitted (os error 1)
Permission denied (os error 13)

如果不声明,udp bind就会报错

Android 11 上的软件包可见性过滤

如果应用以 Android 11(API 级别 30)或更高版本为目标平台,并查询与设备上已安装的其他应用相关的信息,则系统在默认情况下会过滤此信息。从您的应用的角度来看,有限的软件包可见性会减少设备上显示的已安装应用数。

此过滤行为有助于最大限度减少显示您的应用在实现其用例时不需要的潜在敏感信息,但您的应用仍然可以访问这些信息。此外,过滤后的软件包可见性可帮助 Google Play 等应用商店评估应用为用户提供的隐私权和安全性。例如,Google Play 会将已安装应用的列表视为个人和敏感用户数据。

有限的应用可见性会影响提供其他应用相关信息的方法的返回结果,例如 queryIntentActivities()、getPackageInfo() 和 getInstalledApplications()。

有限的可见性还会影响与其他应用的显式交互,例如启动另一个应用的服务

某些软件包仍然自动可见。您的应用始终可以在查询其他已安装的应用时看到这些软件包。
=====================================
系统会自动让部分应用对您的应用可见,以便您的应用可与其交互,而无需声明 queries 元素。此行为有助于支持基本功能和常见用例。

即使您的应用以 Android 11(API 级别 30)或更高版本为目标平台,以下类型的应用也始终对您的应用可见:

您自己的应用。
实现 Android 核心功能的某些系统软件包,例如媒体提供程序。
安装了您应用的应用。
使用 startActivityForResult() 方法在您的应用中启动 activity 的任何应用,正如如何获取 activity 的结果这一指南中所述。
启动或绑定到您应用中的某项服务的任何应用。
访问您应用中的 Content Provider 的任何应用。
具有 Content Provider 的任何应用,其中您的应用已被授予 URI 权限来访问该 Content Provider。
接收您应用的输入的任何应用。这种情况仅适用于您的应用作为输入法应用提供输入。

此外,您可以使用隐式或显式 intent 来启动另一应用的 activity,无论这个应用是否对您的应用可见。

实现 Android 核心功能的某些系统软件包会自动对您的应用可见,即使您的应用以 Android 11 或更高版本为目标平台也是如此。这组特定的软件包取决于运行您应用的设备。

如需查看特定设备的完整软件包列表,请在开发机器上的终端中运行以下命令:

adb shell dumpsys package queries

在命令输出中,找到 forceQueryable 部分

=====================================

如需查看其他软件包,请使用 query 元素声明您的应用需要提高软件包可见性。

在极少数情况下,如果遇到 元素无法提供适当的软件包可见性,您还可以使用 QUERY_ALL_PACKAGES 权限。如果您在 Google Play 上发布应用,那么应用是否此权限需要根据即将生效的政策进行批准。

QMI UIM APDU Security Restrictions

qmi_uim.c ADPU rejected due to security restrictions: logical_channel doesnt belong to client

将 NV 67312 (QMI UIM APDU Security Restrictions)设置为0, 可以移除APDU 限制.

此NV项目不能用QXDM工具修改,必须写入 HW MBN 才能生效

所以需要编译modem源代码
modem_proc/mcfg/mcfg_gen/generic/common/Default/mcfg_hw_gen_Default.xml

 <NvEfsItemData name="apdu_security_restrictions" id="67312" description="APDU Security Restrictions" comment="" category="UIM" mcfgAttributes="0x09" mcfgVariant="2" fullpathname="/nv/item_files/modem/qmi/uim/apdu_security_restrictions"> <Member name="apdu_security_restrictions" description="" comment="" sizeOf="1" type="uint8">0 </Member>
 </NvEfsItemData>

重新编译生成 Non-Hlos.bin

用QPST EFS Explorer 也无法 往 /nv/item_files/modem/qmi/uim 目录写入 apdu_security_restrictions 文件,写入别的名称文件倒是可以

可插拔esim

eSIM.me的eUICC卡

可以将带有 SIM 卡槽的Android设备变成兼容 eSIM 的设备的产品。将eSIM.me提供的eUICC卡插入安卓设备,通过Google Play商店安装eSIM.me app,作为控制eUICC的LPA

如果你有一个双 SIM 卡终端,有两个卡槽和两张 eSIM.me 卡,你可以让两个插槽都兼容 eSIM。

eSIM.me app(LPA )通过OMAPI来写卡

在下面的日志中,您可以看到如何使用 ISD-R AID (a0000005591010ffffffff8900000100) 打开逻辑通道

I/esim.me ( 5121): AndroidOmapiApi. Check isReaderAvailable : SIM1
I/SecureElement-Terminal-SIM1( 2133): ATR : 3b9f96803fc7828031e073fe211b57aa8660f0010004fb
I/SecureElementService( 2133): openLogicalChannel() AID = a0000005591010ffffffff8900000100, P2 = 0
W/SecureElement-Terminal-SIM1( 2133): Enable access control on logical channel for esim.me
I/SecureElement-AccessControlEnforcer( 2133): checkCommand() : Access = ALLOWED APDU Access = ALLOWED Reason = Unspecified
I/SecureElement-Terminal-SIM1( 2133): Sent : 81cadf2000
I/SecureElement-Terminal-SIM1( 2133): Received : df20082618f36467f9a02d9000
I/SecureElement-AraController( 2133): Refresh tag unchanged. Using access rules from cache.
I/SecureElement-AccessControlEnforcer( 2133): getAccessRule() appCert = 8d48ecfaf44c5752145ee28b3eb7429cc6627e98
I/SecureElement-AccessControlEnforcer( 2133): getAccessRule() appCert = a2afbbf5681bb26e40c8d69da6c72dd3a62cda7dbd74c5c2f26e7ff1fa819905
I/SecureElement-AccessRuleCache( 2133): findAccessRule() Case C REF_DO: AID_REF_DO: 4f00 Hash_REF_DO: c1148d48ecfaf44c5752145ee28b3eb7429cc6627e98 , com.android.se.security.ChannelAccess
I/SecureElement-AccessRuleCache( 2133):  [mPackageName=esim.me, mAccess=ALLOWED, mApduAccess=ALLOWED, mUseApduFilter=false, mApduFilter=null, mCallingPid=0, mReason=, mNFCEventAllowed=ALLOWED, mPrivilegeAccess=UNDEFINED]
I/SecureElementService( 2133): openLogicalChannel() Success. Channel: 1

OMAPI 应该响应来自未经授权的应用程序的访问请求,但访问规则设置为只接受来自(包括)eSIM.me 应用程序的访问请求。
从下面的日志中可以看到eSIM.me的ARA-M中设置的访问规则的详细信息。
上述日志中eSIM.me应用程序签名的哈希值(SHA1:8d48ecfaf44c5752145ee28b3eb7429cc6627e98)在第二条规则中设置为完全权限(AID_REF_DO:4f00)

I/SecureElement-Terminal-SIM1( 2108): Sent : 81caff4000
I/SecureElement-Terminal-SIM1( 2108): Received : ff4081b4e222e1184f00c114a4ec39717cecd7f1e84e913b22b0555fe7dfdd8ae306d00101d10101e222e1184f00c1148d48ecfaf44c5752145ee28b3eb7429cc6627e98e306d00101d10101e222e1184f00c11446bcaf2247253f695f19d60eece158099fe52c62e306d00101d10101e222e1184f00c114fa4abb42827a0fbceb891ddc0d34f658c21ff88ce306d00101d10101e222e1184f00c11435cc639bb5826fde0d0ef088b0f20cb69f0c49ade306d00101d101019000
I/SecureElement-AccessRuleCache( 2108): Add Access Rule: REF_DO: AID_REF_DO: 4f00 Hash_REF_DO: c114a4ec39717cecd7f1e84e913b22b0555fe7dfdd8a , com.android.se.security.ChannelAccess
I/SecureElement-AccessRuleCache( 2108):  [mPackageName=, mAccess=ALLOWED, mApduAccess=ALLOWED, mUseApduFilter=false, mApduFilter=null, mCallingPid=0, mReason=, mNFCEventAllowed=ALLOWED, mPrivilegeAccess=UNDEFINED]
I/SecureElement-AccessRuleCache( 2108): Add Access Rule: REF_DO: AID_REF_DO: 4f00 Hash_REF_DO: c1148d48ecfaf44c5752145ee28b3eb7429cc6627e98 , com.android.se.security.ChannelAccess
I/SecureElement-AccessRuleCache( 2108):  [mPackageName=, mAccess=ALLOWED, mApduAccess=ALLOWED, mUseApduFilter=false, mApduFilter=null, mCallingPid=0, mReason=, mNFCEventAllowed=ALLOWED, mPrivilegeAccess=UNDEFINED]
I/SecureElement-AccessRuleCache( 2108): Add Access Rule: REF_DO: AID_REF_DO: 4f00 Hash_REF_DO: c11446bcaf2247253f695f19d60eece158099fe52c62 , com.android.se.security.ChannelAccess
I/SecureElement-AccessRuleCache( 2108):  [mPackageName=, mAccess=ALLOWED, mApduAccess=ALLOWED, mUseApduFilter=false, mApduFilter=null, mCallingPid=0, mReason=, mNFCEventAllowed=ALLOWED, mPrivilegeAccess=UNDEFINED]
I/SecureElement-AccessRuleCache( 2108): Add Access Rule: REF_DO: AID_REF_DO: 4f00 Hash_REF_DO: c114fa4abb42827a0fbceb891ddc0d34f658c21ff88c , com.android.se.security.ChannelAccess
I/SecureElement-AccessRuleCache( 2108):  [mPackageName=, mAccess=ALLOWED, mApduAccess=ALLOWED, mUseApduFilter=false, mApduFilter=null, mCallingPid=0, mReason=, mNFCEventAllowed=ALLOWED, mPrivilegeAccess=UNDEFINED]
I/SecureElement-AccessRuleCache( 2108): Add Access Rule: REF_DO: AID_REF_DO: 4f00 Hash_REF_DO: c11435cc639bb5826fde0d0ef088b0f20cb69f0c49ad , com.android.se.security.ChannelAccess
I/SecureElement-AccessRuleCache( 2108):  [mPackageName=, mAccess=ALLOWED, mApduAccess=ALLOWED, mUseApduFilter=false, mApduFilter=null, mCallingPid=0, mReason=, mNFCEventAllowed=ALLOWED, mPrivilegeAccess=UNDEFINED]

默认文件系统
即使在下载eSIM之前使用eUICC,也需要向终端端显示所需的最低文件系统。GSMA SGP.22规定应提供一个MF和极少量的EF,
但eSIM.me卡的默认文件系统还包括DIR、ICCID、IMSI、MSISDN等。它似乎是一个默认文件系统,
或者它被实现为一个默认配置文件。如果您请求一个处于空状态的配置文件列表,则会返回一个配置文件,
如下面的日志所示。

I/esim.me ( 5121): AndroidOmapiApi. execute APDU command :81E2910003BF2D00
I/SecureElement-AccessControlEnforcer( 2133): checkCommand() : Access = ALLOWED APDU Access = ALLOWED Reason = Unspecified
I/SecureElement-Terminal-SIM1( 2133): Sent : 81e2910003bf2d00
I/SecureElement-Terminal-SIM1( 2133): Received : 613e
I/SecureElement-Terminal-SIM1( 2133): Sent : 81c000003e
I/SecureElement-Terminal-SIM1( 2133): Received : bf2d3ba039e3375a0aXXXXXXXXXXXXXXXXXXXX4f10a0000005591010ffffffff89000012009f70010191076553494d2e6d6592076553494d2e6d659501019000
I/esim.me ( 5121): AndroidOmapiApi. received APDU response :BF2D3BA039E3375A0AXXXXXXXXXXXXXXXXXXXX4F10A0000005591010FFFFFFFF89000012009F70010191076553494D2E6D6592076553494D2E6D659501019000
I/esim.me ( 5121): Profile list object: profileInfoListOk: {
I/esim.me ( 5121):           {
I/esim.me ( 5121):                   iccid: XXXXXXXXXXXXXXXXXXXX,
I/esim.me ( 5121):                   isdpAid: A0000005591010FFFFFFFF8900001200,
I/esim.me ( 5121):                   profileState: 1,
I/esim.me ( 5121):                   serviceProviderName: eSIM.me,
I/esim.me ( 5121):                   profileName: eSIM.me,
I/esim.me ( 5121):                   profileClass: 1
I/esim.me ( 5121):           }
I/esim.me ( 5121):   }

MCC / MNC 是 262/24

eUICC卡信息
作为参考,我还将在此处粘贴阅读 EuicInfo2 的日志。

I/esim.me ( 5121): AndroidOmapiApi. execute APDU command :81E2910003BF2200
I/SecureElement-AccessControlEnforcer( 2133): checkCommand() : Access = ALLOWED APDU Access = ALLOWED Reason = Unspecified
I/SecureElement-Terminal-SIM1( 2133): Sent : 81e2910003bf2200
I/SecureElement-Terminal-SIM1( 2133): Received : 617b
I/SecureElement-Terminal-SIM1( 2133): Sent : 81c000007b
I/SecureElement-Terminal-SIM1( 2133): Received : bf2278810302010282030202008303040200840d81010082040007701e830239b68503017f3a8603090200870302030088020490a916041481370f5125d0b1d408d4c3b232e6d25e795bebfbaa16041481370f5125d0b1d408d4c3b232e6d25e795bebfb8b010004030100000c0d45442d5a492d55502d303832329000
I/esim.me ( 5121): AndroidOmapiApi. received APDU response :BF2278810302010282030202008303040200840D81010082040007701E830239B68503017F3A8603090200870302030088020490A916041481370F5125D0B1D408D4C3B232E6D25E795BEBFBAA16041481370F5125D0B1D408D4C3B232E6D25E795BEBFB8B010004030100000C0D45442D5A492D55502D303832329000
I/esim.me ( 5121):  - EUICC info 2 is: {
I/esim.me ( 5121):   profileVersion: 020102,
I/esim.me ( 5121):   svn: 020200,
I/esim.me ( 5121):   euiccFirmwareVer: 040200,
I/esim.me ( 5121):   extCardResource: 81010082040007701E830239B6,
I/esim.me ( 5121):   uiccCapability: 011111110011101,
I/esim.me ( 5121):   javacardVersion: 090200,
I/esim.me ( 5121):   globalplatformVersion: 020300,
I/esim.me ( 5121):   rspCapability: 1001,
I/esim.me ( 5121):   euiccCiPKIdListForVerification: {
I/esim.me ( 5121):           81370F5125D0B1D408D4C3B232E6D25E795BEBFB
I/esim.me ( 5121):   },
I/esim.me ( 5121):   euiccCiPKIdListForSigning: {
I/esim.me ( 5121):           81370F5125D0B1D408D4C3B232E6D25E795BEBFB
I/esim.me ( 5121):   },
I/esim.me ( 5121):   euiccCategory: 0,
I/esim.me ( 5121):   ppVersion: 010000,
I/esim.me ( 5121):   sasAcreditationNumber: ED-ZI-UP-0822
I/esim.me ( 5121): }

SAS-UP的认证号是ED-ZI-UP-0822,貌似是中国一家叫“东信和平”的智能卡厂商生产的卡。

https://www.gsma.com/security/wp-content/uploads/2021/02/GSMA-SAS_UP-Certificate-Eastcompeace-Zhuhai-China-0822.pdf

原文: https://cheerio-the-bear.hatenablog.com/entry/2022/03/04/172109

SAS for UICC Production (SAS-UP)

SAS for Subscription Management (SAS-SM)

Security Accreditation Scheme (SAS)

The SAS-SM is defined only for activities within eSIM Remote Provisioning and Management:

eSIM life-cycle and processes in the scope of SM-SR
Profile life-cycle and processes in the scope of SM-DP and SM-DP+
SM-DS processes

rust global jvm

JNI 接口指针(JNIEnv)仅在当前线程中有效。 如果另一个线程需要访问 Java VM,它必须首先调用 AttachCurrentThread() 将自己附加到 VM 并获取 JNI 接口指针(JNIEnv)。 一旦连接到 VM, native线程就像在native方法中运行的普通 Java 线程一样工作。 native线程一直连接到 VM,直到它调用 DetachCurrentThread() 来分离自己。

https://github.com/sfackler/rust-jni-sys/blob/master/src/lib.rs
这里定义了

pub type JavaVM = *const JNIInvokeInterface_;


#[repr(C)]
#[derive(Copy)]
pub struct JNIInvokeInterface_ {
    pub reserved0: *mut c_void,
    pub reserved1: *mut c_void,
    pub reserved2: *mut c_void,
    pub DestroyJavaVM: Option<unsafe extern  "system" fn(vm:  *mut  JavaVM)  -> jint>,
    pub AttachCurrentThread: Option<
        unsafe extern "system" fn(vm: *mut JavaVM,
                                  penv: *mut *mut c_void,
                                  args: *mut c_void)
                                  -> jint,
    >,
    pub DetachCurrentThread: Option<unsafe extern "system"  fn(vm:  *mut  JavaVM)  -> jint>,
    pub GetEnv: Option<
        unsafe extern "system" fn(vm: *mut JavaVM,
                                  penv: *mut *mut c_void,
                                  version: jint)
                                  -> jint,
    >,
    pub AttachCurrentThreadAsDaemon: Option<
        unsafe extern "system" fn(vm: *mut JavaVM,
                                  penv: *mut *mut c_void,
                                  args: *mut c_void)
                                  -> jint,
    >,
}

impl Clone for JNIInvokeInterface_ {
    fn clone(&self) -> Self {
        *self
    }
}

这就是一个JNI C接口在rust中的表示。

可以Copy, 可以Clone

在 https://github.com/jni-rs/jni-rs/blob/master/src/wrapper/java_vm/vm.rs 中

#[repr(transparent)]
pub struct JavaVM(*mut sys::JavaVM);
...
impl JavaVM {
...
    pub unsafe fn from_raw(ptr: *mut sys::JavaVM) -> Result<self> {
        non_null!(ptr, "from_raw ptr argument");
        Ok(JavaVM(ptr))
    }

    pub fn get_java_vm_pointer(&self) -> *mut sys::JavaVM {
        self.0
    }

可以看到,直接从sys::JavaVM生成的

而JNI_OnLoad在rust中的签名是

pub unsafe extern "system" fn JNI_OnLoad(vm: *const JavaVM, _reserved: *const c_void) -> jint 
pub unsafe extern fn JNI_OnLoad(vm: *mut JavaVM, reserved: *mut c_void) -> jint

或者

                            
#[allow(non_snake_case)]
#[no_mangle]
pub extern "system" fn JNI_OnLoad(vm: JavaVM, _: *mut c_void) -> jint {

    let env = vm.get_env().expect("Cannot get reference to the JNIEnv");

    JNI_VERSION_1_8
}

JNIEnv是一个用来在Java和Native代码之间通信的结构体的指针。
这个通信的ABI几乎由每个JVM(和Android)实现。
这个结构体有多个版本,所以为什么要在JNIEnv里有GetVersion这个玩意。

rust thread spawn 静态生命周期

通过 thread::spawn 产生的线程理论上可以比其父线程寿命更长。 如果子线程可以引用来自父线程的数据,那么当父线程停止时,这些引用将dangle(悬空, 也就是be invalid 无效)。 这就是为什么 thread::spawn 的闭包有个“static bound”。你在主函数中用join来等待线程的结束, 因此 主函数 肯定比 子进程活的时间长, 但是 编译器没办法理解这一点。

use std::thread;
use std::time::Duration;


fn do_task(host: &str) {
    println!("{}", host)
}

fn start(hostz: &'static str) {
//fn start(hostz: &str) {
    thread::spawn(move || do_task(hostz));
}

fn main() {
    let hostx = "tw.com";

    start(hostx);

    thread::sleep(Duration::from_millis(2000));
}

如果hostz不标记为static生命周期就会报错
hostz有一个匿名的生命周期 `_ 有被 move到 lamda函数里, thread::spawn又要求static生命周期

解释:
闭包 捕获了一个 str, 所以它的上下文(环境)生命周期 不会比这个str更长, 但是 spawn又要求 闭包有static 生命周期

另外一种解决方法是:

fn start(hostz: &str) {
    let hostff = hostz.to_owned();
    thread::spawn(move || do_task(&hostff));
}

在spawn之前, 复制一份str

rust中简单粗暴地使用global变量的方法

就是用unsafe
在Rust In Action中给出了两段代码

use rand::{random};
   
  static mut ERROR: isize = 0;
   
  struct File;
   
  #[allow(unused_variables)]
fn read(f: &File, save_to: &mut Vec<u8>) -> usize {
      if random() && random() && random() {
         unsafe {
             ERROR = 1;
         }
     }
     0
 }
  
 #[allow(unused_mut)]
 fn main() {
     let mut f = File;
     let mut buffer = vec![];
  
     read(&f, &mut buffer);
     unsafe {
         if ERROR != 0 {
             panic!("An error has occurred!")
         }
     }
 }

第二个例子,停机标志

#![cfg(not(windows))]

extern crate libc;

use std::time;
use std::thread::{sleep};
use libc::{SIGTERM, SIGUSR1};

static mut SHUT_DOWN: bool = false;

fn main() {
    register_signal_handlers();

    let delay = time::Duration::from_secs(1);

    for i in 1_usize.. {
        println!("{}", i);
        unsafe {
            if SHUT_DOWN {
                println!("*");
                return;
            }
        }

        sleep(delay);

        let signal = if i > 2 {
            SIGTERM
        } else {
            SIGUSR1
        };
        unsafe {
            libc::raise(signal);
        }
    }
    unreachable!();
}

fn register_signal_handlers() {
    unsafe {
        libc::signal(SIGTERM, handle_sigterm as usize);
        libc::signal(SIGUSR1, handle_sigusr1 as usize);
    }
}

#[allow(dead_code)]
fn handle_sigterm(_signal: i32) {
    register_signal_handlers();

    println!("SIGTERM");

    unsafe {
        SHUT_DOWN = true;
    }
}

#[allow(dead_code)]
fn handle_sigusr1(_signal: i32) {
    register_signal_handlers();

    println!("SIGUSR1");
}

如果不想用 unsafe代码,可以用 lazy_static! 宏

use lazy_static::lazy_static; 
use regex::Regex; 

lazy_static! {
    static ref RE: Regex = Regex::new(r"hello (\w+)!").unwrap();
}

fn main() {
    let text = "hello bob!\nhello sue!\nhello world!\n";
    for cap in RE.captures_iter(text) {
        println!("your name is: {}", &cap[1]);
    }
}

如果这个全局变量可变, 还需要加上Mutex(单个修改者)或者RwLock(多个修改者)

另外一个方法是 https://github.com/matklad/once_cell 据说正在提议加入std库中

Android Bluetooth API解释

服务(Service)是一系列特征(Characteristic)。例如,您可能拥有名为“心率监测器”的服务,其中包括“心率测量”等特征。
特征(Characteristic 包含一个值(value) 和 0 至多个描述特征值的描述符(descriptor)
描述符(descriptor)可指定人类可读的描述、特征值的可接受范围或特定于特征值的度量单位。

服务和特征的都由UUID标识。

https://github.com/android/connectivity-samples/blob/main/BluetoothLeGatt/Application/src/main/java/com/example/android/bluetoothlegatt/BluetoothLeService.java

要连接到远程外围设备(提供蓝牙服务的设备),请创建 BluetoothGattCallback 并调用 BluetoothDevice#connectGatt 以获取此类的实例。 可以使用蓝牙设备发现或 BLE 扫描过程发现支持 GATT 的设备。

bluetoothGatt = device.connectGatt(this, false, gattCallback);

connectGatt 将连接到由 BLE 设备托管的 GATT 服务器,并返回 BluetoothGatt 实例,然后您可使用该实例执行 GATT 客户端操作。调用方(Android 应用)是 GATT 客户端。BluetoothGattCallback 用于向客户端传递结果(例如连接状态),以及任何进一步的 GATT 客户端操作。

BluetoothGattCallback可以override几个方法:
1)onConnectionStateChange 连接接状态改变 (连接还是断开了)
2) onServicesDiscovered 在远程设备上发现了Gatt服务
3) onCharacteristicRead 特征值已经读取 (报告 一个 特征读操作的结果)
4) onCharacteristicChanged 特征值 改变了
5) onCharacteristicWrite 特征已写 (指出 特征写操作的结果), 是对 writeCharacteristic(BluetoothGattCharacteristic, byte[], int)操作 的回应。 这时候,可以 characteristic.getValue()来获取远程设备报告 他写入的值,是否与你 调用 writeCharacteristic时写入的值,是否一致
6) onMtuChanged MTU改变了
7) onDescriptorRead 报告 描述符 读操作的结果
8) onDescriptorWrite 指出 描述符 写操作的结果, 是对 writeDescriptor的响应. 写描述符,主要是对特征的功能进行配置,比如特征值改变后,要不要主动通知 客户端(中心设备)
9) onReadRemoteRssi 报告远程设备的信号强度, 是对 readRemoteRssi 的响应
10)onServiceChanged 需要重新同步远程设备

顺序:
onConnectionStateChange 已连接 请求修改MTU
onMtuChanged MTU已修改 请求发现远程设备上的服务
onServicesDiscovered 服务已经发现 (1)枚举服务和其中的特征。 或者 直接获取特定UUID的特征
(2)对特征进行相关设置,比如是否需要通知,写类型(WRITE_TYPE_NO_RESPONSE是否需要写响应)
(3) 读写特征
onCharacteristicRead 读数据已经获得 如果上一步,发出 读操作,当特征值读到时,会出发此回调。

操作:
读—-
如果客户端(中心设备) 要读服务端(外围设备)的 特征的值,直接调用
readCharacteristic, 然后在 onCharacteristicRead 获取读的的值
写—–
如果客户端(中心设备) 要往 服务端的某个特征写,并且想知道回应值,
先打开通知 setCharacteristicNotification, 设置写类型 setWriteType, 设置值 setValue, 最后 writeCharacteristic
写完之后,在 onCharacteristicChanged 获取响应数据


服务端,外围设备开启 GattServer
openGattServer(Context context, BluetoothGattServerCallback callback)

onConnectionStateChange
onCharacteristicReadRequest
onCharacteristicWriteRequest
onDescriptorReadRequest
onDescriptorWriteRequest
onMtuChanged
onNotificationSent
onServiceAdded