首页 文章 精选 留言 我的

精选列表

搜索[android studio],共10000篇文章
优秀的个人博客,低调大师

Android获取设备屏幕宽高pix值的两个方法

private void get1() { Resources resources = this.getResources(); DisplayMetrics dm = resources.getDisplayMetrics(); int width = dm.widthPixels; int height = dm.heightPixels; Log.d("方法1", width + " , " + height); } private void get2() { WindowManager manager = this.getWindowManager(); DisplayMetrics outMetrics = new DisplayMetrics(); manager.getDefaultDisplay().getMetrics(outMetrics); int width = outMetrics.widthPixels; int height = outMetrics.heightPixels; Log.d("方法2", width + " , " + height); } 输出结果一致: 04-20 10:11:41.513 27052-27052/zhangphil.test D/方法1: 1080 , 2040 04-20 10:11:41.514 27052-27052/zhangphil.test D/方法2: 1080 , 2040

优秀的个人博客,低调大师

09.源码阅读(从Android源码角度入手自己实现热修复)

首先我们要知道Activity是如何启动的,在文章https://www.jianshu.com/p/bd5208574430中我们已经看了Activity启动的源码,https://www.jianshu.com/p/40f436881390 ClassLoader加载类的源码,这里再简单回顾一下 Activity加载流程.png 看了Activity的启动流程和ClassLoader加载类的方式之后,我们知道,当一个Activity要启动的时候,最终其实是通过从BaseDexClassLoader中的一个DexPathList类中的dexElements数组中取出来,反射得到对象返回的 那么当开发中出现了问题,比如一个Activity中发生了bug,我们要紧急修复这个问题,同时又不需要应用重启,该怎么做呢,热修复的解决方案有几个可选的阿里 AndFix(不再维护了,并且不支持8.0及以上,不建议使用),腾讯Tinker(需要重启才能生效)。不过今天我们不使用第三方的实现,只从源码方面找答案 关键点就在于dexElements这个数组,经过我们的分析,我们已经知道,所有的类的dexFile文件在应用启动时就已经被保存的这个数组中了,包括发生了错误的文件类,当类被加载的时候,会从前往后遍历这个数组,找到对应的class然后反射得到对象,那么我们的解决办法是,能否将正确的文件类插入到这个数组中,当这个类启动的时候,先加载到我们修复的正确的dexFile文件,从而达到修复的目的,要使用的技术就是反射 实现原理借鉴了腾讯的Tinker,不过Tinker核心思想是利用DexDiff算法对比差异生成Patch补丁包,将生成的差异dex文件插入dexElements,而我们做的却少了生成差分包的过程,是将整个dex文件插入替换,Tinker原理图如下: 20170630144819943.png 我们当前要实现的思路就是现上有一个发生bug的app有待修复,我们在线下生成一个修复后的apk,将其后缀改为.zip,然后解压打开,得到里边的classes.dex文件,客户端下载这个修复的dex到本地,然后将这个dex插入本地apk的dex数组中实现修复bug的功能。 构造方法执行,初始化dex文件的存储位置 public FixDexManager(Context context) { this.mContext = context; //获取系统能够访问的dex目录 this.mDexDir = context.getDir("odex",Context.MODE_PRIVATE); } 遍历所有dex文件,存入集合中,这样做也是仿照了AndFix的处理方式,目录下可能存在多个dex文件,所以我们需要将他们都放入集合中,然后开始修复 public void loadFixDex() throws Exception{ File[] dexFiles = mDexDir.listFiles(); List<File> fixDexFiles = new ArrayList<>(); for (File dexFile : dexFiles) { if (dexFile.getName().endsWith(".dex")){ fixDexFiles.add(dexFile); } } fixDexFiles(fixDexFiles); } 进入fixDexFiles方法 加载到程序已经运行的dexElements数组,这个数组包括存在问题的类,然后通过BaseDexClassLoader加载得到我们修复后的dex文件中的dexElements数组,最终将这个正确的dexElements数组插入到程序已经运行的dexElements中,从而当程序要启动一个类的时候,会从数组中获取到正确的类,达到修复bug的目的 private void fixDexFiles(List<File> fixDexFiles) throws Exception{ //1.先获取已经运行的dexElement ClassLoader applicationClassLoader = mContext.getClassLoader(); //Element数组对象 Object applicationDexElements = getDexElementByClassLoader(applicationClassLoader); File optimizedDirectory = new File(mDexDir,"odex"); if (!optimizedDirectory.exists()){ optimizedDirectory.mkdirs(); } //修复 for (File fixDexFile : fixDexFiles) { //参数: // String dexPath, dex路径 // File optimizedDirectory, // String librarySearchPath, so文件位置 // ClassLoader parent 父classloader ClassLoader fixDexClassLoader = new BaseDexClassLoader( fixDexFile.getAbsolutePath(),//dex路径,必须要在应用目录下的odex文件中 optimizedDirectory, null, applicationClassLoader ); Object fixDexElements = getDexElementByClassLoader(fixDexClassLoader); //3.将下载的dex插入到已经运行的dexElement的最前边,合并 //applicationClassLoader 数组合并fixDexElements数组 applicationDexElements = combineArray(fixDexElements, applicationDexElements); //把合并的数组注入到原来的类中 applicationClassLoader injectDexElements(applicationClassLoader, applicationDexElements); } } 通过反射再次获取到dexElements这个数组的Field,和它所在DexPathList的对象,注入即可 //把dexElements注入到classLoader中 private void injectDexElements(ClassLoader classLoader, Object dexElements) throws Exception{ //先获取pathList Field pathListField = BaseDexClassLoader.class.getDeclaredField("pathList"); pathListField.setAccessible(true); Object pathList = pathListField.get(classLoader); //获取pathList中的dexElements Field dexElementsField = pathList.getClass().getDeclaredField("dexElements"); dexElementsField.setAccessible(true); //利用反射进行注入 dexElementsField.set(pathList,dexElements); } 使用方法: 在Application启动的时候初始化并调用修复方法,这种实现方式有很大的问题,因为是将修复后的dex插入原来的dex数组,以保证加载类的时候可以加载到正确的类,那么这种情况下,如果当前bug页面已经加载出来,这时候再通过这种方式注入dex是不会起作用的,必须在启动bug类之前将dex注入,否则如果不重启,不会有效果。所以这种方式实现的热修复必须要重启生效。 FixDexManager manager = new FixDexManager(this); //加载所有修复的dex包,第一次的时候会从服务器上下载到修复的dex包并放在我们制定的目录下, //第二次进入的时候,直接读取保存的文件进行修复 try { manager.loadFixDex(); File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath(),"fix.apatch"); if (file.exists()){ try { manager.fixDex(file.getAbsolutePath()); Toast.makeText(getApplicationContext(),"修复bug成功",Toast.LENGTH_SHORT).show(); } catch (Exception e) { Toast.makeText(getApplicationContext(),"修复bug失败",Toast.LENGTH_SHORT).show(); e.printStackTrace(); } } } catch (Exception e) { e.printStackTrace(); } 总结一下: 通过这种方式实现热修复,需要重启app,它的缺点也很明显,由于是将整个dex文件注入到原来dex数组中,会使app内存占用增大一倍左右,我这边亲测,原本占空间10.8M的app,注入新的dex后,内存占用变成了19.8M,可以考虑分包的方案解决这个问题,减少体积。

