前言Robolectric是Android的单元测试框架。直接在JVM上运行,无需Android真机环境,因此测试用例的速度和效率都有了很大的提升,接近Java。JUnit测试(JUnit测试>Robolectric>>androidTest)。但是框架本身不支持so本地库的加载和使用,加载的时候会直接报错,因为实际运行环境是电脑机器,而我们敲的so文件是给手机用的,所以当然会报错。虽然GitHub上很多人问过使用so,但基本都是建议不要在单元测试中加载本地库。原则上是这样做的,但在某些项目中可能很难做到,比如在代码结构不够好,依赖耦合较大,或者so库本身依赖比较重的情况下。那么我们就来说说Robolectric是如何解决项目中需要加载运行本地so库的问题。动态库动态库,又称动态链接库(Dynamic-linklibrary缩写DLL),是一个包含可供多个程序同时使用的代码和数据的库。DLL不是可执行文件。动态链接为进程提供了一种方法来调用不属于其可执行代码的函数。函数的可执行代码驻留在包含一个或多个函数的DLL中,这些函数与使用它们的进程分开编译、链接和存储。DLL还有助于共享数据和资源。多个应用程序可以同时访问内存中DLL的单个副本的内容。DLL是一个库,包含可由多个程序同时使用的代码和数据。动态库在Windows下为.dll后缀(通常为PE格式),在Linux下为.so后缀(通常为ELF格式),在macOS下为.dylib后缀(通常为Mach-O格式)。由于CPU架构和动态库文件格式的差异,不能在不同平台下通用。其他的细节就不展开了,因为不会:-)Android本身是Linux系统,所以使用的动态库也是.so文件,所以运行JVM的Robolectric不能直接加载使用(Linux在某些情况下下可用,如下所述)。在Robolectric中使用动态库我们知道动态库一般是针对特定的平台和特定的CPU架构使用的,所以Robolectric下解决so动态库加载运行问题的思路是在不同的Robolectric运行平台上处理加载不同的动态库库,所以你要在Ronbolectriv中使用的so动态库必须有源码,否则在macOS和Windows下不好搞定。注意:注意动态库名称以lib开头。Linux下的Robolectric使用动态库。Android和Linux本着相同的精神,所以很多底层的东西是通用的,动态库也是一样的。我们在Android上使用so的时候,一般需要针对不同CPU架构的手机使用不同的so文件,比如:armeabi-v7a、mips、x86。我们使用的LInux发行版一般都是64位的,所以原则上我们可以使用x86-64的动态库,但是如果你的本地代码包含了其他的依赖,我们可能需要处理依赖库的问题。如果不添加,Robolectric运行时会报如下错误:java.lang.UnsatisfiedLinkError:xxx/xxx.soxxxdynamiclibrarycannotbefound。xxx.so是你用的so的依赖。比如加载新浪微博SDK的x86-64位的libweibosdkcore.so,会报找不到liblog.so,因为libweibosdkcore已经支持Android的liblog等so库。依靠。那么如何解决这个问题。我们考虑在打包so库的时候使用ndk,需要用到ndk-bundle这个工具。大家想一想,类似于编译apk。APK打包需要sdk工具。compileSdk就是我们编译的依赖,里面有android.jar。所以我们可以查看ndk-bundle,***我们发现在不同的CPU架构下都有so依赖库,像我们一般电脑64位CPU都可以使用arch-x86_64下的so动态库,所以我们只需要加载在加载我们程序的so库之前,这些必要的依赖项。处理代码稍后贴出。注意ndk-bundle中的so只能在linux下使用。如果在其他平台上使用,会报错。原因上面已经解释过了。java.lang.UnsatisfiedLinkError:xxx.so:unknownfiletype,firsteightbytes:0x7F0x450x4C0x460x020x010x010x00在macOS下使用Robolectric中的动态库如上文所述,不同平台下的动态链接库不通用,所以必须重新编译打包源码才能移植到不同平台,如果你的so没有源码,在macOS和Windows下是无法运行的。我们可以按照以下两步重新打包:#Mr.into.o,-I并添加Javajni编译依赖cc-c-I/System/Library/Frameworks/JavaVM.framework/Headers*.cpp#Packageinto.dylibg++-dynamiclib-undefinedsuppress-flat_namespace*.o-osomething.dylib一些依赖库可以在/usr/lib下找到,比如libc和libstdc++。Windows下Robolectric中动态库的使用我没有在Windows下开发过,所以这部分略过,思路是一样的。Sample下面是一个简单的处理代码示例。首先新建一个包含jni的工程,在里面写一个基本的本地库,如下:,jobject/*this*/){//简单的返回一个字符串std::stringhello="HellofromNative.";returnenv->NewStringUTF(hello.c_str());}然后这个原生库会在Application启动的时候加载://NativeLibsApplication.javapublicclassNativeLibsApplicationextendsApplication{//Usedtoloadthe'native-lib'libraryonapplicationstartup.static{System.loadLibrary("native-lib");}}这时候运行Robolectric测试用例会出现如下错误:java.lang.UnsatisfiedLinkError:nonative-libinjavaTheprocessafter.library.pathprocessingshouldfirstbeinourcodetoavoidloadingthesodynamic直接加载library,然后Robolectric在启动的时候会自己加载需要的动态库。//NativeLibsApplication.javapublicclassNativeLibsApplicationextendsApplication{@OverridepublicvoidonCreate(){super.onCreate();loadNativeLibraries();}/***子类自己实现简单*/protectedvoidloadNativeLibraries(){//真正加载native库的地方代码,当然你自己的可以处理得更解耦一点。NativeLibrariesManager.loadNativeLibraries();}}然后我们在Robolectric中自定义自己的Application,根据需要加载不同运行平台下需要的本地动态库,首先将我们Robolectric使用的本地库复制到test的libs文件夹下,根据不同的平台进行分类,如下图所示:在Linux下,我们从ndk-bundle中拷贝我们需要的.so,然后在我们自己的本地库中构建一个x86-64的。请注意,compileSdkVersion选择较高以支持x86-64版本。然后在macOS下重新移植动态库,简单写个打包脚本如下://make_macOS_dylib.sh#!/usr/bin/envbashOUTPUT=../../../build/intermediates/dylibsmkdir-p${OUTPUT}#.ofilecc-c-I/System/Library/Frameworks/JavaVM.framework/Headers*.cpp-o${OUTPUT}/libnative-lib.o#.dylibfileg++-dynamiclib-undefinedsuppress-flat_namespace${OUTPUT}/*.o-o${OUTPUT}/libnative-lib.dyliblibnative-lib.dylib就是我们想要的。然后我们自定义Application加载这些动态库://RobolectricApplication.javapublicclassRobolectricApplicationextendsNativeLibsApplication{static{ShadowLog.stream=System.out;//Androidlogcatoutput.}@OverrideprotectedvoidloadNativeLibraries(){//Disablesuperclassloadsofile.//super.loadNativeLibraries();Log.d(TAG,"=====>>Robolectricstartnativelibraries.");StringlibsBasePath=newFile(newFile("").getAbsolutePath()+"/src/test/libs").getAbsolutePath();Stringos=系统。getProperty("os.name");os=!TextUtils.isEmpty(os)?os:"";List
