保持蓝牙BLE非绑定设备的后台连接

对于非绑定设备(也就是扫描后连接的这种模式),Android 上 BLE 的默认行为是应用拥有与 BLE 设备的连接。相比之下,Android 操作系统拥有绑定设备的连接。

这意味着对于非绑定设备的情形,如果您的应用程序由于资源限制在后台被操作系统终止,或者如果您的用户将您的应用程序滑开(立即终止它,效果明显😃),BLE 连接将丢失,您应该会看到大多数 BLE 外围设备再次开始广告。

尝试使您的应用程序进程尽可能长时间保持活动状态的最直接方法是使用前台服务。如何实现自己的前台服务,在 官方文档里 有明确的步骤。
foreground-services

前台服务本质上是一个带着 通知抽屉中的持久横幅 运行的服务(也就是状态栏有个通知 ),让用户了解您的应用程序正在后台执行某些操作。(A foreground service is essentially a Service that runs with a persistent banner in the notification drawer, keeping the user in the loop that your app is doing something in the background. )
由于内存限制而被系统终止的概率非常低,但并非不可能。

值得注意的是,当用户在前台服务运行时滑离(swipes away )您的应用程序时,您的应用程序进程仍然存在,但您的 Activity 堆栈和任何其他面向用户的元素都会被吹走(get blown away)。因此,必须格外小心以确保您的 BLE 逻辑存在于这些实体之外,就像在一个名为 ConnectionManager 的单例中处理应用程序的所有 BLE 需求一样。

通常,您的Activities和Fragments 不应该对 BLE 连接的状态做出任何假设。相反,他们应该依赖 ConnectionManager(或任何管理应用程序 BLE 需求的实体)作为唯一的事实来源,并根据 UI 的需要在 onCreate() 和 onResume() 中相应地更新 UI。

使用前台服务也不是完全安全的,因为 Android P 及更高版本具有自适应电池功能,有时会限制运行前台服务的应用程序进程的正常运行时间,但根据我们的经验,这似乎是随机发生的。到目前为止,解决此限制的唯一方法是要求您的用户关闭应用程序的电池优化,这是隐藏在“设置”应用程序中的一个选项。

其他 Android 后台处理技术,如依赖 AlarmManager 和 WorkManager 也是可行的,但连接事件不会是即时的,并且可能会丢失一些 BLE 通知或指示,如果这些事件发生时应用程序碰巧没有运行。
也就是说,如果您的应用程序不需要在事件发生时了解它们,并且可以定期唤醒,再次连接到 BLE 设备(如果它在附近),并查询新的数据, 那么 这些后台技术就是好的,可以接受的。

BluetoothManager getConnectionState(android.bluetooth.BluetoothDevice, int)

public int getConnectionState (BluetoothDevice device, int profile)

得到 远程设备profile的 连接状态
profile 为 GATT 或者 GATT_SERVER

返回值为:
STATE_DISCONNECTED = 0
STATE_CONNECTING = 1
STATE_CONNECTED = 2
STATE_DISCONNECTING = 3

BluetoothAdapter.getProfileConnectionState(int profile)
profile只能为 BluetoothProfile#HEADSET 或者 BluetoothProfile#A2DP.

    @SuppressLint("MissingPermission")
    public boolean isConnected() {
        if(mBluetoothGatt != null) {
             int state = mBluetoothManager.getConnectionState(mBluetoothGatt.getDevice(),  BluetoothProfile.GATT);
             return state == BluetoothProfile.STATE_CONNECTED;
        }
        return false;
    }

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