耐克球鞋发售怎么抢(JNI 从入门到实践,万字爆肝详解!)

[复制链接]
发表于 2023-5-7 11:03:17 | 显示全部楼层 | 阅读模式

JNI 从进门到理论,万字爆肝详解!媒介正在 Android 死态中重要有 C/C++、Java、Kotlin 三种言语 ,它们的干系没有是更换而是互补此中,C/C++ 的语境是算法和下机能,Java 的语境是平台无闭和内乱存办理,而 Kotlin 则交融了多种言语中的良好特征,带去了一种更当代化的编程方法;。

JNI 是完成 Java 代码取 C/C++ 代码交互的特征, 思索一个Question —— Java 假造机是怎样完成两种绝不干系的言语的交互的呢?本日,我们去片面总结 JNI 开辟常识框架,为 NDK 开辟打下底子。

本文部门演示代码能够从 DemoHall·HelloJni下载检察https://github.com/pengxurui/DemoHall 那篇文章是 NDK 系列文章第 5 篇,专栏文章列表:1、言语底子:。

1、NDK 进修门路:怎样教 & 我的履历2、C 言语底子3、C ++ 言语底子4、C/C++ 编译历程:从源码到步伐运转2、NDK 开辟:1、JNI 底子:Java 取 Native 交互(本文)https://juejin.cn/post/7125338583959306248

2、注册 JNI 函数:静态注册 & 静态注册 3、NDK 底子:ndk-build & CMake4、so 文件减载历程阐发:明白 Android 中 loadLibrary 的实行流程https://juejin.cn/post/6892793299427491854

5、so 文件适配 64 位架构:Gradle 插件一键检索已适配项 6、so 文件静态化:静态下载7、so 文件体积劣化:文件粗简3、底子实际1、视频底子实际2、音频底子实际3、H.264 视频紧缩编码

4、音频紧缩编码5、FFMPEG 底子6、OPENSL ES 底子7、PNG 图片:无益紧缩编码https://juejin.cn/post/69056350703976120394、盘算机底子1、字符编码:ASCII、Unicode、UTF-8、UTF-16、UTF-32

JNI 进修门路图:

1 熟悉 JNI 1.1 为何要利用 JNI?JNI(Java Native Interface,Java 当地接心)是 Java 死态的特征,它扩大了 Java 假造机的本领,使得 Java 代码能够取 C/C++ 代码举行交互。

经由过程 JNI 接心,Java 代码能够挪用 C/C++ 代码,C/C++ 代码也能够挪用 Java 代码那便引出第 1 个Question(为何要那么做):Java 为何要挪用 C/C++ 代码,而没有是间接用 Java 开辟需供呢?我以为重要有 4 个缘故原由:

• 缘故原由 1 - Java 自然必要 JNI 技能:固然 Java 是平台无闭性言语,但运转 Java 言语的假造机是运转正在详细平台上的,以是 Java 假造机是平台相干的因而,对付挪用平台 API 的功效(比方翻开文件功效,正在 Window 平台是 openFile 函数,而正在 Linux 平台是 open 函数)时,固然正在 Java 言语层是平台无闭的,但背后只能经由过程 JNI 技能正在 Native 层别离挪用差别平台 API。

雷同的,对付有操纵硬件需供的步伐,也只能经由过程 C/C++ 完成对硬件的操纵,再经由过程 JNI 挪用; • 缘故原由 2- Java 运转服从没有及 C/C++:Java 代码的运转服从相对 C/C++ 要低一些,因而,对付有麋集盘算(比方及时衬着、音视频处置惩罚、游戏引擎等)需供的步伐,会挑选用 C/C++ 完成,再经由过程 JNI 挪用;

• 缘故原由 3 - Native 层代码宁静性更下:反编译 so 文件的易度比反编译 Class 文件下,一些跟暗码相干的功效会挑选用 C/C++ 完成,再经由过程 JNI 挪用; • 缘故原由 4- 复用现有代码:当 C/C++ 存正在步伐必要的功效时,则能够间接复用。

另有第 2 个Question(为何能够那么做):为何两种自力的言语能够完成交互呢?由于 Java 假造机自己便是 C/C++ 完成的,不管是 Java 代码照旧 C/C++ 代码,终极皆是由那个假造机支持,配合利用一个历程空间。

JNI 要做的只是正在两种言语之间做桥接1.2 JNI 开辟的根本流程一个尺度的 JNI 开辟流程重要包罗以下步调:1、创立 HelloWorld.java,并声明 native 要领 sayHi;2、利用 javac 下令编译源文件,天生

HelloWorld.class字节码文件;3、利用 javah 下令导出 HelloWorld.h头文件(头文件中包罗了当地要领的函数本型);4、正在源文件 HelloWorld.cpp 中完成函数本型;

5、编译当地代码,天生 Hello-World.so 静态本死库文件;6、正在 Java 代码中挪用 System.loadLibrary(...)减载 so 文件;7、利用 Java 下令运转 HelloWorld 步伐。

该流程用表示图表现以下:

1.3 JNI 的机能误区JNI 自己自己其实不能办理机能Question,毛病天利用 JNI 反而大概引进新的机能Question,那些Question皆是要留意的:• Question 1 - 超过 JNI 界限的挪用:从 Java 挪用 Native 或从 Native 挪用 Java 的本钱很下,利用 JNI 时要限定超过 JNI 界限的挪用次数;

Question 2- 援用范例数据的接纳:因为援用范例数据(比方字符串、数组)通报到 JNI 层的只是一个指针,为制止该工具被渣滓接纳假造时机牢固住(pin)工具,正在 JNI 要领返回前会制止其渣滓接纳因而,要只管收缩 JNI 挪用的实行工夫,它可以或许收缩工具被牢固的工夫(闭于援用范例数据的处置惩罚,鄙人文会道到)。

1.4 注册 JNI 函数的方法Java 的native 要领和 JNI 函数是逐一对应的映照干系,创建这类映照干系的注册方法有 2 种:• 方法 1- 静态注册:基于定名商定创建映照干系; • 方法 2

- 静态注册:经由过程 JNINativeMethod布局体创建映照干系闭于注册 JNI 函数的更多道理阐发,睹 注册 JNI 函数 1.5 减载 so 库的机遇so 库必要正在运转时挪用System.loadLibrary(…) 。

减载,一样平常有 2 种挪用机遇:1、正在类静态初初化中:假如只正在一个类大概很少类中利用到该 so 库,则最多见的方法是正在类的静态初初化块中挪用;2、正在 Application 初初化时挪用:假如有许多类必要利用到该 so 库,则能够思量正在 Application 初初化等场景中提早减载。

