關於在Android中使用CMake你所須要瞭解的一切(一)

​ 相信你們在開發的過程當中,都或多或少的接觸過JNI,而後每次要接觸JNI的時候,倒吸一口冷氣,太難啦!java

​ 只有Java代碼和C++代碼 還好,在新建項目的時候把那個 "Include C++ support"勾選上,而後一路next,最後finish,一個簡單的帶有C++代碼的Android項目就算完成了,而後在看下CMakeLists.txt怎麼寫的,照貓畫虎就能夠了,可是實際開發中,並不簡單,由於底層(C++)那裏給過來的有多是.a靜態庫,也有多是.so動態庫,而後如何集成進項目裏就是:一臉懵逼,二臉茫然,三臉不知所措。因爲客觀事實只能去百度,而後百度到的全是用Android.mk實現的居多,然而如今都Android Studio 3.1+時代了,還mk?關於CMake的資料又不多,因此就有了本文。android

​ 在去年在公司仍是實習的時候,老大丟給我一個.a靜態庫(當時並不知道這是一個靜態庫),非常好奇,很想知道.a是怎麼構建出來的,a和so的區別又是什麼,總之一大堆疑問,在轉正後,也嘗試過要構建一個.a靜態庫,無奈!百度到的都是用mk的咯,找到一篇CMake的,但居然要去Linux下面編譯,還要什麼自定義工具鏈,總之,麻煩的一批。c++

​ 因此就有了這一系列,本系列不闡述什麼是CMake,什麼是NDK,什麼是JNI,只是寫一下在Android中使用CMake常遇的問題,解決方法是什麼。git

​ 本系列涉及到的有:github


初次使用CMake構建native項目

​ 這個就很簡單啦,新建項目的時候把 include C++ support 勾選上就能夠了,但,咱們仍是本身動手來一遍,不依靠IDE,看看可不能夠,新建普通項目,名字爲:AndCmake,新建完成後以下:數組

新項目,什麼都沒有,咱們都知道用CMake的話,必需要有CMakeLists.txt(注意名字不能寫錯),第二個就是須要的cpp資源啦,這就很簡單了,在src/main目錄下新建就行了,爲了整潔在main目錄下新建cpp文件夾,且在裏面新建CMakeLists.txt和native_hello.cpp文件,而後在去CMakeLists.txt簡單的配置下就行了,完成後,以下:架構

  1. ../src/main/cpp/native_hello.cpp:app

    //
    // Created by xong on 2018/9/28.
    //
    #include<jni.h>
    #include <string>
    
    extern "C" JNIEXPORT jstring JNICALL Java_com_xong_andcmake_jni_NativeFun_stringFromJNI(JNIEnv *env, jclass thiz) {
        std::string hello = "Hello,I from C++";
        return env->NewStringUTF(hello.c_str());
    }
    複製代碼

    這裏須要說明如下,Java_com_xong_andcmake_jni_是去java的一層一層的包下面去尋找,好比這個就是去com.xong.andcmake.jni下面找,後面的NativeFun_stringFromJNI就是類和方法名了。函數的形參兩個必須是這樣的,第二個形參能夠寫成"jobject",我最近寫JNI的時候,提示改爲"jclass"可是,"jobject"也不能算錯,也是對的。函數

  2. ../src/main/cpp/CMakeLists.txt工具

    # CMake最低版本
    cmake_minimum_required(VERSION 3.4.1)
    # 將須要打包的資源添加進來
    add_library(
    		# 庫名字
            native_hello
            # 庫類型
            SHARED
            # 包含的cpp
            native_hello.cpp
    )
    # 連接到項目中
    target_link_libraries(
            native_hello
            android
            log
    )
    複製代碼

    這就把C++部分寫好了

  3. 修改../app/build.gradle,修改後以下:

    android {
        ...
        defaultConfig {
            ...
            externalNativeBuild {
                cmake {
                    arguments '-DANDROID_STL=c++_static'
                }
            }
            ...
        }
        ...
        externalNativeBuild {
            cmake {
                path 'src/main/cpp/CMakeLists.txt'
            }
        }
        ...
    }
    複製代碼
  4. 寫對應的Java層代碼,在com.xong.andcmake包下新建jni,而後新建NativeFun類,代碼以下:

    package com.xong.andcmake.jni;
    
    /** * Create by xong on 2018/9/28 */
    public class NativeFun {
    
        static {
            System.loadLibrary("native_hello");
        }
    
        public static native String stringFromJNI();
    }
    
    複製代碼
  5. 調用NativeFun.stringFromJNI(),查看是否有正確的信息輸出:這就很簡單啦,在Activity中添加一個TextView而後顯示下就行了,正確的話,應該會有以下顯示:

OK,這樣咱們的第一個帶有CPP代碼的APP就算完成了,而後咱們來思考一下,先來看native_hello.cpp的代碼:

//
// Created by xong on 2018/9/28.
//
#include<jni.h>
#include <string>

extern "C" JNIEXPORT
jstring JNICALL
// 這裏可不能夠優化一下?
Java_com_xong_andcmake_jni_NativeFun_stringFromJNI(JNIEnv *env, jclass thiz)
{
    std::string hello = "Hello,I from C++";
    // 這裏呢?
    return env->NewStringUTF(hello.c_str());
}
複製代碼

對於咱們來說:

  1. 儘可能不寫重複代碼
  2. 代碼要高效

對於上面的函數名來說,Java_com_xong_andcmake_jni_,假如咱們在Java中對應的native類和方法,感受在這個包中不合適,要換下包名,可是我這個類中有不少native方法,只要一換包,那麼對應的cpp中的函數名就要挨個改,函數少還好辦,假若有1000個,改起來真的是不要太爽,並且容易丟,那麼有沒有辦法來儘可能的減小工做量呢?類和包是相對固定的,那麼咱們能不能把包名抽取出來用一個表達式來表示呢?沒錯!就是宏定義啦!

對於下面返回的那一句,我爲啥要這樣寫呢,由於用Android Studio建立一個帶有cpp項目的時候,裏面就是這樣寫的,而後就很天然的抄來了,可是咱們想一下,有這樣寫的必要嗎?能夠看到的是在最後返回的時候,string對象又通過了一層轉換點進去後能夠發現,是將 string對象又轉成了char數組(固然在C++中是char*指針仍是const的,這裏不須要知道const是啥,只須要知道,c_str()實際上是又將string對象轉成了char[]數組),而後咱們這樣寫是否是就…畫蛇添足,讓計算機多作了一些事情呢?因此改完後的代碼以下:

//
// Created by xong on 2018/9/28.
//
#include<jni.h>
#define XONGFUNC(name)Java_com_xong_andcmake_jni_##name

extern "C" JNIEXPORT jstring JNICALL XONGFUNC(NativeFun_stringFromJNI)(JNIEnv *env, jclass thiz) {
    return env->NewStringUTF("Hello,I from C++");
}
複製代碼

這樣的話,假如NativeFun不在jni包下了,而是在jnia包下,那麼咱們只須要將上面的宏定義改下就行了;

由於咱們沒有對string作操做,那麼咱們就不須要它啦,直接返回就行了,這樣計算機就不那麼累了。


make project後,發生了什麼?

接觸過JNI的同窗應該瞭解,項目裏面添加了cpp代碼,apk會變的很大,那麼緣由嘞,那麼我就來捋一捋,cpp的代碼再make project後都發生了什麼,都很清楚會生成so,那麼…在默認的狀況下會生成幾個?

打開 ../app/build/intermediates/cmake/debug/obj 目錄以下:

在默認的狀況下會生成4個,v7a的是最大的,x86_64是最小的,默認狀況下,這4個是都會打到apk裏面的(由於不清楚究竟是往那臺手機上安裝呀,只能將全部的資源都打到apk裏面了),要想縮小apk,那麼就把須要的so打入apk就行了。而後,問題來了,怎麼才能生成規定的so庫呢?上代碼,以下:

apply plugin: 'com.android.application'

android {
	...
    defaultConfig {
    	...
        externalNativeBuild {
            cmake {
                arguments '-DANDROID_STL=c++_static'
            }
            ndk {
                abiFilters "armeabi-v7a", "x86"
            }
        }
        ...
    }
    ...
}
...
複製代碼

在../app/build.gradle中添加ndk標籤,且在裏面寫上須要生成的架構名稱就能夠了,從新build下項目,以下圖:

這樣打入apk中的就只有v7a和x86兩種架構的so庫啦! 下篇咱們來說解一下 現有的cpp代碼集成到項目中有幾種方式,以及如何操做。

點擊跳轉到下一篇:如何將現有的cpp代碼集成到項目中

Demo連接:UseCmakeBuildLib


END

相關文章
相關標籤/搜索