优秀的个人博客,低调大师

Android NDK开发系列教程3:基本方法调用及传参(续)

终于建了一个自己个人小站:https://huangtianyu.gitee.io,以后优先更新小站博客,欢迎进站,O(∩_∩)O~~ 上一节主要讲解Java向native传参,下面主要讲解从native传相应的数据到java层。 接着上一节,下面主要讲解内容如下: 1. native向java返回字符串类型 2. native向java返回java对象 3. native向java返回数组类型 4. native向Java返回List对象 对于上面的每个都给出对应的例子。 本节所有案例代码均已放到GitHub上,欢迎下载: https://github.com/huangtianyu/JNILearnCourse 1. native向java返回字符串类型 传基本数据类型很简单,是什么就传什么就行。传字符串类型也很简单,具体jni代码如下: extern "C" JNIEXPORT jstring JNICALL Java_zqc_com_example_NativeTest_jni2javaMethod1(JNIEnv *env, jobject instance) { //jstring NewStringUTF(const char* bytes),jstring NewString(const jchar* unicodeChars, jsize len) char *returnValue = "你在native做你的操作后,生成char*后,通过env->NewStringUTF即可返回Java的String类型"; return env->NewStringUTF(returnValue); } 其中最主要用的是以下几个方法: //创建Unicode格式的jstring串 jstring NewString(const jchar* unicodeChars, jsize len) { return functions->NewString(this, unicodeChars, len); } //获取jstring长度 jsize GetStringLength(jstring string) { return functions->GetStringLength(this, string); } //获取jstring对应的字符串,isCopy表示是否拷贝生成副本。 //这个函数返回一个指向特定jstring中字符顺序的指针,该指针保持有效直到ReleaseStringChars函数被调用: const jchar* GetStringChars(jstring string, jboolean* isCopy) { return functions->GetStringChars(this, string, isCopy); } //释放指针 void ReleaseStringChars(jstring string, const jchar* chars) { functions->ReleaseStringChars(this, string, chars); } ////创建UTF-8格式的jstring串 jstring NewStringUTF(const char* bytes) { return functions->NewStringUTF(this, bytes); } //获取utf字符串的长度 jsize GetStringUTFLength(jstring string) { return functions->GetStringUTFLength(this, string); } //同GetStringChars const char* GetStringUTFChars(jstring string, jboolean* isCopy) { return functions->GetStringUTFChars(this, string, isCopy); } //同ReleaseStringChars void ReleaseStringUTFChars(jstring string, const char* utf) { functions->ReleaseStringUTFChars(this, string, utf); } 以上是处理字符串常用的一些方法。 2 native向java返回java对象 具体看native的代码如下: extern "C" JNIEXPORT jobject JNICALL Java_zqc_com_example_NativeTest_jni2javaMethod2(JNIEnv *env, jobject instance) { jclass pcls = env->FindClass("zqc/com/example/Person"); jmethodID constructor = env->GetMethodID(pcls, "<init>", "()V"); jmethodID setIdMid = env->GetMethodID(pcls, "setId", "(J)V"); jmethodID setNameMid = env->GetMethodID(pcls, "setName", "(Ljava/lang/String;)V"); jmethodID setAgeMid = env->GetMethodID(pcls, "setName", "(I)V"); jobject person = env->NewObject(pcls, constructor); env->CallVoidMethod(person, setIdMid, 100L); env->CallVoidMethod(person, setNameMid, env->NewStringUTF("天宇")); env->CallVoidMethod(person, setAgeMid, 18); return person; } 常用新建Object的方法由以下几个: //将传递给构造函数的所有参数紧跟着放在 methodID 参数的后面。NewObject() 收到这些参数后,将把它们传给所要调用的Java 方法。 jobject NewObject(jclass clazz, jmethodID methodID, ...) { va_list args; va_start(args, methodID); jobject result = functions->NewObjectV(this, clazz, methodID, args); va_end(args); return result; } //将传递给构造函数的所有参数放在 va_list 类型的参数 args 中,该参数紧跟着放在 methodID 参数的后面。NewObject() 收到这些参数后,将把它们传给所要调用的 Java 方法。 jobject NewObjectV(jclass clazz, jmethodID methodID, va_list args) { return functions->NewObjectV(this, clazz, methodID, args); } //将传递给构造函数的所有参数放在jvalues类型的数组args中,该数组紧跟着放在methodID参数的后面。NewObject() 收到数组中的这些参数后,将把它们传给所要调用的 Java 方法。 jobject NewObjectA(jclass clazz, jmethodID methodID, jvalue* args) { return functions->NewObjectA(this, clazz, methodID, args); } //测试对象是否为某个类的实例。 jboolean IsInstanceOf(jobject obj, jclass clazz) { return functions->IsInstanceOf(this, obj, clazz); } //测试两个引用是否引用同一 Java 对象。 jboolean IsSameObject(jobject ref1, jobject ref2) { return functions->IsSameObject(this, ref1, ref2); } //分配新 Java 对象而不调用该对象的任何构造函数。返回该对象的引用。 //该方法会抛出:InstantiationException:如果该类为一个接口或抽象类。OutOfMemoryError:如果系统内存不足。 jobject AllocObject(jclass clazz) { return functions->AllocObject(this, clazz); } //判断某个Object是否是某个class的具体实例 jboolean (*IsInstanceOf)(JNIEnv*, jobject, jclass); 3 native向java返回数组类型 3.1 基本类型数组 这里直接看native层代码如下: extern "C" JNIEXPORT jintArray JNICALL Java_zqc_com_example_NativeTest_jni2javaMethod3(JNIEnv *env, jobject instance) { int nat[] = {2, 1, 4, 3, 5}; jintArray jnat = env->NewIntArray(5); env->SetIntArrayRegion(jnat, 0, 5, nat); return jnat; } 基本数据类型数组都有相应的env->NewXXXArray(jsize length);通过该方法可以生成对应的数组。 jbooleanArray NewBooleanArray(jsize length) { return functions->NewBooleanArray(this, length); } jbyteArray NewByteArray(jsize length) { return functions->NewByteArray(this, length); } jcharArray NewCharArray(jsize length) { return functions->NewCharArray(this, length); } jshortArray NewShortArray(jsize length) { return functions->NewShortArray(this, length); } jintArray NewIntArray(jsize length) { return functions->NewIntArray(this, length); } jlongArray NewLongArray(jsize length) { return functions->NewLongArray(this, length); } jfloatArray NewFloatArray(jsize length) { return functions->NewFloatArray(this, length); } jdoubleArray NewDoubleArray(jsize length) { return functions->NewDoubleArray(this, length); } 在生成了对应的数组后,可以通过setXXXArrayRegion(jxxxArray array, jsize start, jsize len, const jchar* buf)来填充数组 void SetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len, const jboolean* buf) { functions->SetBooleanArrayRegion(this, array, start, len, buf); } void SetByteArrayRegion(jbyteArray array, jsize start, jsize len, const jbyte* buf) { functions->SetByteArrayRegion(this, array, start, len, buf); } void SetCharArrayRegion(jcharArray array, jsize start, jsize len, const jchar* buf) { functions->SetCharArrayRegion(this, array, start, len, buf); } void SetShortArrayRegion(jshortArray array, jsize start, jsize len, const jshort* buf) { functions->SetShortArrayRegion(this, array, start, len, buf); } void SetIntArrayRegion(jintArray array, jsize start, jsize len, const jint* buf) { functions->SetIntArrayRegion(this, array, start, len, buf); } void SetLongArrayRegion(jlongArray array, jsize start, jsize len, const jlong* buf) { functions->SetLongArrayRegion(this, array, start, len, buf); } void SetFloatArrayRegion(jfloatArray array, jsize start, jsize len, const jfloat* buf) { functions->SetFloatArrayRegion(this, array, start, len, buf); } void SetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len, const jdouble* buf) { functions->SetDoubleArrayRegion(this, array, start, len, buf); } 3.2 对象类型数组 直接看native代码: extern "C" JNIEXPORT jobjectArray JNICALL Java_zqc_com_example_NativeTest_jni2javaMethod4(JNIEnv *env, jobject instance) { jclass cls = env->FindClass("zqc/com/example/Person"); jmethodID cMid = env->GetMethodID(cls, "<init>", "()V"); jclass pcls = env->FindClass("zqc/com/example/Person"); jmethodID cmid = env->GetMethodID(pcls, "<init>", "()V"); jmethodID setNameMid = env->GetMethodID(pcls, "setName", "(Ljava/lang/String;)V"); jmethodID setAgeMid = env->GetMethodID(pcls, "setAge", "(I)V"); jobject obj = env->NewObject(pcls, cmid); env->CallVoidMethod(obj, setNameMid, env->NewStringUTF("天宇")); int len = 3; jobjectArray joa = env->NewObjectArray(len, cls, obj); for (int i = 0; i < len; ++i) { jobject tmp = env->GetObjectArrayElement(joa,i); env->CallVoidMethod(tmp, setAgeMid, i + 10); } return joa; } 其在native生成的方法是 jobjectArray joa = env->NewObjectArray(len, cls, obj); //第一个参数表示生成的长度,第二参数表示里面元素的对象类,第三个表示原始初始化时的值。在生成后每个元素都是该值。 jobjectArray NewObjectArray(jsize length, jclass elementClass, jobject initialElement) { return functions->NewObjectArray(this, length, elementClass, initialElement); } 4 native向Java返回List对象 直接看native代码如下: extern "C" JNIEXPORT jobject JNICALL Java_zqc_com_example_NativeTest_jni2javaMethod5(JNIEnv *env, jobject instance) { jclass listCls = env->FindClass("java/util/ArrayList");//获得ArrayList类引用 jmethodID listCon = env->GetMethodID(listCls, "<init>", "()V");//获取构造函数的methodID jmethodID addMid = env->GetMethodID(listCls,"add","(Ljava/lang/Object;)Z");//获取add函数的methodID jobject listObj = env->NewObject(listCls, listCon);//利用NewObject创建一个ArrayList对象 jobject jperon = Java_zqc_com_example_NativeTest_jni2javaMethod2(env, instance);//利用上面方法新建一个Person对象 env->CallBooleanMethod(listObj, addMid, jperon);//在listObj中add一个Person对象 //返回ArrayList的对象 return listObj; } 对应jni而言,List,ArrayList以及Map,HashMap,Set,HashSet都只是一个Object,对应于jni而言也就都是jobject,操作jobject都可以用最开始介绍的方法。 总结 jni里面的方法很多,多用用就熟悉了。常用的上面都有,自己之前还总结了很多常用的类型转换函数。以后有时间再写篇博客分享下。不会就要查手册:http://www.ceeger.com/Script/AndroidJNI/AndroidJNI.html