闭于减载 so 库的更多道理阐发,睹 so 文件减载历程阐发https://juejin.cn/post/68927932994274918542 JNI 模板代码 本节我们经由过程一个简朴的 HelloWorld 步伐去帮忙您认识 JNI 的模板代码。

JNI DemoJNIEXPORT voidJNICALL Java_com_xurui_hellojni_HelloWorld_sayHi(JNIEnv *, jobject); 2.1 JNI 函数名

为何 JNI 函数名要接纳 Java_com_xurui_HelloWorld_sayHi 的定名方法呢?—— 那是 JNI 函数静态注册商定的函数定名规矩Java 的 native 要领和 JNI 函数是逐一对应的映照干系,而创建这类映照干系的注册方法有 2 种:静态注册 + 静态注册。

此中,静态注册是基于定名商定创建的映照干系,一个 Java 的 native 要领对应的 JNI 函数会接纳商定的函数名,即 Java_[类的齐限制名 (带下划线)]_[要领名] JNI 挪用 sayHi 。

要领时,便会从 JNI 函数库中探求函数 Java_com_xurui_HelloWorld_sayHi,更多内乱容睹 注册 JNI 函数 2.2 要害词 JNIEXPORTJNIEXPORT 是宏界说,表现一个函数必要表露给同享库内部利用时。

JNIEXPORT 正在 Window 和 Linux 上有差别的界说:jni.h// Windows 平台 :# defineJNIEXPORT __declspec(dllexport) # define

JNIIMPORT __declspec(dllimport) // Linux 平台:# defineJNIIMPORT # defineJNIEXPORT __attribute__ ((visibility (

"default"))) 2.3 要害词 JNICALLJNICALL 是宏界说,表现一个函数是 JNI 函数JNICALL 正在 Window 和 Linux 上有差别的界说:jni.h// Windows 平台 :。

# defineJNICALL __stdcall // __stdcall 是一种函数挪用参数的商定 ,表现函数的挪用参数是从左往左// Linux 平台:# defineJNICALL 2.4 参数 jobject。

jobject 范例是 JNI 层对付 Java 层使用范例工具的表现每个从 Java 挪用的native 要领,正在 JNI 函数中都市通报一个当前工具的援用辨别 2 种环境:1、静态 native 要领:第两个参数为 jclass 范例,指背 native 要领地点类的 Class 工具;。

2、真例 native 要领:第两个参数为 jobject 范例,指背挪用 native 要领的工具2.5 JavaVM 和 JNIEnv 的感化JavaVM 和 JNIEnv 是界说正在 jni.h 头文件中最要害的两个数据布局:。

• JavaVM:代表 Java 假造机,每一个 Java 历程有且唯一一个齐局的 JavaVM 工具,JavaVM 能够跨线程同享; • JNIEnv:代表 Java 运转情况,每一个 Java 线程皆有各自自力的 JNIEnv 工具,JNIEnv 不成以跨线程同享。

JavaVM 和 JNIEnv 的范例界说正在 C 和 C++ 中略有差别,但素质上是雷同的,内乱部由一系列指背假造机内乱部的函数指针构成雷同于 Java 中的 Interface 观点,差别的假造机完成会从它们派死出差别的完成类,而背 JNI 层屏障了假造机内乱部完成(比方正在 Android ART 假造机中,它们的完成别离是 JavaVMExt 和 JNIEnvExt)。

jni.hstruct_ JNIEnv; struct_ JavaVM; # ifdefined(__cplusplus) // 假如界说了 __cplusplus 宏,则根据 C++ 编译typedef

_JNIEnv JNIEnv; typedef_JavaVM JavaVM; # else// 根据 C 编译typedefconststructJNINativeInterface* JNIEnv;

typedefconststructJNIInvokeInterface* JavaVM; # endif/* * C++ 版本的 _JavaVM,内乱部是对 JNIInvokeInterface* 的包拆

*/ struct_ JavaVM{ // 相称于 C 版本中的 JNIEnvconststructJNIInvokeInterface* functions; // 转发给 functions 署理jint

DestroyJavaVM{ returnfunctions->DestroyJavaVM( this); } ... }; /* * C++ 版本的 JNIEnv,内乱部是对 JNINativeInterface* 的包拆

*/ struct_ JNIEnv{ // 相称于 C 版本的 JavaVMconststructJNINativeInterface* functions; // 转发给 functions 署理jint

GetVersion{ returnfunctions->GetVersion( this); } ... }; 能够看到,不论是正在 C 言语中照旧正在 C++ 中,JNINativeInterface*

和 JNINativeInterface*那两个布局体指针才是 JavaVM 和 JNIEnv 的真体不外 C++ 中减了一层包拆,正在语法上更简便,比方:示例步伐// 正在 C 言语中,要利用 (*env)->。

// 留意看那一句:typedef const struct JNINativeInterface* JNIEnv;(*env)->FindClass(env, "java/lang/String");

// 正在 C++ 中,要利用 env->// 留意看那一句:jclass FindClass(const char* name)//{ return functions->FindClass(this, name); }

env->FindClass( "java/lang/String"); 后文提到的大批 JNI 函数,实在皆是界说正在JNINativeInterface和 JNINativeInterface内乱部的函数指针。

jni.h/* * JavaVM */ structJNIInvokeInterface{ // 一系列函数指针jint (*DestroyJavaVM)(JavaVM*); jint (*AttachCurrentThread)(JavaVM*, JNIEnv**,

void*); jint (*DetachCurrentThread)(JavaVM*); jint (*GetEnv)(JavaVM*, void**, jint); jint (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**,

void*); }; /* * JNIEnv */ structJNINativeInterface{ // 一系列函数指针jint (*GetVersion)(JNIEnv *); jclass (*DefineClass)(JNIEnv*,

constchar*, jobject, constjbyte*, jsize); jclass (*FindClass)(JNIEnv*, constchar*); ... }; 3 数据范例转换 那一节我们去会商 Java 层取 Native 层之间的数据范例转换。

3.1 Java 范例映照(重面明白)JNI 对付 Java 的底子数据范例(int 等)和援用数据范例(Object、Class、数组等)的处置惩罚方法差别那个道理十分紧张,明白那个道理才气明白背面全部 JNI 函数的计划思绪:。

• 底子数据范例:会间接转换为 C/C++ 的底子数据范例,比方 int 范例映照为 jint 范例因为 jint 是 C/C++ 范例,以是能够间接看成平凡 C/C++ 变量利用,而没有必要依靠 JNIEnv 情况工具;。

• 援用数据范例:工具只会转换为一个 C/C++ 指针,比方 Object 范例映照为 jobject 范例因为指针指背 Java 假造机内乱部的数据布局,以是不成能间接正在 C/C++ 代码中操纵工具,而是必要依靠 JNIEnv 情况工具。

