用NDK生成cURL和OpenSSL庫


最近在用Qt開發Android應用時需要獲取https頁面內容,但Qt內置的QNetworkAccessManager類只支持下面這些協議(調用其supportedSchemes成員函數獲取):

("ftp", "file", "qrc", "http", "data")

而網上我找到的支持https的介紹是使用QSslConfiguration類,然后把OpenSSL的兩個DLL(libeay32.dll和ssleay32.dll)復制到Qt庫目錄中,但我始終沒成功,也就懶得在手機上折騰了。

這個思路不行,還有兩個方案:一是通過QAndroidJniEnvironment和QAndroidJniObject調用Android SDK中封裝的Https訪問代碼,還有就是通過cURL庫,可以復用以前的代碼,而且性能也不錯,所以選擇這個方案。

首先是准備工作:

  • Windows(Win10 x64)
    • 下載並安裝msys2(http://repo.msys2.org/distrib/x86_64/),啟動msys2_shell.cmd腳本,運行“pacman -Syuu”升級后關閉控制台窗口,重新啟動后再運行一遍。這一步是可選的,如果不更新應該也可以,但我是更新后開始下一步的。
    • 下載Windows版NDK並安裝
    • 設置ANDROID_NDK_HOME環境變量:exprot ANDROID_NDK_HOME=/d/android-ndk-r20
    • 更新PATH環境變量:export PATH="/d/android-ndk-r20/toolchains/llvm/prebuilt/windows-x86_64/bin/":$PATH
  • Linux(CentOS 8)
    • 下載Linux版NDK並安裝
    • 設置ANDROID_NDK_HOME環境變量:exprot ANDROID_NDK_HOME=/home/user/android-ndk-r20
    • 更新PATH環境變量:export PATH="/home/user/android-ndk-r20/toolchains/llvm/prebuilt/linux-x86_64/bin":$PATH
  • 下載OpenSSL源碼包,版本必須是1.1.x,開始我沒注意,用的是之前下載的1.0.x,折騰了很長時間也沒搞定,郁悶!
  • 下載cURL源碼包,我用的版本是7.66.0

然后就可以開工,下面列出的命令都是Linux平台,Windows里面msys2的命令大同小異。

生成OpenSSL

  • cd openssl-1.1.1d
  • ./Configure shared android-arm -D__ANDROID_API__=23 no-asm no-ssl2 no-ssl3 no-comp no-hw no-engine --prefix=/usr/local/ssl
  • make -j && make install

上面第二行命令中的android-arm參數要注意,腳本提示可選的系統/編譯器有很多:

android-arm android-arm64 android-armeabi android-mips android-mips64 android-x86 android-x86_64 android64 android64-aarch64 android64-mips64 android64-x86_64

但要選擇哪個取決於你所用NDK根目錄下“platforms/android-xx”里面的子目錄能對應上才行,例如android-ndk-r20中的android-16里面只有arch-arm和arch-x86,所以如果命令行設置__ANDROID_API__=16的話,是不能生成android-arm64版庫文件的。

接下來是cURL

  • cd curl-7.66.0
  • CC=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi23-clang ./configure --prefix=/usr/local/curl --host=arm-linux-androideabi --with-ssl=/usr/local/ssl/
  • make -j && make install

在用到Qt項目之前,先寫個控制台程序測試一下:

 1 #include <iostream>
 2 #include <map>
 3 #include <string>
 4 #include <curl/curl.h>
 5 
 6 
 7 using namespace std;
 8 
 9 
10 #define URL u8R"(https://raw.githubusercontent.com/LCTT/LFS-BOOK/9.0-translating/README.md)"
11 
12 
13 typedef std::map<std::string, std::string> HeaderFields;
14 typedef struct {
15     int code;
16     std::string body;
17     HeaderFields headers;
18 } Response;
19 
20 
21 size_t write_callback( void *data, size_t size, size_t nmemb, void *userdata )
22 {
23     Response *r = reinterpret_cast< Response * >( userdata );
24     r->body.append( reinterpret_cast< char * >( data ), size * nmemb );
25     return size * nmemb;
26 }
27 
28 
29 int _cURL()
30 {
31     if( curl_global_init( CURL_GLOBAL_ALL ) != CURLE_OK )
32     {
33         cout << "Call curl_global_init failed! \n";
34         return 1;
35     }// if
36 
37     CURL *curlHandle = curl_easy_init();
38     CURLcode res = CURLE_OK;
39     curl_slist *headerList = nullptr;
40     Response ret = {};
41     curl_easy_setopt( curlHandle, CURLOPT_URL, URL );
42     curl_easy_setopt( curlHandle, CURLOPT_WRITEFUNCTION, write_callback );
43     curl_easy_setopt( curlHandle, CURLOPT_WRITEDATA, &ret );
44     curl_easy_setopt( curlHandle, CURLOPT_SSL_VERIFYPEER, 0 );
45     curl_easy_setopt( curlHandle, CURLOPT_SSL_VERIFYHOST, 0 );
46     res = curl_easy_perform( curlHandle );
47     if( res != CURLE_OK )
48         cout << "The result code is: " << ( int )res << "\n";
49     if( false == ret.body.empty() )
50         cout << ret.body << "\n";
51     curl_easy_cleanup( curlHandle );
52     curl_global_cleanup();
53 
54     return 0;
55 }
56 
57 
58 int main()
59 {
60     return _cURL();
61 }

保存為test.cc,然后生成Android控制台程序:

$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi23-clang++ test.cc -I/usr/local/curl/include -Wl,-Bstatic -lcurl -lssl -lcrypto -Wl,-Bdynamic -o test

注意curl、ssl和crypto三個庫用-Wl,-Bstatic方式指定鏈接靜態庫(順序不能錯),否則默認會鏈接動態庫,但非root手機沒有復制動態庫到系統目錄的權限,所以需要靜態鏈接得到test,然后開啟Android手機的調試模式並連接主機USB,最后在命令行切換到Android SDK的platform-tools目錄:

  • 把文件復制到手機存儲器:adb push test /data/local/tmp
  • 啟動adb Shell:adb shell
  • 文件添加可執行權限:chmod +x /data/local/tmp/test
  • 啟動:/data/local/tmp/test

順利的話就可以正確獲取並顯示頁面中的文本,然后就可以導入Qt項目,首先把cURL的頭文件都復制到項目的目錄中,然后把libcurl.a、libssl.a和libcrypto.a三個庫文件復制到項目路徑的android/lib中,修改*.pro文件,添加下面一行:

LIBS += -L$$PWD/android/lib -lcurl -lssl -lcrypto

如果已經有LIBS就在后面加上-lcurl -lssl -lcrypto三個庫的引用,最后即可生成APK。

注意:

  • 如果系統沒有GNU binutils,運行cURL的configure腳本前需要把NDK的路徑添加到PATH,而且是添加對應生成目標的路徑,例如要生成64位arm指令集:
PATH=“$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/aarch64-linux-android/bin/”:$PATH

然后在生成不同目標平台庫的時候先切換,例如通用arm指令集:

PATH=“$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/arm-linux-androideabi/bin/”:$PATH
  • Qt應用項目的Android系統版本號要大於等於生成cURL和OpenSSL設置的__ANDROID_API__版本號,否則鏈接時可能會出現找不到stdin、stdout和stderr外部符號的錯誤,一般生成庫都是選個低點的系統版本兼容性更好。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM