Android中so使用知识和问题总结以及插件开发过程中加载so的方案解析

2023-11-16

转自:https://blog.csdn.net/jiangwei0910410003/article/details/52312451

一、前言

Android中有时候为了效率以及平台开发库的支持,难免会用到NDK开发,那么都会产生一个so文件,通过native方法进行调用,开发和调用步骤很简单,这里就不多说了,本文主要来介绍,我们在使用so的时候总是会出现一些常见的问题,而现在插件化开发也很普遍了,有时候插件中也会包含一些so文件,需要加载,这时候也会出现一些问题。本文就来详细总结一下这些问题出现的原因,以及解决方法,主要还是通过源码来分析。

 

二、涉及到的源码类

因为本文主要通过分析源码来分析so使用的知识点和问题总结,所以涉及到了很多的源码类,这里就现提供一下:

1、PackageManagerService.java
+setNativeLibraryPaths:设置应用的native库路径
+scanPackageDirtyLI:扫描包内容初始化应用信息

2、ActivityManagerService.java
+startProcessLocked:发送命令给Zygote进程启动一个虚拟机

3、NativeLibraryHelper.java

底层实现类:com_android_internal_content_NativeLibraryHelper.cpp

+copyNativeBinariesWithOverride:释放apk中的so文件到本地目录

+findSupportedAbi:遍历apk中的so文件结合abiList值得到应用支持的abi类型索引值

4、LoadApk类和ApplicationLoaders类

5、VMRuntime.java

底层实现类:dalvik_system_VMRuntime.c

+getInstructionSet:获取虚拟机的指令集类型

+is64BitAbi:判断VM是否为64位

6、Runtime.java

底层实现类:dalvik/vm/native/java_lang_Runtime.cpp,dalvik/vm/Native.cpp

+nativeLoad:加载so文件

 

三、Android中so文件的编译平台

Android中在进行NDK开发的时候,都知道因为机型杂而多的原因,没有一个大的标准,所以很多厂商都会采用不同型号的cpu,那么在编译so文件的时候,就需要进行交叉编译出多个cpu平台版本,现在主流的cpu架构版本:

armeabi/armeabi-v7a:这个架构是arm类型的,主要用于Android4.0之后的,cpu值32位的

x86/x86_64:这个架构是x86类型的,有32位和64位,占用的设备比例比较小

arm64-v8:这个架构是arm类型,主要用于Android5.0之后,cpu是64位的

这里可以看到,其中arm类型的是往下兼容策略,比如arm64-v8a肯定兼容armeabi/armeabi-v7a,也就是说armeabi/armeabi-v7a架构的so文件可以用在arm64-v8a的设备中的,而armeabi-v7a也是兼容armeabi的,但是因为cpu型号不同,所以arm体系和x86体系之间是不能相互兼容的。

 

四、Android中so加载流程

在Android中如果想使用so的话,首先得先加载,加载现在主要有两种方法,一种是直接System.loadLibrary方法加载工程中的libs目录下的默认so文件,这里的加载文件名是xxx,而整个so的文件名为:libxxx.so。还有一种是加载指定目录下的so文件,使用System.load方法,这里需要加载的文件名是全路径,比如:xxx/xxx/libxxx.so。

上面的两种加载方式,在大部分场景中用到的都是第一种方式,而第二种方式用的比较多的就是在插件中加载so文件了。

不管是第一种方式还是第二种方式,其实到最后都是调用了Runtime.java类的加载方法doLoad:

这里会先从类加载中获取到nativeLib路径,然后在调用native方法nativeLoad(java_lang_Runtime.cpp)

这里调用了一个核心的方法dvmLoadNativeCode(dalvik/vm/Native.cpp)

注意:

这里有一个检测异常的代码,而这个错误,是我们在使用插件开发加载so的时候可能会遇到的错误,比如现在我们使用DexClassLoader类去加载插件,但是因为我们为了插件能够实时更新,所以每次都会赋值新的DexClassLoader对象,但是第一次加载so文件到内存中了,这时候退出程序,但是没有真正意义上的退出,只是关闭了Activity了,这时候再次启动又会赋值新的加载器对象,那么原先so已经加载到内存中了,但是这时候是新的类加载器那么就报错了,解决办法其实很简单,主要有两种方式:

第一种方式:在退出程序的时候采用真正意义上的退出,比如调用System.exit(0)方法,这时候进程被杀了,加载到内存的so也就被释放了,那么下次赋值新的类加载就在此加载so到内存了,

第二种方式:就是全局定义一个static类型的类加载DexClassLoader也是可以的,因为static类型是保存在当前进程中,如果进程没有被杀就一直存在这个对象,下次进入程序的时候判断当前类加载器是否为null,如果不为null就不要赋值了,但是这个方法有一个弊端就是类加载器没有从新赋值,如果插件这时候更新了,但是还是使用之前的加载器,那么新插件将不会进行加载。

 

继续往下看:

这里主要调用了两个核心的系统方法,dlopen和dlsym,这两个方法用途还是很多的,一般是先加载so文件,然后得到指定函数的指针,最后直接调用即可,主要用于调用动态的调用so中的指定函数功能。而且这里注意到了最开始先调用so中的JNI_OnLoad函数,这个函数是so被加载之后调用的第一个方法。

 

到这里我们就总结一下Android中加载so的流程:

1、调用System.loadLibrary和System.load方法进行加载so文件

2、通过Runtime.java类的nativeLoad方法进行最终调用,这里需要通过类加载器获取到nativeLib路径。

3、到底层之后,就开始使用dlopen方法加载so文件,然后使用dlsym方法调用JNI_OnLoad方法,最终开始了so的执行。

 

五、Android中类加载器关联so路径

上面分析so加载过程中可以发现有一个地方,就是通过类加载器来获取到so的路径,那么Android中的主要类加载器有两个,一个是PathClassLoader和DexClassLoader,关于这两个类加载不多说了,网上资料很多可以自行查找阅读。而PathClassLoader是我们Android中默认的类加载器,也就是apk文件就是由他来加载的,我们可以通过查看源码得知,Android中加载apk的类加载可以从LoadApk.java类查找到:

注意:

这个类很重要的,而这个类加载器也是我们在做插件的时候,需要做一些操作,比如需要把加载插件的DexClassLoader类给添加到这个系统加载器中,就可以解决插件中组件的生命周期问题。

 

看看这个类加载器在哪里赋值的:

去看看ApplicationLoaders.java类:

看到了,这里就是定义了PathClassLoader类了,所以我们Android中应用的默认加载器是PathClassLoader,再去看看这个类加载器的nativeLib是哪里:

 

六、Android中so文件如何释放

我们在使用System.loadLibrary加载so的时候,传递的是so文件的libxxx.so中的xxx部分,那么系统是如何找到这个so文件然后进行加载的呢?这个就要先从apk文件安装时机说起。

我们如果还没有分析源码之前,大致能够猜想到的流程是:

在安装apk的时候,系统解析apk文件,因为so文件肯定是存放在libs下指定平台目录中的,而apk文件本身就是一个压缩文件,所以可以进行解压,然后读取libs目录下的so文件,进行本地释放解压到指定目录,然后在加载的时候就先拼接so文件的全路径,最后在进行加载工作即可。

通过猜想,下面就通过源码来分析一下流程,系统在安装apk的时候,是调用系统类:PackageManagerService.java类:

主要的核心方法是scanPackageDirtyLI:

这个方法主要通过传递的pkg变量,开始构造applicationInfo信息。我们往下面看,找到设置nativeLib信息的代码:

这里注意有一个判断,是不是多平台架构的应用:

所以,我们看看info.flags有没有设置这个标志,我们看到上面的pkg变量是通过解析apk文件的类PackageParser.java类中获取到的,所以可以去这个类中找这个标志位的设置。

这里看到了,如果在AndroidManifest.xml中设置了Application中的multiArch属性值的话就有,但是我们默认都没有设置这个属性值,那么就是false,也就是说一般应用都不是多平台的。所以上面的isMultiArch方法就返回false,代码就走到了这里:

在这里就有很多知识点了,而这里可以看到,就涉及到了so文件的释放工作了,主要是在NativeLibraryHelper类中,但是这里看到首先获取abiList值:

通过Build.SUPPORTED_ABIS来获取到的:

最终是通过获取系统属性:ro.product.cpu.abilist的值来得到的,我们可以使用getprop命令来查看这个属性值:

这里获取到的值是:arm64-v8a,armeabi-v7a,armeabi,我用的是64位的cpu设备,所以可以看到他有多个cpu架构可选,而且看到这个顺序会想到,这个顺序正好是向下兼容的顺序。

 

现在去看看NativeLibraryHelper类的copyNativeBinariesForSupportedAbi方法:

这个方法中主要干了三件事:

第一件事是获取应用所支持的arch架构类型

第二件事是通过架构类型获取so释放的目录

第三件事是native层中释放apk中的指定架构的so到设备目录中

 

第一件事:获取应用所支持的arch架构类型

NativeLibraryHelper类的findSupportedAbi方法,其实这个方法就是查找系统当前支持的架构型号索引值:

看看native方法的实现:

这里看到了,会先读取apk文件,然后遍历apk文件中的so文件,得到全路径然后在和传递进来的abiList进行比较,得到合适的索引值,其实实现逻辑很简单:abiList是:arm64-v8a,armeabi-v7a,armeabi,然后就开始比例apk中有没有这些架构平台的so文件,如果有,就直接返回abiList中的索引值即可,比如说apk中的libs结构如下:

那么这时候返回来的索引值就是0,代表的是arm64-v8a架构的。如果apk文件中没有arm64-v8a目录的话,那么就返回1,代表的是armeabi-v7a架构的。依次类推。得到应用支持的架构索引之后就可以获取so释放到设备中的目录了。

 

第二件事:获取so释放之后的目录

这里主要通过VMRuntime.java中的getInstructionSet方法:

这里调用了一个map结构值:

这里的arch架构和目录对应关系,如果arch是arm64-v8a的话,那么目录就是arm64了。

 

第三件事:释放apk中的so文件

直接调用的是native层方法iterateOverNativeFiles:

 

好了到这里就讲完了上面的三件事了,而这三件事做完之后,apk中的so文件就会被释放到本地设备中的指定目录中了,当然这里系统会根据abiList中的值以及apk中包含的arch类型的so来决定释放哪个目录中的so文件,比如这里通过ApplicationInfo类来打印当前应用的nativeLibraryDir值:

打印的结果:

看到了,因为是arm64-v8a类型的,所以目录是arm64的,而且可以看到这个应用不是多平台的。

 

我们可以看到Android中是如何释放apk中的so文件到本地目录的:

1、通过遍历apk文件中的so文件的全路径,然后和系统的abiList中的类型值进行比较,如果匹配到了就返回arch类型的索引值

2、得到了应用所支持的arch类型之后,就开始获取创建本地释放so的目录

3、然后开始释放so文件

 

我们在PackageMangerService类中继续往下看:

这里还要保存上面获取到应用支持的arch类型值,我们可以使用反射打印这个值:

打印结果:

这个值在后面应用创建VM的时候会用到。

 

接着开始设置应用的nativeLib路径了:

看看这个方法的实现:

这里先判断是不是64位:

通过arch类型对应的目录来判断的:

这里如果是64位,目录就是lib,如果是32位就是lib64:

这样就和我们上面释放so文件的目录保持一致了,所以这里的ApplicationInfo类中的lib路径就是我们上面释放so之后的路径了。

 

在之前说到了类加载器中的lib路径,我们可以打印一下库路径的,这里直接使用getClassLoader得到加载器打印即可:

这里看到Library的目录包含很多路径。

 

七、Android中64位系统如何兼容32位的so

上面分析完了,so文件的释放工作,下面继续来看一下如果一个64位系统的Android设备如何做到能够运行32位的so文件,这个就需要从应用的启动说起了,那么这个类就是ActivityManagerService.java,有一个核心的方法:startProcessLocked,这个方法就是向Zygote进程发送一个消息,为这个应用创建虚拟机开始运行程序了:

这里在发送消息给Zygote进程,看到这里通过ApplicationInfo中的primaryCpuAbi类型告诉Zygote改创建多少位的虚拟机,我们查看系统启动文件init.rc内容:

这里会启动一个64位的Zygote进程

然后启动一个32位的Zygot进程

 

所以这里应该就可以想明白了,原来系统启动的时候,如果是64位的系统设备,会启动两个Zygote进程用来兼容32位类型的应用,我们可以使用ps命令查看进程:

看到了,这里果然启动了两个Zygote进程,一个64位的,一个是32位的。所以兼容功能的大致流程图应该是这样的:

上层启动应用的时候会把应用的abi类型带过来,然后这里会根据这个类型发送给具体的Zygote进程消息,来创建虚拟机开始运行程序,这样就做到了兼容。

 

八、Android插件中如何加载so文件

有时候我们在开发插件的时候,可能会调用so文件,一般来说有两种方案:

一种是在加载插件的时候,先把插件中的so文件释放到本地目录,然后在把目录设置到DexClassLoader类加载器的nativeLib中。

一种在插件初始化的时候,释放插件中的so文件到本地目录,然后使用System.load方法去全路径加载so文件

这两种方式的区别在于,第一种方式的代码逻辑放在了宿主工程中,同时so文件可以放在插件的任意目录中,然后在解压插件文件找到这个so文件释放即可。第二种方式的代码逻辑是放在了插件中,同时so文件只能放在插件的assets目录中,然后通过把插件文件设置到程序的AssetManager中,最后通过访问assets中的so文件进行释放。

 

上面就全部分析完了Android中关于so加载的相关内容:

1、so编译平台问题

2、so加载流程分析

3、so文件释放功能分析

4、so文件兼容功能分析

5、插件中so文件调用功能分析

 

九、常见问题分析

第一个问题:Could not find libxxx.so

这个问题看上去很好理解,就是在调用加载so的方法的时候,到底层使用dlopen方法打开so文件,发现找不到这个so文件,那么这个问题产生的原因主要有两个:

第一个是我们的确忘了在工程的libs下存放so文件了;

第二个是我们把so文件放错目录了;

第一个原因就不多说了,主要来看第二原因:

有时候我们在开发项目的时候,可能会放多个架构类型的so文件,那么现在假如我的设备是arm64-v8a类型的,我的项目中有三个so文件,比如叫做AAA.so,BBB.so,CCC.so,然后我再arm64-v8a目录中放了AAA.so,BBB.so,而CCC.so忘了放了,但是会放到armeabi-v7a和armeabi目录中,那么这时候就会发生找不到CCC.so的错误,原因很简单:

上面分析了apk中so文件的释放逻辑,系统会先遍历apk中所有so文件的全路径,然后在结合abiList的值来决定最终释放哪个目录中的so文件,那么现在系统是arm64-v8a了,而apk中的libs下也有arm64-v8a,所以这里就会把apk中的libs\arm64-v8a中的所有so文件释放解压到本地目录中,而不会在去释放armeabi/armeabi-v7a了。因为arm64-v8a中没有CCC.so文件,所以最终释放到本地目录中也是没有这个so文件的,所以加载时找不到文件了。

解决办法:就是在使用so文件的时候,需要确定在每个架构类型目录中都要有相同的so文件即可。

 

第二个问题:32-bit instead of 64-bit

这个问题的原因主要是因为64位的Zygote进程创建的虚拟机中加载了32位的so文件,这个问题的产生原因主要有两个:

第一个是我们把不同架构类型的so文件放错目录了,比如armeabi/armeabi-v7a的so文件放到了arm64-v8a中了

第二个是我们在开发插件的过程中,宿主工程中有arm64-v8a目录,但是插件中加载so却是armeabi/armeabi-v7a类型的

第一个原因就不多说了,主要是因为so放错目录了,来看一下第二个原因,我们在开发插件的时候有时候需要在插件中去加载so文件,一般都是使用System.load方式去加载全路径的so文件,那么这里就可能存在一个问题,比如宿主工程中,放了所有架构的目录,包括了64位的,因为考虑插件的大小,所以在插件中只放了armeabi-v7a目录的so文件,如果设备是64位的系统,那么这时候插件加载so文件就会报错。原因就在于上面分析的so兼容问题中说到了,因为宿主工程中包含了64位的架构arm64-v8a类型,系统的abiList中也有arm64-v8a类型,所以这时候应用的ApplicationInfo的abi就是arm64-v8a了,那么就会发送消息给Zygote64的进程,创建的也是64位的虚拟机了,而最后插件中加载so的类型是32位的armeabi-v7a,那么就会报错了,因为32位的so文件不能运行在64位的虚拟机中的。

解决办法:宿主工程和插件工程中的so文件的架构类型保持一致,这个将会带来一个很大的问题,就是插件包会变得很大,因为宿主工程为了兼容多数机型,加入了多个类型的架构so文件,但是插件为了减小包大小,就放了指定类型的so文件,但是最终会存在这种问题,所以这个解决办法就要看项目需要了。

 

还有一个类似的问题:64-bit instead of 32-bit:

原理都是一样的,32位的虚拟机中加载了64位的so文件问题导致的。

 

第三个问题:Shared library already opened

这个问题在上面介绍so加载流程中已经介绍过了,原因主要是因为之前使用DexClassLoader加载so之后,so没有释放还在内存中,而在此启动有弄了一个新的DexClassLoader对象去加载so问题,就出错了。

我们使用DexClassLoader类去加载插件,但是因为我们为了插件能够实时更新,所以每次都会赋值新的DexClassLoader对象,但是第一次加载so文件到内存中了,这时候退出程序,但是没有真正意义上的退出,只是关闭了Activity了,这时候再次启动又会赋值新的加载器对象,那么原先so已经加载到内存中了,但是这时候是新的类加载器那么就报错了。

解决办法:

 

第一种方式:在退出程序的时候采用真正意义上的退出,比如调用System.exit(0)方法,这时候进程被杀了,加载到内存的so也就被释放了,那么下次赋值新的类加载就在此加载so到内存了。

第二种方式:就是全局定义一个static类型的类加载DexClassLoader也是可以的,因为static类型是保存在当前进程中,如果进程没有被杀就一直存在这个对象,下次进入程序的时候判断当前类加载器是否为null,如果不为null就不要赋值了,但是这个方法有一个弊端就是类加载器没有从新赋值,如果插件这时候更新了,但是还是使用之前的加载器,那么新插件将不会进行加载。

 

十、技术概要

本文主要介绍了Android中关于so的相关知识,主要包括so编译多架构问题,so加载流程问题,so释放问题,so系统兼容问题以及插件中加载so文件的功能解析,看完本文之后,我们需要了解到的知识点:

1、在NDK开发时,可以指定多种架构类型编译出多种类型的so文件。

2、so的加载流程主要是System类中的两个加载方法,最终都会调用Runtime中的nativeLoad的native方法,而这个native方法最终会调用dlopen来打开so文件,然后在调用dlsym方法调用so的JNI_OnLoad方法。

3、关于apk文件在安装的时候释放so文件到本地目录中,主要是结合当前设备的abiList信息(这个信息主要是通过系统属性:ro.product.cpu.abilist值来获取的)和apk中不同类型架构,来决定最终释放哪个类型目录中的so文件,释放完成之后,还需要设置应用的nativeLib路径,以及应用的abi信息,因为这个abi信息在后面启动虚拟机的时候需要用到。

4、因为现在有很多设备已经是64位系统了,但是为了兼容32位的so文件,所以这些64位系统就会在系统启动的时候创建两个Zygote进程,一个是64位的,一个是32位的,当一个应用启动的时候,需要创建虚拟机,那么这时候就会把应用的架构类型传递过去,系统会根据这个类型来交给哪个Zygote进程来处理这个应用启动事件。这样就可以做到so调用的兼容问题了。

5、插件中加载so文件现阶段主要有两种方式,一种是先释放插件中的so文件到本地目录,然后设置DexClassLoader的nativeLib路径;还有一种方式是先释放插件中的so文件,然后调用System.load来加载全局路径的so文件。

 

十一、问题总结

本文还总结了在使用so文件的时候,会遇到的一些问题,主要是三个问题:

1、so文件找不到问题

这个问题一般是因为我们忘记放了so文件,或者是so文件没有放置全部,也就是没有在libs目录中所有的架构类型目录中放置。

2、不同位数的虚拟机运行了不同位数的so文件

这个问题一般是因为我们在libs目录中把so文件放错目录了,或者是宿主工程和插件工程中的so文件架构类型目录没有保持一致。

3、类加载器加载so文件再次加载

这个问题一般是因为插件开发中使用了不同的DexClassLoader去加载多次相同的so文件导致的。

 

十二、知识延展

我们在开发的过程中有时候想知道系统的位数,那么这里网上告知说有好几种方法,其实那些都是忽悠人的,特别是在使用这个api的时候:android.os.Build.CPU_ABI,我就是在项目中被这个方法坑爹了,这个方法其实不是获取系统的位数,而是获取当前应用的架构类型位数,就是我们前面分析的ApplicationInfo中的abi信息,我们可以查看一下源码:

这里可以看到,这个字段已经被废弃了,因为他不靠谱呀,这个字段在Build类的static块中进行赋值的:

这里会通过VMRuntime类的is64Bit方法来判断当前虚拟机的位数,来获取这个值

这里还有两个系统属性:

ro.product.cpu.abilist32是32位的所有arch架构类型

ro.product.cpu.abilist64是64位的所有arch架构类型

而这两个字段值的合集就是前面的ro.product.cpu.abilist属性值。

而VMRuntime的is64Bit方法是native方法,实现如下:

看到了,这里得到的是虚拟机的位数,那么就是上面的Zygote进程的位数了。那么问题就来了,假如我的设备是64位的,但是我的项目中没有arm64-v8a类型的so文件,这时候在解析apk进行释放so文件的时候,就会得知架构类型是armeabi/armeabi-v7a了,因为遍历apk文件,没有找到arm64-v8a类型的so文件,这时候应用的abi类型就是armeabi/armeabi-v7a了,这就是32位的了,就会通知32位的Zygote进程创建了一个32位的虚拟机,那么此时我的项目中通过Build.CPU_ABI得到的系统位数就是32了,那么完全不是我们想要的了。

 

所以正确的获取系统位数的方法是:

Android5.0系统之后,可以通过ro.product.cpu.abilist属性字段值来判断,如果这个字段值中包含了64的话,那么就是64位系统了

Android5.0系统之前,需要通过ro.product.cpu.abi属性字段值来判断,不过5.0系统之前都是32位的,还没有出现64位呢。

 

十三、选择适当架构类型减小包大小

我们上面分析之后可以看到,如果想做到万无一失即,项目不报错,而且so运行效率也是非常高的话,就需要把那几个架构类型的so文件都要在项目中放一遍,那么这个问题就来了,如果so文件较大的话,apk包最终也是很大的,所以这里就需要做一次选择了。

1、我们在开发一个项目的时候因为,整个项目的so文件结构我们可以控制,所以为了防止apk包增大,我们可以考虑只放几个架构类型的so文件,比如最好的是放armeabi类型的,因为首先现在大部分设备采用cpu型号都是arm的,少数采用x86或者是mips类型的,其次是防止了armeabi类型之后,对于armeabi-v7a和arm64-v8a就可以兼容了,不会存在报错问题。但是因为系统需要兼容所以就会出现so运行效率的问题了,最好的效率就是指定架构类型的so运行在对应架构类型的设备中。因为现在大部分的设备系统版本都是4.0以上了,所以armeabi-v7a架构类型用的比较多了,所以有时候为了效率问题,项目中只放了这个架构类型的so文件,那么像老版本的手机armeabi的话就会报错了,当然这个错误是可以接受的即可。

2、有时候像x86和mips等少数类型架构的设备,开发程序的时候会单独出一个版本比如叫做xxx应用x86版本

3、在开发SDK的时候,因为开发之后的SDK包是给其他app接入的,而对于接入的app,我们不能做太多的限制,所以理论上应该把所有架构类型的so都要提供,这样给需要接入的app进行选择即可,比如像百度地图SDK:

 

十四、总结

本文主要是介绍了Android中关于so的相关知识,而这些知识点都是在使用so文件中会经常用到的,同时一些问题也是我们会遇到的,这里只是做了一个总结,同时也给出了插件中加载so文件的方案已经遇到的问题解决思路等内容。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android中so使用知识和问题总结以及插件开发过程中加载so的方案解析 的相关文章

  • 在 Visual Studio 中逐步完成“托管到本机转换”?

    在试图回答的同时这个问题 https stackoverflow com questions 9378626我决定需要在调试器视图中手动单步执行编组过程 不幸的是 Visual Studio 似乎直接跳过了所有这些有趣的代码 这是 P In
  • 如何从本机 c 调用 C#(mono、.net)方法、委托

    是否可以从本机 C 代码调用以托管代码 可能在类或库中 编写的 C 方法 以及如何调用 thx 编辑 对于 c 我主要指的是 mono 甚至 portable net 操作系统是 Linux 您的 C 代码可以定义函数来注册回调 C 代码可
  • JPA 本机查询删除

    我尝试使用此本机查询从表中删除行列表 NamedNativeQuery name WebGroup DeleteIn query DELETE FROM WebGroup WHERE WebGroup GROUP ID IN IDsList
  • 相当于本机 javascript 中的 $(this)

    我想向按钮添加事件侦听器 并且我对纯 javascript 编码还比较陌生 所以我不知道本机等效项是什么 this 在我的代码中 the markup ul class menu li a href text a li li a href
  • 无 DOM、静态类型、提前编译的 JavaScript 代码与本机代码的性能相比如何?

    为什么 Javascript 比本机代码慢 的传统答案是 因为它被解释了 这种说法的问题在于解释并不是语言本身的品质 事实上 现在大多数 Javascript 代码都经过 JIT 处理 但这还远未接近本机速度 如果我们从方程中删除解释因素并
  • setprop libc.debug.malloc = 1 不起作用

    我尝试使用 setprop libc debug malloc 1 来找出泄漏 我制作了一个演示程序并在其中引入了内存泄漏 但上述标志无法检测到此泄漏 我尝试了以下命令 adb shell setprop libc debug malloc
  • Flutter - ListView.builder:初始滚动位置

    我想设置 ListView builder 的初始滚动位置 我希望列表从底部开始0 0 如果我设置reverse当然 在 listView 上 我将初始滚动位置设置为所需的位置 但我需要的是让最后一个孩子位于底部 是一个聊天应用程序 这是列
  • 停止调用 JNI 函数的 Java 线程

    在这里我想停止我的线程或杀死我的线程它是在Java层创建的 该线程正在调用JNI函数 有时根据我的应用程序要求 我必须停止此操作JNI函数执行在某些条件下 如果它正在进行 否则不会 new Thread new Runnable Overr
  • CryptAPI 与 .NET 代码的本机互操作

    我已成功使用 Crypto API 以本机代码加密数据 并使用 RC2 算法和 SHA 创建密钥 以 NET C 代码对其进行解密 这是本机代码 在本例中为 Delphi Get handle to CSP If Not CryptAcqu
  • PropTypes React Native 不是一个对象

    我的代码在本地反应时遇到 PropTypes 问题 import React Component PropTypes from react import Text from react native export default class
  • Android webapp 中调用 JS 的原生代码

    我正在编写一个Android 网络 应用程序 我不会将应用程序上传到网络 而是在应用程序资源中设置HTML JS 这意味着 GUI 将是 HTML5 我将使用另一个 本机 线程从麦克风读取数据 并希望将 解析后的文本 发送到 HTML5 J
  • React Native 应用程序在启动时在 Android 11 上崩溃,但没有给出错误

    this is my build gradle setting and this is my system info 我正在尝试在 android 11 上运行 React Native 应用程序 但它在启动时不断崩溃而没有给出任何错误我尝
  • 加载两个包含相同符号的本机库时出现内存问题

    我正在尝试使用 JNA 同时操作本机 非线程安全的 Fortran 库 由于该库不是线程安全的 我尝试实例化同一库的不同副本 但显然它们似乎共享内存地址 如果我修改一个库中的一个变量 则另一个库中的变量也会被修改 这种行为使得它们不可能在单
  • Android 6.0 原生 tgkill 崩溃

    从今天开始我就遇到了本地崩溃 据报道仅适用于Android 6 0 与堆栈跟踪 native pc 0000000000049c34 system lib libc so tgkill 12 native pc 00000000000474
  • 如何在 maven 的 java.library.path 变量中包含本机库

    我正在尝试为我的应用程序使用 JNotify 它有以下要求 只需使用以下命令运行 jar 文件即可测试 JNotify java Djava library path jar jnotify VER jar 目录 然后 JNotify 将监
  • GWT 中的本机 Javascript 方法

    我的 GWT Java 类之一中有一个本机 Javascript 方法 但我在从本机 Javascript 代码调用我的 Java 方法时遇到问题 我试着跟随this http code google com webtoolkit doc
  • 纯android原生隐藏导航栏

    我看过关于通过java隐藏android应用程序导航栏的文章和文章 然而 我想知道的是如何通过纯 android c 本机活动应用程序删除全屏应用程序 游戏 的导航栏 根本没有 JAVA Android 清单中的全屏可隐藏顶部栏 但导航栏保
  • 我可以在 iOS 上使用 MongoDB 来替代 CoreData 吗?

    我刚刚开始阅读 MongoDB 和 CouchDB 等 NoSQL 技术 我有兴趣知道是否可以使用 MongoDB 或任何 NoSQL 技术来替代 Core Data 应用程序 核心数据应用程序可能需要很长时间来学习和实现 特别是如果您的应
  • android可以在本机模式下从sd卡加载dll吗

    我想把我写的程序移植到android上 该程序是在 Windows 和 Linux 上用 C 编写的 该程序使用 dll 作为插件架构 通过下载程序从特定文件夹加载的 dll 可以将新插件添加到程序中 我的问题是 是否可以将为 androi
  • 奇怪的本机崩溃 - pid:0,tid:0 信号 11 (SIGSEGV),代码 1 (SEGV_MAPERR)

    我在 Android 上遇到了奇怪的崩溃 pid 0 tid 0 gt gt gt com oimvo discdj lt lt lt backtrace 00 pc 000000000001d050 data app com oimvo

随机推荐

  • 一篇博文教你SpringMVC中JSON注解&异常处理的使用

    目录 一 JSON数据返回 1 2 Jackson的介绍 1 3 3 案例演示 二 异常处理 2 4 1 异常处理方式 2 4 2 异常处理方式 2 4 3 异常处理方式 一 JSON数据返回 1 1 1 2 Jackson的介绍 1 2
  • steam创客教育

    社会竞争日益激烈 为了让子女将来能 成龙成凤 父母们都非常重视孩子的教育 格物斯坦小坦克看到这样普遍的现象 为了让自己的孩子不输在起跑线上 在各种各样兴趣班盛行的当下 很多家长常常盲目跟风 看到别的孩子学着还不错或升学考试需要 也给自己孩子
  • oracle中sqlload,oracle----sqlldr用法(转)

    SQL LOADER是ORACLE的数据加载工具 通常用来将操作系统文件迁移到ORACLE数据库中 SQL LOADER是大型数据 仓库选择使用的加载方法 因为它提供了最快速的途径 DIRECT PARALLEL 现在 我们抛开其理论不谈
  • elasticsearch 2.3.4 java API 连接,ik分词器,设置集群节点,创建index,mapping的几种方式...

    1 默认集群连接 Client client TransportClient builder build addTransportAddress new InetSocketTransportAddress InetAddress getB
  • mybatis的熟练运用以及反射知识讲解

    JSP常用设计模式MVC模式 Mybatis mybatis的使用 我们在写项目的时候必定要写DAO 写DAO的时候不难发现对每张表的DAO都差不多 只是sql语句不同 DAO中的每个方法其实也差不多 所以直接用JDBC写DAO是在太麻烦
  • Linux系统(二)——Linux环境下的开发工具

    接着上一篇博客 把Linux环境下常用的vim编辑器 gcc工具链 makefile和gdb等工具的使用理一理 一 vim编辑器 1 工作模式 vim是Linux常用文本编辑器 vim有两种基本工作模式 命令模式 输入的字符作为命令使用 不
  • 华为OD机试 - 表达式括号匹配(Java)

    题目描述 1 2 3 3 8 0 1 2 这是一个简单的数学表达式 今天不是计算它的值 而是比较它的括号匹配是否正确 前面这个式子可以简化为 这样的括号我们认为它是匹配正确的 而 这样的我们就说他是错误的 注意括号里面的表达式可能是错的 也
  • c++ 使用类模板时类声明和类方法定义分开导致`undefined reference to` 错误的一种解决方法

    文章目录 1 问题描述 2 问题分析 3 解决方案 1 问题描述 在编写c 代码时使用到了类模板 并且将类的声明与类方法的定义分别放到了两个文件中 fun h与fun cpp 结果编译时报错 error undefined referenc
  • Flutter实现发送Http请求获取后端接口数据以及如何工程化封装返回数据

    当我们在开发Flutter的时候 肯定会和后端接口对接去请求后端的数据 那么本次我们将 讲解Flutter如何请求数据 以及如何封装返回结果 话不多说直接上代码 首先根据Flutter官方网站的描述 需要在pubspec yaml引入一个h
  • 马上:Android pins 模块化架构

    马上 Android pins 模块化架构 主工程的代码堆积 造成包目录结构臃肿 难区分 借用pins工程结构暂时 其相互独立的代码进行区分 为后期模块组件化 留下了灵活性 pins 原理是gradle sourceSets sourceS
  • Visual Studio连接Linux服务器编译CMake项目,生成在Linux上运行的程序

    本文基于的开发环境说明 window系统 Windows 10 企业版 64位操作系统 Linux系统 BigCloud Enterprise Linux 7 8 Core Visual Studio Microsoft Visual St
  • eclipse中设置jsp文件的默认编码格式为utf-8

    eclipse中设置jsp文件的默认编码格式为utf 8 我们在创建jsp文件时 经常遇到生成的jsp文件中的编码格式默认为iso 8859 1 这样的编码由于不支持中文 所以我们需要手动把它改为utf 8编码 这样会浪费我们很多时间 所以
  • 神经网络学习小记录74——Pytorch 设置随机种子Seed来保证训练结果唯一

    神经网络学习小记录74 Pytorch 设置随机种子Seed来保证训练结果唯一 学习前言 为什么每次训练结果不同 什么是随机种子 训练中设置随机种子 学习前言 好多同学每次训练结果不同 最大的指标可能会差到3 4 这样 这是因为随机种子没有
  • Windows+WSL2+SSH实现远程办公

    Windows11 WSL SSH实现远程办公 Windows11 WSL2 SSH实现远程办公 主机配置 远程启动PC 设置DMZ主机 下载WSL 设置WSL为终端默认Shell和SSH自动启动 设置Windows和Ubuntu中SSH的
  • Ubuntu18.04 离线安装gcc,g++,make依赖包

    1 离线安装背景 因为项目现场的服务器无法连接互联网 只有内网环境 但是需要安装redis和nginx 所以需要安装gcc g make等依赖包 2 如何获取依赖包 需要准备一台可以连接互联网的电脑 如 个人电脑上的虚拟机安装一个与服务器一
  • jeecg excel 导入到java

    TSAttachment tsAttachment systemService getEntity TSAttachment class ttachementId 获得excel List
  • 2019最近计算机毕业设计-题目汇总大全-系列3

    课题名称 备注 初等数学类人答题中的读文作图理论研究及应用 数字图像加密关键技术研究与实现 FitzHuge Nagumo 模型及其在图像处理中的应用 基于图形硬件加速的实时布料动画系统设计与实现 基于GAN的人脸图像生成 基于智能视觉理解
  • jetson装jtop

    问题与背景 可以进行远程操作之后 ssh和ui 就可以进行控制台指令 ui界面 文件上传下发的基本操作了 但是jetson毕竟是体积小 所能承载的硬件性能有限制 在程序调试与开发的过程中 还是希望监控一下设备的性能情况 包括cpu gpu
  • VS2013+openCV2.4.10环境配置

    一 openCV环境配置步骤 1 下载opencv 2 4 10到任意文件夹 然后解压 配置环境变量PATH F opencv opencv build x86 vc12 bin 按你自己存放的地址 opencv 2 4 10下载链接 ht
  • Android中so使用知识和问题总结以及插件开发过程中加载so的方案解析

    转自 https blog csdn net jiangwei0910410003 article details 52312451 一 前言 Android中有时候为了效率以及平台开发库的支持 难免会用到NDK开发 那么都会产生一个so文