别的,为了不工具正在利用时忽然被接纳,正在当地要领返回前,假造时机牢固(pin)工具,制止其 GC别的必要特殊留意一面,底子数据范例正在映照时是间接映照,而没有会产生数据格局转换比方,Java char 范例正在映照为 jchar 后旧是连结 Java 层的模样,数据少度仍旧是 2 个字节,而字符编码仍旧是 UNT-16 编码。

详细映照干系皆界说正在jni.h头文件中,文件择要以下:jni.htypedefuint8_tjboolean; /* unsigned 8 bits */typedefint8_tjbyte; /* signed 8 bits */

typedefuint16_tjchar; /* unsigned 16 bits *//* 留意:jchar 是 2 个字节 */typedefint16_tjshort; /* signed 16 bits */

typedefint32_tjint; /* signed 32 bits */typedefint64_tjlong; /* signed 64 bits */typedeffloatjfloat;

/* 32-bit IEEE 754 */typedefdoublejdouble; /* 64-bit IEEE 754 */typedefjint jsize; # ifdef__cplusplus

// 内乱部的数据布局由假造机完成,只能从假造机源码看class_ jobject{ }; class_ jclass: public_jobject {}; class_ jstring: public

_jobject {}; class_ jarray: public_jobject {}; class_ jobjectArray: public_jarray {}; class_ jbooleanArray

: public_jarray {}; ... // 阐明我们打仗到到 jobject、jclass 实际上是一个指针typedef_jobject* jobject; typedef_jclass* jclass;

typedef_jstring* jstring; typedef_jarray* jarray; typedef_jobjectArray* jobjectArray; typedef_jbooleanArray* jbooleanArray;

... # else/* not __cplusplus */... # endif/* not __cplusplus */我将全部 Java 范例取 JNI 范例的映照干系总结为下表:Java 范例

JNI 范例形貌少度(字节)booleanjbooleanunsigned char1bytejbytesigned char1charjcharunsigned short2shortjshortsigned short

2intjint、jsizesigned int4longjlongsigned long8floatjfloatsigned float4doublejdoublesigned double8Class

jclassClass 类工具1Stringjstrting字符串工具/Objectjobject工具/Throwablejthrowable非常工具/boolean[]jbooleanArray布我数组

/byte[]jbyteArraybyte 数组/char[]jcharArraychar 数组/short[]jshortArrayshort 数组/int[]jinitArrayint 数组/long[]

jlongArraylong 数组/float[]jfloatArrayfloat 数组/double[]jdoubleArraydouble 数组/3.2 字符串范例操纵上里提到 Java 工具会映照为一个 jobject 指针,那末 Java 中的

java.lang.String字符串范例也会映照为一个 jobject 指针多是由于字符串的利用频次着实是太下了,以是 JNI 范例借特地界说了一个 jobject 的派死类 jstring 去表现 Java String 范例,那个相对特别。

jni.h// 内乱部的数据布局照旧看没有到,由假造机完成class_ jstring: public_jobject {}; typedef_jstring* jstring; structJNINativeInterface

{ // String 转换为 UTF-8 字符串constchar* (*GetStringUTFChars)(JNIEnv*, jstring, jboolean*); // 开释 GetStringUTFChars 天生的 UTF-8 字符串

void(*ReleaseStringUTFChars)(JNIEnv*, jstring, constchar*); // 结构新的 String 字符串jstring (*NewStringUTF)(JNIEnv*,

constchar*); // 获得 String 字符串的少度jsize (*GetStringUTFLength)(JNIEnv*, jstring); // 将 String 复制到预分派的 char* 数组中

void(*GetStringUTFRegion)(JNIEnv*, jstring, jsize, jsize, char*); }; 因为 Java 取 C/C++ 默许利用差别的字符编码,因而正在操纵字符数据时,必要特殊留意正在 UTF-16 和 UTF-8 两种编码之间转换。

闭于字符编码,我们正在 Unicode 和 UTF-8 是甚么干系? 那篇文章里会商过,那里便简朴回首一下:• Unicode:同一化字符编码尺度,为全球全部字符界说同一的码面,比方 U+0011; •

UTF-8:Unicode 尺度的完成编码之一,利用 1~4 字节的变少编码UTF-8 编码中的一字节编码取 ASCII 编码兼容 • UTF-16:Unicode 尺度的完成编码之一,利用 2 / 4 字节的变少编码。

UTF-16 是 Java String 利用的字符编码; • UTF-32:Unicode 尺度的完成编码之一,利用 4 字节定少编码 以下为 2 种较为常睹的转换场景:1、Java String 工具转换为 C/C++ 字符串:挪用。

GetStringUTFChars函数将一个 jstring 指针转换为一个 UTF-8 的 C/C++ 字符串,并正在没有再利用时挪用 ReleaseStringChars 函数开释内乱存;2、结构 Java String 工具:挪用

NewStringUTF函数结构一个新的 Java String 字符串工具我们间接看一段示例步伐:示例步伐// 示例 1:将 Java String 转换为 C/C++ 字符串jstring jStr = ...; 。

// Java 层通报过去的 Stringconstchar *str = env->GetStringUTFChars(jStr, JNI_FALSE); if(!str) { // OutOfMemoryError

return; } // 开释 GetStringUTFChars 天生的 UTF-8 字符串env->ReleaseStringUTFChars(jStr, str); // 示例 2:结构 Java String 工具(将 C/C++ 字符串转换为 Java String)

jstring newStr = env->NewStringUTF( "正在 Native 层结构 Java String"); if(newStr) { // 经由过程 JNIEnv 要领将 jstring 挪用 Java 要领(jstring 自己便是 Java String 的映照,能够间接通报到 Java 层)

... } 此处对 GetStringUTFChars 函数的第 3 个参数 isCopy 做表明:它是一个布我值参数,将决议利用拷贝形式照旧复用形式:1、 JNI_TRUE:利用拷贝形式,JVM 将拷贝一份本初数据去天生 UTF-8 字符串;

2、 JNI_FALSE:利用复用形式,JVM 将复用统一份本初数据去天生 UTF-8 字符串复用形式毫不能修正字符串内乱容,不然 JVM 中的本初字符串也会被修正,冲破 String 不成变性 别的另有一个基于范畴的转换函数:。

GetStringUTFRegion:预分派一块字符数组缓冲区,然后将 String 数据复制到那块缓冲区中因为那个函数自己没有会做任何内乱存分派,以是没有必要挪用对应的开释资本函数,也没有会扔出 OutOfMemoryError。

