热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

深入分析Android加载so文件源码

这篇文章主要介绍了深入分析Android加载so文件源码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

Android系统中使用ndk进行编程,有很多的好处(Java的跨平台特性导致其本地交互的能力不够强大,一些和操作系统相关的特性Java无法完成;代码的保护:由于apk的java层代码很容易被反编译,而C/C++库反汇难度较大;可以方便地使用C/C++开源库;便于移植,用C/C++写的库可以方便在其他平台上再次使用;提供程序在某些特定情形下的执行效率,但是并不能明显提升Android程序的性能)。

要使用ndk进行编程,在Java层就必须要对so进行加载。Java层加载so的函数有两个:

System.load(String pathName)
System.loadLibraray(String libName)

两个函数的区别就是load函数的参数是so文件的绝对地址。loadLibrary的参数是so的名称,这个so文件必须放在apk的lib目录下,而且so的名称必须去掉前面的lib和后边的“.so”。如下所示:

System.load("/data/local/tmp/libhello.so");
System.loadLibrary("hello");

System.java

load和loadLibraray函数在/android6.0/libcore/luni/src/main/java/java/lang/System.java中:

public static void load(String pathName) {
    Runtime.getRuntime().load(pathName, VMStack.getCallingClassLoader());
  }

  /**
   * See {@link Runtime#loadLibrary}.
   */
  public static void loadLibrary(String libName) {
    Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());
  }

Runtime.java

getRuntime()函数用于获取Runtime的一个实例。

 public static Runtime getRuntime() {
    return mRuntime;
  }

loadLibrary():

public void loadLibrary(String nickname) {
    loadLibrary(nickname, VMStack.getCallingClassLoader());
  }
  
   void loadLibrary(String libraryName, ClassLoader loader) {
    if (loader != null) {
      String filename = loader.findLibrary(libraryName);
      if (filename == null) {
        // It's not necessarily true that the ClassLoader used
        // System.mapLibraryName, but the default setup does, and it's
        // misleading to say we didn't find "libMyLibrary.so" when we
        // actually searched for "liblibMyLibrary.so.so".
        throw new UnsatisfiedLinkError(loader + " couldn't find \"" +
                        System.mapLibraryName(libraryName) + "\"");
      }
      String error = doLoad(filename, loader);
      if (error != null) {
        throw new UnsatisfiedLinkError(error);
      }
      return;
    }

    String filename = System.mapLibraryName(libraryName);
    List candidates = new ArrayList();
    String lastError = null;
    for (String directory : mLibPaths) {
      String candidate = directory + filename;
      candidates.add(candidate);

      if (IoUtils.canOpenReadOnly(candidate)) {
        String error = doLoad(candidate, loader);
        if (error == null) {
          return; // We successfully loaded the library. Job done.
        }
        lastError = error;
      }
    }

    if (lastError != null) {
      throw new UnsatisfiedLinkError(lastError);
    }
    throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
}

loadLibrary()函数主要进行了两步操作。

第一步:获取library的path:

根据ClassLoader的不同,会有两种不同的处理方法。

如果ClassLoader非空,会利用ClassLoader的findLibrary()方法获取library的path。

如果ClassLoader为空,会通过传入的library name和System.mapLibraryName获得真正的library name。例如传入的是hello,

