Android之.so文件奇巧淫技

MyrtleV18 8年前
   <h2>.so文件的前世今生</h2>    <p>早期的Android系统几乎只支持ARMv5的CPU架构,而现在它可以支持7种,几乎涵盖了市面上大部分的CPU架构。</p>    <p>Android系统目前支持的CPU架构主要包含以下7种:ARMv5,ARMv7 (从2010年起),x86 (从2011年起),MIPS (从2012年起),ARMv8,MIPS64和x86_64 (从2014年起),每一种都关联着一个相应的ABI。</p>    <h2>二进制接口(ABI)</h2>    <p>应用程序二进制接口(Application Binary Interface)定义了二进制文件(尤其是.so文件)如何运行在相应的系统平台上,从使用的指令集,内存对齐到可用的系统函数库。在Android中调用动态库文件(*.so)都是通过jni的方式。形如我们常见的:</p>    <pre>  <code class="language-java">void System.load(String pathName);</code></pre>    <p>在Android系统上,每一个CPU架构对应一个ABI:armeabi,armeabi-v7a,x86,mips,arm64-v8a,mips64,x86_64。</p>    <p>比较常见的百度地图等sdk一般都会提供好几套的架构库。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/1850a4ed28cd4375ebec42dca512a931.jpg"></p>    <h2>Android平台生成.so文件</h2>    <p>说了这么多,那么如何在Android平台上生成.so文件呢?</p>    <h2>NDK环境搭建</h2>    <p>关于ndk详细的理论请查看之前的讲解 <a href="/misc/goto?guid=4959741076814115142" rel="nofollow,noindex">ndk详解</a> ,这里只做一个简单的环境搭建。</p>    <ol>     <li>下载NDK</li>    </ol>    <p>在Android Studio上下载即可。下载完后可以在structs目录查看。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/e03bee01db729437bd73493887fba4fc.png"></p>    <ol>     <li>打开项目根目录的local.properties文件</li>    </ol>    <p style="text-align:center"><img src="https://simg.open-open.com/show/2f4971182ed7176efd153deb2a7245d0.png"></p>    <ol>     <li>打开项目根目录的gradle.properties文件,添加</li>    </ol>    <pre>  <code class="language-java">android.useDeprecatedNdk=true</code></pre>    <p style="text-align:center"><img src="https://simg.open-open.com/show/c00982f6e62204b197c9cdefd30147c7.png"></p>    <p>这样ndk环境就搭建好了。</p>    <h2>编写代码</h2>    <p>1 . 创建一个Java类,以实现jni调用。</p>    <pre>  <code class="language-java">public class JniUtil {      static {          //括号的参数可以任意修改          System.loadLibrary("jniutil");      }        //java调C/C++中的方法都需要用native声明且方法名必须和C/C++的方法名一样      public native String test();  }</code></pre>    <p>然后Make Project,完成后便生成字节码文件。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/d62869269c9f1a9a778c2ce1911c693a.png"></p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/ad5e11c1977a22696fb2ea6f5ca50552.png"></p>    <p>2 . 根据JniUtil.class生成.h文件</p>    <p>打开Android Studio的Terminal,执行以下命令:</p>    <pre>  <code class="language-java">javah -d jni -classpath 编译后的class文件的绝对路径</code></pre>    <p style="text-align:center"><img src="https://simg.open-open.com/show/f741a0f36873962dcc1d9e0447c61c44.png"></p>    <p>执行上述命令后便会在app/src/main目录下自动创建一个包含.h文件的jni文件夹。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/08e806e7abaefa809c483c098be48741.png"></p>    <p>我们直接打开文件:</p>    <pre>  <code class="language-java">#include <jni.h>    #ifndef _Included_com_othershe_jnitest_JniUtil  #define _Included_com_othershe_jnitest_JniUtil  #ifdef __cplusplus  extern "C" {  #endif  JNIEXPORT jstring JNICALL Java_com_othershe_jnitest_JniUtil_test    (JNIEnv *, jobject);    #ifdef __cplusplus  }  #endif  #endif</code></pre>    <p>3 . 编写.c文件(jniutil.c)</p>    <p>这里的jniutil文件名需要和JniUtil类中System.loadLibrary("jniutil");的参数一致。</p>    <p>jniutil.c具体的编写可根据自己的业务实现,这里仅做测试:</p>    <pre>  <code class="language-java">#include "com_othershe_jnitest_JniUtil.h"    JNIEXPORT jstring JNICALL Java_com_othershe_jnitest_JniUtil_test          (JNIEnv *env, jobject obj) {      return (*env)->NewStringUTF(env, "jni调用成功");  }</code></pre>    <p>注:这里需要注意包名的统一。</p>    <p>4 . 配置项目app目录下的build.gradle文件</p>    <pre>  <code class="language-java">defaultConfig {          applicationId "com.othershe.jnitest"          minSdkVersion 14          targetSdkVersion 25          versionCode 1          versionName "1.0"            ndk {              moduleName "jniutil"              abiFilters 'armeabi', 'x86', 'armeabi-v7a' //输出指定三种abi体系结构下的so库。          }      }</code></pre>    <p>其中ndk标签是新添加的,moduleName 的值同样为System.loadLibrary("jniutil");的参数。由于配置了abiFilters,则只会得到armeabi、x86、armeabi-v7a三种ABI对应的.so文件。</p>    <p>最后还需要在生成的jni文件夹下创建一个空的util.c文件,否则会有如下异常:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/145f9546967cfa23d86b23e328c1eecb.png"></p>    <p>完成以上操作后,jni文件的目录如下:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/5f861ed44a09a9fd2865880ef5a993d8.png"></p>    <h2>生成.so文件</h2>    <p>其实到这一步就已经完成了,那么我们怎么验证我们是否成功的创建了.so文件呢?现在我们来测试一下,写个TextView显示一下调用的C:</p>    <pre>  <code class="language-java">public class MainActivity extends AppCompatActivity {        @Override      protected void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          setContentView(R.layout.activity_main);          TextView tv = (TextView)findViewById(R.id.tv);            tv.setText(new JniUtil().getString());      }  }</code></pre>    <p style="text-align:center"><img src="https://simg.open-open.com/show/1c6f80b5a3153d1b35a28ac53c056be8.png"></p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/14085654650ee80d82a624967bdb67b3.png"></p>    <h2>使用.so文件需要注意的地方</h2>    <p>当你编译.so文件时,经常会出现一些错误,其中最多的是"UnsatisfiedLinkError","dlopen: failed"以及其他类型的crash或者低下的性能:</p>    <p>高版本编译的.so文件运行在低版本手机上</p>    <p>NDK平台不是向后兼容的,而是向前兼容的,推荐使用app的minSdkVersion对应的编译平台。</p>    <p>每个支持的CPU架构都需要一套对应的.so文件</p>    <p>这个就好比32位的软件没办法运行在64位的CPU上,必须为每一个CPU架构提供一套.so文件。</p>    <p>参考: <a href="/misc/goto?guid=4959741076907707894" rel="nofollow,noindex">Android中.so文件的Hook</a></p>    <p> </p>    <p>来自:https://yq.aliyun.com/articles/71696?utm_campaign=wenzhang&utm_medium=article&utm_source=QQ-qun&201738&utm_content=m_12787</p>    <p> </p>