别的,GetStringUTFRegion那个函数会做越界查抄并扔出 StringIndexOutOfBoundsException非常示例步伐jstring jStr = ...; // Java 层通报过去的 String。

char outbuf[ 128]; intlen= env->GetStringLength(jStr); env->GetStringUTFRegion(jStr, 0, len, outbuf);

3.3 数组范例操纵取 jstring 的处置惩罚方法雷同,JNI 范例将 Java 数组界说为 jobject 的派死类 jarray :• 底子范例数组:界说为 jbooleanArray 、jintArray 等;

• 援用范例数组:界说为 jobjectArray 上面辨别底子范例数组和援用范例数组两种环境:操纵底子范例数组(以 jintArray 为例):1、 Java 根本范例数组转换为 C/C++ 数组:挪用

GetIntArrayElements 函数将一个 jintArray 指针转换为 C/C++ int 数组;2、 修正 Java 根本范例数组:挪用 ReleaseIntArrayElements 函数并利用形式 0;

3、 结构 Java 根本范例数组:挪用 NewIntArray 函数结构 Java int 数组我们间接看一段示例步伐:示例步伐extern"C"JNIEXPORT jintArray JNICALL 。

Java_com_xurui_hellojni_HelloWorld_generateIntArray(JNIEnv *env, jobject thiz, jint size){ // 新建 Java int[]

jintArray jarr = env->NewIntArray(size); // 转换为 C/C ++ int[]int*carr = env->GetIntArrayElements(jarr, JNI_FALSE);

// 赋值for( inti = 0; i ReleaseIntArrayElements(jarr, carr,

0); // 返回数组returnjarr; } 此处重面对 ReleaseIntArrayElements函数的第 3 个参数 mode 做表明:它是一个形式参数:参数 mode形貌0将 C/C++ 数组的数据回写到 Java 数组,并开释 C/C++ 数组

JNI_COMMIT将 C/C++ 数组的数据回写到 Java 数组,其实不开释 C/C++ 数组JNI_ABORT没有回写数据,但开释 C/C++ 数组别的 JNI 借供给了基于范畴函数:GetIntArrayRegion

和 SetIntArrayRegion,利用要领和留意事项和GetStringUTFRegion也是雷同的,也是基于一块预分派的数组缓冲区操纵援用范例数组(jobjectArray):1、 将 Java 援用范例数组转换为 C/C++ 数组。

:没有撑持!取根本范例数组差别,援用范例数组的元素 jobject 是一个指针,没有存正在转换为 C/C++ 数组的观点; 2、 修正 Java 援用范例数组:挪用 SetObjectArrayElement

函数修正指定下标元素;3、 结构 Java 援用范例数组:先挪用 FindClass函数获得 Class 工具,再挪用 NewObjectArray函数结构工具数组我们间接看一段示例步伐:示例步伐extern。

"C"JNIEXPORT jobjectArray JNICALL Java_com_xurui_hellojni_HelloWorld_generateStringArray(JNIEnv *env, jobject thiz, jint size)

{ // 获得 String Classjclass jStringClazz = env->FindClass( "java/lang/String"); // 初初值(可为空)jstring initialStr = env->NewStringUTF(

"初初值"); // 创立 Java String[]jobjectArray jarr = env->NewObjectArray(size, jStringClazz, initialStr); // 赋值

for( inti = 0; i NewStringUTF(str);

env->SetObjectArrayElement(jarr, i, jStr); } // 返回数组returnjarr; } 4 JNI 拜候 Java 字段取要领 那一节我们去会商怎样从 Native 层拜候 Java 的字段取要领。

正在开端拜候前,JNI 起首要找到念拜候的字段和要领,那便依赖字段形貌符和要领形貌符4.1 字段形貌符取要领形貌符正在 Java 源码中界说的字段和要领,正在编译后都市根据既定的规矩记载正在 Class 文件中的字段表和要领表布局中。

比方,一个 public String str; 字段会被拆分为字段拜候标识表记标帜(public)、字段简朴称号(str)和字段形貌符(Ljava/lang/String)因而,从 JNI 拜候 Java 层的字段或要领时,起首便是要获得正在 Class 文件中记载的简朴称号和形貌符。

Class 文件的一级布局:字段表布局:包罗字段的拜候标识表记标帜、简朴称号、字段形貌符等信息比方字段 String str 的简朴称号为 str,字段形貌符为 Ljava/lang/String;要领表布局:包罗要领的拜候标识表记标帜、简朴称号、要领形貌符等信息。

比方要领 void fun; 的简朴称号为 fun,要领形貌符为V4.2 形貌符规矩• 字段形貌符:字段形貌符实在便是形貌字段的范例,JVM 对每种底子数据范例界说了牢固的形貌符,而援用范例则是以 L 开首的情势:

Java 范例形貌符booleanZbyteBcharCshortSintIlongJfloagFdoubleDvoidV援用范例以 L 开首 ; 末端,中心是 / 分开的包名和类名比方 String 的字段形貌符为 Ljava/lang/String;。

• 要领形貌符:要领形貌符实在便是形貌要领的返回值范例和参数表范例,参数范例用一对圆括号括起去,根据参数声明挨次枚举参数范例,返回值呈现正在括号背面比方要领 void fun; 的简朴称号为 fun,要领形貌符为。

V4.3 JNI 拜候 Java 字段当地代码拜候 Java 字段的流程分为 2 步:1、 经由过程 jclass 获得字段 ID,比方: Fid = env->GetFieldId(clz, "name", "Ljava/lang/String;");

行动不断,风韵绰约。球鞋是潮流潮流的代表,也是活动生存的必备品。正在我们的球鞋网站上,您将找到最新最潮的球鞋格局,从典范款到限量款,从活动鞋到潮流潮鞋,满意您的种种需供。我们供给下品格球鞋,确保您正在活动和一样平常生存皆能感觉到最舒服的穿戴体验。

2、 经由过程字段 ID 拜候字段,比方: Jstr = env->GetObjectField(thiz, Fid);Java 字段分为静态字段和真例字段,相干要领以下:• GetFieldId:获得真例要领的字段 ID。

• GetStaticFieldId:获得静态要领的字段 ID • GetField:获得范例为 Type 的真例字段(比方 GetIntField)• SetField:设置范例为 Type 的真例字段(比方 。

SetIntField)• GetStaticField:获得范例为 Type 的静态字段(比方 GetStaticIntField)• SetStaticField:设置范例为 Type 的静态字段(比方 。

SetStaticIntField)示例步伐extern"C"JNIEXPORT voidJNICALL Java_com_xurui_hellojni_HelloWorld_accessField(JNIEnv *env, jobject thiz)

