重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
好久没有写点东西发了,工作中的事情有点杂,也找不到整块东西可以写的。
为海湖新等地区用户提供了全套网页设计制作服务,及海湖新网站建设行业解决方案。主营业务为成都网站设计、做网站、海湖新网站设计,以传统方式定制建设网站,并提供域名空间备案等一条龙服务,秉承以专业、用心的态度为用户提供真诚的服务。我们深信只要达到每一位用户的要求,就会得到认可,从而选择与我们长期合作。这样,我们也可以走得更远!
最近调查了一个问题,稍微追了一下流程,这里记录一下。
由于我们支持的设备相对比竞品,zygote进程多占用了好几倍的内存空间。通过dump meminfo后发现,我们的设备在so库,ttf,和unkonwn mmap的内存空间相比竞品一共大了20多M,其中so库多了15M左右。
通过查看zygote进程的smaps,确定了占用空间最大的几个so库确实是我们自己的。虽然确定了内存占用大的原因,还是得把这些so库是加载在zygote进程中的时机确定了才行。
我的第一反应就是在zygote启动时,加载sharedLibrary()时,把这些库加载了,于是去看了这部分源码,并没有。
通过反复调试以及追踪源码,最后发现是在JVM启动的过程中加载了这些so库,这些so库的配置在“system/etc/public.libraries.txt”下。
这个文件里配置的都是public的so库,能够被普通app访问的。类似的配置文件还有“vendor/etc/”下面的,还有一些其他的配置地方,我没有深入去看,想要看的盆友可以自己去看源码或者注释。
下面我就带大家一起看看虚拟机加载这些so库的流程。
调用栈:
frameworks/base/cmds/app_process/app_main.cpp
----runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
frameworks/base/core/jni/AndroidRuntime.cpp
----AndroidRuntime::startVm
---JNI_CreateJavaVM(pJavaVM, pEnv, initArgs)
art/runtime/jni/java_vm_ext.cc
---android::InitializeNativeLoader();
system/core/libnativeloader/native_loader.cpp
----Initialize()
----ReadConfig(public_native_libraries_system_config, sonames, always_true, error_msg)
其中,public_native_libraries_system_config 为 system/etc/public.libraries.txt
这部分流程是在安卓设备开机过程中的,在执行ZygoteInit.main()之前会先启动java虚拟机的,这样fork其他java进程的时候,java环境就已经有了,不用再创建虚拟机了。
最后贴一下Initialize()函数:
android项目中如何加载已有so库方法:
1、在项目根目录下建立文件夹libs/armeabi文件夹。
2、将so库放入libs/armeabi文件夹注意事项:
(1)如果采用静态注册的方式请注意C文件中严格按照命名规则Java_packageName_className_method()的方式命名。
(2)在Android项目中建立同上述命名规则中packageName中相同的包名,在此包名下建立同上述命名规则中className相同的类名。
(3)在className声明native方法。
(4)程序中加载so库System.loadLibrary。(data/data/xxx.xxx.xxx/lib/xx.so)或者System.loadLibrary(xx),例如:System.loadLibrary(data/data/com.dtBank.app.service/lib/libjnixcld.so)。
有这两种办法:
第一种:
需求:
有时候应用修复了native层一个小BUG,应用需要更新了,但是用户必须下载整个APK包进行安装,而我们需要的只是替换SO
于是想,能不能加载自定义路径下的 SO 文件呢
答案是完全没问题:
使用系统方法:
void java.lang.System.load(String pathName)
但是有一点,pathName 路径必须有执行权限,意思就是说我们不能加载SD卡上的SO,因为没有执行权限
那也没关系,我们复制到应用私有目录下就OK嘛。
看码
private void load() {
File dir = getDir("libs", Context.MODE_PRIVATE);
File soFile = new File(dir, "libTestJNI.so");
FileUtils.assetToFile(this, "libTestJNI.so", soFile);
try {
System.load(soFile.getAbsolutePath());
} catch (Exception e) {
}
}
这样就完全OK,
我们只需要架个服务器,每次启动时动态监测 SO 文件有没有更新,有则下载SO,然后加载,这样就可以避免用户安装新的应用,
要知道重新安装应用的用户体验是很差的,要让用户无感知的更新他。
第二种:
采用dlopen动态加载第三方库,无非和system.load一样,就是要实现指定路径加载so的目的,这种方法升级so的话,那就的需要一个基本so,一直不变,用来调用dlopen,然后升级另一个so。
这两种办法都会遇到一个问题,就是不能直接加载SD卡中的so,因为sd卡没有执行权限,不能直接加载这种二进制文件,需要拷贝到data/data/packagename/files/ 目录下,再次进行加载即可,拷贝也是有讲究的,需要用到context.openFileOutput方法。
Android studio方法:
1、先在Android studio导入一个项目,然后进入到项目中,依次进入到appsrcmain下。
2、在main的文件下进行创建一个jnilibs,然后选中main的文件,进行右键,弹出框中点击“new”,移动下一级菜单中选择“directory”。
3、对创建的文件夹进行昵称,在昵称中进行输入“jnilibs”,然后点击“ok”。
4、这样在main的文件中创建一个为jnilibs的文件夹。
5、然后在jinlibs中文件进行添加so的文件,进入到so文件存放的文件中,直接把so的文件拖动到jinlibs的文件中。
6、拖动完成之后,会提示一个确认款提示信息,直接点击“ok”即可。
7、在non-project file access中第一个我希望编辑文件,第二个为我想在当前会话中编辑任何非项目文件,根据的自己需要进行选择,可以默认即可,点击ok。
8、这样就把so文件添加到jnilibs的文件中,这样的话程序的代码就可以进行调用其中方法。
我们在Android应用程序会常常的加载一些So文件来完成我们的目标,那么我们的APK加载So是有哪些平时我们没有注意到的事情呢?
1. 首先我们一般开发会遇见两种APK(其实一般大部分只会遇到一种),一种为系统级APK,另外一种为普通APK。那么这个两种APK跟So加载有什么关系呢?别急,让我们先聊聊我们那些操作会产生这些类型的APK。
普通级AKP:
pm install + 包名将会把APK安装到 /data/app 目录下,同时会把So映射到/data/app-lib/包命/ 目录下。这个就是普通的APK(pm Install -r 会替换原有的APK,当然必须是一样的签名)。
系统级APK:
push + 绝对路径 + 包名 /system/app 目录下(必须把原有的包名删除哦!),这时APK就会在System/app下面了,这时你需要把你的APK的So 同时push到system/lib里面。因为apk里面的So并不会自动映射到system/lib下面。
一般我们在使用加载So的方法时候,会使用到System.load(pathName)和 System.loadLibrary(libName)这两种方法。这篇文章主要讲讲System.load(pathName)这个绝对路径加载的注意点。
我们通常会直接使用
context.getApplicationInfo().nativeLibraryDir +/具体名字.so 来让系统帮我寻找加载So所需要的路径。那么这里问题就来了。
如果是系统级APK
context.getApplicationInfo().nativeLibraryDir = /system/lib/
如果是普通级APK
context.getApplicationInfo().nativeLibraryDir =/data/data-lib/PackageName/ 对!就是那个映射的So系统会根据这个去data/app/包名下面寻找真正的So文件。
这个需要注意的细节,主要用于在中间件,系统预置程序的研发人员与测试上面。我们在拿到芯片厂商给予调试模式的开发硬件上进行Demo和So的更换测试的时候,需要自己和测试都需要知道,自己安装的APK是什么类型,会加载什么路径,以免我们的底层老司机在帮忙测试问题的时候造成不必要的麻烦。