得到的是libhello.so,然后在mLibPaths查找`libhello.so',最终确定library的path。

第二步:调用doLoad()方法。

第一步目前我不关心,不去深究。主要看doLoad的实现。

 private String doLoad(String name, ClassLoader loader) {
 String ldLibraryPath = null;
    String dexPath = null;
    if (loader == null) {
      // We use the given library path for the boot class loader. This is the path
      // also used in loadLibraryName if loader is null.
      ldLibraryPath = System.getProperty("java.library.path");
    } else if (loader instanceof BaseDexClassLoader) {
      BaseDexClassLoader dexClassLoader = (BaseDexClassLoader) loader;
      ldLibraryPath = dexClassLoader.getLdLibraryPath();
    }
    // nativeLoad should be synchronized so there's only one LD_LIBRARY_PATH in use regardless
    // of how many ClassLoaders are in the system, but dalvik doesn't support synchronized
    // internal natives.
    synchronized (this) {
      return nativeLoad(name, loader, ldLibraryPath);
    }
  }

获得libbrary的路径;

调用native函数nativeLoad()进行加载加载。

java_lang_Runtime.cc

文件位置:/android6.0.1_r66/art/runtime/native/java_lang_Runtime.cc

static jstring Runtime_nativeLoad(JNIEnv* env, jclass, jstring javaFilename, jobject javaLoader,
                 jstring javaLdLibraryPathJstr) {
 ScopedUtfChars filename(env, javaFilename);
 if (filename.c_str() == nullptr) {
  return nullptr;
 }

 SetLdLibraryPath(env, javaLdLibraryPathJstr);

 std::string error_msg;
 {
  JavaVMExt* vm = Runtime::Current()->GetJavaVM();
  bool success = vm->LoadNativeLibrary(env, filename.c_str(), javaLoader, &error_msg);
  if (success) {
   return nullptr;
  }
 }

 // Don't let a pending exception from JNI_OnLoad cause a CheckJNI issue with NewStringUTF.
 env->ExceptionClear();
 return env->NewStringUTF(error_msg.c_str());
}

nativeLoad()主要做了两件事:

第一件事:利用SetLdLibraryPath()将Java的library的path转换成native的。

第二件事情:调用LoadNativeLibrary进行加载。<关键>

java_vm_ext.cc

位置:/android6.0/art/runtime/java_vm_ext.cc

bool JavaVMExt::LoadNativeLibrary(JNIEnv* env, const std::string& path, jobject class_loader,
                 std::string* error_msg) {
...
const char* path_str = path.empty() &#63; nullptr : path.c_str();
 void* handle = dlopen(path_str, RTLD_NOW);

...
 if (needs_native_bridge) {
  library->SetNeedsNativeBridge();
  sym = library->FindSymbolWithNativeBridge("JNI_OnLoad", nullptr);
 } else {
  sym = dlsym(handle, "JNI_OnLoad");
 }
 if (sym == nullptr) {
  VLOG(jni) <<"[No JNI_OnLoad found in \"" <

利用dlopen()打开so文件,得到函数的指针

利用dlsym()调用so文件中的JNI_OnLoad方法,开始so文件的执行。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


推荐阅读
  • Android 自定义控件基础 canvas paint
    1、首先说一下canvas类:ClassOverviewTheCanvasclassholdsthedrawcalls.Todrawsomething,youne ... [详细]
  • 抓取百万知乎用户设计之实体设计
    一.实体的关系实体是根据返回的Json数据来设计的教育经历方面用户可以有很多教育经理,USER和education是一对多的关系,一个education对应一个education一 ... [详细]
  • 我们需要监听ScroView的滑动情况,比如滑动了多少距离,是否滑到布局的顶部或者底部。可惜的是SDK并没有相应的方法,不过倒是提供了一个protectedvoidonScrollChang ... [详细]
  • 以SOA服务为导向的信息系统构建是通过有计划地构建信息系统时,一种简单而有柔性的方法,就是组件化与服务导向架构。过去的信息系统,是在使用者需要新功能时才开发的,也就是响应不同时 ... [详细]
  • 简单动态字符串redis里面很多地方都用到了字符串,我们知道redis是一个键值对存储的非关系型数据库,那么所有的key都是用字符串存储的,还有字符串类型,这些都是用字符串存储的 ... [详细]
  • ADK安装步骤
    1、下载AndroidSDK,解压后即可(全英文路径)下载地址:http:ools.android-studio.orgindex.phpsdk,下载完成后2、新建一个环境变量,变 ... [详细]
  • 例子如Table表有性别字段,1代表男2代表女、3代表中性、还有没填就代表未说明selectid,decode(sex,'1','男', ... [详细]
  • Android的四种启动模式
     对Android的启动模式不是很了解,这里记录下简单的理解内容以便日后查看。 Androi的四种启动模式分别为:standard,singleTop,singleTask,sing ... [详细]
  • 成功入职字节跳动Android岗,定级22,入职就是30K16薪
    Android线程间切换用什么,Handler的运行机制是什么?Android处理异步任务用什么,AsyncTask线程池溢出是怎么回事& ... [详细]
  • 接口测试的方式有很多,比如可以用工具(jmeter,postman)之类,也可以自己写代码进行接口测试,工具的使用相对来说都比较简单,重点是要搞清楚项目接口的协议是什么,然后有针对 ... [详细]
  • C#的Type对象的简单应用
    通过Type对象可以获取类中所有的公有成员直接贴代码:classMyClass{privatestringname;privateintid;publicstringcity;pu ... [详细]
  • 作业迁移
    背景:数据库服务器更换,1、数据库迁移(BACKUPRESTORE);2、数据库登录名用户迁移(注意孤立用户);3、作业迁移数据库迁移,备份数据库、拷贝备份文件到新服务器,还原数据 ... [详细]
  • 如何通过Wifi将Android设备与PC连接
    什么是Appium?设置AppiumAppium基础知识Appium中级要知道的事情如何查找APK文件的appPackage和appActivity转移APK从手机到 ... [详细]
  • Linux     系统安装
    Linux系统安装linux系统安装准备工作电脑、u盘、光盘、网络、硬盘主要使用光盘、网络虚拟化软件vmwarevi ... [详细]
  • 定义:定义两个数论函数\(f\)、\(g\)的Dirichlet卷积为:\[\left(f*g\right)\left(n\right)\sum_{d|n}f\left(d\rig ... [详细]
author-avatar
54打工仔i_858
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有