{ // 获得 jclassjclass clz = env->GetObjectClass(thiz); // 示例:修正 Java 静态变量值// 静态字段 IDjfieldID sFieldId = env->GetStaticFieldID(clz,

"sName", "Ljava/lang/String;"); // 拜候静态字段if(sFieldId) { // Java 要领的返回值 String 映照为 jstringjstring jStr =

static_cast(env->GetStaticObjectField(clz, sFieldId)); // 将 jstring 转换为 C 气势派头字符串constchar*sStr = env->GetStringUTFChars(jStr, JNI_FALSE);

// 开释资本env->ReleaseStringUTFChars(jStr, sStr); // 结构 jstringjstring newStr = env->NewStringUTF( "静态字段 - Peng"

); if(newStr) { // jstring 自己便是 Java String 的映照,能够间接通报到 Java 层env->SetStaticObjectField(clz, sFieldId, newStr);

} } // 示例:修正 Java 成员变量值// 真例字段 IDjfieldID mFieldId = env->GetFieldID(clz, "mName", "Ljava/lang/String;"

); // 拜候真例字段if(mFieldId) { jstring jStr = static_cast(env->GetObjectField(thiz, mFieldId));

// 转换为 C 字符串constchar*sStr = env->GetStringUTFChars(jStr, JNI_FALSE); // 开释资本env->ReleaseStringUTFChars(jStr, sStr);

// 结构 jstringjstring newStr = env->NewStringUTF( "真例字段 - Peng"); if(newStr) { // jstring 自己便是 Java String 的映照,能够间接通报到 Java 层

env->SetObjectField(thiz, mFieldId, newStr); } } } 4.4 JNI 挪用 Java 要领当地代码拜候 Java 要领取拜候 Java 字段雷同,拜候流程分为 2 步:

1、 经由过程 jclass 获得「要领 ID」,比方: Mid = env->GetMethodID(jclass, "helloJava", "V");2、 经由过程要领 ID 挪用要领,比方: env->CallVoidMethod(thiz, Mid);

Java 要领分为静态要领和真例要领,相干要领以下:• GetMethodId:获得真例要领 ID • GetStaticMethodId:获得静态要领 ID • CallMethod:挪用返回范例为 Type 的真例要领(比方 。

GetVoidMethod)• CallStaticMethod:挪用返回范例为 Type 的静态要领(比方 CallStaticVoidMethod)• CallNonvirtualMethod:挪用返回范例为 Type 的女类要领(比方

CallNonvirtualVoidMethod)示例步伐extern "C"JNIEXPORT void JNICALL Java_com_xurui_hellojni_HelloWorld_accessMethod(JNIEnv *env, jobject thiz) { 。

// 获得 jclassjclass clz = env->GetObjectClass(thiz); // 示例:挪用 Java 静态要领// 静态要领 IDjmethodID sMethodId = env->GetStaticMethodID(clz,

"sHelloJava", "V"); if(sMethodId) { env->CallStaticVoidMethod(clz, sMethodId); } // 示例:挪用 Java 真例要领// 真例要领 ID

jmethodID mMethodId = env->GetMethodID(clz, "helloJava", "V"); if(mMethodId) { env->CallVoidMethod(thiz, mMethodId);

} } 4.5 缓存 ID拜候 Java 层字段或要领时,必要先使用字段名 / 要领名和形貌符举行检索,得到 jfieldID / jmethodID那个检索历程比力耗时,劣化要领是将字段 ID 和要领 ID 缓存起去,淘汰反复检索。

提醒:从差别线程中获得统一个字段或要领 的 ID 是雷同的,缓存 ID 没有会有多线程Question缓存字段 ID 和 要领 ID 的要领重要有 2 种:1、 利用时缓存:利用时缓存是指正在初次拜候字段或要领时,将字段 ID 或要领 ID 存储正在静态变量中。

如许未来再次挪用当地要领时,便没有必要反复检索 ID 了比方: 2、 类初初化时缓存:静态初初化时缓存是指正在 Java 类初初化的时间,提早缓存字段 ID 和要领 ID能够挑选正在 JNI_要领中缓存,也能够正在减载 so 库后挪用一个 native 要领举行缓存。

两种缓存 ID 方法的重要区分正在于缓存产生的机遇和时效性:1、 机遇差别:利用时缓存是耽误按需缓存,只要正在初次拜候 Java 时才会获得 ID 并缓存,而类初初化时缓存是提早缓存; 2、 时效性差别:利用时缓存的 ID 正在类卸载后生效,正在类卸载后不克不及利用,而类减载时缓存正在每次减载 so 静态库时会从头更新缓存,因而缓存的 ID 是连结有用的。

5 JNI 中的工具援用办理 5.1 Java 和 C/C++ 中工具内乱存接纳区分(重面明白)正在会商 JNI 中的工具援用办理,我们先回首一下 Java 和 C/C++ 正在工具内乱存接纳上的区分:• Java

:工具正在堆 / 要领区上分派,由渣滓接纳器扫描工具可达性举行接纳假如利用部分变量指背工具,正在没有再利用工具时能够脚动隐式置空,也能够比及要领返回时主动隐式置空假如利用齐局变量(static)指背工具,正在没有再利用工具时必需脚动隐式置空。

• C/C++:栈上分派的工具会正在要领返回时主动接纳,而堆上分派的工具没有会跟着要领返回而接纳,也出有渣滓接纳器办理,因而必需脚动接纳(free/delete) 而 JNI 层做为 Java 层和 C/C++ 层之间的桥接层,那末它便会兼具二者的特性:对付。

• 部分 Java 工具援用:正在 JNI 层能够经由过程 NewObject 等函数创立 Java 工具,而且返回工具的援用,那个援用便是 Local 型的部分援用对付部分援用,能够经由过程 DeleteLocalRef 。

函数脚动隐式开释(那雷同于正在 Java 中隐式置空部分变量),也能够比及函数返回时主动开释(那雷同于正在 Java 中要领返回时隐式置空部分变量);• 齐局 Java 工具援用:因为部分援用正在函数返回后必然会开释,能够经由过程

NewGlobalRef函数将部分援用晋级为 Global 型齐局变量,如许就能够正在要领利用工具(那雷同于正在 Java 中利用 static 变量指背工具)正在没有再利用工具时必需挪用 DeleteGlobalRef 。

函数开释齐局援用(那雷同于正在 Java 中隐式置空 static 变量)提醒:我们那里所道的 ”置空“ 只是将指背变量的值赋值为 null,而没有是接纳工具,Java 工具接纳是交给渣滓接纳器处置惩罚的5.2 JNI 中的三种援用