优秀的个人博客,低调大师

Android NDK开发系列教程5:局部引用,全局引用,弱全局引用

终于建了一个自己个人小站:https://huangtianyu.gitee.io,以后优先更新小站博客,欢迎进站,O(∩_∩)O~~ 1. 简介 从Java虚拟机创建的对象当传入到native层时会产生一个引用,在进行垃圾回收时如果有native的引用,改对象同样也不会被回收。在native引用中分局部引用和全局引用。 1.1 局部引用 局部引用又称本地引用,大多数见到的引用都是局部引用,例如通过NewLocalRef和各种JNI接口创建(FindClass、NewObject、GetObjectClass和NewCharArray等),局部引用只会在本次native调用中有效,当本次调用结束后该引用即被自动释放。局部引用会阻止GC进行回收。同时也可以调用DeleteLocalRef函数来手动释放(比如在循环里面用到了局部引用而退出循环没有使用该局部引用,那么就需要在循环中释放该局部引用)。通常使用NewObject创建的实例返回的也是局部引用。千万不要把局部引用保存为c++的全局变量或者把它定义为静态变量,局部引用的有效期是一次Java本地调用。 JNI提供了一系列函数来管理局部引用的生命周期。这些函数包括:EnsureLocalCapacity、NewLocalRef、PushLocalFrame、PopLocalFrame、DeleteLocalRef。 //申请扩充局部引用的最大个数限制,返回值等于0的时候表示成功,>0时表示内存溢出。默认至少16个局部引用可以使用,引用数超出时报FatalError jint (*EnsureLocalCapacity)(JNIEnv*, jint); /* PushLocalFrame是一个创建本地引用新作用域的有用函数,这使得PushLocalFrame函数可以释放其使用的框架中所有已分配的本地引用。当该函数被调用时,本地引用的最低数量将在本框架中被创建。该函数如果执行成功则返回0,如果由于错误抛出一个OutOfMemoryException,则返回一个负值。*/ jint PushLocalFrame(jint capacity); /*PopLocalFrame函数释放当前框架中的所有本地引用(弹出一个框架)。因为存储该函数的结果(返回值)可能会导致在即将被弹出的框架中创建一个本地引用,该函数接收一个可以导致引用在当前框架被弹出之后的最高框架中创建的参数。这就确保可以维护一个存储PopLocalFrame函数结果的引用。*/ jobject PopLocalFrame(jobject result); PushLocalFrame为当前函数中局部引用创建了一个引用堆栈,在每遍历一次调用(*env)->GetObjectArrayElement(env, arr, i);返回一个局部引用时,JVM会自动将该引用压入当前局部引用栈中。而PopLocalFrame负责将栈中所有引用释放。这样一来,Push/PopLocalFrame函数对提供了对局部引用生命周期更方便的管理,不用再去一个个Delete了。 1.2 全局引用 全局引用可以在当前线程使用,也可以在其他线程使用,可以保存在本地的static静态变量或全局变量中,全局引用需要调用NewGlobalRel函数创建,释放时采用ReleaseGlobalRef函数释放。有效作用域在创建后,一直到调用ReleaseGlobalRef释放时。 1.3 弱全局引用 在Java1.2中,新增了弱全局引用,与全局变量一样其创建、删除均需要编程写出,也可以在本地多个代码中使用,也可以跨进程使用。不一样的是,它的存在不影响垃圾回收机制对该引用所指向对象实例的回收。其创建采用NewWeakGlobalRef,释放采用ReleaseWeakGlobalRel。 以上涉及的函数主要有以下几个: //创建局部引用 jobject NewLocalRef(jobject obj); //释放局部引用 void DeleteLocalRef(jobject obj); //创建全局引用 jobject NewGlobalRef(jobject obj); //释放全局引用 void DeleteGobalRef(jobject obj); //创建弱全局引用 jobject NewWeakGlobalRef(jobject obj); //释放弱全局引用 void DeleteWeakGlobalRef(jobject obj); //该方法判断两个引用是否相等,对于弱全局引用如果对比的是NULL那么还可以判断该引用指向的对象是否被回收 jboolean IsSameObject(jobject obj1 , jobject obj2); 上述三中引用会影响内存的回收,在C/C++中没有向Java一样的垃圾回收机制,自己申请的内存要记得自己去释放了,否则会导致内存泄漏。虽然现在C/C++里面也有智能指针,但相对而言这个智能指针用起来不如Java。所以在C的世界里要遵循谁申请,谁释放的基本原则。 2. 举个栗子 上面介绍了基本知识,下面给出相应的例子来进行说明下。 2.1局部引用 //1. 局部引用不要存储在static变量中,即使存了下次也不能用 //static jclass cls; //以下创建的局部引用都放入到栈中 env->PushLocalFrame(16); jclass cls; if (!cls) {//这里就错误了,前一次方法完成后jvm会释放局部引用,这里static存的值仅第一次有效 cls = env->GetObjectClass(instance);//这里的cls是局部引用 } //删除栈里面的局部引用 env->PopLocalFrame(NULL); env->EnsureLocalCapacity(20);//将本地引用的最大限制改为20 //下面可以进行其他操作。。。 在局部引用中要注意以下几方面: 1. 循环体内创建的局部引用,要在循环体内就直接释放了。 2. 编写的工具函数,里面创建的局部引用,要在该工具函数里面释放了。 3. 局部引用引用了一个大的Java对象,这时候一定一定要早点释放了。 4. 局部引用不要缓存在native层 2.2 全局引用 extern "C" JNIEXPORT void JNICALL Java_zqc_com_example_NativeTest_jniGlobalRef(JNIEnv *env, jobject instance) { static jobject obj; static jclass pCls; if (obj) {//第二次点击时,这里就不会空 //由于obj和personCls被保存为全局引用了,所有这里使用仍然有效 jmethodID getId = env->GetMethodID(pCls, "getName", "()Ljava/lang/String;"); jstring name = (jstring) env->CallObjectMethod(obj, getId); LOGE("obj is not null, name:%s", jstringToChar(env, name)); return; } if (!pCls) {//为空就去新建 jclass tmpCls = env->FindClass("zqc/com/example/Person"); pCls = (jclass) env->NewGlobalRef(tmpCls); env->DeleteLocalRef(tmpCls); } jmethodID conMid = env->GetMethodID(pCls, "<init>", "()V"); jobject tmpObj = env->NewObject(pCls, conMid); jmethodID setId = env->GetMethodID(pCls, "setName", "(Ljava/lang/String;)V"); env->CallVoidMethod(tmpObj, setId, env->NewStringUTF("看看姓名")); obj = env->NewGlobalRef(tmpObj); env->DeleteLocalRef(tmpObj); } 2.3 弱全局引用 弱全局引用和全局引用基本差不多,最大的区别就是弱全局引用不影响GC的回收。在使用弱全局引用的时候一定要注意,使用前要检查下是不是被GC回收了。 extern "C" JNIEXPORT void JNICALL Java_zqc_com_example_NativeTest_jniWeakGlobalRef(JNIEnv *env, jobject instance) { static jclass pCls; if (!pCls) { jclass tmpCls = env->FindClass("zqc/com/example/Person"); pCls = (jclass) env->NewWeakGlobalRef(tmpCls); env->DeleteLocalRef(tmpCls); } //除了第一次需要FindClass外,在没有回收pCls之前都可以使用 //这里使用... //可以手动释放 //env->DeleteWeakGlobalRef(pCls); } 3. 引用的比较 jni提供了相应的函数 jboolean IsSameObject(jobject ref1, jobject ref2) { return functions->IsSameObject(this, ref1, ref2); } 如果两个引用指向同一个实例则返回JNI_TRUE,否则返回JNI_FALSE。

资源下载

更多资源
Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册