大橙子网站建设,新征程启航
为企业提供网站建设、域名注册、服务器等服务
好久没有写点东西发了,工作中的事情有点杂,也找不到整块东西可以写的。
创新互联是一家专注于做网站、成都网站制作与策划设计,扎兰屯网站建设哪家好?创新互联做网站,专注于网站建设十余年,网设计领域的专业建站公司;建站业务涵盖:扎兰屯等地区。扎兰屯做网站价格咨询:18982081108
最近调查了一个问题,稍微追了一下流程,这里记录一下。
由于我们支持的设备相对比竞品,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()函数:
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);
作者君主要做SDK开发,对接一些厂商或运行商的普通应用或系统应用。
当对接系统应用时,由于系统应用是由于覆盖机型比较广,会碰到Android多个版本机型,有的可能出现so找不到的问题。
普通install安装apk的方式,apk会被安装在 /data/app 目录下,那么So则会被映射到/data/app/项目目录下/lib。
首次安装只能通过直接push到/system/app/下的方式来安装,而不是如普通应用般采取install的方式。
android在开机扫描应用的时候会对 相应目录进行扫描,如果发现 data/app目录下 存在和系统应用同包名的应用,并且版本号比系统应用的版本号更高则构成升级关系,校验签名等安全验证通过。此时data/app下的这个应用就是系统应用
而push到系统目录下如system/app/....,则会优先寻找system/lib(lib64)目录下的so,由于so
不会自动释放到该目录下,所以需要手动push到该路径下。
作者君还遇到过这样的问题,有时候为了减少包的大小或者其他,不会把所有ABI类型的so都放进目录,另外so的提供者团队没有提供64的so(哈哈,如果提供了,下面的问题直接就没有了,那为什么还拿出来说呢,主要是简单地了解一下系统底层的一些基本原理,感兴趣的可以看下一下~~)
机型类型是64位的,其他apk仅提供了64位的so。而某个apk由于只集成了32位so, install安装是正常的,但是把apk通过push到/system/app下,so放到/system/lib下则报如下错误:
32位机器上当然是正常的。
那么为什么会出错呢?首先是系统级应用,需要理解Android系统的原理(当然啦,也许厂商定制了一番,那则另一回事。):
系统有几个属性,其中app.info.primaryCpuAbi这个值用来决定apk关联ABI类型。而PackageManager会对这个值有所影响。比如:通过apk包里包含的so库的架构来决定app的primaryCpuAbi的值。
另外:
如果机器里有64位的apk,且PackageManager扫描到第一正好是这个apk,PackageManager调整所有apk要加载的都是64位的so。不再去加载32位的so,那么只含32位so的apk就会跑出异常。反之,则64位的apk正常运行,32位的则出错。
作者君能力有限,如有错处,请书友们指导,作者君会第一时间修改。
一起学习 一起进步 ღ( ´・ᴗ・` )比心
1、在src/main中添加
jniLibs文件夹
,把.so复制进去
2、在build.gradle中就添加这么几行
,
看图
复制内容到剪贴板
sourceSets
{
main
{
jniLibs.srcDirs
=
['libs']
}
}
3、然后make
project
4、切换到android结构下,你会看到
jniLibs
中.so已经变成了.jar文件,证明已经成功
android中加载so文件:
在Android中调用动态库文件(*.so)都是通过jni的方式,而且往往在apk或jar包中调用so文件时,都要将对应so文件打包进apk或jar包,工程目录下图:
Android中加载so文件的提供的API:
void System.load(String pathName);
说明:
1、pathName:文件名+文件路劲;
2、该方法调用成功后so文件中的导出函数都将插入的系统提供的一个映射表(类型Map);
3、具体代码如下:
try {
String localPath = Environment.getExternalStorageDirectory() + path;
Log.v(TAG, "LazyBandingLib localPath:" + localPath);
String[] tokens = mPatterns.split(path);
if (null == tokens || tokens.length = 0
|| tokens[tokens.length - 1] == "") {
Log.v(TAG, "非法的文件路径!");
return -3;
}
// 开辟一个输入流
File inFile = new File(localPath);
// 判断需加载的文件是否存在
if (!inFile.exists()) {
// 下载远程驱动文件
Log.v(TAG, inFile.getAbsolutePath() + " is not fond!");
return 1;
}
FileInputStream fis = new FileInputStream(inFile);
File dir = context.getDir("libs", Context.MODE_PRIVATE);
// 获取驱动文件输出流
File soFile = new File(dir, tokens[tokens.length - 1]);
if (!soFile.exists()) {
Log.v(TAG, "### " + soFile.getAbsolutePath() + " is not exists");
FileOutputStream fos = new FileOutputStream(soFile);
Log.v(TAG, "FileOutputStream:" + fos.toString() + ",tokens:"
+ tokens[tokens.length - 1]);
// 字节数组输出流,写入到内存中(ram)
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = -1;
while ((len = fis.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
// 从内存到写入到具体文件
fos.write(baos.toByteArray());
// 关闭文件流
baos.close();
fos.close();
}
fis.close();
Log.v(TAG, "### System.load start");
// 加载外设驱动
System.load(soFile.getAbsolutePath());
Log.v(TAG, "### System.load End");
return 0;
} catch (Exception e) {
Log.v(TAG, "Exception " + e.getMessage());
e.printStackTrace();
return -1;
}
我们在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是什么类型,会加载什么路径,以免我们的底层老司机在帮忙测试问题的时候造成不必要的麻烦。