1、 部分援用:年夜部门 JNI 函数会创立部分援用,部分援用只要正在创立援用的当地要领返回前有用,也只正在创立部分援用的线程中有用正在要领返回后,部分援用会主动开释,也能够经由过程 DeleteLocalRef 。

函数脚动开释;2、 齐局援用:部分援用要跨要领和跨线程必需晋级为齐局援用,齐局援用经由过程 NewGlobalRef 函数创立,没有再利用工具时必需经由过程DeleteGlobalRef 函数开释3、强齐局援用:强援用取齐局援用雷同,区分正在于强齐局援用没有会持有强援用,因而没有会制止渣滓接纳器接纳援用指背的工具。

强齐局援用经由过程 NewGlobalWeakRef 函数创立,没有再利用工具时必需经由过程DeleteGlobalWeakRef 函数开释示例步伐// 部分援用jclass localRefClz = env->FindClass( 。

"java/lang/String"); env->DeleteLocalRef(localRefClz); // 齐局援用jclass globalRefClz = env->NewGlobalRef(localRefClz);

env->DeleteGlobalRef(globalRefClz); // 强齐局援用jclass weakRefClz = env->NewWeakGlobalRef(localRefClz); env->DeleteGlobalWeakRef(weakRefClz);

5.3 JNI 援用的完成道理正在 JavaVM 和 JNIEnv 中,会别离创建多个表办理援用:• JavaVM 内乱有 globals 和 weak_globals 两个表办理齐局援用和强齐局援用因为 JavaVM 是历程同享的,因而齐局援用能够跨要领和跨线程同享;。

• JavaEnv 内乱有 locals 表办理部分援用,因为 JavaEnv 是线程独有的,因而部分援用不克不及跨线程别的假造机正在进进和退出当地要领经由过程 Cookie 信息记载哪些部分援用是正在哪些当地要领中创立的,因而部分援用是不克不及跨要领的。

5.4 比力援用能否指背雷同工具可使用 JNI 函数IsSameObject 判定两个援用能否指背雷同工具(实用于三种援用范例),返回值为JNI_TRUE时表现雷同,返回值为 JNI_FALSE表现差别。

比方:示例步伐jclass localRef = ...jclass globalRef = ...bool isSampe = env->IsSamObject(localRef, globalRef)

别的,当援用取 NULL 比力时寄义略有差别:• 部分援用和齐局援用取 NULL 比力:用于判定援用能否指背 NULL 工具;• 强齐局援用取 NULL 比力:用于判定援用指背的工具能否被接纳6 JNI 中的非常处置惩罚 。

6.1 JNI 的非常处置惩罚机制(重面明白)JNI 中的非常机制取 Java 和 C/C++ 的处置惩罚机制皆差别:• Java 和 C/C++:步伐利用要害字 throw 扔出非常,假造时机停止当前实行流程,转而来探求婚配的

catch{} 块,大概继承背中层扔出探求婚配 catch {} 块• JNI:步伐利用 JNI 函数 ThrowNew 扔出非常,步伐没有会停止当前实行流程,而是返回 Java 层后,假造机才会扔出那个非常。

因而,正在 JNI 层呈现非常时,有 2 种处置惩罚挑选:要领 1:间接 return 当火线法,让 Java 层行止理那个非常(那雷同于正在 Java 中背要领中层扔出非常);要领 2:经由过程 JNI 函数 ExceptionClear

扫除那个非常,再实行非常处置惩罚步伐(那雷同于正在 Java 中 try-catch 处置惩罚非常)必要留意的是,当非常产生时,必需先处置惩罚-扫除非常,再实行其他 JNI 函数挪用 由于当运转情况存正在已处置惩罚的非常时,只能挪用 2 种 JNI 函数:非常照顾护士函数和清算资本函数。

JNI 供给了以下取非常处置惩罚相干的 JNI 函数:• ThrowNew:背 Java 层扔出非常;• ExceptionDescribe:打印非常形貌信息;• ExceptionOccurred:查抄当前情况能否产生非常,假如存正在非常则返回该非常工具;。

• ExceptionCheck:查抄当前情况能否产生非常,假如存正在非常则返回JNI_TRUE,不然返回JNI_FALSE;• ExceptionClear:扫除当前情况的非常jni.hstructJNINativeInterface

{ // 扔出非常jint (*ThrowNew)(JNIEnv *, jclass, constchar*); // 查抄非常jthrowable (*ExceptionOccurred)(JNIEnv*);

// 查抄非常jboolean (*ExceptionCheck)(JNIEnv*); // 扫除非常void(*ExceptionClear)(JNIEnv*); }; 示例步伐// 示例 1:背 Java 层扔出非常

jclass exceptionClz = env->FindClass( "java/lang/IllegalArgumentException"); env->ThrowNew(exceptionClz,

"去自 Native 的非常"); // 示例 2:查抄当前情况能否产生非常(雷同于 Java try{})jthrowable exc = env->ExceptionOccurred(env); if

(exc) { // 处置惩罚非常(雷同于 Java 的 catch{})} // 示例 3:扫除非常env->ExceptionClear; 6.2 查抄能否产生非常的方法非常处置惩罚的步调我懂了,因为假造机正在碰到 ThrowNew 时没有会停止当前实行流程,那我怎样明白当前曾经产生非常呢?有 2 种要领:

要领 1:经由过程函数返回值毛病码,年夜部门 JNI 函数和库函数都市有特定的返回值去标示毛病,比方 -1、NULL 等正在步伐流程中能够多查抄函数返回值去判定非常要领 2:经由过程 JNI 函数ExceptionOccurred。

或ExceptionCheck查抄当前能否有非常产生7 JNI 取多线程 那一节我们去会商 JNI 层中的多线程操纵7.1 不克不及跨线程的援用正在 JNI 中,有 2 类援用是没法跨线程挪用的,必需时候服膺:。

• JNIEnv:JNIEnv 只正在地点的线程有用,正在差别线程中挪用 JNI 函数时,必需利用该线程特地的 JNIEnv 指针,不克不及跨线程通报和利用经由过程 AttachCurrentThread 函数将当火线程依靠到 JavaVM 上,得到属于当火线程的 JNIEnv 指针。

假如当火线程曾经依靠到 JavaVM,也能够间接利用GetEnv函数示例步伐JNIEnv * env_child; vm->AttachCurrentThread(&env_child, nullptr); 。

// 利用 JNIEnv*vm->DetachCurrentThread; • 部分援用:部分援用只正在创立的线程和要领中有用,不克不及跨线程利用能够将部分援用晋级为齐局援用后跨线程利用示例步伐// 部分援用。

jclass localRefClz = env->FindClass( "java/lang/String"); // 开释齐局援用(非必需)env->DeleteLocalRef(localRefClz);

// 部分援用晋级为齐局援用jclass globalRefClz = env->NewGlobalRef(localRefClz); // 开释齐局援用(必需)env->DeleteGlobalRef(globalRefClz);

7.2 监督器同步正在 JNI 中也会存正在多个线程同时拜候一个内乱存资本的环境,此时必要包管并发宁静正在 Java 中我们会经由过程synchronized 要害字去完成互斥块(背后是利用监督器字节码),正在 JNI 层也供给了雷同结果的 JNI 函数:。

• MonitorEnter:进进同步块,假如另外一个线程曾经进进该 jobject 的监督器,则当火线程会壅闭; • MonitorExit:退出同步块,假如当火线程已进进该 jobject 的监督器,则会扔出

IllegalMonitorStateException 非常jni.hstructJNINativeInterface { jint(*MonitorEnter)(JNIEnv*, jobject); 。

jint(*MonitorExit)(JNIEnv*, jobject); } 示例步伐// 进进监督器if(env->MonitorEnter(obj) != JNI_OK) { // 创建监督器的资本分派没有乐成等

} // 此处为同步块if(env->ExceptionOccurred) { // 必需包管有对应的 MonitorExit,不然大概呈现逝世锁if(env->MonitorExit(obj) != JNI_OK) {

... }; return; } // 退出监督器if(env->MonitorExit(obj) != JNI_OK) { ... }; 7.3 等候取叫醒JNI 出有供给 Object 的wati/notify

相干功效的函数,必要经由过程 JNI 挪用 Java 要领的方法去完成:示例步伐staticjmethodID MID_Object_wait; staticjmethodID MID_Object_notify;

staticjmethodID MID_Object_notifyAll; voidJNU_MonitorWait( JNIEnv *env, jobject object, jlong timeout

) { env->CallVoidMethod( object, MID_Object_wait, timeout); } voidJNU_MonitorNotify( JNIEnv *env, jobject

object) { env->CallVoidMethod( object, MID_Object_notify); } voidJNU_MonitorNotifyAll( JNIEnv *env, jobject

object) { env->CallVoidMethod( object, MID_Object_notifyAll); } 7.4 创立线程的要领正在 JNI 开辟中,有两种创立线程的方法:要领 1 - 经由过程 Java API 创立

:利用我们认识的Thread#start 能够创立线程,长处是能够便利天设置线程称号和调试;要领 2 - 经由过程 C/C++ API 创立:利用pthread_create 或std::thread也能够创立线程。

示例步伐// void* thr_fn( void*arg) { printids( "new thread: "); returnNULL; } intmain( void) { pthread_tntid;

// 第 4 个参数将通报到 thr_fn 的参数 arg 中err = pthread_create(&ntid, NULL, thr_fn, NULL); if(err != 0) { printf

( "cant create thread: %s\n", strerror(err)); } return0; } 8 通用 JNI 开辟模板 言而不行假把势,以下给出一个简朴的 JNI 开辟模板,将包罗上文提到的一些比力紧张的常识面。

步伐逻辑很简朴:Java 层通报一个媒体文件途径到 Native 层后,由 Native 层播放媒体并回调到 Java 层为了步伐简化,全部实在的媒体播放代码皆移除,只保存模板代码• Java 层:由

start要领开端,挪用startNative 要领进进 Native 层;• Native 层:创立 MediaPlayer 工具,此中正在子线程播放媒体文件,并经由过程预先持有的 JavaVM 指针获得子线程的 JNIEnv 工具回调到 Java 层

onStarted 要领MediaPlayer.kt// Java 层模板classMediaPlayer{ companionobject{ init { // 留意面:减载 so 库System.loadLibrary( 。

"hellondk") } } // Native 层指针privatevarnativeObj: Long? = nullfunstart(path : String) { // 留意面:记载 Native 层指针,后绝操纵才气拿到 Native 的工具

nativeObj = startNative(path) } funrelease{ // 留意面:利用 start 中记载的指针挪用 native 要领nativeObj?.let { releaseNative(it)

} nativeObj = null} privateexternal funstartNative(path : String) : Longprivateexternal funreleaseNative

(nativeObj: Long) funonStarted{ // Native 层回调(去自 JNICallbackHelper#onStarted)... } } native-lib.cpp// 留意面:记载 JavaVM 指针,用于正在子线程得到 JNIEnv

JavaVM *vm = nullptr; jint JNI_(JavaVM *vm, void*args) { ::vm = vm; returnJNI_VERSION_1_6; } extern"C"

JNIEXPORT jlong JNICALL Java_com_pengxr_hellondk_MediaPlayer_startNative(JNIEnv *env, jobject thiz, jstring path)

{ // 留意面:String 转 C 气势派头字符串constchar*path_ = env->GetStringUTFChars(path, nullptr); // 结构一个 Native 工具auto

*helper = newJNICallbackHelper(vm, env, thiz); auto*player = newMediaPlayer(path_, helper); player->start;

// 返回 Native 工具的指针returnreinterpret_cast(player); } extern"C"JNIEXPORT voidJNICALL Java_com_pengxr_hellondk_MediaPlayer_releaseNative

(JNIEnv *env, jobject thiz, jlong native_obj){ auto* player = reinterpret_cast(native_obj);

player->release; } JNICallbackHelper.h# ifndefHELLONDK_JNICALLBACKHELPER_H # defineHELLONDK_JNICALLBACKHELPER_H

# include# include"util.h"classJNICallbackHelper{ private: // 齐局同享的 JavaVM*// 留意面:指针要初初化 0 值JavaVM *vm =

0; // 主线程的 JNIEnv*JNIEnv *env = 0; // Java 层的工具 MediaPlayer.ktjobject job; // Java 层的要领 MediaPlayer#onStarted

jmethodID jmd_prepared; public: JNICallbackHelper(JavaVM *vm, JNIEnv *env, jobject job); ~JNICallbackHelper;

voidonStarted; }; # endif//HELLONDK_JNICALLBACKHELPER_HJNICallbackHelper.cpp# include"JNICallbackHelper.h"

JNICallbackHelper::JNICallbackHelper(JavaVM *vm, JNIEnv *env, jobject job) { // 齐局同享的 JavaVM*this->vm = vm;

// 主线程的 JNIEnv*this->env = env; // C 回调 Javajclass mediaPlayerKTClass = env->GetObjectClass(job); jmd_prepared = env->GetMethodID(mediaPlayerKTClass,

"onPrepared", "V"); // 留意面:jobject 没法超过线程,必要转换为齐局援用// Error:this->job = job;this->job = env->NewGlobalRef(job);

} JNICallbackHelper::~JNICallbackHelper { vm = nullptr; // 留意面:开释齐局援用env->DeleteGlobalRef(job); job =

nullptr; env = nullptr; } voidJNICallbackHelper:nStarted { // 留意面:子线程不克不及间接利用持有的主线程 env,必要经由过程 AttachCurrentThread 获得子线程的 env

JNIEnv * env_child; vm->AttachCurrentThread(&env_child, nullptr); // 回调 Java 要领env_child->CallVoidMethod(job, jmd_prepared);

vm->DetachCurrentThread; } MediaPlayer.h# ifndefHELLONDK_MEDIAPLAYER_H # defineHELLONDK_MEDIAPLAYER_H

# include# include# include"JNICallbackHelper.h"classMediaPlayer{ private: char*path =

0; JNICallbackHelper *helper = 0; pthread_tpid_start; public: MediaPlayer( constchar*path, JNICallbackHelper *helper);

~MediaPlayer; voiddoOpenFile; voidstart; voidrelease; }; # endif//HELLONDK_MEDIAPLAYER_HMediaPlayer.cpp

# include"MediaPlayer.h"MediaPlayer::MediaPlayer( constchar*path, JNICallbackHelper *helper) { // 留意面:参数 path 指背的空间被接纳会形成悬空指针,应复制一份

// this->path = path;this->path = newchar[ strlen(path) + 1]; strcpy( this->path, path); this->helper = helper;

} MediaPlayer::~MediaPlayer { if(path) { deletepath; } if(helper) { deletehelper; } } // 正在子线程实行voidMediaPlayer::doOpenFile {

// 省略实在播放逻辑...// 媒体文件翻开乐成helper->onStarted; } // 正在子线程实行void* task_open( void*args) { // args 是 主线程 MediaPlayer 的真例的 this变量

auto*player = static_cast(args); player->doOpenFile; returnnullptr; } voidMediaPlayer::start {

// 切换到子线程实行pthread_create(&pid_start, 0, task_open, this); } voidMediaPlayer::release { ... } 9 总结 到那里,JNI 的常识便讲完了,您能够根据进修门路图去看。

下一篇,我们开端讲 Android NDK 开辟存眷我,带您创建焦点合作力,我们下次睹参考材料《JNI 编程指北》JNI 提醒—— Android 民圆文档https://developer.android.谷歌.cn/training/articles/perf-jni

Java 本死接心范例 —— Java 民圆文档https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/jniTOC.html深化明白 Android:卷 1(第 2 章 · 深化明白 JNI)—— 邓凡是平 著

https://weread.qq.com/web/reader/e3d32fb0593388e3dde8006k9bf32f301f9bf31c7ff0a60深化明白 Android:Java 假造机 ART(第 11 章 · ART 中的 JNI)—— 邓凡是平 著

https://weread.qq.com/web/reader/3ee32e60717f5af83ee7b37Android 使用宁静防护和顺背阐发(底子篇) —— 姜维 著https://weread.qq.com/web/bookDetail/6ef32f805e0b836efa707cb

Java 机能权势巨子指北:第 2 版(第 12.5 节:Java 本死接心)—— [好]Scott Oaks 著https://weread.qq.com/web/reader/f5d32af0729dd0d6f5d1789kf03328d0250f033ab37c722

Android 对 so 体积劣化的探究取理论 —— 洪凯 常强(好团技能团队)著末了保举一下我做的网站,玩Android: wanandroid.com,包罗细致的常识系统、好用的东西,另有本民众号文章合散,接待体验和保藏!

: Android硬键盘弹起交互的几种计划,怎样做结果最丝滑? 裸辞-闭闭-温习-年夜厂offer(两) Android暗影完成的几种计划 面击存眷我的民众号 假如您念要跟各人分享您的文章,接待投稿~┏(^0^)┛来日诰日睹!

椰子350v2球鞋网聚合最新SNEAKER资讯和发售信息,包罗最新的潮流新闻、球鞋发售信息、SNEAKER喜好者交换平台。
回复

使用道具 举报

1

主题

1万

帖子

26

积分

新手上路

Rank: 1

积分
26
发表于 2023-5-10 10:08:41 | 显示全部楼层
哥们,我就是刷到你,一直在纠结这件毛衣买不买,感觉还可以,但是1k实在是离谱了
回复

使用道具 举报

1

主题

1万

帖子

368

积分

中级会员

Rank: 3Rank: 3

积分
368
发表于 2023-5-27 13:00:52 | 显示全部楼层
新百伦中国生产厂家
回复

使用道具 举报

2

主题

1万

帖子

10

积分

新手上路

Rank: 1

积分
10
发表于 2023-6-2 16:11:19 | 显示全部楼层
nike正品查询扫一扫查到了就是正品吗
回复

使用道具 举报

3

主题

1万

帖子

32

积分

新手上路

Rank: 1

积分
32
发表于 2023-7-10 00:45:12 | 显示全部楼层
converse官网旗舰店
回复

使用道具 举报

0

主题

1万

帖子

1580

积分

金牌会员

Rank: 6Rank: 6

积分
1580
发表于 2023-7-24 10:46:02 | 显示全部楼层
nike防伪二维码查询
回复

使用道具 举报

1

主题

1万

帖子

19

积分

新手上路

Rank: 1

积分
19
发表于 2023-7-26 04:46:30 | 显示全部楼层
上半身挺帅
回复

使用道具 举报

0

主题

1万

帖子

1531

积分

金牌会员

Rank: 6Rank: 6

积分
1531
发表于 2023-8-23 02:58:15 | 显示全部楼层
匡威鞋子查询
回复

使用道具 举报

0

主题

1万

帖子

1492

积分

金牌会员

Rank: 6Rank: 6

积分
1492
发表于 2023-9-17 20:57:12 | 显示全部楼层
阿迪达斯官网椰子鞋怎么抢
回复

使用道具 举报

0

主题

1万

帖子

1580

积分

金牌会员

Rank: 6Rank: 6

积分
1580
发表于 2023-12-22 02:45:32 | 显示全部楼层
aj2023春季新鞋
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

0

关注

0

粉丝

37112

主题
  • 联系我们
  • 邮箱:1595675868#qq.com(请把#改成@)
  • 电话:0000-000000
  • QQ客服 1595675868
  • 工作时间:周一至周五(早上9点至下午5点)
  • 微信公众平台

  • 扫描访问手机版

Archiver|手机版|小黑屋|椰子350v2球鞋网 |网站地图

GMT+8, 2024-10-2 01:26 , Processed in 0.365889 second(s), 55 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

  • QQ: 1595675868

    客服电话

    400-8888-8888

    电子邮件

    admin@admin.com

    在线时间:8:00-16:00